From 7f5f648b33944e5919e9af70d084bcef69f974a7 Mon Sep 17 00:00:00 2001 From: Andy Seaborne Date: Sun, 15 Dec 2024 14:45:44 +0000 Subject: [PATCH 1/8] GH-2902: Fuseki Server Modules --- .../java/org/apache/jena/atlas/net/Host.java | 23 - .../apache/jena/fuseki/server/Counter.java | 19 +- jena-fuseki2/jena-fuseki-main/pom.xml | 53 +- .../fuseki/authz/AuthorizationFilter403.java | 58 ++ .../apache/jena/fuseki/authz/DenyFilter.java | 33 + .../jena/fuseki/authz/LocalhostFilter.java | 71 ++ .../fuseki/main/cmds/FusekiServerCmd.java | 54 ++ .../apache/jena/fuseki/mgt/ActionBackup.java | 77 ++ .../jena/fuseki/mgt/ActionBackupList.java | 94 ++ .../apache/jena/fuseki/mgt/ActionCompact.java | 23 + .../jena/fuseki/mgt/ActionDatasets.java | 499 +++++++++++ .../apache/jena/fuseki/mgt/ActionLogs.java | 55 ++ .../apache/jena/fuseki/mgt/ActionReload.java | 66 ++ .../org/apache/jena/fuseki/mgt/Backup.java | 122 +++ .../apache/jena/fuseki/mgt/FusekiAdmin.java | 23 + .../org/apache/jena/fuseki/mgt/FusekiApp.java | 425 +++++++++ .../jena/fuseki/mgt/ServerMgtConst.java | 41 + .../org/apache/jena/fuseki/mgt/Template.java | 68 ++ .../jena/fuseki/mgt/TemplateFunctions.java | 86 ++ .../mod/access/FMod_GraphAccessCtl.java | 46 + .../fuseki/mod/admin/ActionServerStatus.java | 99 +++ .../jena/fuseki/mod/admin/ArgModuleAdmin.java | 69 ++ .../jena/fuseki/mod/admin/FMod_Admin.java | 178 ++++ .../jena/fuseki/mod/admin/LocalhostOnly.java | 94 ++ .../jena/fuseki/mod/blank/FMod_BLANK.java | 43 + .../apache/jena/fuseki/mod/package-info.java | 2 + .../fuseki/mod/prometheus/ActionMetrics.java | 49 + .../mod/prometheus/FMod_Prometheus.java | 63 ++ .../prometheus/PrometheusMetricsProvider.java | 59 ++ .../jena/fuseki/mod/shiro/FMod_Shiro.java | 188 ++++ .../jena/fuseki/mod/shiro/FusekiShiroLib.java | 62 ++ .../shiro/ShiroEnvironmentLoaderListener.java | 77 ++ .../jena/fuseki/mod/ui/ActionStats.java | 165 ++++ .../jena/fuseki/mod/ui/ActionStatsTxt.java | 189 ++++ .../apache/jena/fuseki/mod/ui/FMod_UI.java | 185 ++++ .../org/apache/jena/fuseki/server/config.ttl | 0 .../org/apache/jena/fuseki/server/shiro.ini | 38 + .../jena/fuseki/server/templates/config-mem | 29 + .../jena/fuseki/server/templates/config-tdb | 32 + .../fuseki/server/templates/config-tdb-dir | 30 + .../fuseki/server/templates/config-tdb-mem | 30 + .../jena/fuseki/server/templates/config-tdb2 | 32 + .../fuseki/server/templates/config-tdb2-dir | 31 + .../fuseki/server/templates/config-tdb2-mem | 30 + .../apache/jena/fuseki/TC_FusekiServer.java | 32 + .../apache/jena/fuseki/mod/TC_FusekiMods.java | 40 + .../jena/fuseki/mod/TestFusekiServer.java | 39 + .../fuseki/mod/admin/TS_FusekiServerApp.java | 32 + .../jena/fuseki/mod/admin/TestAdmin.java | 836 ++++++++++++++++++ .../fuseki/mod/admin/TestFusekiReload.java | 169 ++++ .../mod/admin/TestTemplateAddDataset.java | 183 ++++ .../fuseki/mod/metrics/TestModPrometheus.java | 77 ++ .../jena/fuseki/mod/shiro/TestModShiro.java | 220 +++++ .../src/test/resources/log4j2-test.properties | 15 +- .../testing/Config/config-ds-bad-name-1.ttl | 15 + .../testing/Config/config-ds-bad-name-2.ttl | 15 + .../testing/Config/config-ds-bad-name-3.ttl | 15 + .../testing/Config/config-ds-bad-name-4.ttl | 15 + .../testing/Config/config-ds-inf.ttl | 30 + .../testing/Config/config-ds-plain-1.ttl | 17 + .../testing/Config/config-ds-plain-2.ttl | 18 + .../testing/Config/config-tdb2a.ttl | 18 + .../testing/Config/config-tdb2b.ttl | 18 + .../testing/Shiro/shiro_localhost.ini | 13 + .../testing/Shiro/shiro_userpassword.ini | 18 + 65 files changed, 5480 insertions(+), 65 deletions(-) create mode 100644 jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/authz/AuthorizationFilter403.java create mode 100644 jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/authz/DenyFilter.java create mode 100644 jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/authz/LocalhostFilter.java create mode 100644 jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/FusekiServerCmd.java create mode 100644 jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/ActionBackup.java create mode 100644 jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/ActionBackupList.java create mode 100644 jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/ActionCompact.java create mode 100644 jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/ActionDatasets.java create mode 100644 jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/ActionLogs.java create mode 100644 jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/ActionReload.java create mode 100644 jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/Backup.java create mode 100644 jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/FusekiAdmin.java create mode 100644 jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/FusekiApp.java create mode 100644 jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/ServerMgtConst.java create mode 100644 jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/Template.java create mode 100644 jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/TemplateFunctions.java create mode 100644 jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/access/FMod_GraphAccessCtl.java create mode 100644 jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/admin/ActionServerStatus.java create mode 100644 jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/admin/ArgModuleAdmin.java create mode 100644 jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/admin/FMod_Admin.java create mode 100644 jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/admin/LocalhostOnly.java create mode 100644 jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/blank/FMod_BLANK.java create mode 100644 jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/package-info.java create mode 100644 jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/prometheus/ActionMetrics.java create mode 100644 jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/prometheus/FMod_Prometheus.java create mode 100644 jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/prometheus/PrometheusMetricsProvider.java create mode 100644 jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/shiro/FMod_Shiro.java create mode 100644 jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/shiro/FusekiShiroLib.java create mode 100644 jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/shiro/ShiroEnvironmentLoaderListener.java create mode 100644 jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/ui/ActionStats.java create mode 100644 jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/ui/ActionStatsTxt.java create mode 100644 jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/ui/FMod_UI.java create mode 100644 jena-fuseki2/jena-fuseki-main/src/main/resources/org/apache/jena/fuseki/server/config.ttl create mode 100644 jena-fuseki2/jena-fuseki-main/src/main/resources/org/apache/jena/fuseki/server/shiro.ini create mode 100644 jena-fuseki2/jena-fuseki-main/src/main/resources/org/apache/jena/fuseki/server/templates/config-mem create mode 100644 jena-fuseki2/jena-fuseki-main/src/main/resources/org/apache/jena/fuseki/server/templates/config-tdb create mode 100644 jena-fuseki2/jena-fuseki-main/src/main/resources/org/apache/jena/fuseki/server/templates/config-tdb-dir create mode 100644 jena-fuseki2/jena-fuseki-main/src/main/resources/org/apache/jena/fuseki/server/templates/config-tdb-mem create mode 100644 jena-fuseki2/jena-fuseki-main/src/main/resources/org/apache/jena/fuseki/server/templates/config-tdb2 create mode 100644 jena-fuseki2/jena-fuseki-main/src/main/resources/org/apache/jena/fuseki/server/templates/config-tdb2-dir create mode 100644 jena-fuseki2/jena-fuseki-main/src/main/resources/org/apache/jena/fuseki/server/templates/config-tdb2-mem create mode 100644 jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/TC_FusekiServer.java create mode 100644 jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/TC_FusekiMods.java create mode 100644 jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/TestFusekiServer.java create mode 100644 jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/admin/TS_FusekiServerApp.java create mode 100644 jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/admin/TestAdmin.java create mode 100644 jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/admin/TestFusekiReload.java create mode 100644 jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/admin/TestTemplateAddDataset.java create mode 100644 jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/metrics/TestModPrometheus.java create mode 100644 jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/shiro/TestModShiro.java create mode 100644 jena-fuseki2/jena-fuseki-main/testing/Config/config-ds-bad-name-1.ttl create mode 100644 jena-fuseki2/jena-fuseki-main/testing/Config/config-ds-bad-name-2.ttl create mode 100644 jena-fuseki2/jena-fuseki-main/testing/Config/config-ds-bad-name-3.ttl create mode 100644 jena-fuseki2/jena-fuseki-main/testing/Config/config-ds-bad-name-4.ttl create mode 100644 jena-fuseki2/jena-fuseki-main/testing/Config/config-ds-inf.ttl create mode 100644 jena-fuseki2/jena-fuseki-main/testing/Config/config-ds-plain-1.ttl create mode 100644 jena-fuseki2/jena-fuseki-main/testing/Config/config-ds-plain-2.ttl create mode 100644 jena-fuseki2/jena-fuseki-main/testing/Config/config-tdb2a.ttl create mode 100644 jena-fuseki2/jena-fuseki-main/testing/Config/config-tdb2b.ttl create mode 100644 jena-fuseki2/jena-fuseki-main/testing/Shiro/shiro_localhost.ini create mode 100644 jena-fuseki2/jena-fuseki-main/testing/Shiro/shiro_userpassword.ini diff --git a/jena-base/src/main/java/org/apache/jena/atlas/net/Host.java b/jena-base/src/main/java/org/apache/jena/atlas/net/Host.java index 1028472c30c..12449dbd711 100644 --- a/jena-base/src/main/java/org/apache/jena/atlas/net/Host.java +++ b/jena-base/src/main/java/org/apache/jena/atlas/net/Host.java @@ -128,27 +128,4 @@ public static InetAddress getLocalHostLANAddress() { throw unknownHostException; } } - - // @formatter:off -// public static void main(String ... arg) throws UnknownHostException { -// try { -// for (Enumeration ifaces = NetworkInterface.getNetworkInterfaces(); ifaces.hasMoreElements();) { -// NetworkInterface iface = ifaces.nextElement(); -// // Iterate all IP addresses assigned to each card... -// for (Enumeration inetAddrs = iface.getInetAddresses(); inetAddrs.hasMoreElements();) { -// InetAddress inetAddr = inetAddrs.nextElement(); -// System.out.println("IP Address : '" +inetAddr.getHostAddress()+"'"); -// } -// } -// System.out.println(); -// -// InetAddress inetAddr = getLocalHostLANAddress(); -// //InetAddress localhost = InetAddress.getLocalHost(); -// System.out.println("System IP Address : '" +inetAddr.getHostAddress()+"'"); -// } catch (Exception ex) { -// ex.printStackTrace(); -// System.exit(0); -// } -// } - // @formatter:on } diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/Counter.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/Counter.java index 71c6c31d0e7..7889b3ce32f 100644 --- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/Counter.java +++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/Counter.java @@ -18,22 +18,21 @@ package org.apache.jena.fuseki.server; -import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.LongAdder; -/** A statistics counter */ -public class Counter -{ - private AtomicLong counter = new AtomicLong(0); +/** A statistics counter. The value is "eventual consistent" */ +public class Counter { + // Not for synchronization + private LongAdder counter = new LongAdder(); - public Counter() {} + public Counter() {} - public void inc() { counter.incrementAndGet(); } - public void dec() { counter.decrementAndGet(); } - public long value() { return counter.get(); } + public void inc() { counter.increment(); } + public void dec() { counter.decrement(); } + public long value() { return counter.sum(); } @Override public String toString() { return counter.toString(); } } - diff --git a/jena-fuseki2/jena-fuseki-main/pom.xml b/jena-fuseki2/jena-fuseki-main/pom.xml index e620084863a..b72e8613279 100644 --- a/jena-fuseki2/jena-fuseki-main/pom.xml +++ b/jena-fuseki2/jena-fuseki-main/pom.xml @@ -83,6 +83,19 @@ jetty-xml + + + org.apache.shiro + shiro-core + + + + org.apache.shiro + shiro-web + jakarta + + + org.junit.vintage junit-vintage-engine @@ -96,41 +109,39 @@ - org.junit.platform - junit-platform-suite + org.junit.jupiter + junit-jupiter-params test - + - org.apache.logging.log4j - log4j-slf4j2-impl - true - compile + org.junit.platform + junit-platform-suite + test - - - - org.apache.shiro - shiro-core - jakarta + + - org.apache.shiro - shiro-config-core + org.awaitility + awaitility test - true - org.apache.shiro - shiro-web - jakarta - test + org.apache.logging.log4j + log4j-slf4j2-impl true + compile diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/authz/AuthorizationFilter403.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/authz/AuthorizationFilter403.java new file mode 100644 index 00000000000..715331d1f3d --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/authz/AuthorizationFilter403.java @@ -0,0 +1,58 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.authz; + +import java.io.IOException; + +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletResponse; +import org.apache.jena.web.HttpSC; +import org.apache.shiro.web.filter.authz.AuthorizationFilter; +import org.apache.shiro.web.util.WebUtils; + +/** Specialise AuthorizationFilter to yield HTTP 403 on access denied */ +public abstract class AuthorizationFilter403 extends AuthorizationFilter +{ + private String message; + + protected AuthorizationFilter403(String text) { setMessage(text); } + protected AuthorizationFilter403() { this(null); } + + /** Set the message used in HTTP 403 responses */ + public void setMessage(String msg) { message = msg; } + + public String getMessage() { return message; } + + @Override + protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException { + HttpServletResponse httpResponse; + try { httpResponse = WebUtils.toHttp(response); } + catch (ClassCastException ex) { + // Not a HTTP Servlet operation + return super.onAccessDenied(request, response); + } + if ( message == null ) + httpResponse.sendError(HttpSC.FORBIDDEN_403); + else + httpResponse.sendError(HttpSC.FORBIDDEN_403, message); + return false; // No further processing. + } +} + diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/authz/DenyFilter.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/authz/DenyFilter.java new file mode 100644 index 00000000000..9e7a5d8e421 --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/authz/DenyFilter.java @@ -0,0 +1,33 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.authz; + +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; + +/** An authorization filter that always denies access and sends back HTTP 403 */ +public class DenyFilter extends AuthorizationFilter403 { + + public DenyFilter() { super("Access denied"); } + + @Override + protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { + return false; + } +} diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/authz/LocalhostFilter.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/authz/LocalhostFilter.java new file mode 100644 index 00000000000..f029b56fff1 --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/authz/LocalhostFilter.java @@ -0,0 +1,71 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.authz; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Set; + +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import org.apache.shiro.web.filter.authz.PortFilter; + +/** + * A Filter that can allow or deny access based on whether the + * the host that sent the request is the loopback address (AKA localhost). + * Use of the external IP address of the local machine does not permit access, + * only the loopback interface is authorized. + * Responds with HTTP 403 on any denied request. + * + * Example: + *
+ * [main]
+ * localhost=org.apache.jena.fuseki.authz.LocalhostFilter
+ * ...
+ * [urls]
+ * /LocalFilesForLocalPeople/** = localhost
+ * 
+ * @see PortFilter + */ + +public class LocalhostFilter extends AuthorizationFilter403 { + + private static final String message = "Access denied : only localhost access allowed"; + + public LocalhostFilter() { super(message); } + + private static String LOCALHOST_IpV6_a = "[0:0:0:0:0:0:0:1]"; + private static String LOCALHOST_IpV6_b = "0:0:0:0:0:0:0:1"; + // This is what appears in the Chrome developer tools client-side. + // "[0:0:0:0:0:0:0:1]" by the time it arrives here, It is not clear which + // software component is responsible for that. + // To be safe we add "[::1]". + private static String LOCALHOST_IpV6_c = "[::1]"; + private static String LOCALHOST_IpV4 = "127.0.0.1"; // Strictly, 127.*.*.* + + private static final Collection localhosts = Set.copyOf( + Arrays.asList(LOCALHOST_IpV4, LOCALHOST_IpV6_a, LOCALHOST_IpV6_b, LOCALHOST_IpV6_c)); + + @Override + protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception { + return localhosts.contains(request.getRemoteAddr()); + } +} + + diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/FusekiServerCmd.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/FusekiServerCmd.java new file mode 100644 index 00000000000..2c61c0ed09b --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/FusekiServerCmd.java @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.main.cmds; + +import org.apache.jena.fuseki.run.FusekiModServer; +import org.apache.jena.fuseki.system.FusekiLogging; + +/** Fuseki command that runs a Fuseki server with the admin UI. + *

+ * Use {@code --conf=} for multiple datasets and specific service names. + *

+ * The command line dataset setup only supports a single dataset. + */ + +public class FusekiServerCmd { + // This class wraps FusekiMain so that it can take control of logging setup. + // This class does not depend via inheritance on any Jena code + // and does not trigger Jena initialization. + // FusekiLogging runs before any Jena code can trigger logging setup. + // + // Inheritance causes initialization in the super class first, before class + // initialization code in this class. + + static { FusekiLogging.setLogging(); } + + /** + * Build and run, a server based on command line syntax. This operation does not + * return. See {@link FusekiMain#build} to build a server using command line + * syntax but not start it. + */ + static public void main(String... args) { + // Fix up args + // --empty + // --modules=true + FusekiModServer.runAsync(args).join(); + } +} + diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/ActionBackup.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/ActionBackup.java new file mode 100644 index 00000000000..758af27d8ac --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/ActionBackup.java @@ -0,0 +1,77 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.mgt; + +import static java.lang.String.format; + +import org.apache.jena.fuseki.Fuseki; +import org.apache.jena.fuseki.ctl.ActionAsyncTask; +import org.apache.jena.fuseki.ctl.TaskBase; +import org.apache.jena.fuseki.servlets.HttpAction; +import org.apache.jena.fuseki.servlets.ServletOps; +import org.slf4j.Logger; + +public class ActionBackup extends ActionAsyncTask +{ + public ActionBackup() { super("Backup"); } + + @Override + public void validate(HttpAction action) {} + + @Override + protected Runnable createRunnable(HttpAction action) { + String name = getItemName(action); + if ( name == null ) { + action.log.error("Null for dataset name in item request"); + ServletOps.errorOccurred("Null for dataset name in item request"); + return null; + } + + action.log.info(format("[%d] Backup dataset %s", action.id, name)); + // ** Error changing in TaskBase + BackupTask task = new BackupTask(action); + if ( task.dataset == null ) { + ServletOps.errorBadRequest("Dataset not found"); + return null; + } + return task; + } + + static class BackupTask extends TaskBase { + static private Logger log = Fuseki.backupLog; + + public BackupTask(HttpAction action) { + super(action); + } + + @Override + public void run() { + try { + String backupFilename = Backup.chooseFileName(datasetName); + log.info(format("[%d] >>>> Start backup %s -> %s", actionId, datasetName, backupFilename)); + Backup.backup(transactional, dataset, backupFilename); + log.info(format("[%d] <<<< Finish backup %s -> %s", actionId, datasetName, backupFilename)); + } catch (Throwable ex) { + log.warn(format("[%d] **** Exception in backup", actionId), ex); + // Pass on - the async task tracking infrastructure will record this. + throw ex; + } + } + } +} diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/ActionBackupList.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/ActionBackupList.java new file mode 100644 index 00000000000..1e52a65b696 --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/ActionBackupList.java @@ -0,0 +1,94 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.mgt; + +import static java.lang.String.format; + +import java.io.File; +import java.io.IOException; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import org.apache.jena.atlas.json.JsonBuilder; +import org.apache.jena.atlas.json.JsonValue; +import org.apache.jena.fuseki.ctl.ActionCtl; +import org.apache.jena.fuseki.servlets.HttpAction; +import org.apache.jena.fuseki.servlets.ServletOps; + +/** + * A JSON API to list all the backups in the backup directory + */ +public class ActionBackupList extends ActionCtl { + + @Override + public void execGet(HttpAction action) { + executeLifecycle(action); + } + + @Override + public void execPost(HttpAction action) { + executeLifecycle(action); + } + + @Override + public void validate(HttpAction action) {} + + @Override + public void execute(HttpAction action) { + JsonValue result = description(action); + ServletOps.setNoCache(action); + ServletOps.sendJsonReponse(action, result); + } + + private static DirectoryStream.Filter filterVisibleFiles = (entry) -> { + File f = entry.toFile(); + return f.isFile() && !f.isHidden(); + }; + + private JsonValue description(HttpAction action) { + if ( ! Files.isDirectory(FusekiApp.dirBackups) ) + ServletOps.errorOccurred(format("[%d] Backup area '%s' is not a directory", action.id, FusekiApp.dirBackups)); + + List paths = new ArrayList<>(); + try (DirectoryStream stream = Files.newDirectoryStream(FusekiApp.dirBackups, filterVisibleFiles)) { + stream.forEach(paths::add); + } catch (IOException ex) { + action.log.error(format("[%d] Backup file list :: IOException :: %s", action.id, ex.getMessage())); + ServletOps.errorOccurred(ex); + } + + List fileNames = paths.stream().map((p)->p.getFileName().toString()).sorted().collect(Collectors.toList()); + + JsonBuilder builder = new JsonBuilder(); + builder.startObject("top"); + builder.key("backups"); + + builder.startArray(); + fileNames.forEach(builder::value); + builder.finishArray(); + + builder.finishObject("top"); + return builder.build(); + + } +} diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/ActionCompact.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/ActionCompact.java new file mode 100644 index 00000000000..3639db5267c --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/ActionCompact.java @@ -0,0 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.mgt; + +public class ActionCompact { + +} diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/ActionDatasets.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/ActionDatasets.java new file mode 100644 index 00000000000..7630b8c6f02 --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/ActionDatasets.java @@ -0,0 +1,499 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.mgt; + +import static java.lang.String.format; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.StringReader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; + +import jakarta.servlet.http.HttpServletRequest; +import org.apache.commons.lang3.StringUtils; +import org.apache.jena.atlas.RuntimeIOException; +import org.apache.jena.atlas.io.IO; +import org.apache.jena.atlas.json.JsonBuilder; +import org.apache.jena.atlas.json.JsonValue; +import org.apache.jena.atlas.lib.FileOps; +import org.apache.jena.atlas.lib.InternalErrorException; +import org.apache.jena.atlas.logging.FmtLog; +import org.apache.jena.atlas.web.ContentType; +import org.apache.jena.datatypes.xsd.XSDDatatype; +import org.apache.jena.fuseki.build.DatasetDescriptionMap; +import org.apache.jena.fuseki.build.FusekiConfig; +import org.apache.jena.fuseki.ctl.ActionContainerItem; +import org.apache.jena.fuseki.ctl.JsonDescription; +import org.apache.jena.fuseki.server.DataAccessPoint; +import org.apache.jena.fuseki.server.DataService; +import org.apache.jena.fuseki.server.FusekiVocab; +import org.apache.jena.fuseki.server.ServerConst; +import org.apache.jena.fuseki.servlets.ActionLib; +import org.apache.jena.fuseki.servlets.HttpAction; +import org.apache.jena.fuseki.servlets.ServletOps; +import org.apache.jena.fuseki.system.DataUploader; +import org.apache.jena.fuseki.system.FusekiNetLib; +import org.apache.jena.graph.Node; +import org.apache.jena.rdf.model.*; +import org.apache.jena.riot.*; +import org.apache.jena.riot.system.StreamRDF; +import org.apache.jena.riot.system.StreamRDFLib; +import org.apache.jena.shared.JenaException; +import org.apache.jena.sparql.core.DatasetGraph; +import org.apache.jena.sparql.core.Quad; +import org.apache.jena.sparql.core.assembler.AssemblerUtils; +import org.apache.jena.sparql.util.FmtUtils; +import org.apache.jena.vocabulary.RDF; +import org.apache.jena.web.HttpSC; + +public class ActionDatasets extends ActionContainerItem { + + + static private Property pServiceName = FusekiVocab.pServiceName; + //static private Property pStatus = FusekiVocab.pStatus; + + private static final String paramDatasetName = "dbName"; + private static final String paramDatasetType = "dbType"; + private static final String tDatabaseTDB = "tdb"; + private static final String tDatabaseTDB2 = "tdb2"; + private static final String tDatabaseMem = "mem"; + + public ActionDatasets() { super(); } + + @Override + public void validate(HttpAction action) { } + + // ---- GET : return details of dataset or datasets. + @Override + protected JsonValue execGetContainer(HttpAction action) { + action.log.info(format("[%d] GET datasets", action.id)); + JsonBuilder builder = new JsonBuilder(); + builder.startObject("D"); + builder.key(ServerConst.datasets); + JsonDescription.arrayDatasets(builder, action.getDataAccessPointRegistry()); + builder.finishObject("D"); + return builder.build(); + } + + @Override + protected JsonValue execGetItem(HttpAction action) { + String item = getItemDatasetName(action); + action.log.info(format("[%d] GET dataset %s", action.id, item)); + JsonBuilder builder = new JsonBuilder(); + DataAccessPoint dsDesc = getItemDataAccessPoint(action, item); + if ( dsDesc == null ) + ServletOps.errorNotFound("Not found: dataset "+item); + JsonDescription.describe(builder, dsDesc); + return builder.build(); + } + + // ---- POST + + @Override + protected JsonValue execPostContainer(HttpAction action) { + UUID uuid = UUID.randomUUID(); + + ContentType ct = ActionLib.getContentType(action); + + boolean hasParams = action.getRequestParameterNames().hasMoreElements(); + + if ( ct == null && ! hasParams ) + ServletOps.errorBadRequest("Bad request - Content-Type or both parameters dbName and dbType required"); + + boolean succeeded = false; + String systemFileCopy = null; + String configFile = null; + + DatasetDescriptionMap registry = new DatasetDescriptionMap(); + + synchronized (FusekiAdmin.systemLock) { + try { + // Where to build the templated service/database. + Model descriptionModel = ModelFactory.createDefaultModel(); + StreamRDF dest = StreamRDFLib.graph(descriptionModel.getGraph()); + + if ( hasParams || WebContent.isHtmlForm(ct) ) + assemblerFromForm(action, dest); + else if ( WebContent.isMultiPartForm(ct) ) + assemblerFromUpload(action, dest); + else + assemblerFromBody(action, dest); + + // ---- + // Keep a persistent copy immediately. This is not used for + // anything other than being "for the record". + systemFileCopy = FusekiApp.dirSystemFileArea.resolve(uuid.toString()).toString(); + try ( OutputStream outCopy = IO.openOutputFile(systemFileCopy) ) { + RDFDataMgr.write(outCopy, descriptionModel, Lang.TURTLE); + } + + // ---- + // Add the dataset and graph wiring for assemblers + Model model = ModelFactory.createDefaultModel(); + model.add(descriptionModel); + // See AssemblerUtils.readAssemblerFile(String filename) + AssemblerUtils.addRegistered(model); + + // ---- + // Process configuration. + + // Returns the "service fu:name NAME" statement + Statement stmt = findService(model); + + Resource subject = stmt.getSubject(); + Literal object = stmt.getObject().asLiteral(); + + if ( object.getDatatype() != null && ! object.getDatatype().equals(XSDDatatype.XSDstring) ) + action.log.warn(format("[%d] Service name '%s' is not a string", action.id, FmtUtils.stringForRDFNode(object))); + + String datasetPath; + { // Check the name provided. + String datasetName = object.getLexicalForm(); + // This duplicates the code FusekiBuilder.buildDataAccessPoint to give better error messages and HTTP status code." + + // ---- Check and canonicalize name. + if ( datasetName.isEmpty() ) + ServletOps.error(HttpSC.BAD_REQUEST_400, "Empty dataset name"); + if ( StringUtils.isBlank(datasetName) ) + ServletOps.error(HttpSC.BAD_REQUEST_400, format("Whitespace dataset name: '%s'", datasetName)); + if ( datasetName.contains(" ") ) + ServletOps.error(HttpSC.BAD_REQUEST_400, format("Bad dataset name (contains spaces) '%s'",datasetName)); + if ( datasetName.equals("/") ) + ServletOps.error(HttpSC.BAD_REQUEST_400, format("Bad dataset name '%s'",datasetName)); + datasetPath = DataAccessPoint.canonical(datasetName); + // ---- Check whether it already exists + if ( action.getDataAccessPointRegistry().isRegistered(datasetPath) ) + ServletOps.error(HttpSC.CONFLICT_409, "Name already registered "+datasetPath); + } + + action.log.info(format("[%d] Create database : name = %s", action.id, datasetPath)); + + configFile = FusekiApp.generateConfigurationFilename(datasetPath); + List existing = FusekiApp.existingConfigurationFile(datasetPath); + if ( ! existing.isEmpty() ) + ServletOps.error(HttpSC.CONFLICT_409, "Configuration file for '"+datasetPath+"' already exists"); + + // Write to configuration directory. + try ( OutputStream outCopy = IO.openOutputFile(configFile) ) { + RDFDataMgr.write(outCopy, descriptionModel, Lang.TURTLE); + } + + // Need to be in Resource space at this point. + DataAccessPoint dataAccessPoint = FusekiConfig.buildDataAccessPoint(subject.getModel().getGraph(), subject.asNode(), registry); + if ( dataAccessPoint == null ) { + FmtLog.error(action.log, "Failed to build DataAccessPoint: datasetPath = %s; DataAccessPoint name = %s", datasetPath, dataAccessPoint); + ServletOps.errorBadRequest("Failed to build DataAccessPoint"); + return null; + } + dataAccessPoint.getDataService().setEndpointProcessors(action.getOperationRegistry()); + dataAccessPoint.getDataService().goActive(); + if ( ! datasetPath.equals(dataAccessPoint.getName()) ) + FmtLog.warn(action.log, "Inconsistent names: datasetPath = %s; DataAccessPoint name = %s", datasetPath, dataAccessPoint); + succeeded = true; + + action.getDataAccessPointRegistry().register(dataAccessPoint); + action.setResponseContentType(WebContent.contentTypeTextPlain); + ServletOps.success(action); + } catch (IOException ex) { IO.exception(ex); } + finally { + if ( ! succeeded ) { + if ( systemFileCopy != null ) FileOps.deleteSilent(systemFileCopy); + if ( configFile != null ) FileOps.deleteSilent(configFile); + } + } + return null; + } + } + + /** Find the service resource. There must be only one in the configuration. */ + private Statement findService(Model model) { + // Try to find by unique pServiceName (max backwards compatibility) + // then try to find by rdf:type fuseki:Service. + + Statement stmt = getOne(model, null, pServiceName, null); + // null means 0 or many, not one. + + if ( stmt == null ) { + // This calculates { ?x rdf:type fu:Service ; ?x fu:name ?name } + // One and only one service. + Statement stmt2 = getOne(model, null, RDF.type, FusekiVocab.fusekiService); + if ( stmt2 == null ) { + int count = model.listStatements(null, RDF.type, FusekiVocab.fusekiService).toList().size(); + if ( count == 0 ) + ServletOps.errorBadRequest("No triple rdf:type fuseki:Service found"); + else + ServletOps.errorBadRequest("Multiple Fuseki service descriptions"); + } + Statement stmt3 = getOne(model, stmt2.getSubject(), pServiceName, null); + if ( stmt3 == null ) { + StmtIterator sIter = model.listStatements(stmt2.getSubject(), pServiceName, (RDFNode)null ); + if ( ! sIter.hasNext() ) + ServletOps.errorBadRequest("No name given in description of Fuseki service"); + sIter.next(); + if ( sIter.hasNext() ) + ServletOps.errorBadRequest("Multiple names given in description of Fuseki service"); + throw new InternalErrorException("Inconsistent: getOne didn't fail the second time"); + } + stmt = stmt3; + } + + if ( ! stmt.getObject().isLiteral() ) + ServletOps.errorBadRequest("Found "+FmtUtils.stringForRDFNode(stmt.getObject())+" : Service names are strings, then used to build the external URI"); + + return stmt; + } + + @Override + protected JsonValue execPostItem(HttpAction action) { + String name = getItemDatasetName(action); + if ( name == null ) + name = "''"; + action.log.info(format("[%d] POST dataset %s", action.id, name)); + + // Not in the action - this not an ActionService. + DataAccessPoint dap = getItemDataAccessPoint(action, name); + + if ( dap == null ) + ServletOps.errorNotFound("Not found: dataset "+name); + + DataService dSrv = dap.getDataService(); + if ( dSrv == null ) + // If not set explicitly, take from DataAccessPoint + dSrv = action.getDataAccessPoint().getDataService(); + + String s = action.getRequestParameter("state"); + if ( s == null || s.isEmpty() ) + ServletOps.errorBadRequest("No state change given"); + return null; + } + + // ---- DELETE + + @Override + protected void execDeleteItem(HttpAction action) { + // Does not exist? + String name = getItemDatasetName(action); + if ( name == null ) + name = ""; + action.log.info(format("[%d] DELETE dataset=%s", action.id, name)); + + if ( ! action.getDataAccessPointRegistry().isRegistered(name) ) + ServletOps.errorNotFound("No such dataset registered: "+name); + + boolean succeeded = false; + + synchronized(FusekiAdmin.systemLock ) { + try { + // Here, go offline. + // Need to reference count operations when they drop to zero + // or a timer goes off, we delete the dataset. + + // Redo check inside transaction. + DataAccessPoint ref = action.getDataAccessPointRegistry().get(name); + if ( ref == null ) + ServletOps.errorNotFound("No such dataset registered: "+name); + + // Get a reference before removing. + DataService dataService = ref.getDataService(); + // ---- Make it invisible in this running server. + action.getDataAccessPointRegistry().remove(name); + + // Find the configuration. + String filename = name.startsWith("/") ? name.substring(1) : name; + List configurationFiles = FusekiApp.existingConfigurationFile(filename); + + if ( configurationFiles.isEmpty() ) { + // ---- Unmanaged + action.log.warn(format("[%d] Can't delete database configuration - not a managed database", action.id, name)); +// ServletOps.errorOccurred(format("Can't delete database - not a managed configuration", name)); + succeeded = true; + ServletOps.success(action); + return; + } + + if ( configurationFiles.size() > 1 ) { + // -- This should not happen. + action.log.warn(format("[%d] There are %d configuration files, not one.", action.id, configurationFiles.size())); + ServletOps.errorOccurred(format("There are %d configuration files, not one. Delete not performed; manual clean up of the filesystem needed.", + configurationFiles.size())); + return; + } + + // ---- Remove managed database. + String cfgPathname = configurationFiles.get(0); + + // Delete configuration file. + // Once deleted, server restart will not have the database. + FileOps.deleteSilent(cfgPathname); + + // Delete the database for real only when it is in the server "run/databases" + // area. Don't delete databases that reside elsewhere. We do delete the + // configuration file, so the databases will not be associated with the server + // anymore. + + boolean isTDB1 = org.apache.jena.tdb1.sys.TDBInternal.isTDB1(dataService.getDataset()); + boolean isTDB2 = org.apache.jena.tdb2.sys.TDBInternal.isTDB2(dataService.getDataset()); + + // TODO This occasionally fails in tests due to outstanding transactions. + try { + dataService.shutdown(); + } catch (JenaException ex) { + return; + } + // JENA-1481: Really delete files. + if ( ( isTDB1 || isTDB2 ) ) { + // Delete databases created by the UI, or the admin operation, which are + // in predictable, unshared location on disk. + // There may not be any database files, the in-memory case. + Path pDatabase = FusekiApp.dirDatabases.resolve(filename); + if ( Files.exists(pDatabase)) { + try { + if ( Files.isSymbolicLink(pDatabase)) { + action.log.info(format("[%d] Database is a symbolic link, not removing files", action.id, pDatabase)); + } else { + IO.deleteAll(pDatabase); + action.log.info(format("[%d] Deleted database files %s", action.id, pDatabase)); + } + } catch (RuntimeIOException ex) { + action.log.error(format("[%d] Error while deleting database files %s: %s", action.id, pDatabase, ex.getMessage()), ex); + // But we have managed to remove it from the running server, and removed its configuration, so declare victory. + } + } + } + + succeeded = true; + ServletOps.success(action); + } finally { + // No clearup needed + } + } + } + + private static void assemblerFromBody(HttpAction action, StreamRDF dest) { + bodyAsGraph(action, dest); + } + + private static Map dbTypeToTemplate = new HashMap<>(); + static { + dbTypeToTemplate.put(tDatabaseTDB, Template.templateTDB1_FN); + dbTypeToTemplate.put(tDatabaseTDB2, Template.templateTDB2_FN); + dbTypeToTemplate.put(tDatabaseMem, Template.templateTIM_MemFN); + } + + private static void assemblerFromForm(HttpAction action, StreamRDF dest) { + String x = action.getRequestQueryString(); + String dbType = action.getRequestParameter(paramDatasetType); + String dbName = action.getRequestParameter(paramDatasetName); + if ( StringUtils.isBlank(dbType) || StringUtils.isBlank(dbName) ) + ServletOps.errorBadRequest("Received HTML form. Both parameters 'dbName' and 'dbType' required"); + + Map params = new HashMap<>(); + + if ( dbName.startsWith("/") ) + params.put(Template.NAME, dbName.substring(1)); + else + params.put(Template.NAME, dbName); + FusekiApp.addGlobals(params); + + //action.log.info(format("[%d] Create database : name = %s, type = %s", action.id, dbName, dbType )); + + String template = dbTypeToTemplate.get(dbType.toLowerCase(Locale.ROOT)); + if ( template == null ) { + List keys = new ArrayList<>(dbTypeToTemplate.keySet()); + Collections.sort(keys); + ServletOps.errorBadRequest(format("dbType can be only one of %s", keys)); + } + + String instance = TemplateFunctions.templateFile(template, params, Lang.TTL); + RDFParser.create().source(new StringReader(instance)).base("http://base/").lang(Lang.TTL).parse(dest); + } + + private static void assemblerFromUpload(HttpAction action, StreamRDF dest) { + DataUploader.incomingData(action, dest); + } + + // [ADMIN] +// // Persistent state change. +// private static void setDatasetState(String name, Resource newState) { +// boolean committed = false; +// system.begin(ReadWrite.WRITE); +// try { +// String dbName = name; +// if ( dbName.startsWith("/") ) +// dbName = dbName.substring(1); +// +// String update = StrUtils.strjoinNL +// (PREFIXES, +// "DELETE { GRAPH ?g { ?s fu:status ?state } }", +// "INSERT { GRAPH ?g { ?s fu:status "+FmtUtils.stringForRDFNode(newState)+" } }", +// "WHERE {", +// " GRAPH ?g { ?s fu:name '"+dbName+"'; ", +// " fu:status ?state .", +// " }", +// "}" +// ); +// UpdateRequest req = UpdateFactory.create(update); +// UpdateAction.execute(req, system); +// system.commit(); +// committed = true; +// } finally { +// if ( ! committed ) system.abort(); +// system.end(); +// } +// } + + // ---- Auxiliary functions + + private static Quad getOne(DatasetGraph dsg, Node g, Node s, Node p, Node o) { + Iterator iter = dsg.findNG(g, s, p, o); + if ( ! iter.hasNext() ) + return null; + Quad q = iter.next(); + if ( iter.hasNext() ) + return null; + return q; + } + + private static Statement getOne(Model m, Resource s, Property p, RDFNode o) { + StmtIterator iter = m.listStatements(s, p, o); + if ( ! iter.hasNext() ) + return null; + Statement stmt = iter.next(); + if ( iter.hasNext() ) + return null; + return stmt; + } + + // TODO Merge with Upload.incomingData + + private static void bodyAsGraph(HttpAction action, StreamRDF dest) { + HttpServletRequest request = action.getRequest(); + String base = ActionLib.wholeRequestURL(request); + ContentType ct = FusekiNetLib.getContentType(request); + Lang lang = RDFLanguages.contentTypeToLang(ct.getContentTypeStr()); + if ( lang == null ) { + ServletOps.errorBadRequest("Unknown content type for triples: " + ct); + return; + } + dest.prefix("root", base+"#"); + ActionLib.parseOrError(action, dest, lang, base); + } +} diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/ActionLogs.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/ActionLogs.java new file mode 100644 index 00000000000..3b91abf0c47 --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/ActionLogs.java @@ -0,0 +1,55 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.mgt; + +import static org.apache.jena.riot.WebContent.charsetUTF8; +import static org.apache.jena.riot.WebContent.contentTypeTextPlain; + +import java.io.IOException; + +import jakarta.servlet.ServletOutputStream; +import org.apache.jena.fuseki.ctl.ActionCtl; +import org.apache.jena.fuseki.servlets.HttpAction; +import org.apache.jena.fuseki.servlets.ServletOps; + +public class ActionLogs extends ActionCtl +{ + public ActionLogs() { super(); } + + @Override + public void validate(HttpAction action) {} + + @Override + public void execGet(HttpAction action) { + executeLifecycle(action); + } + + @Override + public void execute(HttpAction action) { + try { + ServletOutputStream out = action.getResponseOutputStream(); + action.setResponseContentType(contentTypeTextPlain); + action.setResponseCharacterEncoding(charsetUTF8); + out.println("Not implemented yet"); + out.println(); + out.flush(); + ServletOps.success(action); + } catch (IOException ex) { ServletOps.errorOccurred(ex); } + } +} diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/ActionReload.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/ActionReload.java new file mode 100644 index 00000000000..b85fddbbd99 --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/ActionReload.java @@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.mgt; + +import org.apache.jena.atlas.logging.FmtLog; +import org.apache.jena.fuseki.Fuseki; +import org.apache.jena.fuseki.ctl.ActionCtl; +import org.apache.jena.fuseki.main.FusekiLib; +import org.apache.jena.fuseki.main.FusekiServer; +import org.apache.jena.fuseki.servlets.HttpAction; +import org.apache.jena.fuseki.servlets.ServletOps; +import org.apache.jena.rdf.model.Model; +import org.apache.jena.riot.RDFParser; +import org.apache.jena.riot.web.HttpNames; + +/** + * Administration action to reload the server's dataset configuration. + *

+ * This is done by reading the configuration file which may have changed since server startup. + *

+ * If the server does not have a configuration file (e.g. command line or programmatic configuration) + */ +public class ActionReload extends ActionCtl { + + @Override + public void validate(HttpAction action) { + if ( action.getRequestMethod() != HttpNames.METHOD_POST ) { + ServletOps.errorMethodNotAllowed(action.getRequestMethod()); + } + } + + @Override + public void execute(HttpAction action) { + FusekiServer server = FusekiServer.get(action.getRequest().getServletContext()); + if ( server == null ) { + ServletOps.errorOccurred("Failed to find the server for this action"); + return; + } + + String configFilename = server.getConfigFilename(); + if ( configFilename == null ) { + FmtLog.warn(Fuseki.serverLog, "[%d] Server does not have an associated configuration file", action.id); + ServletOps.errorBadRequest("Server does not have an associated configuration file"); + return; + } + Model model = RDFParser.source(configFilename).toModel(); + FmtLog.info(Fuseki.serverLog, "[%d] Reload configuration", action.id); + FusekiLib.reload(server, model); + } +} diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/Backup.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/Backup.java new file mode 100644 index 00000000000..70ea5f51563 --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/Backup.java @@ -0,0 +1,122 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.mgt; + +import java.io.BufferedOutputStream; +import java.io.OutputStream; +import java.nio.file.Path; +import java.util.Collections; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.zip.GZIPOutputStream; + +import org.apache.jena.atlas.io.IOX; +import org.apache.jena.atlas.lib.DateTimeUtils; +import org.apache.jena.atlas.logging.FmtLog; +import org.apache.jena.fuseki.Fuseki; +import org.apache.jena.fuseki.FusekiException; +import org.apache.jena.riot.Lang; +import org.apache.jena.riot.RDFDataMgr; +import org.apache.jena.sparql.core.DatasetGraph; +import org.apache.jena.sparql.core.Transactional; +import org.apache.jena.sparql.core.TransactionalNull; +import org.apache.jena.system.Txn; + +/** Perform a backup */ +public class Backup +{ + public static String chooseFileName(String dsName) { + // Without the "/" - i.e. a relative name. + String ds = dsName; + if ( ds.startsWith("/") ) + ds = ds.substring(1); + if ( ds.contains("/") ) { + Fuseki.adminLog.warn("Dataset name: weird format: "+dsName); + // Some kind of fixup + ds = ds.replace("/", "_"); + } + + String timestamp = DateTimeUtils.nowAsString("yyyy-MM-dd_HH-mm-ss"); + String filename = ds + "_" + timestamp; + filename = FusekiApp.dirBackups.resolve(filename).toString(); + return filename; + } + + // Record of all backups so we don't attempt to backup the + // same dataset multiple times at the same time. + private static Set activeBackups = Collections.newSetFromMap(new ConcurrentHashMap<>()); + + /** + * Perform a backup. + *

+ * A backup is a dump of the dataset in compressed N-Quads, done inside a transaction. + */ + public static void backup(Transactional transactional, DatasetGraph dsg, String backupfile) { + if ( transactional == null ) + transactional = new TransactionalNull(); + Txn.executeRead(transactional, ()->backup(dsg, backupfile)); + } + + // This seems to achieve about the same as "gzip -6" + // It's not too expensive in elapsed time but it's not + // zero cost. GZip, large buffer. + private static final boolean USE_GZIP = true; + + /** + * Perform a backup. + * + * @see #backup(Transactional, DatasetGraph, String) + */ + + private static void backup(DatasetGraph dsg, String backupfile) { + if (dsg == null) { + throw new FusekiException("No dataset provided to backup"); + } + + // Per backup source lock. + synchronized(activeBackups) { + // Atomically check-and-set + if ( activeBackups.contains(dsg) ) + FmtLog.warn(Fuseki.serverLog, "Backup already in progress"); + activeBackups.add(dsg); + } + + if ( !backupfile.endsWith(".nq") ) + backupfile = backupfile + ".nq"; + + if ( USE_GZIP ) + backupfile = backupfile + ".gz"; + + try { + IOX.safeWrite(Path.of(backupfile), outfile -> { + OutputStream out = outfile; + if ( USE_GZIP ) + out = new GZIPOutputStream(outfile, 8 * 1024); + try (OutputStream out2 = new BufferedOutputStream(out)) { + RDFDataMgr.write(out2, dsg, Lang.NQUADS); + } + }); + } finally { + // Remove lock. + synchronized(activeBackups) { + activeBackups.remove(dsg); + } + } + } +} diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/FusekiAdmin.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/FusekiAdmin.java new file mode 100644 index 00000000000..2481c4f6996 --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/FusekiAdmin.java @@ -0,0 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.mgt; + +public class FusekiAdmin { + public final static Object systemLock = new Object(); +} diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/FusekiApp.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/FusekiApp.java new file mode 100644 index 00000000000..d6cd0dc5708 --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/FusekiApp.java @@ -0,0 +1,425 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.mgt; + +import static java.lang.String.format; +import static org.apache.jena.atlas.lib.Lib.getenv; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; + +import org.apache.jena.atlas.RuntimeIOException; +import org.apache.jena.atlas.io.IOX; +import org.apache.jena.atlas.lib.FileOps; +import org.apache.jena.atlas.lib.InternalErrorException; +import org.apache.jena.atlas.lib.Lib; +import org.apache.jena.atlas.logging.FmtLog; +import org.apache.jena.cmd.CmdException; +import org.apache.jena.dboe.sys.Names; +import org.apache.jena.fuseki.Fuseki; +import org.apache.jena.fuseki.FusekiConfigException; +import org.apache.jena.fuseki.build.DatasetDescriptionMap; +import org.apache.jena.fuseki.build.FusekiConfig; +import org.apache.jena.fuseki.server.DataAccessPoint; +import org.apache.jena.fuseki.server.DataService; +import org.apache.jena.fuseki.server.FusekiVocabG; +import org.apache.jena.fuseki.servlets.HttpAction; +import org.apache.jena.fuseki.servlets.ServletOps; +import org.apache.jena.graph.Graph; +import org.apache.jena.graph.Node; +import org.apache.jena.rdf.model.*; +import org.apache.jena.riot.Lang; +import org.apache.jena.riot.RDFLanguages; +import org.apache.jena.riot.RDFParser; +import org.apache.jena.sparql.core.DatasetGraph; +import org.apache.jena.sparql.core.assembler.AssemblerUtils; +import org.apache.jena.system.G; + +public class FusekiApp { + // Relative names of directories in the FUSEKI_BASE area. + public static final String databasesLocationBase = "databases"; + // Place to put Lucene text and spatial indexes. + //private static final String databaseIndexesDir = "indexes"; + + public static final String backupDirNameBase = "backups"; + public static final String configDirNameBase = "configuration"; + public static final String logsNameBase = "logs"; + public static final String systemFileAreaBase = "system_files"; + public static final String templatesNameBase = "templates"; + public static final String DFT_SHIRO_INI = "shiro.ini"; + public static final String DFT_CONFIG = "config.ttl"; + + private static int BaseFusekiAutoModuleLevel = 500; + public static int levelFModAdmin = BaseFusekiAutoModuleLevel; + public static int levelFModUI = BaseFusekiAutoModuleLevel+10; + public static int levelFModShiro = BaseFusekiAutoModuleLevel+20; + + + /** Directory for TDB databases - this is known to the assembler templates */ + public static Path dirDatabases = null; + + /** Directory for writing backups */ + public static Path dirBackups = null; + + /** Directory for assembler files */ + public static Path dirConfiguration = null; + + /** Directory for assembler files */ + public static Path dirLogs = null; + +// /** Directory for system database */ +// public static Path dirSystemDatabase = null; + + /** Directory for files uploaded (e.g upload assembler descriptions); not data uploads. */ + public static Path dirSystemFileArea = null; + + /** Directory for assembler files */ + public static Path dirTemplates = null; + + private static boolean initialized = false; + // Marks the end of successful initialization. + /*package*/static boolean serverInitialized = false; + + + +// /** +// * Root of the Fuseki installation for fixed files. +// * This may be null (e.g. running inside a web application container) +// */ +// public static Path FUSEKI_HOME = null; + + /** + * Root of the varying files in this deployment. Often $PWD/run. + * This must be writable. + */ + public static Path FUSEKI_BASE = set_FUSEKI_BASE(); + + public static String envFusekiBase = "FUSEKI_BASE"; + public static String envFusekiShiro = "FUSEKI_SHIRO"; + + private static Path set_FUSEKI_BASE() { + // Does not guarantee existence + Path setting = null; + if ( FUSEKI_BASE == null ) + setting = calc_FUSEKI_BASE(); + setting = setting.toAbsolutePath(); + return setting; + } + + private static Path calc_FUSEKI_BASE() { + String valueFusekiBase = getenv("FUSEKI_BASE"); + if ( valueFusekiBase == null ) + valueFusekiBase = dftFusekiBase; + return Path.of(valueFusekiBase); + } + + // Default - "run" in the current directory. + public static final String dftFusekiBase = "run"; + + static void setEnvironment() { + if ( FUSEKI_BASE == null ) + FUSEKI_BASE = set_FUSEKI_BASE(); + + FmtLog.info(Fuseki.configLog, "FUSEKI_BASE=%s", FUSEKI_BASE); + if ( ! Files.exists(FUSEKI_BASE) ) { + try { + Files.createDirectories(FUSEKI_BASE); + } catch (IOException e) { + throw new FusekiConfigException("Failed to create FUSEKI_BASE: "+FUSEKI_BASE); + } + } + // Further checks in ensureBaseArea + } + + public static Path setup() { + // Command line arguments "--base" ... + setEnvironment(); + // Format the BASE area. + FusekiApp.ensureBaseArea(FUSEKI_BASE); + return FUSEKI_BASE; + } + + /** + * Create directories if found to be missing. + */ + public static void ensureBaseArea(Path FUSEKI_BASE) { + if ( Files.exists(FUSEKI_BASE) ) { + if ( ! Files.isDirectory(FUSEKI_BASE) ) + throw new FusekiConfigException("FUSEKI_BASE is not a directory: "+FUSEKI_BASE); + if ( ! Files.isWritable(FUSEKI_BASE) ) + throw new FusekiConfigException("FUSEKI_BASE is not writable: "+FUSEKI_BASE); + } else { + ensureDir(FUSEKI_BASE); + } + + // Ensure FUSEKI_BASE has the assumed directories. + dirTemplates = writeableDirectory(FUSEKI_BASE, templatesNameBase); + dirDatabases = writeableDirectory(FUSEKI_BASE, databasesLocationBase); + dirBackups = writeableDirectory(FUSEKI_BASE, backupDirNameBase); + dirConfiguration = writeableDirectory(FUSEKI_BASE, configDirNameBase); + dirLogs = writeableDirectory(FUSEKI_BASE, logsNameBase); + dirSystemFileArea = writeableDirectory(FUSEKI_BASE, systemFileAreaBase); + + // ---- Initialize with files. + +// // Copy missing files into FUSEKI_BASE + // Interacts with FMod_Shiro. + if ( Lib.getenv(FusekiApp.envFusekiShiro) == null ) { + copyFileIfMissing(null, DFT_SHIRO_INI, FUSEKI_BASE); + System.setProperty(FusekiApp.envFusekiShiro, FUSEKI_BASE.resolve(DFT_SHIRO_INI).toString()); + } + + copyFileIfMissing(null, DFT_CONFIG, FUSEKI_BASE); + for ( String n : Template.templateNames ) { + copyFileIfMissing(null, n, FUSEKI_BASE); + } + + serverInitialized = true; + } + + /** Copy a file from src to dst under name fn. + * If src is null, try as a classpath resource + * @param src Source directory, or null meaning use java resource. + * @param fn File name, a relative path. + * @param dst Destination directory. + * + */ + private static void copyFileIfMissing(Path src, String fn, Path dst) { + // fn may be a path. + Path dstFile = dst.resolve(fn); + if ( Files.exists(dstFile) ) + return; + if ( src != null ) { + Path srcFile = src.resolve(fn); + if ( ! Files.exists(dstFile) ) + throw new FusekiConfigException("File not found: "+srcFile); + try { + IOX.safeWrite(dstFile, output->Files.copy(srcFile, output)); + } catch (RuntimeIOException e) { + throw new FusekiConfigException("Failed to copy file "+srcFile+" to "+dstFile, e); + } + } else { + copyFileFromResource(fn, dstFile); + } + } + + private static void copyFileFromResource(String fn, Path dstFile) { + try { + // Get from the file from area "org/apache/jena/fuseki/server" + String absName = "org/apache/jena/fuseki/server/"+fn; + InputStream input = FusekiApp.class + // Else prepends classname as path + .getClassLoader() + .getResourceAsStream(absName); + + if ( input == null ) + throw new FusekiConfigException("Failed to find resource '"+absName+"'"); + IOX.safeWrite(dstFile, (output)-> input.transferTo(output)); + } + catch (RuntimeException e) { + throw new FusekiConfigException("Failed to copy "+fn+" to "+dstFile, e); + } + } + + private static List processServerConfigFile(String configFilename) { + if ( ! FileOps.exists(configFilename) ) { + Fuseki.configLog.warn("Configuration file '" + configFilename+"' does not exist"); + return Collections.emptyList(); + } + //Fuseki.configLog.info("Configuration file: " + configFilename); + Model model = AssemblerUtils.readAssemblerFile(configFilename); + if ( model.size() == 0 ) + return Collections.emptyList(); + List x = FusekiConfig.processServerConfiguration(model, Fuseki.getContext()); + return x; + } + + private static DataAccessPoint configFromTemplate(String templateFile, String datasetPath, + boolean allowUpdate, Map params) { + // ---- Setup + if ( params == null ) { + params = new HashMap<>(); + params.put(Template.NAME, datasetPath); + } else { + if ( ! params.containsKey(Template.NAME) ) { + Fuseki.configLog.warn("No NAME found in template parameters (added)"); + params.put(Template.NAME, datasetPath); + } + } + //-- Logging + Fuseki.configLog.info("Template file: " + templateFile); + String dir = params.get(Template.DIR); + if ( dir != null ) { + if ( ! Objects.equals(dir, Names.memName) && !FileOps.exists(dir) ) + throw new CmdException("Directory not found: " + dir); + } + //-- Logging + + datasetPath = DataAccessPoint.canonical(datasetPath); + + // DRY -- ActionDatasets (and others?) + addGlobals(params); + + String str = TemplateFunctions.templateFile(templateFile, params, Lang.TTL); + Lang lang = RDFLanguages.filenameToLang(str, Lang.TTL); + + Graph configuration = RDFParser.fromString(str, lang).toGraph(); + List x = G.listPO(configuration, FusekiVocabG.pServiceName, null); + if ( x.isEmpty() ) + ServletOps.errorBadRequest("No name given in description of Fuseki service"); + if ( x.size() > 1 ) + ServletOps.errorBadRequest("Multiple names given in description of Fuseki service"); + Node fusekiService = x.get(0); + DatasetDescriptionMap registry = new DatasetDescriptionMap(); + DataAccessPoint dap = FusekiConfig.buildDataAccessPoint(configuration, fusekiService, registry); + return dap; + } + + public static void addGlobals(Map params) { + if ( params == null ) { + Fuseki.configLog.warn("FusekiApp.addGlobals : params is null", new Throwable()); + return; + } + + if ( ! params.containsKey("FUSEKI_BASE") ) + params.put("FUSEKI_BASE", pathStringOrElse(FUSEKI_BASE, "unset")); +// if ( ! params.containsKey("FUSEKI_HOME") ) +// params.put("FUSEKI_HOME", pathStringOrElse(FusekiAppEnv.FUSEKI_HOME, "unset")); + } + + private static String pathStringOrElse(Path path, String dft) { + if ( path == null ) + return dft; + return path.toString(); + } + + // DRY -- ActionDatasets (and others?) + private static Statement getOne(Model m, Resource s, Property p, RDFNode o) { + StmtIterator iter = m.listStatements(s, p, o); + if ( ! iter.hasNext() ) + return null; + Statement stmt = iter.next(); + if ( iter.hasNext() ) + return null; + return stmt; + } + + private static DataAccessPoint datasetDefaultConfiguration(String name, DatasetGraph dsg, boolean allowUpdate) { + name = DataAccessPoint.canonical(name); + DataService ds = FusekiConfig.buildDataServiceStd(dsg, allowUpdate); + DataAccessPoint dap = new DataAccessPoint(name, ds); + return dap; + } + + // ---- Helpers + + /** Ensure a directory exists, creating it if necessary. + */ + private static void ensureDir(Path directory) { + File dir = directory.toFile(); + if ( ! dir.exists() ) { + boolean b = dir.mkdirs(); + if ( ! b ) + throw new FusekiConfigException("Failed to create directory: "+directory); + } + else if ( ! dir.isDirectory()) + throw new FusekiConfigException("Not a directory: "+directory); + } + + private static void mustExist(Path directory) { + File dir = directory.toFile(); + if ( ! dir.exists() ) + throw new FusekiConfigException("Does not exist: "+directory); + if ( ! dir.isDirectory()) + throw new FusekiConfigException("Not a directory: "+directory); + } + + private static boolean emptyDir(Path dir) { + return dir.toFile().list().length <= 2; + } + + private static boolean exists(Path directory) { + File dir = directory.toFile(); + return dir.exists(); + } + + private static Path writeableDirectory(Path root , String relName ) { + Path p = makePath(root, relName); + ensureDir(p); + if ( ! Files.isWritable(p) ) + throw new FusekiConfigException("Not writable: "+p); + return p; + } + + private static Path makePath(Path root , String relName ) { + Path path = root.resolve(relName); + // Must exist +// try { path = path.toRealPath(); } +// catch (IOException e) { IO.exception(e); } + return path; + } + + /** + * Dataset set name to configuration file name. Return a configuration file name - + * existing one or ".ttl" form if new + */ + public static String datasetNameToConfigurationFile(HttpAction action, String dsName) { + List existing = existingConfigurationFile(dsName); + if ( ! existing.isEmpty() ) { + if ( existing.size() > 1 ) { + action.log.warn(format("[%d] Multiple existing configuration files for %s : %s", + action.id, dsName, existing)); + ServletOps.errorBadRequest("Multiple existing configuration files for "+dsName); + return null; + } + return existing.get(0).toString(); + } + + return generateConfigurationFilename(dsName); + } + + /** New configuration file name - absolute filename */ + public static String generateConfigurationFilename(String dsName) { + String filename = dsName; + // Without "/" + if ( filename.startsWith("/")) + filename = filename.substring(1); + Path p = FusekiApp.dirConfiguration.resolve(filename+".ttl"); + return p.toString(); + } + + /** Return the filenames of all matching files in the configuration directory (absolute paths returned ). */ + public static List existingConfigurationFile(String baseFilename) { + try { + List paths = new ArrayList<>(); + try (DirectoryStream stream = Files.newDirectoryStream(FusekiApp.dirConfiguration, baseFilename+".*") ) { + stream.forEach((p)-> paths.add(FusekiApp.dirConfiguration.resolve(p).toString() )); + } + return paths; + } catch (IOException ex) { + throw new InternalErrorException("Failed to read configuration directory "+FusekiApp.dirConfiguration); + } + } + +} diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/ServerMgtConst.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/ServerMgtConst.java new file mode 100644 index 00000000000..056b161470c --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/ServerMgtConst.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.mgt; + +/** + * Various constants used in the management API functions and JSON responses in the + * webapp/full server. + */ +public class ServerMgtConst { + public static final String opDatasets = "datasets"; + public static final String opBackup = "backup"; + public static final String opCompact = "compact"; + public static final String opListBackups = "backups-list"; + public static final String opServer = "server"; + + public static final String uptime = "uptime"; + public static final String startDT = "startDateTime"; +// public static final String server = "server"; +// public static final String port = "port"; + public static final String hostname = "hostname"; + public static final String admin = "admin"; + public static final String version = "version"; + public static final String built = "built"; + public static final String services = "services"; +} diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/Template.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/Template.java new file mode 100644 index 00000000000..2ef63652b8e --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/Template.java @@ -0,0 +1,68 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.mgt; + +import java.nio.file.Path; + + +public class Template +{ + public static Path getPath(String templateName) { + return FusekiApp.FUSEKI_BASE.resolve(templateName); + } + + public static final String templateDir = "templates"; + + // These are used by the command line start up. + public static final String templateServiceFN = templateDir+"/config-service"; // Dummy used by dataset-less service. + + // TDB1 - for backwards compatibility, the files are called "tdb" + public static final String templateTDB1_FN = templateDir+"/config-tdb"; + public static final String templateTDB1_MemFN = templateDir+"/config-tdb-mem"; + public static final String templateTDB1_DirFN = templateDir+"/config-tdb-dir"; + public static final String templateTDB1_DirReadFN = templateDir+"/config-tdb-dir-read-only"; + + public static final String templateTDB2_FN = templateDir+"/config-tdb2"; + public static final String templateTDB2_MemFN = templateDir+"/config-tdb2-mem"; + public static final String templateTDB2_DirFN = templateDir+"/config-tdb2-dir"; + public static final String templateTDB2_DirReadFN = templateDir+"/config-tdb2-dir-read-only"; + + + public static final String templateTIM_MemFN = templateDir+"/config-mem"; + + // Template may be in a resources area of a jar file so you can't do a directory listing. + public static final String[] templateNames = { + templateTIM_MemFN, + + templateTDB1_FN , + templateTDB1_MemFN , + templateTDB1_DirFN , + //templateTDB1_DirReadFN, + + templateTDB2_FN , + templateTDB2_MemFN , + templateTDB2_DirFN , + //templateTDB2_DirReadFN + }; + + public static final String NAME = "NAME"; + public static final String DATA = "DATA"; + public static final String DIR = "DIR"; +} + diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/TemplateFunctions.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/TemplateFunctions.java new file mode 100644 index 00000000000..009cc655ae6 --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/TemplateFunctions.java @@ -0,0 +1,86 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.mgt; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Map; +import java.util.Map.Entry; + +import org.apache.jena.atlas.io.IO; +import org.apache.jena.fuseki.Fuseki; +import org.apache.jena.riot.Lang; +import org.apache.jena.util.FileUtils; + +public class TemplateFunctions +{ + /** Read in a template from a file, substitute for {NAME} and return the string. */ + public static String templateFile(String templateName, Map params, Lang lang) { + String templateFilename = Template.getPath(templateName).toString(); + String template; + try { template = FileUtils.readWholeFileAsUTF8(templateFilename); } + catch (IOException ex) { + Fuseki.serverLog.error("File not found: "+templateFilename); + IO.exception(ex); return null; + } + return templateString(template, params, lang); + } + + /** Read a template file, substitute for {NAME} and return the model. */ + public static String templateResource(String resourceName, Map params, Lang lang) { + String template; + try { + InputStream in = TemplateFunctions.class.getClassLoader().getResourceAsStream(resourceName); + if ( in == null ) + Fuseki.serverLog.error("Resource not found: "+resourceName); + template = FileUtils.readWholeFileAsUTF8(in); + } + catch (IOException ex) { + Fuseki.serverLog.error("Error reading resource: "+resourceName); + IO.exception(ex); return null; + } + return templateString(template, params, lang); + } + + /** Create a template from a String */ + public static String templateString(String template, Map params, Lang lang) { + for ( Entry e : params.entrySet() ) { + // Literal string replacement. + // If using .replaceAll, need to use Match.quoteReplacement on the value. + String x = e.getValue(); + String k = "{"+e.getKey()+"}"; + + if ( lang != null ) { + if ( Lang.TTL.equals(lang) || + Lang.TRIG.equals(lang) || + Lang.NT.equals(lang) || + Lang.NQ.equals(lang) || + Lang.JSONLD.equals(lang) || + Lang.RDFJSON.equals(lang) + ) { + // Make safe for a RDF language ""-string - especially MS Windows \ path separators. + x = x.replace("\\", "\\\\"); + x = x.replace("\"", "\\\""); + } + } + template = template.replace(k, x); + } + return template; + } +} diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/access/FMod_GraphAccessCtl.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/access/FMod_GraphAccessCtl.java new file mode 100644 index 00000000000..fc6e74236a3 --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/access/FMod_GraphAccessCtl.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.mod.access; + +import org.apache.jena.fuseki.access.DataAccessCtl; +import org.apache.jena.fuseki.access.VocabSecurity; +import org.apache.jena.fuseki.main.FusekiLib; +import org.apache.jena.fuseki.main.sys.FusekiModule; +import org.apache.jena.fuseki.server.DataAccessPoint; +import org.apache.jena.rdf.model.Model; + +public class FMod_GraphAccessCtl implements FusekiModule { + + public FMod_GraphAccessCtl() { + VocabSecurity.init(); + } + + @Override + public String name() { + return "GraphAccessCtl"; + } + + @Override + public void configDataAccessPoint(DataAccessPoint dap, Model configModel) { + if ( DataAccessCtl.isAccessControlled(dap.getDataService().getDataset()) ) { + dap.getDataService().forEachEndpoint(ep-> + FusekiLib.modifyForAccessCtl(ep, DataAccessCtl.requestUserServlet)); + } + } +} diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/admin/ActionServerStatus.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/admin/ActionServerStatus.java new file mode 100644 index 00000000000..d4589ede33b --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/admin/ActionServerStatus.java @@ -0,0 +1,99 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.mod.admin; + +import static org.apache.jena.riot.WebContent.charsetUTF8; +import static org.apache.jena.riot.WebContent.contentTypeJSON; + +import java.io.IOException; +import java.io.OutputStream; + +import org.apache.jena.atlas.json.JSON; +import org.apache.jena.atlas.json.JsonBuilder; +import org.apache.jena.atlas.json.JsonValue; +import org.apache.jena.fuseki.Fuseki; +import org.apache.jena.fuseki.ctl.ActionCtl; +import org.apache.jena.fuseki.ctl.JsonDescription; +import org.apache.jena.fuseki.mgt.ServerMgtConst; +import org.apache.jena.fuseki.server.DataAccessPointRegistry; +import org.apache.jena.fuseki.server.ServerConst; +import org.apache.jena.fuseki.servlets.HttpAction; +import org.apache.jena.fuseki.servlets.ServletOps; + +/** Description of datasets for a server */ +public class ActionServerStatus extends ActionCtl +{ + public ActionServerStatus() { super(); } + + @Override + public void validate(HttpAction action) {} + + @Override + public void execGet(HttpAction action) { + executeLifecycle(action); + } + + @Override + public void execPost(HttpAction action) { + executeLifecycle(action); + } + + @Override + public void execute(HttpAction action) { + try { + description(action); + ServletOps.success(action); + } catch (IOException e) { + ServletOps.errorOccurred(e); + } + } + + private void description(HttpAction action) throws IOException { + OutputStream out = action.getResponseOutputStream(); + action.setResponseContentType(contentTypeJSON); + action.setResponseCharacterEncoding(charsetUTF8); + + JsonBuilder builder = new JsonBuilder(); + builder.startObject(); + describeServer(builder, action.getRequestLocalPort()); + describeDatasets(builder, action.getDataAccessPointRegistry()); + builder.finishObject(); + + JsonValue v = builder.build(); + JSON.write(out, v); + out.write('\n'); + out.flush(); + } + + private void describeServer(JsonBuilder builder, int requestPort) { + String versionStr = Fuseki.VERSION; + builder + .pair(ServerMgtConst.version, versionStr) + .pair(ServerMgtConst.startDT, Fuseki.serverStartedAt()) + .pair(ServerMgtConst.uptime, Fuseki.serverUptimeSeconds()) + ; + } + + private void describeDatasets(JsonBuilder builder, DataAccessPointRegistry registry) { + builder.key(ServerConst.datasets); + JsonDescription.arrayDatasets(builder, registry); + } + +} + diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/admin/ArgModuleAdmin.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/admin/ArgModuleAdmin.java new file mode 100644 index 00000000000..6cb3ed0b4e5 --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/admin/ArgModuleAdmin.java @@ -0,0 +1,69 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.mod.admin; + +import java.nio.file.Files; +import java.nio.file.Path; + +import org.apache.jena.cmd.ArgDecl; +import org.apache.jena.cmd.ArgModuleGeneral; +import org.apache.jena.cmd.CmdArgModule; +import org.apache.jena.cmd.CmdGeneral; +import org.apache.jena.fuseki.FusekiConfigException; +import org.apache.jena.fuseki.mgt.FusekiApp; + +public class ArgModuleAdmin implements ArgModuleGeneral { + // Add a static of "extra command" + + private ArgDecl argAdmin = new ArgDecl(true, "admin"); + private ArgDecl argAdminArea = new ArgDecl(true, "adminArea", "adminBase"); + + public ArgModuleAdmin() { } + + @Override + public void processArgs(CmdArgModule cmdLine) { + System.out.println("ArgModuleAdmin"); + String admin = cmdLine.getValue(argAdmin); + if ( admin == null ) { + return; + } + + if ( admin.equals("localhost") ) {} + else { + String pwFile = admin; + } + + String dirStr = cmdLine.getValue(argAdminArea); + Path directory = Path.of(dirStr); + + if ( ! Files.isDirectory(directory) ) + throw new FusekiConfigException("Not a directory: "+dirStr); + + if ( ! Files.isWritable(directory) ) + throw new FusekiConfigException("Not writable: "+dirStr); + + FusekiApp.FUSEKI_BASE = directory; + } + + @Override + public void registerWith(CmdGeneral cmdLine) { + cmdLine.add(argAdmin, "--admin=[UserPasswordFile|localhost]", "Enable the admin module"); + cmdLine.add(argAdminArea, "--run=DIR", "Admin state directory"); + } +} \ No newline at end of file diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/admin/FMod_Admin.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/admin/FMod_Admin.java new file mode 100644 index 00000000000..09b0fcffc6a --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/admin/FMod_Admin.java @@ -0,0 +1,178 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.mod.admin; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.Set; + +import org.apache.jena.atlas.logging.FmtLog; +import org.apache.jena.cmd.ArgDecl; +import org.apache.jena.cmd.ArgModuleGeneral; +import org.apache.jena.cmd.CmdArgModule; +import org.apache.jena.cmd.CmdGeneral; +import org.apache.jena.fuseki.Fuseki; +import org.apache.jena.fuseki.FusekiConfigException; +import org.apache.jena.fuseki.build.FusekiConfig; +import org.apache.jena.fuseki.ctl.ActionCtl; +import org.apache.jena.fuseki.main.FusekiServer; +import org.apache.jena.fuseki.main.cmds.ServerArgs; +import org.apache.jena.fuseki.main.sys.FusekiModule; +import org.apache.jena.fuseki.mgt.ActionBackup; +import org.apache.jena.fuseki.mgt.ActionBackupList; +import org.apache.jena.fuseki.mgt.ActionDatasets; +import org.apache.jena.fuseki.mgt.FusekiApp; +import org.apache.jena.fuseki.server.DataAccessPoint; +import org.apache.jena.rdf.model.Model; +import org.slf4j.Logger; + +public class FMod_Admin implements FusekiModule { + + private static FusekiModule singleton = new FMod_Admin(); + // ---- + +// @Override +// public void start() {} +// +// @Override +// public int level() { +// return FusekiApp.levelFModAdmin; +// } + + @Override + public String name() { + return "FMod Admin"; + } + + public static FMod_Admin create() { + return new FMod_Admin(); + } + + public FMod_Admin() {} + + private static Logger LOG = Fuseki.configLog; + + private ArgDecl argAdmin = new ArgDecl(true, "admin"); + private ArgDecl argAdminArea = new ArgDecl(true, "adminArea", "adminBase"); + + @Override + public void serverArgsModify(CmdGeneral fusekiCmd, ServerArgs serverArgs) { + + fusekiCmd.getUsage().startCategory("Admin"); + + ArgModuleGeneral argModule = new ArgModuleGeneral() { + @Override + public void registerWith(CmdGeneral cmdLine) { + cmdLine.add(argAdmin, "--admin", "Enable server admin with user:password"); + cmdLine.add(argAdminArea,"--adminRun", "Directory for server configuration"); + } + @Override + public void processArgs(CmdArgModule cmdLine) {} + }; + argModule.registerWith(fusekiCmd); + } + + @Override + public void serverArgsPrepare(CmdGeneral fusekiCmd, ServerArgs serverArgs) { + String admin = fusekiCmd.getValue(argAdmin); + if ( admin == null ) { + return; + } + + Path directory = null; + String dirStr = fusekiCmd.getValue(argAdminArea); + if ( dirStr != null ) + directory = Path.of(dirStr); + + if ( admin.equals("localhost") ) {} + else { + String pwFile = admin; + } + + if ( directory != null ) { + if ( ! Files.isDirectory(directory) ) + throw new FusekiConfigException("Not a directory: "+dirStr); + + if ( ! Files.isWritable(directory) ) + throw new FusekiConfigException("Not writable: "+dirStr); + } + FusekiApp.FUSEKI_BASE = directory; + } + +// @Override +// public void serverArgsBuilder(FusekiServer.Builder serverBuilder, Model configModel) { +// } + + // ---- + + @Override + public void prepare(FusekiServer.Builder builder, Set datasetNames, Model configModel) { + // Unpack + Path path = FusekiApp.setup(); + + FmtLog.info(LOG, "Fuseki Admin: %s", path); + + // Shiro. + Path shiroIni = path.resolve(FusekiApp.DFT_SHIRO_INI); + if ( Files.exists(shiroIni) ) { + System.setProperty(FusekiApp.envFusekiShiro, shiroIni.toString()); + } else { + FmtLog.info(LOG, "No shiro.ini: dir=%s", path); + } + + String configDir = FusekiApp.dirConfiguration.toString(); + List directoryDatabases = FusekiConfig.readConfigurationDirectory(configDir); + + if ( directoryDatabases.isEmpty() ) + FmtLog.info(LOG, "No databases: dir=%s", configDir); + else { + directoryDatabases.forEach(dap -> FmtLog.info(Fuseki.configLog, "Database: %s", dap.getName())); + } + + directoryDatabases.forEach(db -> { + String dbName = db.getName(); + if ( datasetNames.contains(dbName) ) { + FmtLog.warn(LOG, "Database '%s' already added to the Fuseki server builder", dbName); + // ?? builder.remove(dbName); + } + builder.add(dbName, db.getDataService()); + // ** builder.add(DataAccessPoint); + }); + + // Modify the server to include the admin operations. + // Security is performed by FMod_Shiro. + ActionCtl actionBackup = new ActionBackup(); + builder + .addServlet("/$/datasets/*", new ActionDatasets()) + .addServlet("/$/server", new ActionServerStatus()) + .addServlet("/$/backup/*", actionBackup) + .addServlet("/$/backups/*", actionBackup) + .addServlet("/$/backups-list", new ActionBackupList()) + + // Enables the task subsystem and is also called by enableCompact + .enableTasks(true) + + // Can also be enabled by FMod_UI + .enableStats(true) + .enablePing(true) + .enableCompact(true) + ; + } +} diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/admin/LocalhostOnly.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/admin/LocalhostOnly.java new file mode 100644 index 00000000000..9429b606508 --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/admin/LocalhostOnly.java @@ -0,0 +1,94 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.mod.admin; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; + +import jakarta.servlet.*; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.apache.jena.fuseki.Fuseki; +import org.apache.jena.web.HttpSC; +import org.slf4j.Logger; + +/** + * Responds with HTTP 403 on any denied request. + */ +public class LocalhostOnly implements Filter { + + private static String LOCALHOST_IpV6_a = "[0:0:0:0:0:0:0:1]"; + private static String LOCALHOST_IpV6_b = "0:0:0:0:0:0:0:1"; + // This is what appears in the Chrome developer tools client-side. + // "[0:0:0:0:0:0:0:1]" by the time it arrives here, it is not clear which + // software component is responsible for that. + // To be safe we add "[::1]". + private static String LOCALHOST_IpV6_c = "[::1]"; + private static String LOCALHOST_IpV4 = "127.0.0.1"; // Strictly, 127.*.*.* + + private static final Collection localhosts = new HashSet<>( + Arrays.asList(LOCALHOST_IpV4, LOCALHOST_IpV6_a, LOCALHOST_IpV6_b, LOCALHOST_IpV6_c)); + + private static Logger log = Fuseki.serverLog; + private static final String message = "Access denied : only localhost access allowed"; + + public LocalhostOnly() { } + + // "permit" and "deny" lists + private List secured = Arrays.asList("/$/backup", "/$/compact", "/$/datasets"); + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + try { + HttpServletRequest req = (HttpServletRequest)request; + HttpServletResponse resp = (HttpServletResponse)response; + /** + * Check request from "localhost, else 403. + */ + boolean accept = checkRequest(req, resp); + if ( ! accept ) { + // Log + resp.sendError(HttpSC.FORBIDDEN_403); + return; + } + } catch (Throwable ex) { + log.info("Filter: unexpected exception: "+ex.getMessage(),ex); + } + // Continue. + chain.doFilter(request, response); + } + + public boolean checkRequest(HttpServletRequest req, HttpServletResponse resp) { + String uri = req.getRequestURI(); + for ( String s : secured ) { + if ( uri.startsWith(s) ) { + if ( ! checkLocalhost(req) ) + return false; + } + } + return true; + } + + public static boolean checkLocalhost(HttpServletRequest request) { + return localhosts.contains(request.getRemoteAddr()) ; + } +} diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/blank/FMod_BLANK.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/blank/FMod_BLANK.java new file mode 100644 index 00000000000..4b56d2d7b17 --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/blank/FMod_BLANK.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.mod.blank; + +import org.apache.jena.fuseki.main.sys.FusekiAutoModule; + +/** + * Template. + * + * src/main/resources/META-INF/services/org.apache.jena.fuseki.main.sys.FusekiAutoModule + */ +public class FMod_BLANK implements FusekiAutoModule { + @Override + public String name() { return "BLANK"; } + +// @Override public void start() { } +// @Override public void prepare(FusekiServer.Builder serverBuilder, Set datasetNames, Model configModel) { } +// @Override public void configured(DataAccessPointRegistry dapRegistry, Model configModel) { +// dapRegistry.accessPoints().forEach(accessPoint->configDataAccessPoint(accessPoint, configModel)); +// } +// @Override public void configDataAccessPoint(DataAccessPoint dap, Model configModel) {} +// @Override public void server(FusekiServer server) { } +// @Override public void serverBeforeStarting(FusekiServer server) { } +// @Override public void serverAfterStarting(FusekiServer server) { } +// @Override public void serverStopped(FusekiServer server) { } +// @Override public void stop() {} +} diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/package-info.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/package-info.java new file mode 100644 index 00000000000..7345d58a493 --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/package-info.java @@ -0,0 +1,2 @@ +package org.apache.jena.fuseki.mod; + diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/prometheus/ActionMetrics.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/prometheus/ActionMetrics.java new file mode 100644 index 00000000000..92164563a6a --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/prometheus/ActionMetrics.java @@ -0,0 +1,49 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jena.fuseki.mod.prometheus; + +import org.apache.jena.fuseki.ctl.ActionCtl; +import org.apache.jena.fuseki.metrics.MetricsProviderRegistry; +import org.apache.jena.fuseki.servlets.ActionLib; +import org.apache.jena.fuseki.servlets.HttpAction; +import org.apache.jena.fuseki.servlets.ServletOps; + +public class ActionMetrics extends ActionCtl { + + public ActionMetrics() { super(); } + + @Override + public void execGet(HttpAction action) { + super.executeLifecycle(action); + } + + @Override + public void execOptions(HttpAction action) { + ActionLib.doOptionsGet(action); + ServletOps.success(action); + } + + @Override + public void validate(HttpAction action) {} + + @Override + public void execute(HttpAction action) { + MetricsProviderRegistry.get().scrape( action ); + ServletOps.success(action); + } +} diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/prometheus/FMod_Prometheus.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/prometheus/FMod_Prometheus.java new file mode 100644 index 00000000000..0d51e0bd4fd --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/prometheus/FMod_Prometheus.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.mod.prometheus; + +import java.util.Set; + +import org.apache.jena.fuseki.main.FusekiServer; +import org.apache.jena.fuseki.main.sys.FusekiModule; +import org.apache.jena.fuseki.metrics.MetricsProviderRegistry; +import org.apache.jena.rdf.model.Model; + +/** + * Prometheus Metrics. + * + * PrometheusMetricsProvider + */ +public class FMod_Prometheus implements FusekiModule { + + private static FusekiModule singleton = new FMod_Prometheus(); + public static FusekiModule get() { + return singleton; + } + + public FMod_Prometheus() {} + +// @Override +// public int level() { +// return 5000; +// } +// +// @Override public void start() { +// Fuseki.configLog.info("FMod Prometheus Metrics"); +// MetricsProviderRegistry.set(new PrometheusMetricsProvider()); +// } + + @Override + public String name() { return "FMod Prometheus Metrics"; } + + @Override public void prepare(FusekiServer.Builder serverBuilder, Set datasetNames, Model configModel) { + //MetricsProviderRegistry.set(new PrometheusMetricsProvider()); + serverBuilder.addServlet("/$/metrics", new ActionMetrics()); + } + + @Override public void server(FusekiServer server) { + MetricsProviderRegistry.dataAccessPointMetrics(server.getDataAccessPointRegistry()); + } +} diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/prometheus/PrometheusMetricsProvider.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/prometheus/PrometheusMetricsProvider.java new file mode 100644 index 00000000000..74e13fadc1f --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/prometheus/PrometheusMetricsProvider.java @@ -0,0 +1,59 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.jena.fuseki.mod.prometheus; + +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.prometheusmetrics.PrometheusConfig; +import io.micrometer.prometheusmetrics.PrometheusMeterRegistry; +import jakarta.servlet.ServletOutputStream; +import org.apache.jena.fuseki.metrics.FusekiMetrics; +import org.apache.jena.fuseki.metrics.MetricsProvider; +import org.apache.jena.fuseki.servlets.HttpAction; +import org.apache.jena.fuseki.servlets.ServletOps; +import org.apache.jena.riot.WebContent; + +/** + */ +public class PrometheusMetricsProvider implements MetricsProvider { + + private PrometheusMeterRegistry meterRegistry; + + public PrometheusMetricsProvider() { + meterRegistry = new PrometheusMeterRegistry( PrometheusConfig.DEFAULT ); + meterRegistry.config().commonTags( "application", "fuseki" ); + FusekiMetrics.registerMetrics(meterRegistry); + } + + @Override + public MeterRegistry getMeterRegistry() { + return meterRegistry; + } + + @Override + public void scrape(HttpAction action) { + try (ServletOutputStream out = action.getResponseOutputStream()) { + ServletOps.success(action); + action.setResponseContentType( WebContent.contentTypeTextPlain ); + action.setResponseCharacterEncoding( WebContent.charsetUTF8 ); + + out.write( meterRegistry.scrape().getBytes() ); + } catch (Throwable t) { + ServletOps.errorOccurred( t ); + } + } +} diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/shiro/FMod_Shiro.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/shiro/FMod_Shiro.java new file mode 100644 index 00000000000..e70a44d58d4 --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/shiro/FMod_Shiro.java @@ -0,0 +1,188 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.mod.shiro; + +import java.nio.file.Path; +import java.util.List; +import java.util.Set; + +import jakarta.servlet.Filter; +import org.apache.jena.atlas.io.IOX; +import org.apache.jena.atlas.lib.IRILib; +import org.apache.jena.atlas.lib.Lib; +import org.apache.jena.cmd.ArgDecl; +import org.apache.jena.cmd.CmdException; +import org.apache.jena.cmd.CmdGeneral; +import org.apache.jena.fuseki.Fuseki; +import org.apache.jena.fuseki.FusekiConfigException; +import org.apache.jena.fuseki.main.FusekiServer; +import org.apache.jena.fuseki.main.cmds.ServerArgs; +import org.apache.jena.fuseki.main.sys.FusekiModule; +import org.apache.jena.fuseki.mgt.FusekiApp; +import org.apache.jena.rdf.model.Model; +import org.apache.shiro.web.servlet.ShiroFilter; +import org.eclipse.jetty.ee10.servlet.ServletContextHandler; +import org.eclipse.jetty.ee10.servlet.SessionHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Fuseki Module for Apache Shiro. + *

+ * TODO + * Configuration + */ +public class FMod_Shiro implements FusekiModule { + +// @Override +// public void start() { +// Fuseki.serverLog.info("FMod Shiro"); +// } + // +// @Override +// public int level() { +// return FusekiApp.levelFModShiro; +// } + + @Override + public String name() { + return "FMod Shiro"; + } + + public static FMod_Shiro create() { + return new FMod_Shiro(); + } + + // Assumes the whole system is "Shiro". + // No setup? + + public static final Logger shiroConfigLog = LoggerFactory.getLogger(Fuseki.PATH + ".Shiro"); + + private static List defaultIniFileLocations = List.of("file:shiro.ini", "file:/etc/fuseki/shiro.ini"); + private static List iniFileLocations = null; + + private static ArgDecl argShiroIni = new ArgDecl(true, "shiro", "shiro-ini"); + + // XXX Should be a per build variable. + private String shiroFile = null; + + public FMod_Shiro() { + this(null); + } + + public FMod_Shiro(String shiroFile) { + this.shiroFile = shiroFile; + } + + // ---- If used from the command line + @Override + public void serverArgsModify(CmdGeneral fusekiCmd, ServerArgs serverArgs) { + fusekiCmd.add(argShiroIni); + } + + @Override + public void serverArgsPrepare(CmdGeneral fusekiCmd, ServerArgs serverArgs) { + if ( fusekiCmd.contains(argShiroIni) ) { + shiroFile = fusekiCmd.getValue(argShiroIni); + Path path = Path.of(shiroFile); + IOX.checkReadableFile(path, CmdException::new); + } + } + + // The filter is added in prepare(). + // This allows other Fuseki modules, such as FMod_Admin, to setup shiro.ini. + // FMod_Admin unpacks a default one to $FUSEKI_BASE/shiro.ini (usually "run/shiro.ini") + +// @Override +// public void serverArgsBuilder(FusekiServer.Builder serverBuilder, Model configModel) { +// //Add filter. +// } + + /** + * Determine the Shiro configuration file. + * This applies whether command line arguments used for programmatic setup. + */ + @Override + public void prepare(FusekiServer.Builder serverBuilder, Set datasetNames, Model configModel) { + if ( shiroFile == null ) { + // Environment variable: FUSEKI_SHIRO + shiroFile = Lib.getenv(FusekiApp.envFusekiShiro); + } + + if ( shiroFile == null ) { + return; + } + + if ( shiroFile != null ) { + IOX.checkReadableFile(shiroFile, FusekiConfigException::new); + Filter filter = new FusekiShiroFilter(shiroFile); + // This is a "before" filter. + serverBuilder.addFilter("/*", filter); + } + + // Clear. + shiroFile = null; + } + + /** + * FusekiShiroFilter, includes Shiro initialization. Fuseki is a + * not a webapp so it needs to trigger off servlet initialization. + */ + private static class FusekiShiroFilter extends ShiroFilter { + + private final String shiroInitializationFile; + + FusekiShiroFilter(String filename) { + shiroInitializationFile = IRILib.filenameToIRI(filename); + } + + @Override + public void init() throws Exception { + // Intercept Shiro initialization. + List locations = List.of(); + if ( shiroInitializationFile != null ) { + locations = List.of(shiroInitializationFile); + } + FusekiShiroLib.shiroEnvironment(getServletContext(), locations); + super.init(); + } + } + + @Override + public void serverBeforeStarting(FusekiServer server) { + // Shiro requires a session handler. + // This needs the Jetty server to have been created. + org.eclipse.jetty.server.Server jettyServer = server.getJettyServer(); + try { + ServletContextHandler servletContextHandler = (ServletContextHandler)(jettyServer.getHandler()); + if ( servletContextHandler.getSessionHandler() == null ) { + SessionHandler sessionsHandler = new SessionHandler(); + servletContextHandler.setHandler(sessionsHandler); + } + } catch (RuntimeException ex) { + shiroConfigLog.error("Failed to set a session manager - server aborted"); + throw ex; + } + } + + @Override + public void serverAfterStarting(FusekiServer server) {} + +// @Override public void serverStopped(FusekiServer server) { } +} diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/shiro/FusekiShiroLib.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/shiro/FusekiShiroLib.java new file mode 100644 index 00000000000..c963771efcb --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/shiro/FusekiShiroLib.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.mod.shiro; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + +import jakarta.servlet.ServletContext; +import org.apache.jena.fuseki.FusekiConfigException; +import org.apache.jena.rfc3986.URIScheme; +import org.apache.shiro.lang.io.ResourceUtils; +import org.apache.shiro.web.env.EnvironmentLoaderListener; + +/*package*/ class FusekiShiroLib { + static void shiroEnvironment(ServletContext servletContext, List possibleShiroIniFiles) { + // Shiro environment initialization, done here because we don't have webapp listeners. + EnvironmentLoaderListener shiroListener = new ShiroEnvironmentLoaderListener(possibleShiroIniFiles); + try { + shiroListener.initEnvironment(servletContext); + } catch (org.apache.shiro.config.ConfigurationException ex) { + ShiroEnvironmentLoaderListener.shiroConfigLog.error("Failed to initialize Shiro: "+ex.getMessage()); + throw new FusekiConfigException(ex.getMessage()); + } + } + + private static String fileSchemePrefix = URIScheme.FILE.getPrefix(); + + /** Look for a Shiro ini file, returning the first found, or return null */ + static String huntForShiroIni(List locations) { + for ( String loc : locations ) { + // If file:, look for that file. + if ( loc.startsWith(fileSchemePrefix) ) { + Path p = Path.of(loc.substring(fileSchemePrefix.length())); + if ( Files.exists(p) ) + return loc; + // Ignore. + continue; + } + // No scheme. May be a classpath resource. + if ( ResourceUtils.resourceExists(loc) ) + return loc; + } + return null; + } +} diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/shiro/ShiroEnvironmentLoaderListener.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/shiro/ShiroEnvironmentLoaderListener.java new file mode 100644 index 00000000000..c99f47aee71 --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/shiro/ShiroEnvironmentLoaderListener.java @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.mod.shiro; + +import static java.lang.String.format; + +import java.util.Arrays; +import java.util.List; + +import org.apache.shiro.web.env.EnvironmentLoaderListener; +import org.apache.shiro.web.env.ResourceBasedWebEnvironment; +import org.apache.shiro.web.env.WebEnvironment; +import org.slf4j.Logger; + +/** + * A Shiro {@link EnvironmentLoaderListener} that supports multiple possible + * locations for a {@code shiro.ini} file. It will return the first found in a list + * of possible file names. + */ +class ShiroEnvironmentLoaderListener extends EnvironmentLoaderListener{ + + public static final Logger shiroConfigLog = FMod_Shiro.shiroConfigLog; + + private List locations; + + /*package*/ ShiroEnvironmentLoaderListener(List locations) { + this.locations = locations; + } + + /** + * When given multiple locations for the shiro.ini file, and + * if a {@link ResourceBasedWebEnvironment}, check the list of configuration + * locations, testing whether the name identified an existing resource. + * For the first resource name found to exist, reset the {@link ResourceBasedWebEnvironment} + * to name that resource alone so the normal Shiro initialization executes. + */ + @Override + protected void customizeEnvironment(WebEnvironment environment) { + if ( locations == null ) + return; + + // Look for shiro.ini + if ( environment instanceof ResourceBasedWebEnvironment ) { + ResourceBasedWebEnvironment env = (ResourceBasedWebEnvironment)environment; + String[] configLocations = env.getConfigLocations(); + if ( configLocations != null && configLocations.length > 0 ) { + // Set some other way. + shiroConfigLog.info(format("Shiro file resource %s", Arrays.asList(configLocations))); + return; + } + String loc = FusekiShiroLib.huntForShiroIni(locations); + if ( loc == null ) { + shiroConfigLog.info(format("No Shiro file found (tried: %s)", locations)); + return; + } + shiroConfigLog.info("Shiro INI: "+loc); + String[] configLocationsHere = new String[] {loc}; + env.setConfigLocations(configLocationsHere); + } + } +} \ No newline at end of file diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/ui/ActionStats.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/ui/ActionStats.java new file mode 100644 index 00000000000..51953d15682 --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/ui/ActionStats.java @@ -0,0 +1,165 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.mod.ui; + +import static java.lang.String.format; + +import java.util.List; + +import org.apache.commons.lang3.StringUtils; +import org.apache.jena.atlas.json.JsonBuilder; +import org.apache.jena.atlas.json.JsonObject; +import org.apache.jena.atlas.json.JsonValue; +import org.apache.jena.fuseki.ctl.ActionContainerItem; +import org.apache.jena.fuseki.server.*; +import org.apache.jena.fuseki.servlets.HttpAction; +import org.apache.jena.fuseki.servlets.ServletOps; + +/** + * JSON output for stats, than JSON {@link ActionStats}. + * Separate because content negotiation for known JSON may + * be a simple "Accept: {@literal *}/*}". + *

+ *

+ *   /$/serviceurl -- all datatsets
+ *   /$/serviceurl/name -- one dataset
+ * 
+ * + */ +public class ActionStats extends ActionContainerItem +{ + // For endpoint with "" as name. + private static String emptyNameKeyPrefix = "_"; + + public ActionStats() { super(); } + + @Override + public void validate(HttpAction action) {} + + @Override + protected JsonValue execPostContainer(HttpAction action) { + return execCommonContainer(action); + } + + @Override + protected JsonValue execPostItem(HttpAction action) { + return execCommonItem(action); + } + + @Override + protected JsonValue execGetContainer(HttpAction action) { + return execCommonContainer(action); + } + + @Override + protected JsonValue execGetItem(HttpAction action) { + return execCommonItem(action); + } + + // This does not consult the system database for dormant etc. + protected JsonValue execCommonContainer(HttpAction action) { + if ( action.verbose ) + action.log.info(format("[%d] GET stats all", action.id)); + return generateStats(action.getDataAccessPointRegistry()); + } + + public static JsonObject generateStats(DataAccessPointRegistry registry) { + JsonBuilder builder = new JsonBuilder(); + builder.startObject("top"); + builder.key(ServerConst.datasets); + builder.startObject("datasets"); + registry.forEach((name, access)->statsDataset(builder, access)); + builder.finishObject("datasets"); + builder.finishObject("top"); + return builder.build().getAsObject(); + } + + protected JsonValue execCommonItem(HttpAction action) { + String datasetPath = getItemDatasetName(action); + if ( action.verbose ) + action.log.info(format("[%d] GET stats dataset %s", action.id, datasetPath)); + + JsonBuilder builder = new JsonBuilder(); + DataAccessPoint dap = getItemDataAccessPoint(action, datasetPath); + if ( dap == null ) + ServletOps.errorNotFound(datasetPath); + builder.startObject("TOP"); + + builder.key(ServerConst.datasets); + builder.startObject("datasets"); + statsDataset(builder, datasetPath, action.getDataAccessPointRegistry()); + builder.finishObject("datasets"); + + builder.finishObject("TOP"); + return builder.build(); + } + + public static JsonObject generateStats(DataAccessPoint access) { + JsonBuilder builder = new JsonBuilder(); + statsDataset(builder, access); + return builder.build().getAsObject(); + } + + private void statsDataset(JsonBuilder builder, String name, DataAccessPointRegistry registry) { + DataAccessPoint access = registry.get(name); + statsDataset(builder, access); + } + + private static void statsDataset(JsonBuilder builder, DataAccessPoint access) { + // Object started + builder.key(access.getName()); + DataService dSrv = access.getDataService(); + builder.startObject("counters"); + + builder.key(CounterName.Requests.getName()).value(dSrv.getCounters().value(CounterName.Requests)); + builder.key(CounterName.RequestsGood.getName()).value(dSrv.getCounters().value(CounterName.RequestsGood)); + builder.key(CounterName.RequestsBad.getName()).value(dSrv.getCounters().value(CounterName.RequestsBad)); + + builder.key(ServerConst.endpoints).startObject("endpoints"); + int unique = 0; + for ( Operation operName : dSrv.getOperations() ) { + List endpoints = access.getDataService().getEndpoints(operName); + for ( Endpoint endpoint : endpoints ) { + String k = endpoint.getName(); + if ( StringUtils.isEmpty(k) ) + k = emptyNameKeyPrefix+(++unique); + // Endpoint names are unique for a given service. + builder.key(k); + builder.startObject(); + + operationCounters(builder, endpoint); + builder.key(ServerConst.operation).value(operName.getJsonName()); + builder.key(ServerConst.description).value(operName.getDescription()); + + builder.finishObject(); + } + } + builder.finishObject("endpoints"); + builder.finishObject("counters"); + } + + private static void operationCounters(JsonBuilder builder, Endpoint operation) { + for (CounterName cn : operation.getCounters().counters()) { + Counter c = operation.getCounters().get(cn); + builder.key(cn.getName()).value(c.value()); + } + } +} + + diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/ui/ActionStatsTxt.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/ui/ActionStatsTxt.java new file mode 100644 index 00000000000..7b2bbe7ab67 --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/ui/ActionStatsTxt.java @@ -0,0 +1,189 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.mod.ui; + +import static java.lang.String.format; + +import java.io.IOException; +import java.util.Iterator; + +import jakarta.servlet.ServletOutputStream; +import jakarta.servlet.http.HttpServletResponse; +import org.apache.jena.atlas.json.JsonValue; +import org.apache.jena.fuseki.ctl.ActionContainerItem; +import org.apache.jena.fuseki.server.*; +import org.apache.jena.fuseki.servlets.HttpAction; +import org.apache.jena.fuseki.servlets.ServletOps; +import org.apache.jena.riot.WebContent; + +/** + * Text output for stats, than JSON {@link ActionStats}. + * Separate because content negotiation for known JSON may + * be a simple "Accept: {@literal *}/*}". + *

+ *

+ *   /$/serviceurl -- all datatsets
+ *   /$/serviceurl/name -- one dataset
+ * 
+ * + */ +public class ActionStatsTxt extends ActionContainerItem { + + public ActionStatsTxt() { super(); } + + @Override + public void validate(HttpAction action) {} + + // Stats as plain text. + // Not simple conneg because JSON can be wanted but the HRRP is "Accept */*" + // Probably replace with a "simple" view. + + // All datasets + @Override + protected JsonValue execGetContainer(HttpAction action) { + execContainer(action); + return null; + } + + // One dataset + @Override + protected JsonValue execGetItem(HttpAction action) { + execItem(action); + return null; + } + + // POST is GET (but know to be fresh) + @Override + protected JsonValue execPostContainer(HttpAction action) { + execContainer(action); + return null; + } + + @Override + protected JsonValue execPostItem(HttpAction action) { + execItem(action); + return null; + } + + private void execContainer(HttpAction action) { + if ( action.verbose ) + action.log.info(format("[%d] GET stats text", action.id)); + try { + statsTxt(action.getResponse(), action.getDataAccessPointRegistry()); + } catch (IOException ex) { + action.log.warn(format("[%d] GET stats text: IO error: %s", action.id, ex.getMessage())); + } + } + + private void execItem(HttpAction action) { + String name = getItemDatasetName(action); + if ( name == null ) + ServletOps.errorBadRequest("No dataset name"); + DataAccessPoint desc = action.getDataAccessPointRegistry().get(name); + if ( desc == null ) + ServletOps.errorBadRequest("No such dataset: "+name); + try { + HttpServletResponse resp = action.getResponse(); + ServletOutputStream out = resp.getOutputStream(); + resp.setContentType(WebContent.contentTypeTextPlain); + resp.setCharacterEncoding(WebContent.charsetUTF8); + statsTxt(out, desc); + } catch (IOException ex) { + action.log.warn(format("[%d] GET stats text: IO error: %s", action.id, ex.getMessage())); + } + } + + private void statsTxt(HttpServletResponse resp, DataAccessPointRegistry registry) throws IOException { + ServletOutputStream out = resp.getOutputStream(); + resp.setContentType(WebContent.contentTypeTextPlain); + resp.setCharacterEncoding(WebContent.charsetUTF8); + + Iterator iter = registry.keys().iterator(); + while (iter.hasNext()) { + String ds = iter.next(); + DataAccessPoint desc = registry.get(ds); + statsTxt(out, desc); + if ( iter.hasNext() ) + out.println(); + } + out.flush(); + } + + private void statsTxt(ServletOutputStream out, DataAccessPoint desc) throws IOException { + DataService dSrv = desc.getDataService(); + out.println("Dataset: " + desc.getName()); + out.println(" Requests = " + dSrv.getCounters().value(CounterName.Requests)); + out.println(" Good = " + dSrv.getCounters().value(CounterName.RequestsGood)); + out.println(" Bad = " + dSrv.getCounters().value(CounterName.RequestsBad)); + + if ( desc.getDataService().hasOperation(Operation.Query)) { + out.println(" SPARQL Query:"); + out.println(" Request = " + counter(dSrv, Operation.Query, CounterName.Requests)); + out.println(" Good = " + counter(dSrv, Operation.Query, CounterName.RequestsGood)); + out.println(" Bad requests = " + counter(dSrv, Operation.Query, CounterName.RequestsBad)); + out.println(" Timeouts = " + counter(dSrv, Operation.Query, CounterName.QueryTimeouts)); + out.println(" Bad exec = " + counter(dSrv, Operation.Query, CounterName.QueryExecErrors)); + //out.println(" IO Errors = " + counter(dSrv, Operation.Query, CounterName.QueryIOErrors)); + } + + if ( desc.getDataService().hasOperation(Operation.Update)) { + out.println(" SPARQL Update:"); + out.println(" Request = " + counter(dSrv, Operation.Update, CounterName.Requests)); + out.println(" Good = " + counter(dSrv, Operation.Update, CounterName.RequestsGood)); + out.println(" Bad requests = " + counter(dSrv, Operation.Update, CounterName.RequestsBad)); + out.println(" Bad exec = " + counter(dSrv, Operation.Update, CounterName.UpdateExecErrors)); + } + + if ( desc.getDataService().hasOperation(Operation.Upload)) { + out.println(" Upload:"); + out.println(" Requests = " + counter(dSrv, Operation.Upload, CounterName.Requests)); + out.println(" Good = " + counter(dSrv, Operation.Upload, CounterName.RequestsGood)); + out.println(" Bad = " + counter(dSrv, Operation.Upload, CounterName.RequestsBad)); + } + + if ( desc.getDataService().hasOperation(Operation.GSP_R) || desc.getDataService().hasOperation(Operation.GSP_RW) ) { + out.println(" SPARQL Graph Store Protocol:"); + out.println(" GETs = " + gspValue(dSrv, CounterName.HTTPget) + " (good=" + gspValue(dSrv, CounterName.HTTPgetGood) + + "/bad=" + gspValue(dSrv, CounterName.HTTPgetBad) + ")"); + if ( desc.getDataService().hasOperation(Operation.GSP_RW) ) { + out.println(" PUTs = " + gspValue(dSrv, CounterName.HTTPput) + " (good=" + gspValue(dSrv, CounterName.HTTPputGood) + + "/bad=" + gspValue(dSrv, CounterName.HTTPputBad) + ")"); + out.println(" POSTs = " + gspValue(dSrv, CounterName.HTTPpost) + " (good=" + gspValue(dSrv, CounterName.HTTPpostGood) + + "/bad=" + gspValue(dSrv, CounterName.HTTPpostBad) + ")"); + out.println(" PATCHs = " + gspValue(dSrv, CounterName.HTTPpatch) + " (good=" + gspValue(dSrv, CounterName.HTTPpatchGood) + + "/bad=" + gspValue(dSrv, CounterName.HTTPpatchBad) + ")"); + out.println(" DELETEs = " + gspValue(dSrv, CounterName.HTTPdelete) + " (good=" + gspValue(dSrv, CounterName.HTTPdeleteGood) + + "/bad=" + gspValue(dSrv, CounterName.HTTPdeleteBad) + ")"); + } + out.println(" HEADs = " + gspValue(dSrv, CounterName.HTTPhead) + " (good=" + gspValue(dSrv, CounterName.HTTPheadGood) + + "/bad=" + gspValue(dSrv, CounterName.HTTPheadBad) + ")"); + } + } + + private long counter(DataService dSrv, Operation operation, CounterName cName) { + return 0; + } + + private long gspValue(DataService dSrv, CounterName cn) { + return counter(dSrv, Operation.GSP_RW, cn) + + counter(dSrv, Operation.GSP_R, cn); + } +} + + diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/ui/FMod_UI.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/ui/FMod_UI.java new file mode 100644 index 00000000000..a46ec343aa5 --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/ui/FMod_UI.java @@ -0,0 +1,185 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.mod.ui; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.jena.atlas.io.IOX; +import org.apache.jena.atlas.logging.FmtLog; +import org.apache.jena.cmd.ArgDecl; +import org.apache.jena.cmd.CmdException; +import org.apache.jena.cmd.CmdGeneral; +import org.apache.jena.fuseki.Fuseki; +import org.apache.jena.fuseki.FusekiConfigException; +import org.apache.jena.fuseki.main.FusekiServer; +import org.apache.jena.fuseki.main.cmds.ServerArgs; +import org.apache.jena.fuseki.main.sys.FusekiModule; +import org.apache.jena.fuseki.mgt.FusekiApp; +import org.apache.jena.fuseki.validation.DataValidator; +import org.apache.jena.fuseki.validation.IRIValidator; +import org.apache.jena.fuseki.validation.QueryValidator; +import org.apache.jena.fuseki.validation.UpdateValidator; +import org.apache.jena.rdf.model.Model; +import org.eclipse.jetty.util.resource.Resource; +import org.eclipse.jetty.util.resource.ResourceFactory; +import org.slf4j.Logger; + +public class FMod_UI implements FusekiModule { + + // Only one module needed - it is stateless. + private static FusekiModule singleton = new FMod_UI(); + public static FusekiModule get() { + return singleton; + } + + public FMod_UI() {} + + private static Logger LOG = Fuseki.configLog; + +// // After FMod_admin +// @Override +// public int level() { +// return FusekiApp.levelFModUI; +// } + + private static ArgDecl argUIFiles = new ArgDecl(true, "ui"); + private String uiAppLocation = null; + /** Java resource name used to find the UI files. */ + private static String resourceNameUI = "webapp"; + /** Directory name of the root of UI files with {@code FUSEKI_BASE} */ + private static String directoryNameUI = "webapp"; + + @Override + public String name() { + return "FMod UI"; + } + + // ---- If used from the command line + @Override + public void serverArgsModify(CmdGeneral fusekiCmd, ServerArgs serverArgs) { + fusekiCmd.add(argUIFiles); + } + + @Override + public void serverArgsPrepare(CmdGeneral fusekiCmd, ServerArgs serverArgs) { + if ( fusekiCmd.contains(argUIFiles) ) { + uiAppLocation = fusekiCmd.getValue(argUIFiles); + IOX.checkReadableDirectory(uiAppLocation, CmdException::new); + } + } + + @Override + public void prepare(FusekiServer.Builder builder, Set datasetNames, Model configModel) { + if ( builder.staticFileBase() != null ) { + FmtLog.warn(LOG, "Static content location has already been set: %s", builder.staticFileBase()); + return; + } + + if ( uiAppLocation == null ) { + uiAppLocation = findFusekiApp(); + if ( uiAppLocation == null ) { + LOG.warn("No Static content location has been found"); + return; + } + } else { + FmtLog.info(LOG, "UI file area = %s", uiAppLocation); + } + + builder.staticFileBase(uiAppLocation) // Set the UI files area. + .addServlet("/$/validate/query", new QueryValidator()) + .addServlet("/$/validate/update", new UpdateValidator()) + .addServlet("/$/validate/iri", new IRIValidator()) + .addServlet("/$/validate/data", new DataValidator()) + .enableStats(true); + // LOG.info("Fuseki UI loaded"); + } + + /** + * Locate the UI files. + *
    + *
  1. Command line name of a directory
  2. + *
  3. {@code $FUSEKI_BASE/webapp}
  4. + *
  5. Classpath java resource {@code webapp}
  6. + *
      + */ + private String findFusekiApp() { + // 1:: Command line setting. + if ( uiAppLocation != null ) + return uiAppLocation; + + // 2:: $FUSEKI_BASE/webapp + // If the FUSEKI_BASE does not exists, it is created later in FMod_admin.prepare + // and does not include Fuseki app. + String x = fromPath(FusekiApp.FUSEKI_BASE, directoryNameUI); + if ( x != null ) { + LOG.info("Fuseki UI - path resource: "+x); + return x; + } + + // 3:: From a jar. + // Format jar:file:///.../jena-fuseki-ui-VERSION.jar!/webapp/" + String r = fromClasspath(resourceNameUI); + if ( r != null ) { + // Simplify name. + String displayName = loggingName(r); + FmtLog.info(LOG, "UI Base = %s", displayName); + return r; + } + // Bad! + return null; + } + + // Look for "$resourceName" on the classpath. + private static String fromClasspath(String resourceName) { + // Jetty 12.0.15 => warning "Leaked mount" + // Logger : "org.eclipse.jetty.util.resource.ResourceFactory" + //ResourceFactory resourceFactory = ResourceFactory.root(); + + ResourceFactory resourceFactory = ResourceFactory.closeable(); + Resource resource = resourceFactory.newClassLoaderResource(resourceName); + if ( resource != null ) + return resource.getURI().toString(); + return null; + } + + // Look for "$path/$resourceName" + private static String fromPath(Path path, String resourceName) { + if ( path != null ) { + Path path2 = path.resolve(resourceName); + if ( Files.exists(path2) ) { + IOX.checkReadableDirectory(path2, FusekiConfigException::new); + return path2.toAbsolutePath().toString(); + } + } + return null; + } + + private static Pattern regex = Pattern.compile("([^/]*)!"); + + private String loggingName(String r) { + Matcher matcher = regex.matcher(r); + if ( ! matcher.find() ) + return r; + return matcher.group(1); + } +} diff --git a/jena-fuseki2/jena-fuseki-main/src/main/resources/org/apache/jena/fuseki/server/config.ttl b/jena-fuseki2/jena-fuseki-main/src/main/resources/org/apache/jena/fuseki/server/config.ttl new file mode 100644 index 00000000000..e69de29bb2d diff --git a/jena-fuseki2/jena-fuseki-main/src/main/resources/org/apache/jena/fuseki/server/shiro.ini b/jena-fuseki2/jena-fuseki-main/src/main/resources/org/apache/jena/fuseki/server/shiro.ini new file mode 100644 index 00000000000..72fa8942eb2 --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/src/main/resources/org/apache/jena/fuseki/server/shiro.ini @@ -0,0 +1,38 @@ +# Licensed under the terms of http://www.apache.org/licenses/LICENSE-2.0 + +[main] +# Development +ssl.enabled = false + +plainMatcher=org.apache.shiro.authc.credential.SimpleCredentialsMatcher +localhostFilter=org.apache.jena.fuseki.authz.LocalhostFilter + +[users] +# Implicitly adds "iniRealm = org.apache.shiro.realm.text.IniRealm" +## admin=pw +user1=passwd1 + +[roles] + +[urls] +## Control functions open to anyone +/$/status = anon +/$/server = anon +/$/ping = anon +/$/metrics = anon + +## and the rest are restricted to localhost. +/$/** = localhostFilter + + +## If you want simple, basic authentication user/password +## on the operations, +## 1 - set a better password in [users] above. +## 2 - comment out the "/$/** = localhost" line and use: +##/$/** = authcBasic,user[admin] + +## or to allow any access. +##/$/** = anon + +# Everything else +/**=anon diff --git a/jena-fuseki2/jena-fuseki-main/src/main/resources/org/apache/jena/fuseki/server/templates/config-mem b/jena-fuseki2/jena-fuseki-main/src/main/resources/org/apache/jena/fuseki/server/templates/config-mem new file mode 100644 index 00000000000..6f42e594b4c --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/src/main/resources/org/apache/jena/fuseki/server/templates/config-mem @@ -0,0 +1,29 @@ +# Licensed under the terms of http://www.apache.org/licenses/LICENSE-2.0 + +@prefix : <#> . +@prefix fuseki: . +@prefix rdf: . + +@prefix rdfs: . +@prefix tdb: . +@prefix ja: . + +## --------------------------------------------------------------- +## Updatable in-memory dataset. + +<#service1> rdf:type fuseki:Service ; + # URI of the dataset -- http://host:port/{NAME} + fuseki:name "{NAME}" ; + fuseki:serviceQuery "" ; + fuseki:serviceQuery "sparql" ; + fuseki:serviceQuery "query" ; + fuseki:serviceUpdate "" ; + fuseki:serviceUpdate "update" ; + fuseki:serviceUpload "upload" ; + fuseki:serviceReadWriteGraphStore "data" ; + fuseki:serviceReadGraphStore "get" ; + fuseki:dataset <#dataset> ; + . + +# Transactional, in-memory dataset. Initially empty. +<#dataset> rdf:type ja:DatasetTxnMem . diff --git a/jena-fuseki2/jena-fuseki-main/src/main/resources/org/apache/jena/fuseki/server/templates/config-tdb b/jena-fuseki2/jena-fuseki-main/src/main/resources/org/apache/jena/fuseki/server/templates/config-tdb new file mode 100644 index 00000000000..9b185884e2b --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/src/main/resources/org/apache/jena/fuseki/server/templates/config-tdb @@ -0,0 +1,32 @@ +# Licensed under the terms of http://www.apache.org/licenses/LICENSE-2.0 + +@prefix : <#> . +@prefix fuseki: . +@prefix rdf: . +@prefix rdfs: . +@prefix tdb: . +@prefix ja: . + +## --------------------------------------------------------------- +## Updatable TDB dataset with all services enabled. + +<#service_tdb_all> rdf:type fuseki:Service ; + rdfs:label "TDB {NAME}" ; + fuseki:name "{NAME}" ; + fuseki:serviceQuery "" ; + fuseki:serviceQuery "query" ; + fuseki:serviceQuery "sparql" ; + fuseki:serviceUpdate "" ; + fuseki:serviceUpdate "update" ; + fuseki:serviceUpload "upload" ; + fuseki:serviceReadWriteGraphStore "data" ; + # A separate read-only graph store endpoint: + fuseki:serviceReadGraphStore "get" ; + fuseki:dataset <#tdb_dataset_readwrite> ; + . + +<#tdb_dataset_readwrite> rdf:type tdb:DatasetTDB ; + tdb:location "{FUSEKI_BASE}/databases/{NAME}" ; + ##ja:context [ ja:cxtName "arq:queryTimeout" ; ja:cxtValue "3000" ] ; + ##tdb:unionDefaultGraph true ; + . diff --git a/jena-fuseki2/jena-fuseki-main/src/main/resources/org/apache/jena/fuseki/server/templates/config-tdb-dir b/jena-fuseki2/jena-fuseki-main/src/main/resources/org/apache/jena/fuseki/server/templates/config-tdb-dir new file mode 100644 index 00000000000..f22de072b53 --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/src/main/resources/org/apache/jena/fuseki/server/templates/config-tdb-dir @@ -0,0 +1,30 @@ +# Licensed under the terms of http://www.apache.org/licenses/LICENSE-2.0 + +@prefix : <#> . +@prefix fuseki: . +@prefix rdf: . +@prefix rdfs: . +@prefix tdb: . +@prefix ja: . + +## --------------------------------------------------------------- +## Updatable TDB dataset with all services enabled. + +<#service_tdb_all> rdf:type fuseki:Service ; + rdfs:label "TDB {NAME}" ; + fuseki:name "{NAME}" ; + fuseki:serviceQuery "" ; + fuseki:serviceQuery "sparql" ; + fuseki:serviceQuery "query" ; + fuseki:serviceUpdate "" ; + fuseki:serviceUpdate "update" ; + fuseki:serviceUpload "upload" ; + fuseki:serviceReadWriteGraphStore "data" ; + fuseki:serviceReadGraphStore "get" ; + fuseki:dataset <#tdb_dataset_readwrite> ; + . + +<#tdb_dataset_readwrite> rdf:type tdb:DatasetTDB ; + tdb:location "{DIR}" ; + ##tdb:unionDefaultGraph true ; + . diff --git a/jena-fuseki2/jena-fuseki-main/src/main/resources/org/apache/jena/fuseki/server/templates/config-tdb-mem b/jena-fuseki2/jena-fuseki-main/src/main/resources/org/apache/jena/fuseki/server/templates/config-tdb-mem new file mode 100644 index 00000000000..6b9c65653bd --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/src/main/resources/org/apache/jena/fuseki/server/templates/config-tdb-mem @@ -0,0 +1,30 @@ +# Licensed under the terms of http://www.apache.org/licenses/LICENSE-2.0 + +@prefix : <#> . +@prefix fuseki: . +@prefix rdf: . +@prefix rdfs: . +@prefix tdb: . +@prefix ja: . + +## --------------------------------------------------------------- +## Updatable TDB dataset im-memory with all services enabled. + +<#service_tdb_all> rdf:type fuseki:Service ; + rdfs:label "TDB {NAME}" ; + fuseki:name "{NAME}" ; + fuseki:serviceQuery "" ; + fuseki:serviceQuery "sparql" ; + fuseki:serviceQuery "query" ; + fuseki:serviceUpdate "" ; + fuseki:serviceUpdate "update" ; + fuseki:serviceUpload "upload" ; + fuseki:serviceReadWriteGraphStore "data" ; + fuseki:serviceReadGraphStore "get" ; + fuseki:dataset <#tdb_dataset_readwrite> ; + . + +<#tdb_dataset_readwrite> rdf:type tdb:DatasetTDB ; + tdb:location "--mem--" ; + ## tdb:unionDefaultGraph true ; + . diff --git a/jena-fuseki2/jena-fuseki-main/src/main/resources/org/apache/jena/fuseki/server/templates/config-tdb2 b/jena-fuseki2/jena-fuseki-main/src/main/resources/org/apache/jena/fuseki/server/templates/config-tdb2 new file mode 100644 index 00000000000..3fa7c166089 --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/src/main/resources/org/apache/jena/fuseki/server/templates/config-tdb2 @@ -0,0 +1,32 @@ +# Licensed under the terms of http://www.apache.org/licenses/LICENSE-2.0 + +PREFIX : <#> +PREFIX fuseki: +PREFIX rdf: +PREFIX rdfs: +PREFIX tdb2: +PREFIX ja: + +## --------------------------------------------------------------- +## Updatable TDB dataset with all services enabled. + +<#service_tdb_all> rdf:type fuseki:Service ; + rdfs:label "TDB2 {NAME}" ; + fuseki:name "{NAME}" ; + fuseki:serviceQuery "" ; + fuseki:serviceQuery "sparql" ; + fuseki:serviceQuery "query" ; + fuseki:serviceUpdate "" ; + fuseki:serviceUpdate "update" ; + fuseki:serviceUpload "upload" ; + fuseki:serviceReadWriteGraphStore "data" ; + fuseki:serviceReadGraphStore "get" ; + fuseki:dataset <#tdb_dataset_readwrite> ; + + . + +<#tdb_dataset_readwrite> rdf:type tdb2:DatasetTDB2 ; + tdb2:location "{FUSEKI_BASE}/databases/{NAME}" ; + ##ja:context [ ja:cxtName "arq:queryTimeout" ; ja:cxtValue "3000" ] ; + ##tdb2:unionDefaultGraph true ; + . diff --git a/jena-fuseki2/jena-fuseki-main/src/main/resources/org/apache/jena/fuseki/server/templates/config-tdb2-dir b/jena-fuseki2/jena-fuseki-main/src/main/resources/org/apache/jena/fuseki/server/templates/config-tdb2-dir new file mode 100644 index 00000000000..3312b9348cd --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/src/main/resources/org/apache/jena/fuseki/server/templates/config-tdb2-dir @@ -0,0 +1,31 @@ +# Licensed under the terms of http://www.apache.org/licenses/LICENSE-2.0 + +PREFIX : <#> +PREFIX fuseki: +PREFIX rdf: +PREFIX rdfs: +PREFIX tdb2: +PREFIX ja: + +## --------------------------------------------------------------- +## Updatable TDB2 dataset with all services enabled. + +<#service_tdb_all> rdf:type fuseki:Service ; + rdfs:label "TDB2 {NAME}" ; + fuseki:name "{NAME}" ; + fuseki:serviceQuery "" ; + fuseki:serviceQuery "sparql" ; + fuseki:serviceQuery "query" ; + fuseki:serviceUpdate "" ; + fuseki:serviceUpdate "update" ; + fuseki:serviceUpload "upload" ; + fuseki:serviceReadWriteGraphStore "data" ; + fuseki:serviceReadGraphStore "get" ; + fuseki:dataset <#tdb_dataset_readwrite> ; + + . + +<#tdb_dataset_readwrite> rdf:type tdb2:DatasetTDB2 ; + tdb2:location "{DIR}" ; + ##tdb2:unionDefaultGraph true ; + . diff --git a/jena-fuseki2/jena-fuseki-main/src/main/resources/org/apache/jena/fuseki/server/templates/config-tdb2-mem b/jena-fuseki2/jena-fuseki-main/src/main/resources/org/apache/jena/fuseki/server/templates/config-tdb2-mem new file mode 100644 index 00000000000..de362d09d8b --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/src/main/resources/org/apache/jena/fuseki/server/templates/config-tdb2-mem @@ -0,0 +1,30 @@ +# Licensed under the terms of http://www.apache.org/licenses/LICENSE-2.0 + +PREFIX : <#> +PREFIX fuseki: +PREFIX rdf: +PREFIX rdfs: +PREFIX tdb2: +PREFIX ja: + +## --------------------------------------------------------------- +## Updatable TDB2 dataset im-memory with all services enabled. + +<#service_tdb_all> rdf:type fuseki:Service ; + rdfs:label "TDB2 {NAME}" ; + fuseki:name "{NAME}" ; + fuseki:serviceQuery "" ; + fuseki:serviceQuery "sparql" ; + fuseki:serviceQuery "query" ; + fuseki:serviceUpdate "" ; + fuseki:serviceUpdate "update" ; + fuseki:serviceUpload "upload" ; + fuseki:serviceReadWriteGraphStore "data" ; + fuseki:serviceReadGraphStore "get" ; + fuseki:dataset <#tdb_dataset_readwrite> ; + . + +<#tdb_dataset_readwrite> rdf:type tdb2:DatasetTDB2 ; + tdb2:location "--mem--" ; + ## tdb2:unionDefaultGraph true ; + . diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/TC_FusekiServer.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/TC_FusekiServer.java new file mode 100644 index 00000000000..fd30ecb9460 --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/TC_FusekiServer.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki; + +import org.junit.platform.suite.api.SelectClasses; +import org.junit.platform.suite.api.Suite; + +import org.apache.jena.fuseki.main.TC_FusekiMain; +import org.apache.jena.fuseki.mod.TC_FusekiMods; + +@Suite +@SelectClasses({ + TC_FusekiMain.class, + TC_FusekiMods.class +}) +public class TC_FusekiServer {} diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/TC_FusekiMods.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/TC_FusekiMods.java new file mode 100644 index 00000000000..d2fafda62f6 --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/TC_FusekiMods.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.mod; + +import org.junit.platform.suite.api.SelectClasses; +import org.junit.platform.suite.api.Suite; + +import org.apache.jena.fuseki.mod.admin.TS_FusekiServerApp; +import org.apache.jena.fuseki.mod.metrics.TestModPrometheus; +import org.apache.jena.fuseki.mod.shiro.TestModShiro; + +@Suite +@SelectClasses({ + TS_FusekiServerApp.class, + // UI + + // Prometheus + TestModPrometheus.class, + // Apache Shiro + TestModShiro.class +}) +public class TC_FusekiMods { + public TC_FusekiMods() {} +} diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/TestFusekiServer.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/TestFusekiServer.java new file mode 100644 index 00000000000..e0a6b2392df --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/TestFusekiServer.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.mod; + +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +import org.junit.jupiter.api.Test; + +import org.apache.jena.fuseki.main.FusekiServer; +import org.apache.jena.fuseki.run.FusekiModServer; + +/** + * Test for the whole Fuseki server, not components. + */ +public class TestFusekiServer { + @Test public void run() { + // Setup + FusekiServer server = FusekiModServer.runAsync("--port=0", "--empty"); + int port = server.getPort(); + assertNotEquals(0, port, "Port is zero after async start"); + + } +} diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/admin/TS_FusekiServerApp.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/admin/TS_FusekiServerApp.java new file mode 100644 index 00000000000..b36360b8cba --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/admin/TS_FusekiServerApp.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.mod.admin; + +import org.junit.platform.suite.api.SelectClasses; +import org.junit.platform.suite.api.Suite; + +@Suite +@SelectClasses({ + TestAdmin.class, + TestFusekiReload.class, + TestTemplateAddDataset.class, +}) +public class TS_FusekiServerApp { + +} diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/admin/TestAdmin.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/admin/TestAdmin.java new file mode 100644 index 00000000000..df0f6bafddd --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/admin/TestAdmin.java @@ -0,0 +1,836 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.mod.admin; + +import static org.apache.jena.fuseki.mgt.ServerMgtConst.*; +import static org.apache.jena.fuseki.server.ServerConst.opPing; +import static org.apache.jena.fuseki.server.ServerConst.opStats; +import static org.apache.jena.http.HttpOp.*; +import static org.awaitility.Awaitility.await; +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assumptions.assumeFalse; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.net.http.HttpRequest.BodyPublisher; +import java.net.http.HttpRequest.BodyPublishers; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.TimeUnit; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.apache.commons.lang3.SystemUtils; +import org.apache.jena.atlas.io.IO; +import org.apache.jena.atlas.json.JSON; +import org.apache.jena.atlas.json.JsonArray; +import org.apache.jena.atlas.json.JsonObject; +import org.apache.jena.atlas.json.JsonValue; +import org.apache.jena.atlas.lib.FileOps; +import org.apache.jena.atlas.lib.Lib; +import org.apache.jena.atlas.logging.LogCtl; +import org.apache.jena.atlas.web.HttpException; +import org.apache.jena.atlas.web.TypedInputStream; +import org.apache.jena.fuseki.Fuseki; +import org.apache.jena.fuseki.ctl.ActionSleep; +import org.apache.jena.fuseki.ctl.JsonConstCtl; +import org.apache.jena.fuseki.main.FusekiServer; +import org.apache.jena.fuseki.main.sys.FusekiModules; +import org.apache.jena.fuseki.mgt.FusekiApp; +import org.apache.jena.fuseki.mgt.ServerMgtConst; +import org.apache.jena.fuseki.server.ServerConst; +import org.apache.jena.fuseki.test.HttpTest; +import org.apache.jena.riot.WebContent; +import org.apache.jena.sparql.core.DatasetGraph; +import org.apache.jena.sparql.core.DatasetGraphFactory; +import org.apache.jena.web.HttpSC; +import org.awaitility.Awaitility; + +/** + * Tests of the admin functionality using a pre-configured dataset + * {@link TestTemplateAddDataset}. + */ +public class TestAdmin { + + // Name of the dataset in the assembler file. + static String dsTest = "test-ds1"; + static String dsTestInf = "test-ds4"; + + // There are two Fuseki-TDB2 tests: add_delete_dataset_6() and compact_01(). + // + // On certain build systems (GH action/Linux under load, ASF Jenkins sometimes), + // add_delete_dataset_6 fails (transactions active), or compact_01 (gets a 404), + // if the two databases are the same. + static String dsTestTdb2a = "test-tdb2a"; + static String dsTestTdb2b = "test-tdb2b"; + static String fileBase = "testing/Config/"; + + private String serverURL = null; + private FusekiServer server = null; + + @BeforeEach public void startServer() { + System.setProperty("FUSEKI_BASE", "target/run"); + FileOps.clearAll("target/run"); + + server = createServerForTest(); + serverURL = server.serverURL(); + //String adminURL = server.serverURL()+"$"; + //AuthEnv.get().registerUsernamePassword(adminURL, "admin","pw"); + } + + // Exactly the module under test + private static FusekiModules moduleSetup() { + return FusekiModules.create(FMod_Admin.create()); + } + + private FusekiServer createServerForTest() { + FusekiModules modules = moduleSetup(); + DatasetGraph dsg = DatasetGraphFactory.createTxnMem(); + FusekiServer testServer = FusekiServer.create() + .fusekiModules(modules) + .port(0) + .add(datasetName(), dsg) + .addServlet("/$/sleep/*", new ActionSleep()) + .build() + .start(); + return testServer; + } + + @AfterEach public void stopServer() { + if ( server != null ) + server.stop(); + serverURL = null; + // Clearup FMod_Shiro. + System.getProperties().remove(FusekiApp.envFusekiShiro); + } + + protected String urlRoot() { + return serverURL; + } + + protected String datasetName() { + return "dataset"; + } + + protected String datasetPath() { + return "/"+datasetName(); + } + + @BeforeEach public void setLogging() { + LogCtl.setLevel(Fuseki.backupLogName, "ERROR"); + LogCtl.setLevel(Fuseki.compactLogName,"ERROR"); + Awaitility.setDefaultPollDelay(20,TimeUnit.MILLISECONDS); + Awaitility.setDefaultPollInterval(50,TimeUnit.MILLISECONDS); + } + + @AfterEach public void unsetLogging() { + LogCtl.setLevel(Fuseki.backupLogName, "WARN"); + LogCtl.setLevel(Fuseki.compactLogName,"WARN"); + } + + // --- Ping + + @Test public void ping_1() { + httpGet(urlRoot()+"$/"+opPing); + } + + @Test public void ping_2() { + httpPost(urlRoot()+"$/"+opPing); + } + + // --- Server status + + @Test public void server_1() { + JsonValue jv = httpGetJson(urlRoot()+"$/"+opServer); + JsonObject obj = jv.getAsObject(); + // Now optional : assertTrue(obj.hasKey(JsonConst.admin)); + assertTrue(obj.hasKey(ServerConst.datasets)); + assertTrue(obj.hasKey(ServerMgtConst.uptime)); + assertTrue(obj.hasKey(ServerMgtConst.startDT)); + } + + @Test public void server_2() { + httpPost(urlRoot()+"$/"+opServer); + } + + // --- List all datasets + + @Test public void list_datasets_1() { + try ( TypedInputStream in = httpGet(urlRoot()+"$/"+opDatasets); ) { + IO.skipToEnd(in); + } + } + + @Test public void list_datasets_2() { + try ( TypedInputStream in = httpGet(urlRoot()+"$/"+opDatasets) ) { + assertEqualsIgnoreCase(WebContent.contentTypeJSON, in.getContentType()); + JsonValue v = JSON.parseAny(in); + assertNotNull(v.getAsObject().get("datasets")); + checkJsonDatasetsAll(v); + } + } + + // Specific dataset + @Test public void list_datasets_3() { + checkExists(datasetName()); + } + + // Specific dataset + @Test public void list_datasets_4() { + HttpTest.expect404( () -> getDatasetDescription("does-not-exist") ); + } + + // Specific dataset + @Test public void list_datasets_5() { + JsonValue v = getDatasetDescription(datasetName()); + checkJsonDatasetsOne(v.getAsObject()); + } + + // Specific dataset + @Test public void add_delete_dataset_1() { + checkNotThere(dsTest); + + addTestDataset(); + + // Check exists. + checkExists(dsTest); + + // Remove it. + deleteDataset(dsTest); + checkNotThere(dsTest); + } + + // Try to add twice + @Test public void add_delete_dataset_2() { + checkNotThere(dsTest); + + try { + Path f = Path.of(fileBase+"config-ds-plain-1.ttl"); + { + httpPost(urlRoot()+"$/"+opDatasets, + WebContent.contentTypeTurtle+"; charset="+WebContent.charsetUTF8, + BodyPublishers.ofFile(f)); + } + // Check exists. + checkExists(dsTest); + try { + } catch (HttpException ex) { + httpPost(urlRoot()+"$/"+opDatasets, + WebContent.contentTypeTurtle+"; charset="+WebContent.charsetUTF8, + BodyPublishers.ofFile(f)); + assertEquals(HttpSC.CONFLICT_409, ex.getStatusCode()); + } + } catch (IOException ex) { IO.exception(ex); return; } + + // Check exists. + checkExists(dsTest); + deleteDataset(dsTest); + } + + @Test public void add_delete_dataset_3() { + checkNotThere(dsTest); + addTestDataset(); + checkExists(dsTest); + deleteDataset(dsTest); + checkNotThere(dsTest); + addTestDataset(); + checkExists(dsTest); + deleteDataset(dsTest); + } + + @Test public void add_delete_dataset_4() { + checkNotThere(dsTest); + checkNotThere(dsTestInf); + addTestDatasetInf(); + checkNotThere(dsTest); + checkExists(dsTestInf); + + deleteDataset(dsTestInf); + checkNotThere(dsTestInf); + addTestDatasetInf(); + checkExists(dsTestInf); + deleteDataset(dsTestInf); + } + + @Test public void add_delete_dataset_5() { + // New style operations : cause two fuseki:names + addTestDataset(fileBase+"config-ds-plain-2.ttl"); + checkExists("test-ds2"); + } + + @Test public void add_delete_dataset_6() { + String testDB = dsTestTdb2a; + assumeNotWindows(); + + checkNotThere(testDB); + + addTestDatasetTDB2(testDB); + + // Check exists. + checkExists(testDB); + + // Remove it. + deleteDataset(testDB); + checkNotThere(testDB); + } + + @Test public void add_error_1() { + HttpTest.execWithHttpException(HttpSC.BAD_REQUEST_400, + ()-> addTestDataset(fileBase+"config-ds-bad-name-1.ttl")); + } + + @Test public void add_error_2() { + HttpTest.execWithHttpException(HttpSC.BAD_REQUEST_400, + ()-> addTestDataset(fileBase+"config-ds-bad-name-2.ttl")); + } + + @Test public void add_error_3() { + HttpTest.execWithHttpException(HttpSC.BAD_REQUEST_400, + ()-> addTestDataset(fileBase+"config-ds-bad-name-3.ttl")); + } + + @Test public void add_error_4() { + HttpTest.execWithHttpException(HttpSC.BAD_REQUEST_400, + ()-> addTestDataset(fileBase+"config-ds-bad-name-4.ttl")); + } + + @Test public void delete_dataset_1() { + String name = "NoSuchDataset"; + HttpTest.expect404( ()-> httpDelete(urlRoot()+"$/"+opDatasets+"/"+name) ); + } + + // ---- Backup + + @Test public void create_backup_1() { + String id = null; + try { + JsonValue v = httpPostRtnJSON(urlRoot() + "$/" + opBackup + "/" + datasetName()); + id = v.getAsObject().getString("taskId"); + } finally { + waitForTasksToFinish(1000, 10, 20000); + } + assertNotNull(id); + checkInTasks(id); + + // Check a backup was created + try ( TypedInputStream in = httpGet(urlRoot()+"$/"+opListBackups) ) { + assertEqualsIgnoreCase(WebContent.contentTypeJSON, in.getContentType()); + JsonValue v = JSON.parseAny(in); + assertNotNull(v.getAsObject().get("backups")); + JsonArray a = v.getAsObject().get("backups").getAsArray(); + assertEquals(1, a.size()); + } + + JsonValue task = getTask(id); + assertNotNull(id); + // Expect task success + assertTrue(task.getAsObject().getBoolean(JsonConstCtl.success), "Expected task to be marked as successful"); + } + + @Test + public void create_backup_2() { + HttpTest.expect400(()->{ + JsonValue v = httpPostRtnJSON(urlRoot() + "$/" + opBackup + "/noSuchDataset"); + }); + } + + @Test public void list_backups_1() { + try ( TypedInputStream in = httpGet(urlRoot()+"$/"+opListBackups) ) { + assertEqualsIgnoreCase(WebContent.contentTypeJSON, in.getContentType()); + JsonValue v = JSON.parseAny(in); + assertNotNull(v.getAsObject().get("backups")); + } + } + + // ---- Compact + + @Test public void compact_01() { + assumeNotWindows(); + + String testDB = dsTestTdb2b; + try { + checkNotThere(testDB); + addTestDatasetTDB2(testDB); + checkExists(testDB); + + String id = null; + try { + JsonValue v = httpPostRtnJSON(urlRoot() + "$/" + opCompact + "/" + testDB); + id = v.getAsObject().getString(JsonConstCtl.taskId); + } finally { + waitForTasksToFinish(1000, 500, 20_000); + } + assertNotNull(id); + checkInTasks(id); + + JsonValue task = getTask(id); + // ---- + // The result assertion is throwing NPE occasionally on some heavily loaded CI servers. + // This may be because of server or test code encountering a very long wait. + // These next statements check the assumed structure of the return. + assertNotNull(task, "Task value"); + JsonObject obj = task.getAsObject(); + assertNotNull(obj, "Task.getAsObject()"); + // Provoke code to get a stacktrace. + obj.getBoolean(JsonConstCtl.success); + // ---- + // The assertion we really wanted to check. + // Check task success + assertTrue(task.getAsObject().getBoolean(JsonConstCtl.success), + "Expected task to be marked as successful"); + } finally { + deleteDataset(testDB); + } + } + + @Test public void compact_02() { + HttpTest.expect400(()->{ + JsonValue v = httpPostRtnJSON(urlRoot() + "$/" + opCompact + "/noSuchDataset"); + }); + } + + private void assumeNotWindows() { + assumeFalse(SystemUtils.IS_OS_WINDOWS, "Test may be unstable on Windows due to inability to delete memory-mapped files"); + } + + // ---- Server + + // ---- Stats + + @Test public void stats_1() { + JsonValue v = execGetJSON(urlRoot()+"$/"+opStats); + checkJsonStatsAll(v); + } + + @Test public void stats_2() { + addTestDataset(); + JsonValue v = execGetJSON(urlRoot()+"$/"+opStats+datasetPath()); + checkJsonStatsAll(v); + deleteDataset(dsTest); + } + + @Test public void stats_3() { + addTestDataset(); + HttpTest.expect404(()-> execGetJSON(urlRoot()+"$/"+opStats+"/DoesNotExist")); + deleteDataset(dsTest); + } + + @Test public void stats_4() { + JsonValue v = execPostJSON(urlRoot()+"$/"+opStats); + checkJsonStatsAll(v); + } + + @Test public void stats_5() { + addTestDataset(); + JsonValue v = execPostJSON(urlRoot()+"$/"+opStats+datasetPath()); + checkJsonStatsAll(v); + deleteDataset(dsTest); + } + + @Test public void sleep_1() { + String x = execSleepTask(null, 1); + } + + @Test public void sleep_2() { + try { + String x = execSleepTask(null, -1); + fail("Sleep call unexpectedly succeed"); + } catch (HttpException ex) { + assertEquals(400, ex.getStatusCode()); + } + } + + @Test public void sleep_3() { + try { + String x = execSleepTask(null, 20*1000+1); + fail("Sleep call unexpectedly succeed"); + } catch (HttpException ex) { + assertEquals(400, ex.getStatusCode()); + } + } + + // Async task testing + + @Test public void task_1() { + String x = execSleepTask(null, 10); + assertNotNull(x); + Integer.parseInt(x); + } + + @Test public void task_2() { + String x = "NoSuchTask"; + String url = urlRoot()+"$/tasks/"+x; + HttpTest.expect404(()->httpGetJson(url) ); + try { + checkInTasks(x); + fail("No failure!"); + } catch (AssertionError ex) {} + } + + + @Test public void task_3() { + // Timing dependent. + // Create a "long" running task so we can find it. + String x = execSleepTask(null, 100); + checkTask(x); + checkInTasks(x); + assertNotNull(x); + Integer.parseInt(x); + } + + @Test public void task_4() { + // Timing dependent. + // Create a "short" running task + String x = execSleepTask(null, 1); + // Check exists in the list of all tasks (should be "finished") + checkInTasks(x); + String url = urlRoot()+"$/tasks/"+x; + + boolean finished = false; + for ( int i = 0; i < 10; i++ ) { + if ( i != 0 ) + Lib.sleep(25); + JsonValue v = httpGetJson(url); + checkTask(v); + if ( v.getAsObject().hasKey("finished") ) { + finished = true; + break; + } + } + if ( ! finished ) + fail("Task has not finished"); + } + + @Test public void task_5() { + // Short running task - still in info API call. + String x = execSleepTask(null, 1); + checkInTasks(x); + } + + @Test public void task_6() { + String x1 = execSleepTask(null, 1000); + String x2 = execSleepTask(null, 1000); + await().timeout(500,TimeUnit.MILLISECONDS).until(() -> runningTasks().size() > 1); + await().timeout(2000, TimeUnit.MILLISECONDS).until(() -> runningTasks().isEmpty()); + } + + @Test public void task_7() { + try { + String x1 = execSleepTask(null, 1000); + String x2 = execSleepTask(null, 1000); + String x3 = execSleepTask(null, 1000); + String x4 = execSleepTask(null, 1000); + try { + // Try to make test more stable on a loaded CI server. + // Unloaded the first sleep will fail but due to slowness/burstiness + // some tasks above may have completed. + String x5 = execSleepTask(null, 4000); + String x6 = execSleepTask(null, 4000); + String x7 = execSleepTask(null, 4000); + String x8 = execSleepTask(null, 10); + fail("Managed to add a 5th test"); + } catch (HttpException ex) { + assertEquals(HttpSC.BAD_REQUEST_400, ex.getStatusCode()); + } + } finally { + waitForTasksToFinish(1000, 250, 4000); + } + } + + /** Expect two string to be non-null and be {@link String#equalsIgnoreCase} */ + private void assertEqualsIgnoreCase(String expected, String actual) { + if ( expected == null && actual == null ) + return; + if ( expected == null || actual == null ) + fail("Expected: "+expected+" Got: "+actual); + if ( ! expected.equalsIgnoreCase(actual) ) + fail("Expected: "+expected+" Got: "+actual); + } + + private JsonValue getTask(String taskId) { + String url = urlRoot()+"$/tasks/"+taskId; + return httpGetJson(url); + } + + private JsonValue getDatasetDescription(String dsName) { + if ( dsName.startsWith("/") ) + dsName = dsName.substring(1); + try (TypedInputStream in = httpGet(urlRoot() + "$/" + opDatasets + "/" + dsName)) { + assertEqualsIgnoreCase(WebContent.contentTypeJSON, in.getContentType()); + JsonValue v = JSON.parse(in); + return v; + } + } + + // -- Add + + private void addTestDataset() { + addTestDataset(fileBase+"config-ds-plain-1.ttl"); + } + + private void addTestDatasetInf() { + addTestDataset(fileBase+"config-ds-inf.ttl"); + } + + private void addTestDatasetTDB2(String DBname) { + Objects.nonNull(DBname); + if ( DBname.equals(dsTestTdb2a) ) { + addTestDataset(fileBase+"config-tdb2a.ttl"); + return; + } + if ( DBname.equals(dsTestTdb2b) ) { + addTestDataset(fileBase+"config-tdb2b.ttl"); + return; + } + throw new IllegalArgumentException("No configuration for "+DBname); + } + + private void addTestDataset(String filename) { + try { + Path f = Path.of(filename); + BodyPublisher body = BodyPublishers.ofFile(f); + String ct = WebContent.contentTypeTurtle; + httpPost(urlRoot()+"$/"+opDatasets, ct, body); + } catch (FileNotFoundException e) { + IO.exception(e); + } + } + + private void deleteDataset(String name) { + httpDelete(urlRoot()+"$/"+opDatasets+"/"+name); + } + + private String execSleepTask(String name, int millis) { + String url = urlRoot()+"$/sleep"; + if ( name != null ) { + if ( name.startsWith("/") ) + name = name.substring(1); + url = url + "/"+name; + } + + JsonValue v = httpPostRtnJSON(url+"?interval="+millis); + String id = v.getAsObject().getString("taskId"); + return id; + } + + private void checkTask(String x) { + String url = urlRoot()+"$/tasks/"+x; + JsonValue v = httpGetJson(url); + checkTask(v); + } + + private void checkTask(JsonValue v) { + assertNotNull(v); + assertTrue(v.isObject()); + //System.out.println(v); + JsonObject obj = v.getAsObject(); + try { + assertTrue(obj.hasKey("task")); + assertTrue(obj.hasKey("taskId")); + // Not present until it runs : "started" + } catch (AssertionError ex) { + System.out.println(obj); + throw ex; + } + } + + private void checkInTasks(String x) { + String url = urlRoot()+"$/tasks"; + JsonValue v = httpGetJson(url); + assertTrue(v.isArray()); + JsonArray array = v.getAsArray(); + int found = 0; + for ( int i = 0; i < array.size(); i++ ) { + JsonValue jv = array.get(i); + assertTrue(jv.isObject()); + JsonObject obj = jv.getAsObject(); + checkTask(obj); + if ( obj.getString("taskId").equals(x) ) { + found++; + } + } + assertEquals(1, found, "Occurrence of taskId count"); + } + + private List runningTasks(String... x) { + String url = urlRoot()+"$/tasks"; + JsonValue v = httpGetJson(url); + assertTrue(v.isArray()); + JsonArray array = v.getAsArray(); + List running = new ArrayList<>(); + for ( int i = 0; i < array.size(); i++ ) { + JsonValue jv = array.get(i); + assertTrue(jv.isObject()); + JsonObject obj = jv.getAsObject(); + if ( isRunning(obj) ) + running.add(obj.getString("taskId")); + } + return running; + } + + /** + * Wait for tasks to all finish. + * Algorithm: wait for {@code pause}, then start polling for upto {@code maxWaitMillis}. + * Intervals in milliseconds. + * @param pauseMillis + * @param pollInterval + * @param maxWaitMillis + * @return + */ + private boolean waitForTasksToFinish(int pauseMillis, int pollInterval, int maxWaitMillis) { + // Wait for them to finish. + // Divide into chunks + if ( pauseMillis > 0 ) + Lib.sleep(pauseMillis); + long start = System.currentTimeMillis(); + long endTime = start + maxWaitMillis; + final int intervals = maxWaitMillis/pollInterval; + long now = start; + for (int i = 0 ; i < intervals ; i++ ) { + // May have waited (much) longer than the pollInterval : heavily loaded build systems. + if ( now-start > maxWaitMillis ) + break; + List x = runningTasks(); + if ( x.isEmpty() ) + return true; + Lib.sleep(pollInterval); + now = System.currentTimeMillis(); + } + return false; + } + + private boolean isRunning(JsonObject taskObj) { + checkTask(taskObj); + return taskObj.hasKey("started") && ! taskObj.hasKey("finished"); + } + + private void askPing(String name) { + if ( name.startsWith("/") ) + name = name.substring(1); + try ( TypedInputStream in = httpGet(urlRoot()+name+"/sparql?query=ASK%7B%7D") ) { + IO.skipToEnd(in); + } + } + + private void adminPing(String name) { + try ( TypedInputStream in = httpGet(urlRoot()+"$/"+opDatasets+"/"+name) ) { + IO.skipToEnd(in); + } + } + + private void checkExists(String name) { + adminPing(name); + askPing(name); + } + + private void checkExistsNotActive(String name) { + adminPing(name); + try { askPing(name); + fail("askPing did not cause an Http Exception"); + } catch ( HttpException ex ) {} + JsonValue v = getDatasetDescription(name); + assertFalse(v.getAsObject().get("ds.state").getAsBoolean().value()); + } + + private void checkNotThere(String name) { + String n = (name.startsWith("/")) ? name.substring(1) : name; + // Check gone exists. + HttpTest.expect404(()-> adminPing(n) ); + HttpTest.expect404(() -> askPing(n) ); + } + + private void checkJsonDatasetsAll(JsonValue v) { + assertNotNull(v.getAsObject().get("datasets")); + JsonArray a = v.getAsObject().get("datasets").getAsArray(); + for ( JsonValue v2 : a ) + checkJsonDatasetsOne(v2); + } + + private void checkJsonDatasetsOne(JsonValue v) { + assertTrue(v.isObject()); + JsonObject obj = v.getAsObject(); + assertNotNull(obj.get("ds.name")); + assertNotNull(obj.get("ds.services")); + assertNotNull(obj.get("ds.state")); + assertTrue(obj.get("ds.services").isArray()); + } + + private void checkJsonStatsAll(JsonValue v) { + assertNotNull(v.getAsObject().get("datasets")); + JsonObject a = v.getAsObject().get("datasets").getAsObject(); + for ( String dsname : a.keys() ) { + JsonValue obj = a.get(dsname).getAsObject(); + checkJsonStatsOne(obj); + } + } + + private void checkJsonStatsOne(JsonValue v) { + checkJsonStatsCounters(v); + JsonObject obj1 = v.getAsObject().get("endpoints").getAsObject(); + for ( String srvName : obj1.keys() ) { + JsonObject obj2 = obj1.get(srvName).getAsObject(); + assertTrue(obj2.hasKey("description")); + assertTrue(obj2.hasKey("operation")); + checkJsonStatsCounters(obj2); + } + } + + private void checkJsonStatsCounters(JsonValue v) { + JsonObject obj = v.getAsObject(); + assertTrue(obj.hasKey("Requests")); + assertTrue(obj.hasKey("RequestsGood")); + assertTrue(obj.hasKey("RequestsBad")); + } + + private JsonValue execGetJSON(String url) { + try ( TypedInputStream in = httpGet(url) ) { + assertEqualsIgnoreCase(WebContent.contentTypeJSON, in.getContentType()); + return JSON.parse(in); + } + } + + private JsonValue execPostJSON(String url) { + try ( TypedInputStream in = httpPostStream(url, null, null, null) ) { + assertEqualsIgnoreCase(WebContent.contentTypeJSON, in.getContentType()); + return JSON.parse(in); + } + } + + /* + GET /$/ping + POST /$/ping + POST /$/datasets/ + GET /$/datasets/ + DELETE /$/datasets/*{name}* + GET /$/datasets/*{name}* + POST /$/datasets/*{name}*?state=offline + POST /$/datasets/*{name}*?state=active + POST /$/backup/*{name}* + POST /$/compact/*{name}* + GET /$/server + POST /$/server/shutdown + GET /$/stats/ + GET /$/stats/*{name}* + */ +} + diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/admin/TestFusekiReload.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/admin/TestFusekiReload.java new file mode 100644 index 00000000000..ee68e96df1b --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/admin/TestFusekiReload.java @@ -0,0 +1,169 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.mod.admin; + +import static org.junit.Assert.assertEquals; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; + +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.Test; + +import org.apache.jena.atlas.io.IOX; +import org.apache.jena.atlas.logging.LogCtl; +import org.apache.jena.fuseki.Fuseki; +import org.apache.jena.fuseki.main.FusekiServer; +import org.apache.jena.fuseki.main.FusekiTestLib; +import org.apache.jena.fuseki.mgt.ActionReload; +import org.apache.jena.http.HttpOp; +import org.apache.jena.rdf.model.Model; +import org.apache.jena.riot.RDFParser; +import org.apache.jena.sparql.core.DatasetGraph; +import org.apache.jena.sparql.core.DatasetGraphFactory; +import org.apache.jena.sparql.engine.http.QueryExceptionHTTP; +import org.apache.jena.sparql.exec.QueryExec; +import org.apache.jena.sparql.exec.RowSetOps; +import org.apache.jena.sparql.exec.http.QueryExecHTTP; + +public class TestFusekiReload { + + private static Path fConfigServer = Path.of("target/config-reload.ttl"); + private static Path DIR = Path.of("testing/Config/"); + private static Path fConfig1 = DIR.resolve("reload-config1.ttl"); + private static Path fConfig2 = DIR.resolve("reload-config2.ttl"); + + @Before public void before() { + // Initial state + copyFile(fConfig1, fConfigServer); + } + + @AfterClass public static void after() { + try { + Files.delete(fConfigServer); + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } + } + + + @Test public void serverReload_1() { + FusekiServer server = server(fConfigServer); + try { + server.start(); + + serverBeforeReload(server); + + // Change configuration file. + copyFile(fConfig2, fConfigServer); + Model newConfig = RDFParser.source(fConfigServer).toModel(); + + // Reload operation on the server + HttpOp.httpPost("http://localhost:"+server.getPort()+"/$/reload"); + + serverAfterReload(server); + } + finally { server.stop(); } + } + + @Test public void serverReload_2() { + FusekiServer server = serverNoConfigFile(); + try { + server.start(); + + serverBeforeReload(server); + + // Change configuration file. + // Error! + copyFile(fConfig2, fConfigServer); + Model newConfig = RDFParser.source(fConfigServer).toModel(); + + LogCtl.withLevel(Fuseki.serverLog, "ERROR", ()-> + // Poke server - Operation denied - no configuration file. + FusekiTestLib.expect400(()->HttpOp.httpPost("http://localhost:"+server.getPort()+"/$/reload")) + ); + + // No change + serverBeforeReload(server); + } + finally { server.stop(); } + } + + private static void copyFile(Path pathSrc, Path pathDest) { + try { + Files.copy(pathSrc, pathDest, StandardCopyOption.REPLACE_EXISTING); + } catch (IOException ex) { throw IOX.exception(ex); } + } + + private static void serverBeforeReload(FusekiServer server) { + String URL = "http://localhost:"+server.getPort()+"/$/ping"; + String x = HttpOp.httpGetString(URL); + query(server, "/ds", "SELECT * { }", 200); + query(server, "/dataset2", "SELECT * { ?s ?p ?o }", 404); + query(server, "/zero", "SELECT * { }", 404); + query(server, "/codedsg", "SELECT * { }", 200); + } + + private static void serverAfterReload(FusekiServer server) { + String URL = "http://localhost:"+server.getPort()+"/$/ping"; + String x = HttpOp.httpGetString(URL); + query(server, "/ds", "SELECT * { }", 404); + query(server, "/dataset2", "SELECT * { ?s ?p ?o }", 200); + query(server, "/zero", "SELECT * { }", 404); + // Replaced. + query(server, "/codedsg", "SELECT * { }", 404); + } + + private static void query(FusekiServer server, String datasetName, String queryString, int expectedStatusCode) { + QueryExec qExec = QueryExecHTTP.service(server.datasetURL(datasetName)).query(queryString).build(); + try { + RowSetOps.consume(qExec.select()); + assertEquals(datasetName, expectedStatusCode, 200); + } catch (QueryExceptionHTTP ex) { + assertEquals(datasetName, expectedStatusCode, ex.getStatusCode()); + } + } + + private static FusekiServer serverNoConfigFile() { + DatasetGraph dsg = DatasetGraphFactory.createTxnMem(); + FusekiServer server = FusekiServer.create().port(0) + // .verbose(true) + .addServlet("/$/reload", new ActionReload()) + .add("/ds", dsg) + .add("/codedsg", dsg) + .build(); + return server; + } + + + private static FusekiServer server(Path fConfig) { + DatasetGraph dsg = DatasetGraphFactory.createTxnMem(); + FusekiServer server = FusekiServer.create().port(0) + // .verbose(true) + .addServlet("/$/reload", new ActionReload()) + .parseConfigFile(fConfig) + .add("/codedsg", dsg) + .build(); + return server; + } +} diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/admin/TestTemplateAddDataset.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/admin/TestTemplateAddDataset.java new file mode 100644 index 00000000000..975876095dc --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/admin/TestTemplateAddDataset.java @@ -0,0 +1,183 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.mod.admin; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.concurrent.TimeUnit; + +import org.junit.jupiter.api.*; + +import org.apache.jena.atlas.lib.FileOps; +import org.apache.jena.atlas.logging.LogCtl; +import org.apache.jena.atlas.web.HttpException; +import org.apache.jena.atlas.web.TypedInputStream; +import org.apache.jena.fuseki.Fuseki; +import org.apache.jena.fuseki.main.FusekiServer; +import org.apache.jena.fuseki.main.sys.FusekiModules; +import org.apache.jena.fuseki.mgt.FusekiApp; +import org.apache.jena.http.HttpOp; +import org.apache.jena.query.QueryExecution; +import org.apache.jena.rdfconnection.RDFConnection; +import org.apache.jena.sparql.core.DatasetGraph; +import org.apache.jena.sparql.core.DatasetGraphFactory; +import org.apache.jena.sparql.exec.http.Params; +import org.apache.jena.web.HttpSC; +import org.awaitility.Awaitility; + +/** + * Tests of the admin functionality on an empty server and using the template mechanism. + * See also {@link TestAdmin}. + */ +public class TestTemplateAddDataset { + + // One server for all tests + private static String serverURL = null; + private static FusekiServer server = null; + + @BeforeAll public static void startServer() { + System.setProperty("FUSEKI_BASE", "target/run"); + FileOps.clearAll("target/run"); + + server = createServerForTest(); + serverURL = server.serverURL(); + } + + // Exactly the module under test + private static FusekiModules moduleSetup() { + return FusekiModules.create(FMod_Admin.create()); + } + + private static FusekiServer createServerForTest() { + FusekiModules modules = moduleSetup(); + DatasetGraph dsg = DatasetGraphFactory.createTxnMem(); + FusekiServer testServer = FusekiServer.create() + .fusekiModules(modules) + .port(0) + .build() + .start(); + return testServer; + } + + @AfterAll public static void stopServer() { + if ( server != null ) + server.stop(); + serverURL = null; + // Clearup FMod_Shiro. + System.getProperties().remove(FusekiApp.envFusekiShiro); + } + + protected String urlRoot() { + return serverURL; + } + + protected String adminURL() { + return serverURL+"$/"; + } + + @BeforeEach public void setLogging() { + LogCtl.setLevel(Fuseki.backupLogName, "ERROR"); + LogCtl.setLevel(Fuseki.compactLogName,"ERROR"); + Awaitility.setDefaultPollDelay(20,TimeUnit.MILLISECONDS); + Awaitility.setDefaultPollInterval(50,TimeUnit.MILLISECONDS); + } + + @AfterEach public void unsetLogging() { + LogCtl.setLevel(Fuseki.backupLogName, "WARN"); + LogCtl.setLevel(Fuseki.compactLogName,"WARN"); + } + + @Order(value = 1) + @Test public void add_delete_api_1() throws Exception { + if ( org.apache.jena.tdb1.sys.SystemTDB.isWindows ) + return; + testAddDelete("db_mem", "mem", false, false); + } + + @Order(value = 2) + @Test public void add_delete_api_2() throws Exception { + if ( org.apache.jena.tdb1.sys.SystemTDB.isWindows ) + return; + // This should fail. + HttpException ex = assertThrows(HttpException.class, ()->testAddDelete("db_mem", "mem", true, false)); + // 409 conflict - "a request conflicts with the current state of the target resource." + // and the target resource is the container "/$/datasets" + assertEquals(HttpSC.CONFLICT_409, ex.getStatusCode()); + } + + private void testAddDelete(String dbName, String dbType, boolean alreadyExists, boolean hasFiles) { + String datasetURL = server.datasetURL(dbName); + Params params = Params.create().add("dbName", dbName).add("dbType", dbType); + + if ( alreadyExists ) + assertTrue(exists(datasetURL)); + else + assertFalse(exists(datasetURL)); + + // Use the template + HttpOp.httpPostForm(adminURL()+"datasets", params); + + RDFConnection conn = RDFConnection.connect(server.datasetURL(dbName)); + conn.update("INSERT DATA { 123 }"); + int x1 = count(conn); + assertEquals(1, x1); + + Path pathDB = FusekiApp.dirDatabases.resolve(dbName); + + if ( hasFiles ) + assertTrue(Files.exists(pathDB)); + + HttpOp.httpDelete(adminURL()+"datasets/"+dbName); + + assertFalse(exists(datasetURL)); + + //if ( hasFiles ) + assertFalse(Files.exists(pathDB)); + + // Recreate : no contents. + HttpOp.httpPostForm(adminURL()+"datasets", params); + assertTrue(exists(datasetURL), ()->"false: exists("+datasetURL+")"); + int x2 = count(conn); + assertEquals(0, x2); + if ( hasFiles ) + assertTrue(Files.exists(pathDB)); + } + + private static boolean exists(String url) { + try ( TypedInputStream in = HttpOp.httpGet(url) ) { + return true; + } catch (HttpException ex) { + if ( ex.getStatusCode() == HttpSC.NOT_FOUND_404 ) + return false; + throw ex; + } + } + + static int count(RDFConnection conn) { + try ( QueryExecution qExec = conn.query("SELECT (count(*) AS ?C) { ?s ?p ?o }")) { + return qExec.execSelect().next().getLiteral("C").getInt(); + } + } +} + diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/metrics/TestModPrometheus.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/metrics/TestModPrometheus.java new file mode 100644 index 00000000000..ce3be524fb9 --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/metrics/TestModPrometheus.java @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.mod.metrics; + +import static org.apache.jena.http.HttpLib.handleResponseRtnString; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.InputStream; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.net.http.HttpResponse.BodyHandlers; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.apache.jena.fuseki.main.FusekiServer; +import org.apache.jena.fuseki.main.sys.FusekiModules; +import org.apache.jena.fuseki.mod.prometheus.FMod_Prometheus; +import org.apache.jena.http.HttpEnv; +import org.apache.jena.http.HttpLib; +import org.apache.jena.riot.WebContent; +import org.apache.jena.riot.web.HttpNames; +import org.apache.jena.sparql.core.DatasetGraph; +import org.apache.jena.sparql.core.DatasetGraphFactory; + +public class TestModPrometheus { + + private FusekiServer testServer = null; + + @BeforeEach void setupServer() { + DatasetGraph dsg = DatasetGraphFactory.createTxnMem(); + FusekiModules fusekiModules = FusekiModules.create(FMod_Prometheus.get()); + testServer = FusekiServer.create() + .add("/ds", dsg) + .enableMetrics(false) // N.B. false. Instead, use module to setup. + .fusekiModules(fusekiModules) + .build(); + testServer.start(); + } + + @AfterEach void teardownServer() { + if ( testServer != null ) + testServer.stop(); + } + + @Test + public void can_retrieve_metrics() { + String metricsURL = testServer.serverURL()+"$/metrics"; + HttpRequest request = HttpRequest.newBuilder().uri(HttpLib.toRequestURI(metricsURL)).build(); + HttpResponse response = HttpLib.executeJDK(HttpEnv.getDftHttpClient(), request, BodyHandlers.ofInputStream()); + String body = handleResponseRtnString(response); + + String ct = response.headers().firstValue(HttpNames.hContentType).orElse(null); + assertNotNull(ct, "No Content-Type"); + assertTrue(ct.contains(WebContent.contentTypeTextPlain)); + assertTrue(ct.contains(WebContent.charsetUTF8)); + assertTrue(body.contains("fuseki_requests_good")); + } +} diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/shiro/TestModShiro.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/shiro/TestModShiro.java new file mode 100644 index 00000000000..dafc636f78d --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/shiro/TestModShiro.java @@ -0,0 +1,220 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.mod.shiro; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.net.Authenticator; +import java.net.http.HttpClient; +import java.util.regex.Pattern; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.apache.jena.atlas.net.Host; +import org.apache.jena.atlas.web.HttpException; +import org.apache.jena.fuseki.main.FusekiServer; +import org.apache.jena.fuseki.main.cmds.FusekiMain; +import org.apache.jena.fuseki.main.sys.FusekiModule; +import org.apache.jena.fuseki.main.sys.FusekiModules; +import org.apache.jena.fuseki.mgt.FusekiApp; +import org.apache.jena.fuseki.system.FusekiLogging; +import org.apache.jena.graph.Graph; +import org.apache.jena.http.HttpEnv; +import org.apache.jena.http.HttpOp; +import org.apache.jena.http.auth.AuthEnv; +import org.apache.jena.http.auth.AuthLib; +import org.apache.jena.sparql.core.DatasetGraph; +import org.apache.jena.sparql.core.DatasetGraphFactory; +import org.apache.jena.sparql.exec.http.GSP; +import org.apache.jena.sparql.exec.http.QueryExecHTTP; + +public class TestModShiro { + static final String unlocal = Host.getHostAddress(); + static final String localRE = Pattern.quote("localhost"); + + static { + FusekiLogging.setLogging(); + // Incase it is finding file:log4j.properties first. +// LogCtl.disable(Fuseki.serverLog); +// LogCtl.disable(Fuseki.actionLog); +// LogCtl.disable(FMod_Shiro.shiroConfigLog); + } + + @BeforeEach void before() { + System.getProperties().remove(FusekiApp.envFusekiShiro); + AuthEnv.get().clearAuthEnv(); + } + + @AfterEach void after() { + AuthEnv.get().clearAuthEnv(); + } + + @AfterAll static void afterAll() { + System.getProperties().remove(FusekiApp.envFusekiShiro); + } + + private String unlocalhost(FusekiServer server, String dataset) { + String local = server.datasetURL(dataset); + if ( unlocal != null ) + local = local.replaceFirst(localRE, unlocal); + return local; + } + + /** Builder for a server with Shiro */ + private FusekiServer.Builder serverBuilderWithShiro(String filename) { + System.getProperties().setProperty(FusekiApp.envFusekiShiro, filename); + FusekiModules modules = FusekiModules.create(FMod_Shiro.create()); + return FusekiServer.create() + .port(0) + .fusekiModules(modules); + } + + @Test public void access_localhost() { + DatasetGraph dsg = DatasetGraphFactory.createTxnMem(); + FusekiModules modules = FusekiModules.create(FMod_Shiro.create()); + FusekiServer server = serverBuilderWithShiro("testing/Shiro/shiro_localhost.ini") + .add("/local/ds", dsg) + .add("/public/ds", dsg) + .build(); + server.start(); + String dsPublic = "/public/ds"; + String dsLocal = "/local/ds"; + try { + attemptByAddr(server, dsPublic); + HttpException httpEx = assertThrows(HttpException.class, ()->attemptByAddr(server, dsLocal)); + assertEquals(403, httpEx.getStatusCode(), "Expected HTTP 403"); + + attemptByLocalhost(server, dsLocal); + } finally { + server.stop(); + AuthEnv.get().clearAuthEnv(); + } + } + + @Test public void access_userPassword() { + String dsname = "/ds"; + DatasetGraph dsg = DatasetGraphFactory.createTxnMem(); + FusekiServer server = serverBuilderWithShiro("testing/Shiro/shiro_userpassword.ini") + .add(dsname, dsg) + .enablePing(true) + .build(); + server.start(); + + String URL = server.datasetURL(dsname); + + try { + // No user-password + { + HttpException httpEx = assertThrows(HttpException.class, ()->attemptByLocalhost(server, dsname)); + assertEquals(401, httpEx.getStatusCode(), "Expected HTTP 401"); + } + + // user-password via authenticator: localhost + { + Authenticator authenticator = AuthLib.authenticator("user1", "passwd1"); + HttpClient httpClient = HttpEnv.httpClientBuilder().authenticator(authenticator).build(); + attemptByLocalhost(server, httpClient, dsname); + // and a SPARQL query + QueryExecHTTP.service(URL).httpClient(httpClient).query("ASK{}").ask(); + } + + // user-password via registration + { + AuthEnv.get().registerUsernamePassword(server.serverURL(), "user1", "passwd1"); + attemptByLocalhost(server, dsname); + AuthEnv.get().unregisterUsernamePassword(server.serverURL()); + } + + // try the ping (proxy for admin operations) + { + Authenticator authenticator = AuthLib.authenticator("admin", "pw"); + HttpClient httpClient = HttpEnv.httpClientBuilder().authenticator(authenticator).build(); + HttpOp.httpGetString(httpClient, server.serverURL()+"$/ping"); + AuthEnv.get().unregisterUsernamePassword(server.serverURL()); + } + + { + // Bad password + AuthEnv.get().registerUsernamePassword(server.serverURL(), "user1", "passwd2"); + HttpException httpEx = assertThrows(HttpException.class, ()->attemptByLocalhost(server, dsname)); + assertEquals(401, httpEx.getStatusCode(), "Expected HTTP 401"); + AuthEnv.get().unregisterUsernamePassword(server.serverURL()); + } + + } finally { + server.stop(); + AuthEnv.get().clearAuthEnv(); + } + } + + @Test public void shiroByCommandLine() { + String dsname = "/ds"; + FusekiModule fmod = FMod_Shiro.create(); + FusekiMain.addCustomiser(fmod); + + // And also a module! + FusekiServer server = FusekiMain.builder("--port=0", "--shiro=testing/Shiro/shiro_userpassword.ini", "--mem", dsname) + // Must be same instance. + .fusekiModules(FusekiModules.create(fmod)) + .build(); + server.start(); + try { + // No user-password + HttpException httpEx = assertThrows(HttpException.class, ()->attemptByLocalhost(server, dsname)); + assertEquals(401, httpEx.getStatusCode(), "Expected HTTP 401"); + } finally { server.stop(); } + FusekiMain.resetCustomisers(); + } + + // ---------------------------- + + // TEST **** command line + + private void attemptByAddr(FusekiServer server, String dsname) { + attemptByAddr(server, null, dsname); + } + + private void attemptByAddr(FusekiServer server, HttpClient httpClient, String dsname) { + String URL = server.datasetURL(dsname); + String URLip = unlocalhost(server, dsname); + attempt(URLip, httpClient); + } + + private void attemptByLocalhost(FusekiServer server, String dsname) { + attemptByLocalhost(server, null, dsname); + } + + private void attemptByLocalhost(FusekiServer server, HttpClient httpClient, String dsname) { + String URL = server.datasetURL(dsname); + attempt(URL, httpClient); + } + + private void attempt(String URL, HttpClient httpClient) { + GSP gsp = GSP.service(URL).defaultGraph(); + if ( httpClient != null ) + gsp.httpClient(httpClient); + Graph g = gsp.GET(); + assertNotNull(g); + } +} diff --git a/jena-fuseki2/jena-fuseki-main/src/test/resources/log4j2-test.properties b/jena-fuseki2/jena-fuseki-main/src/test/resources/log4j2-test.properties index ac4c56bbcd6..26b8c4b2e2d 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/resources/log4j2-test.properties +++ b/jena-fuseki2/jena-fuseki-main/src/test/resources/log4j2-test.properties @@ -25,18 +25,8 @@ logger.arq-exec.level = INFO logger.fuseki.name = org.apache.jena.fuseki logger.fuseki.level = WARN -## Some tests generate request WARNings. logger.fuseki-fuseki.name = org.apache.jena.fuseki.Fuseki -logger.fuseki-fuseki.level = ERROR - -#logger.fuseki-server.name = org.apache.jena.fuseki.Server -#logger.fuseki-server.level = INFO -# -#logger.fuseki-admin.name = org.apache.jena.fuseki.Admin -#logger.fuseki-admin.level = INFO -# -#logger.fuseki-compact.name = org.apache.jena.fuseki.Compact -#logger.fuseki-compact.level = WARN +logger.fuseki-fuseki.level = WARN logger.fuseki-autoload.name = org.apache.jena.fuseki.main.sys.FusekiAutoModules logger.fuseki-autoload.level = ERROR @@ -47,6 +37,9 @@ logger.http.level = INFO logger.riot.name = org.apache.jena.riot logger.riot.level = INFO +logger.riot.name = org.apache.shiro +logger.riot.level = WARN + logger.jetty.name = org.eclipse.jetty logger.jetty.level = WARN diff --git a/jena-fuseki2/jena-fuseki-main/testing/Config/config-ds-bad-name-1.ttl b/jena-fuseki2/jena-fuseki-main/testing/Config/config-ds-bad-name-1.ttl new file mode 100644 index 00000000000..05640fd0cf8 --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/testing/Config/config-ds-bad-name-1.ttl @@ -0,0 +1,15 @@ +@prefix : <#> . +@prefix fuseki: . +@prefix rdf: . + +@prefix rdfs: . +@prefix ja: . + +<#service1> rdf:type fuseki:Service ; + # URI of the dataset -- http://host:port/ds + fuseki:name "" ; + fuseki:serviceQuery "sparql" ; + fuseki:dataset <#emptyDataset> ; + . + +<#emptyDataset> rdf:type ja:RDFDataset . diff --git a/jena-fuseki2/jena-fuseki-main/testing/Config/config-ds-bad-name-2.ttl b/jena-fuseki2/jena-fuseki-main/testing/Config/config-ds-bad-name-2.ttl new file mode 100644 index 00000000000..27df06bcb45 --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/testing/Config/config-ds-bad-name-2.ttl @@ -0,0 +1,15 @@ +@prefix : <#> . +@prefix fuseki: . +@prefix rdf: . + +@prefix rdfs: . +@prefix ja: . + +<#service1> rdf:type fuseki:Service ; + # URI of the dataset -- http://host:port/ds + fuseki:name " " ; + fuseki:serviceQuery "sparql" ; + fuseki:dataset <#emptyDataset> ; + . + +<#emptyDataset> rdf:type ja:RDFDataset . diff --git a/jena-fuseki2/jena-fuseki-main/testing/Config/config-ds-bad-name-3.ttl b/jena-fuseki2/jena-fuseki-main/testing/Config/config-ds-bad-name-3.ttl new file mode 100644 index 00000000000..19caef108be --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/testing/Config/config-ds-bad-name-3.ttl @@ -0,0 +1,15 @@ +@prefix : <#> . +@prefix fuseki: . +@prefix rdf: . + +@prefix rdfs: . +@prefix ja: . + +<#service1> rdf:type fuseki:Service ; + # URI of the dataset -- http://host:port/ds + fuseki:name "ABC DEF" ; + fuseki:serviceQuery "sparql" ; + fuseki:dataset <#emptyDataset> ; + . + +<#emptyDataset> rdf:type ja:RDFDataset . diff --git a/jena-fuseki2/jena-fuseki-main/testing/Config/config-ds-bad-name-4.ttl b/jena-fuseki2/jena-fuseki-main/testing/Config/config-ds-bad-name-4.ttl new file mode 100644 index 00000000000..f57840e4185 --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/testing/Config/config-ds-bad-name-4.ttl @@ -0,0 +1,15 @@ +@prefix : <#> . +@prefix fuseki: . +@prefix rdf: . + +@prefix rdfs: . +@prefix ja: . + +<#service1> rdf:type fuseki:Service ; + # URI of the dataset -- http://host:port/ds + fuseki:name "/ABC DEF " ; + fuseki:serviceQuery "sparql" ; + fuseki:dataset <#emptyDataset> ; + . + +<#emptyDataset> rdf:type ja:RDFDataset . diff --git a/jena-fuseki2/jena-fuseki-main/testing/Config/config-ds-inf.ttl b/jena-fuseki2/jena-fuseki-main/testing/Config/config-ds-inf.ttl new file mode 100644 index 00000000000..b75cf18ce38 --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/testing/Config/config-ds-inf.ttl @@ -0,0 +1,30 @@ +PREFIX : <#> +PREFIX fuseki: +PREFIX rdf: + +PREFIX rdfs: +PREFIX ja: +PREFIX tdb2: + +<#service2> rdf:type fuseki:Service ; + fuseki:name "test-ds4" ; + fuseki:serviceQuery "sparql" ; + fuseki:dataset <#infDataset> ; + . + +<#infDataset> rdf:type ja:RDFDataset ; + ja:defaultGraph <#infGraph> ; + . + +<#infGraph> a ja:InfModel; + ja:reasoner [ja:reasonerURL ] ; + ja:baseModel <#tdb_graph> ; + . + +<#tdb_graph> rdf:type tdb2:GraphTDB ; + tdb2:dataset <#tdb_dataset> ; + . + +<#tdb_dataset> rdf:type tdb2:DatasetTDB ; + tdb2:location "--mem--" ; + . diff --git a/jena-fuseki2/jena-fuseki-main/testing/Config/config-ds-plain-1.ttl b/jena-fuseki2/jena-fuseki-main/testing/Config/config-ds-plain-1.ttl new file mode 100644 index 00000000000..f993f350fd6 --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/testing/Config/config-ds-plain-1.ttl @@ -0,0 +1,17 @@ +## Licensed under the terms of http://www.apache.org/licenses/LICENSE-2.0 + +PREFIX : <#> +PREFIX fuseki: +PREFIX rdf: + +PREFIX rdfs: +PREFIX ja: + +<#service1> #rdf:type fuseki:Service ; + # URI of the dataset -- http://host:port/ds + fuseki:name "test-ds1" ; + fuseki:serviceQuery "sparql" ; + fuseki:dataset <#emptyDataset> ; + . + +<#emptyDataset> rdf:type ja:RDFDataset . diff --git a/jena-fuseki2/jena-fuseki-main/testing/Config/config-ds-plain-2.ttl b/jena-fuseki2/jena-fuseki-main/testing/Config/config-ds-plain-2.ttl new file mode 100644 index 00000000000..d4b3fc90221 --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/testing/Config/config-ds-plain-2.ttl @@ -0,0 +1,18 @@ +## Licensed under the terms of http://www.apache.org/licenses/LICENSE-2.0 + +PREFIX : <#> +PREFIX fuseki: +PREFIX rdf: +PREFIX rdfs: +PREFIX ja: + +<#service1> rdf:type fuseki:Service ; + fuseki:name "test-ds2" ; + fuseki:endpoint [ + fuseki:operation fuseki:query ; + fuseki:name "sparql" + ] ; + fuseki:dataset <#emptyDataset> ; + . + +<#emptyDataset> rdf:type ja:MemoryDataset . diff --git a/jena-fuseki2/jena-fuseki-main/testing/Config/config-tdb2a.ttl b/jena-fuseki2/jena-fuseki-main/testing/Config/config-tdb2a.ttl new file mode 100644 index 00000000000..6ad26b9835f --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/testing/Config/config-tdb2a.ttl @@ -0,0 +1,18 @@ +## Licensed under the terms of http://www.apache.org/licenses/LICENSE-2.0 + +PREFIX : <#> +PREFIX fuseki: +PREFIX rdf: + +PREFIX rdfs: +PREFIX ja: +PREFIX tdb2: + +<#service1> rdf:type fuseki:Service ; + fuseki:name "test-tdb2a" ; + fuseki:endpoint [ fuseki:name "sparql" ; + fuseki:operation fuseki:query ] ; + fuseki:dataset <#dataset> . + +<#dataset> rdf:type tdb2:DatasetTDB2 ; + tdb2:location "target/tdb2a" . diff --git a/jena-fuseki2/jena-fuseki-main/testing/Config/config-tdb2b.ttl b/jena-fuseki2/jena-fuseki-main/testing/Config/config-tdb2b.ttl new file mode 100644 index 00000000000..572927c331d --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/testing/Config/config-tdb2b.ttl @@ -0,0 +1,18 @@ +## Licensed under the terms of http://www.apache.org/licenses/LICENSE-2.0 + +PREFIX : <#> +PREFIX fuseki: +PREFIX rdf: + +PREFIX rdfs: +PREFIX ja: +PREFIX tdb2: + +<#service1> rdf:type fuseki:Service ; + fuseki:name "test-tdb2b" ; + fuseki:endpoint [ fuseki:name "sparql" ; + fuseki:operation fuseki:query ] ; + fuseki:dataset <#dataset> . + +<#dataset> rdf:type tdb2:DatasetTDB2 ; + tdb2:location "target/tdb2b" . diff --git a/jena-fuseki2/jena-fuseki-main/testing/Shiro/shiro_localhost.ini b/jena-fuseki2/jena-fuseki-main/testing/Shiro/shiro_localhost.ini new file mode 100644 index 00000000000..2b9cce0c489 --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/testing/Shiro/shiro_localhost.ini @@ -0,0 +1,13 @@ +# Licensed under the terms of http://www.apache.org/licenses/LICENSE-2.0 + +[main] +localhostFilter = org.apache.jena.fuseki.authz.LocalhostFilter + +[users] +admin=pw + +[roles] + +[urls] +/local/** = localhostFilter +/public/** = anon diff --git a/jena-fuseki2/jena-fuseki-main/testing/Shiro/shiro_userpassword.ini b/jena-fuseki2/jena-fuseki-main/testing/Shiro/shiro_userpassword.ini new file mode 100644 index 00000000000..c0b57297263 --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/testing/Shiro/shiro_userpassword.ini @@ -0,0 +1,18 @@ +# Licensed under the terms of http://www.apache.org/licenses/LICENSE-2.0 + +[main] +plainMatcher=org.apache.shiro.authc.credential.SimpleCredentialsMatcher + +[users] +admin=pw + +[roles] + +[users] +admin=pw +user1=passwd1 + +[urls] +/$/ping = authcBasic,user[admin] +/ds = authcBasic,user[user1] +/**=anon From cde8bcb224fdebb5de6191673bcbea782bfda00d Mon Sep 17 00:00:00 2001 From: Andy Seaborne Date: Tue, 17 Dec 2024 20:23:33 +0000 Subject: [PATCH 2/8] Convert Fuseki-main tests to JUnit5 --- jena-fuseki2/jena-fuseki-core/pom.xml | 6 + .../org/apache/jena/fuseki/TS_FusekiCore.java | 14 +- .../UtilsTests.java => TestPrefixesCore.java} | 21 +- .../fuseki/servlets/prefixes/RegexTests.java | 44 --- .../jena/fuseki/mod/admin/FMod_Admin.java | 8 + .../apache/jena/fuseki/TC_FusekiServer.java | 12 +- .../jena/fuseki/main/AbstractFusekiTest.java | 9 +- .../fuseki/main/AbstractTestAuth_JDK.java | 9 +- .../jena/fuseki/main/FusekiTestLib.java | 2 +- .../jena/fuseki/main/TC_FusekiMain.java | 40 --- .../jena/fuseki/main/TS_FusekiMain.java | 5 +- .../jena/fuseki/main/TestAuthQuery_JDK.java | 2 +- .../jena/fuseki/main/TestAuthUpdate_JDK.java | 2 +- .../jena/fuseki/main/TestConfigFile.java | 13 +- .../fuseki/main/TestCrossOriginFilter.java | 32 ++- .../jena/fuseki/main/TestFileUpload.java | 4 +- .../main/TestFusekiCustomOperation.java | 28 +- .../main/TestFusekiCustomScriptFunc.java | 15 +- .../fuseki/main/TestFusekiDatasetSharing.java | 25 +- .../jena/fuseki/main/TestFusekiMainCmd.java | 8 +- .../main/TestFusekiMainCmdArguments.java | 47 ++-- .../TestFusekiMainCmdCustomArguments.java | 14 +- .../fuseki/main/TestFusekiServerBuild.java | 4 +- .../main/TestFusekiShaclValidation.java | 15 +- .../main/TestFusekiStdReadOnlySetup.java | 10 +- .../jena/fuseki/main/TestFusekiStdSetup.java | 10 +- .../jena/fuseki/main/TestHttpOperations.java | 41 +-- .../jena/fuseki/main/TestHttpOptions.java | 2 +- .../apache/jena/fuseki/main/TestMetrics.java | 4 +- .../fuseki/main/TestMultipleEmbedded.java | 16 +- .../jena/fuseki/main/TestPatchFuseki.java | 8 +- .../jena/fuseki/main/TestPlainServer.java | 20 +- .../apache/jena/fuseki/main/TestQuery.java | 35 ++- .../jena/fuseki/main/TestSPARQLProtocol.java | 11 +- .../main/TestSPARQLProtocolTimeout.java | 21 +- .../AbstractTestFusekiSecurityAssembler.java | 16 +- .../AbstractTestServiceDatasetAuth.java | 2 +- .../fuseki/main/access/AccessTestLib.java | 3 +- .../fuseki/main/access/TS_SecurityFuseki.java | 8 +- .../fuseki/main/access/TestAuthorized.java | 10 +- .../fuseki/main/access/TestPasswdOnly.java | 23 +- .../main/access/TestSecurityBuilderSetup.java | 21 +- .../main/access/TestSecurityConfig.java | 7 +- .../main/access/TestSecurityFilterFuseki.java | 256 +++++++++++------- .../main/access/TestServiceDataAuthBuild.java | 10 +- .../access/TestServiceDataAuthConfig.java | 11 +- .../fuseki/main/access/TestSimpleBearer.java | 7 +- ...viceTests.java => TS_PrefixesService.java} | 2 +- .../fuseki/main/sys/TestFusekiModules.java | 43 ++- ...{TC_FusekiMods.java => TS_FusekiMods.java} | 4 +- .../fuseki/mod/admin/TestFusekiReload.java | 16 +- .../src/test/resources/log4j2-test.properties | 3 +- 52 files changed, 505 insertions(+), 494 deletions(-) rename jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/servlets/{prefixes/UtilsTests.java => TestPrefixesCore.java} (89%) delete mode 100644 jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/servlets/prefixes/RegexTests.java delete mode 100644 jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TC_FusekiMain.java rename jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/prefixes/{PrefixesServiceTests.java => TS_PrefixesService.java} (96%) rename jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/{TC_FusekiMods.java => TS_FusekiMods.java} (95%) diff --git a/jena-fuseki2/jena-fuseki-core/pom.xml b/jena-fuseki2/jena-fuseki-core/pom.xml index 62913c924ac..6a947e75c4b 100644 --- a/jena-fuseki2/jena-fuseki-core/pom.xml +++ b/jena-fuseki2/jena-fuseki-core/pom.xml @@ -110,6 +110,12 @@ test + + org.junit.platform + junit-platform-suite + test + + io.micrometer diff --git a/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/TS_FusekiCore.java b/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/TS_FusekiCore.java index b67669aa9fd..4a4600f53da 100644 --- a/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/TS_FusekiCore.java +++ b/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/TS_FusekiCore.java @@ -18,21 +18,21 @@ package org.apache.jena.fuseki; -import org.junit.runner.RunWith; -import org.junit.runners.Suite; -import org.junit.runners.Suite.SuiteClasses; +import org.junit.platform.suite.api.SelectClasses; +import org.junit.platform.suite.api.Suite; import org.apache.jena.fuseki.server.TestDatasetDescriptionMap; import org.apache.jena.fuseki.server.TestDispatchOnURI; import org.apache.jena.fuseki.servlets.TestCrossOriginFilterMock; +import org.apache.jena.fuseki.servlets.TestPrefixesCore; -// Most testing needs a server. -@RunWith(Suite.class) -@SuiteClasses({ +@Suite +@SelectClasses({ TestValidators.class, TestDispatchOnURI.class, TestCrossOriginFilterMock.class, - TestDatasetDescriptionMap.class + TestDatasetDescriptionMap.class, + TestPrefixesCore.class }) public class TS_FusekiCore {} diff --git a/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/servlets/prefixes/UtilsTests.java b/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/servlets/TestPrefixesCore.java similarity index 89% rename from jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/servlets/prefixes/UtilsTests.java rename to jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/servlets/TestPrefixesCore.java index f2810811afd..56e82c1de47 100644 --- a/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/servlets/prefixes/UtilsTests.java +++ b/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/servlets/TestPrefixesCore.java @@ -16,14 +16,16 @@ * limitations under the License. */ -package org.apache.jena.fuseki.servlets.prefixes; +package org.apache.jena.fuseki.servlets; import org.junit.jupiter.api.Test; +import org.apache.jena.fuseki.servlets.prefixes.PrefixUtils; + import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -public class UtilsTests { +public class TestPrefixesCore { @Test public void prefixIsValidTrue0() { assertTrue(PrefixUtils.prefixIsValid("prefix1")); @@ -48,10 +50,17 @@ public void prefixIsValidTrue3() { public void prefixIsValidTrue4() { assertTrue(PrefixUtils.prefixIsValid("ca7--t")); } + @Test public void prefixIsValidTrue5() { assertTrue(PrefixUtils.prefixIsValid("a__b")); } + + @Test + public void prefixIsValidTrue6() { + assertTrue(PrefixUtils.prefixIsValid("")); + } + @Test public void prefixIsValidFalse0() { assertFalse(PrefixUtils.prefixIsValid("-prefix1")); @@ -64,16 +73,11 @@ public void prefixIsValidFalse1() { @Test public void prefixIsValidFalse2() { - assertFalse(PrefixUtils.prefixIsValid("")); - } - - @Test - public void prefixIsValidFalse4() { assertFalse(PrefixUtils.prefixIsValid("c-b--")); } @Test - public void prefixIsValidFalse5() { + public void prefixIsValidFalse3() { assertFalse(PrefixUtils.prefixIsValid("pre/fix")); } @@ -86,5 +90,4 @@ public void uriIsValidTrue0() { public void uriIsValidFalse0() { assertFalse(PrefixUtils.uriIsValid("...")); } - } diff --git a/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/servlets/prefixes/RegexTests.java b/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/servlets/prefixes/RegexTests.java deleted file mode 100644 index 3340e95c80a..00000000000 --- a/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/servlets/prefixes/RegexTests.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.jena.fuseki.servlets.prefixes; - -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; - -import java.util.regex.Pattern; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -public class RegexTests { - String serviceName = "prefixes"; - public final Pattern regex = Pattern.compile("^/" + serviceName + "(?:/.*)?$"); - - @ParameterizedTest - @ValueSource(strings = {"abc", "pre_fix/abc", "abcde", "prefix"}) - public void patternMatchFalse(String name) { - assertFalse(regex.matcher(name).matches()); - } - - @ParameterizedTest - @ValueSource(strings = {"/prefixes", "/prefixes/abc", "/prefixes/"}) - public void patternMatchTrue(String name) { - assertTrue(regex.matcher(name).matches()); - } -} diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/admin/FMod_Admin.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/admin/FMod_Admin.java index 09b0fcffc6a..c48bd290bb3 100644 --- a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/admin/FMod_Admin.java +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/admin/FMod_Admin.java @@ -125,6 +125,14 @@ public void serverArgsPrepare(CmdGeneral fusekiCmd, ServerArgs serverArgs) { @Override public void prepare(FusekiServer.Builder builder, Set datasetNames, Model configModel) { // Unpack + + // XXX Do better! + FusekiApp.FUSEKI_BASE = null; + +// FusekiApp fusekiApp = new FusekiApp(); +// //fusekiApp.init(); +// String fusekiApp.FUSEKI_BASE + Path path = FusekiApp.setup(); FmtLog.info(LOG, "Fuseki Admin: %s", path); diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/TC_FusekiServer.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/TC_FusekiServer.java index fd30ecb9460..6b4d86c8fb6 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/TC_FusekiServer.java +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/TC_FusekiServer.java @@ -21,12 +21,16 @@ import org.junit.platform.suite.api.SelectClasses; import org.junit.platform.suite.api.Suite; -import org.apache.jena.fuseki.main.TC_FusekiMain; -import org.apache.jena.fuseki.mod.TC_FusekiMods; +import org.apache.jena.fuseki.main.TS_FusekiMain; +import org.apache.jena.fuseki.main.access.TS_SecurityFuseki; +import org.apache.jena.fuseki.main.prefixes.TS_PrefixesService; +import org.apache.jena.fuseki.mod.TS_FusekiMods; @Suite @SelectClasses({ - TC_FusekiMain.class, - TC_FusekiMods.class + TS_FusekiMain.class, + TS_SecurityFuseki.class, + TS_FusekiMods.class, + TS_PrefixesService.class }) public class TC_FusekiServer {} diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/AbstractFusekiTest.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/AbstractFusekiTest.java index 79042214789..826ad1529a8 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/AbstractFusekiTest.java +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/AbstractFusekiTest.java @@ -18,10 +18,11 @@ package org.apache.jena.fuseki.main; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; + import org.apache.jena.sparql.core.DatasetGraph; import org.apache.jena.sparql.core.DatasetGraphFactory; -import org.junit.After; -import org.junit.Before; /** * Common setup for running a server with services and an initially empty database. @@ -39,7 +40,7 @@ public class AbstractFusekiTest { protected String serviceGSP_R() { return databaseURL()+"/get"; } protected String serviceGSP() { return databaseURL()+"/data"; } - @Before public void startServer() { + @BeforeEach public void startServer() { DatasetGraph dsgTesting = DatasetGraphFactory.createTxnMem(); server = FusekiServer.create() .port(0) @@ -50,7 +51,7 @@ public class AbstractFusekiTest { .start(); } - @After public void stopServer() { + @AfterEach public void stopServer() { if ( server != null ) server.stop(); } diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/AbstractTestAuth_JDK.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/AbstractTestAuth_JDK.java index b897f074463..956701b8450 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/AbstractTestAuth_JDK.java +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/AbstractTestAuth_JDK.java @@ -21,6 +21,9 @@ import java.net.Authenticator; import java.net.http.HttpClient; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; + import org.apache.jena.http.auth.AuthLib; import org.apache.jena.sparql.core.DatasetGraph; import org.apache.jena.sparql.core.DatasetGraphFactory; @@ -28,8 +31,6 @@ import org.apache.jena.sparql.exec.http.QueryExecutionHTTPBuilder; import org.apache.jena.sparql.exec.http.UpdateExecutionHTTP; import org.apache.jena.sparql.exec.http.UpdateExecutionHTTPBuilder; -import org.junit.After; -import org.junit.Before; /** * Common setup for running a server with an initially empty database with authentication by password file. @@ -44,7 +45,7 @@ public class AbstractTestAuth_JDK { protected String databaseURL() { return server.datasetURL(datasetPath()); } protected String serverURL() { return server.serverURL(); } - @Before public void startServer() { + @BeforeEach public void startServer() { String passwordFile = "testing/Access/auth-jdk-passwd"; DatasetGraph dsgTesting = DatasetGraphFactory.createTxnMem(); server = FusekiServer.create() @@ -57,7 +58,7 @@ public class AbstractTestAuth_JDK { server.start(); } - @After public void stopServer() { + @AfterEach public void stopServer() { if ( server != null ) server.stop(); } diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/FusekiTestLib.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/FusekiTestLib.java index 7c539314fa3..c57f85fa27f 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/FusekiTestLib.java +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/FusekiTestLib.java @@ -18,7 +18,7 @@ package org.apache.jena.fuseki.main; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.fail; import org.apache.jena.atlas.web.HttpException; import org.apache.jena.sparql.engine.http.QueryExceptionHTTP; diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TC_FusekiMain.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TC_FusekiMain.java deleted file mode 100644 index d79a974fbce..00000000000 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TC_FusekiMain.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.jena.fuseki.main; - -import org.apache.jena.fuseki.main.access.TS_SecurityFuseki; -import org.junit.BeforeClass; -import org.junit.runner.RunWith; -import org.junit.runners.Suite; - -@RunWith(Suite.class) -@Suite.SuiteClasses( { - TS_FusekiMain.class, - TS_SecurityFuseki.class -}) -public class TC_FusekiMain { - @BeforeClass public static void setupForFusekiServer() { - // controlled by src/test/resources/log4j.properties. -// LogCtl.setLevel(Fuseki.serverLogName, "WARN"); -// LogCtl.setLevel(Fuseki.actionLogName, "WARN"); -// LogCtl.setLevel(Fuseki.requestLogName, "WARN"); -// LogCtl.setLevel(Fuseki.adminLogName, "WARN"); -// LogCtl.setLevel("org.eclipse.jetty", "WARN"); - } -} diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TS_FusekiMain.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TS_FusekiMain.java index 92348e10d1d..f2ba8dda13c 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TS_FusekiMain.java +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TS_FusekiMain.java @@ -21,12 +21,11 @@ import org.junit.platform.suite.api.SelectClasses; import org.junit.platform.suite.api.Suite; -import org.apache.jena.fuseki.main.prefixes.PrefixesServiceTests; +import org.apache.jena.fuseki.main.prefixes.TS_PrefixesService; import org.apache.jena.fuseki.main.sys.TestFusekiModules; @Suite @SelectClasses({ - TestPlainServer.class // This tests modules and modifies the system state. @@ -55,7 +54,7 @@ , TestPatchFuseki.class , TestFusekiCustomScriptFunc.class - , PrefixesServiceTests.class + , TS_PrefixesService.class , TestMetrics.class , TestFusekiShaclValidation.class diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestAuthQuery_JDK.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestAuthQuery_JDK.java index 996bacc440c..8c29bff30a2 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestAuthQuery_JDK.java +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestAuthQuery_JDK.java @@ -26,7 +26,7 @@ import org.apache.jena.sparql.exec.http.QueryExecutionHTTP; import org.junit.Assert; import org.junit.FixMethodOrder; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.junit.runners.MethodSorters; @FixMethodOrder(MethodSorters.NAME_ASCENDING) diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestAuthUpdate_JDK.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestAuthUpdate_JDK.java index fe2becd3ca1..d125091df93 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestAuthUpdate_JDK.java +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestAuthUpdate_JDK.java @@ -28,7 +28,7 @@ import org.apache.jena.update.UpdateExecution; import org.apache.jena.update.UpdateRequest; import org.junit.FixMethodOrder; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.junit.runners.MethodSorters; @FixMethodOrder(MethodSorters.NAME_ASCENDING) diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestConfigFile.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestConfigFile.java index bb891b7916a..a0e8c176346 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestConfigFile.java +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestConfigFile.java @@ -20,10 +20,12 @@ import static org.apache.jena.fuseki.test.HttpTest.expect400; import static org.apache.jena.fuseki.test.HttpTest.expect404; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; import java.io.IOException; +import org.junit.jupiter.api.Test; + import org.apache.jena.atlas.io.IO; import org.apache.jena.atlas.web.TypedInputStream; import org.apache.jena.base.Sys; @@ -34,7 +36,6 @@ import org.apache.jena.rdfconnection.RDFConnectionRemote; import org.apache.jena.rdfconnection.RDFConnectionRemoteBuilder; import org.apache.jena.sparql.core.Var; -import org.junit.Test; /** Test server configuration by configuration file */ public class TestConfigFile { @@ -371,21 +372,21 @@ private static void assertCxtValue(RDFConnection conn, String contextSymbol, Str private static void assertCxtValueNotNull(RDFConnection conn, String contextSymbol) { boolean b = conn.queryAsk(PREFIXES+"ASK { FILTER (afn:context('"+contextSymbol+"') != '' ) }"); - assertTrue(contextSymbol, b); + assertTrue(b, contextSymbol); } private static void assertCxtValueNull(RDFConnection conn, String contextSymbol) { boolean b = conn.queryAsk(PREFIXES+"ASK { FILTER (afn:context('"+contextSymbol+"') = '' ) }"); - assertTrue("Not null: "+contextSymbol, b); + assertTrue(b, "Not null: "+contextSymbol); } private static void assertQueryTrue(RDFConnection conn, String qs) { boolean b = conn.queryAsk(PREFIXES+qs); - assertTrue(qs, b); + assertTrue(b, qs); } private static void assertQueryFalse(RDFConnection conn, String qs) { boolean b = conn.queryAsk(PREFIXES+qs); - assertFalse(qs, b); + assertFalse(b, qs); } private FusekiServer server(int port, String configFile) { diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestCrossOriginFilter.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestCrossOriginFilter.java index 34bc5303b35..624eca49cce 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestCrossOriginFilter.java +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestCrossOriginFilter.java @@ -20,10 +20,10 @@ import static org.apache.jena.fuseki.servlets.CrossOriginFilter.*; import static org.apache.jena.http.HttpLib.handleResponseNoBody; import static org.apache.jena.riot.web.HttpNames.*; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.fail; import java.io.IOException; import java.io.InputStream; @@ -39,14 +39,15 @@ import java.util.Set; import java.util.function.Consumer; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + import org.apache.jena.atlas.lib.Lib; import org.apache.jena.atlas.web.WebLib; import org.apache.jena.fuseki.system.FusekiLogging; import org.apache.jena.http.HttpLib; import org.apache.jena.riot.web.HttpNames; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; /** * Integration tests for CORS handling. @@ -62,13 +63,13 @@ public class TestCrossOriginFilter { // private static String URL = null; private static Optional systemValue = null; - @BeforeClass + @BeforeAll public static void beforeClass() { // Allow pretending to be another host systemValue = Optional.ofNullable(System.setProperty(jdkAllowRestrictedHeaders, "host")); } - @AfterClass + @AfterAll public static void afterClass() { if ( systemValue != null ) { systemValue.ifPresentOrElse((x)->System.setProperty(jdkAllowRestrictedHeaders, x), @@ -172,8 +173,9 @@ private static Set getHeaderSet(HttpResponse response, String header) } private static void assertEqualsIgnoreCase(String allowCreds, String string) { - assertEquals("Not equals (ignoring case)", - Lib.lowercase(allowCreds), Lib.lowercase(string)); + assertEquals(Lib.lowercase(allowCreds), + Lib.lowercase(string), + "Not equals (ignoring case)"); } // Assumes no repeated but different case. @@ -215,7 +217,7 @@ private static void assertSetContains(Set set, String value) { assertNotNull(response); assertEquals(response.statusCode(), 200); String actualAllowedHeaders = HttpLib.responseHeader(response, HttpNames.hAccessControlAllowHeaders); - assertNotNull("Expecting valid headers", actualAllowedHeaders); + assertNotNull(actualAllowedHeaders, "Expecting valid headers"); assertEquals(expectedAllowedHeaders, actualAllowedHeaders); handleResponseNoBody(response); }); @@ -234,7 +236,7 @@ private static void assertSetContains(Set set, String value) { assertNotNull(response); assertEquals(response.statusCode(), 200); String actualAllowedHeaders = HttpLib.responseHeader(response, HttpNames.hAccessControlAllowHeaders); - assertNull("No headers expected given invalid request", actualAllowedHeaders); + assertNull(actualAllowedHeaders, "No headers expected given invalid request"); handleResponseNoBody(response); }); } @@ -254,7 +256,7 @@ private static void assertSetContains(Set set, String value) { assertNotNull(response); assertEquals(response.statusCode(), 200); String actualAllowedHeaders = HttpLib.responseHeader(response, HttpNames.hAccessControlAllowHeaders); - assertNotNull("Expecting valid headers", actualAllowedHeaders); + assertNotNull(actualAllowedHeaders, "Expecting valid headers"); assertEquals(expectedAllowedHeaders, actualAllowedHeaders); handleResponseNoBody(response); }); @@ -273,7 +275,7 @@ private static void assertSetContains(Set set, String value) { assertNotNull(response); assertEquals(response.statusCode(), 200); String actualAllowedHeaders = HttpLib.responseHeader(response, HttpNames.hAccessControlAllowHeaders); - assertNull("No headers expected given invalid request", actualAllowedHeaders); + assertNull(actualAllowedHeaders, "No headers expected given invalid request"); handleResponseNoBody(response); }); } diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFileUpload.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFileUpload.java index 965f54a9da9..957460c3fd1 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFileUpload.java +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFileUpload.java @@ -18,7 +18,7 @@ package org.apache.jena.fuseki.main; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import org.apache.jena.atlas.iterator.Iter; import org.apache.jena.atlas.web.TypedInputStream; @@ -35,7 +35,7 @@ import org.apache.jena.sparql.graph.GraphFactory; import org.apache.jena.web.FileSender; import org.apache.jena.web.HttpSC; -import org.junit.Test; +import org.junit.jupiter.api.Test; /** * Tests for multi-part file upload. diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiCustomOperation.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiCustomOperation.java index c7dba503ff3..fa19be5eb4e 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiCustomOperation.java +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiCustomOperation.java @@ -19,7 +19,7 @@ package org.apache.jena.fuseki.main; import static org.apache.jena.fuseki.main.FusekiTestLib.expectFail; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; import java.io.IOException; import java.net.http.HttpRequest.BodyPublishers; @@ -45,7 +45,7 @@ import org.apache.jena.sparql.core.DatasetGraph; import org.apache.jena.sparql.core.DatasetGraphFactory; import org.apache.jena.web.HttpSC; -import org.junit.Test; +import org.junit.jupiter.api.Test; /** Test for adding a new operation */ public class TestFusekiCustomOperation { @@ -166,20 +166,24 @@ public void cfg_builder_CT_noName() { testServer(server, url, "", false, true); } - @Test(expected = FusekiConfigException.class) + @Test public void cfg_bad_01() { - FusekiServer.create().port(port).registerOperation(newOp, null, customHandler).addEndpoint("/UNKNOWN", endpointName, newOp); - // .build(); + assertThrows(FusekiConfigException.class, ()-> + FusekiServer.create().port(port).registerOperation(newOp, null, customHandler).addEndpoint("/UNKNOWN", endpointName, newOp) + // .build(); + ); } - @Test(expected = FusekiConfigException.class) + @Test public void cfg_bad_02() { - FusekiServer.create().port(port) - // .registerOperation(newOp, null, customHandler) - .add("/ds", DatasetGraphFactory.createTxnMem(), true) - // Unregistered. - .addEndpoint("/ds", endpointName, newOp); - // .build(); + assertThrows(FusekiConfigException.class, ()-> + FusekiServer.create().port(port) + // .registerOperation(newOp, null, customHandler) + .add("/ds", DatasetGraphFactory.createTxnMem(), true) + // Unregistered. + .addEndpoint("/ds", endpointName, newOp) + // .build(); + ); } // Bad test: MIME type must match. diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiCustomScriptFunc.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiCustomScriptFunc.java index d6b0a82f2dc..f3f0d3dc469 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiCustomScriptFunc.java +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiCustomScriptFunc.java @@ -18,8 +18,12 @@ package org.apache.jena.fuseki.main; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; import org.apache.jena.atlas.lib.StrUtils; import org.apache.jena.fuseki.server.DataService; @@ -36,9 +40,6 @@ import org.apache.jena.sparql.util.Context; import org.apache.jena.sparql.util.Symbol; import org.apache.jena.sys.JenaSystem; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; public class TestFusekiCustomScriptFunc { @@ -56,7 +57,7 @@ public class TestFusekiCustomScriptFunc { private static String dsName = "/ds" ; private static FusekiServer server = null; - @BeforeClass public static void enableScripting() { + @BeforeAll public static void enableScripting() { systemPropertyScriptingOldValue = System.getProperty(ARQ.systemPropertyScripting); scriptFunctionsOldValue = ARQ.getContext().get(symFunctions); // Enable @@ -79,7 +80,7 @@ public class TestFusekiCustomScriptFunc { .build().start(); } - @AfterClass public static void disableScripting() { + @AfterAll public static void disableScripting() { if ( server != null ) server.stop(); if ( systemPropertyScriptingOldValue != null ) diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiDatasetSharing.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiDatasetSharing.java index 3e075954adf..f89465d831b 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiDatasetSharing.java +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiDatasetSharing.java @@ -18,14 +18,18 @@ package org.apache.jena.fuseki.main; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.HashSet; import java.util.List; import java.util.Set; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + import org.apache.jena.graph.Graph; import org.apache.jena.riot.Lang; import org.apache.jena.riot.RDFParser; @@ -33,16 +37,10 @@ import org.apache.jena.sparql.exec.http.GSP; import org.apache.jena.sparql.sse.SSE; import org.apache.jena.sys.JenaSystem; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.FixMethodOrder; -import org.junit.Test; -import org.junit.runners.MethodSorters; /** * Testing configurations involving shared datasets */ -@FixMethodOrder(MethodSorters.NAME_ASCENDING) public class TestFusekiDatasetSharing { static { JenaSystem.init(); @@ -63,7 +61,7 @@ public class TestFusekiDatasetSharing { private static String URL_ds_view_unnamed_1; private static String URL_ds_view_unnamed_2; - @BeforeClass public static void beforeClass() { + @BeforeAll public static void beforeClass() { NamedDatasetAssembler.sharedDatasetPool.clear(); Graph g = RDFParser.source(DIR+"ds-sharing.ttl").lang(Lang.TTL).toGraph(); @@ -103,7 +101,7 @@ public class TestFusekiDatasetSharing { assertEquals(all1.size(), all2.size()); } - @AfterClass public static void afterClass() { + @AfterAll public static void afterClass() { if ( server != null ) server.stop(); NamedDatasetAssembler.sharedDatasetPool.clear(); @@ -162,9 +160,8 @@ private static void test(String URL1, String URL2, boolean canSee) { Graph data2 = GSP.service(URL2).defaultGraph().GET(); if ( canSee ) - assertFalse(msg, data2.isEmpty()); + assertFalse(data2.isEmpty(),msg); else - assertTrue(msg,data2.isEmpty()); + assertTrue(data2.isEmpty(), msg); } - } diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiMainCmd.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiMainCmd.java index c32fcda6d0f..27133a93de1 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiMainCmd.java +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiMainCmd.java @@ -18,9 +18,9 @@ package org.apache.jena.fuseki.main; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import java.io.IOException; import java.util.Arrays; @@ -35,7 +35,7 @@ import org.apache.jena.query.ResultSetFormatter; import org.apache.jena.rdfconnection.RDFConnection; import org.junit.After; -import org.junit.Test; +import org.junit.jupiter.api.Test; /** * Test Fuseki Main command line. diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiMainCmdArguments.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiMainCmdArguments.java index f8afce0dc17..d38b2d1b3a2 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiMainCmdArguments.java +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiMainCmdArguments.java @@ -18,13 +18,18 @@ package org.apache.jena.fuseki.main; import static java.util.Collections.emptyList; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.util.List; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + import org.apache.jena.atlas.logging.LogCtl; import org.apache.jena.atlas.web.WebLib; import org.apache.jena.cmd.CmdException; @@ -34,11 +39,6 @@ import org.apache.jena.fuseki.system.FusekiLogging; import org.apache.jena.riot.SysRIOT; -import org.junit.After; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; - /** * NOTE: we will randomise the port (--port=0) on all happy paths in order to avoid conflict with existing runs. */ @@ -48,7 +48,7 @@ public class TestFusekiMainCmdArguments { private static File jettyConfigFile; private static String jettyConfigFilename; - @BeforeClass public static void beforeClass() throws IOException { + @BeforeAll public static void beforeClass() throws IOException { // This is not reset by each running server. FusekiLogging.setLogging(); level = LogCtl.getLevel(Fuseki.serverLog); @@ -59,14 +59,14 @@ public class TestFusekiMainCmdArguments { jettyConfigFilename = jettyConfigFile.getAbsolutePath(); } - @AfterClass public static void afterClass() { + @AfterAll public static void afterClass() { if ( level != null ) LogCtl.setLevel(Fuseki.serverLog, level); jettyConfigFile.delete(); } private FusekiServer server = null; - @After public void after() { + @AfterEach public void after() { if ( server != null ) server.stop(); } @@ -76,8 +76,8 @@ public class TestFusekiMainCmdArguments { @Test public void argDefaults() { ServerArgs serverArgs = new ServerArgs(); - assertFalse("Wrong default setting: allowEmpty", serverArgs.allowEmpty); - assertFalse("Wrong default setting: bypassStdArgs", serverArgs.bypassStdArgs); + assertFalse(serverArgs.allowEmpty, "Wrong default setting: allowEmpty"); + assertFalse(serverArgs.bypassStdArgs, "Wrong default setting: bypassStdArgs"); } @Test @@ -165,16 +165,10 @@ public void test_error_noArguments_emptyString() { String emptyString = ""; String expectedMessage = "No dataset or configuration specified on the command line"; // when - Throwable actual = null; - try { - buildServer(emptyString); - } catch (Exception e) { - actual = e; - } + CmdException actual = assertThrows(CmdException.class, ()-> buildServer(emptyString)); + // then - assertNotNull(actual); - assertTrue("Expecting correct exception", (actual instanceof CmdException)); - assertEquals("Expecting correct message", expectedMessage, actual.getMessage()); + assertEquals(expectedMessage, actual.getMessage(), "Expecting correct message"); } @Test @@ -183,16 +177,9 @@ public void test_error_noArguments_null() { String nullString = null; String expectedMessage = "No dataset or configuration specified on the command line"; // when - Throwable actual = null; - try { - buildServer(nullString); - } catch (Exception e) { - actual = e; - } + CmdException actual = assertThrows(CmdException.class, ()-> buildServer(nullString)); // then - assertNotNull(actual); - assertTrue("Expecting correct exception", (actual instanceof CmdException)); - assertEquals("Expecting correct message", expectedMessage, actual.getMessage()); + assertEquals(expectedMessage, actual.getMessage(), "Expecting correct message"); } @Test @@ -447,7 +434,7 @@ private void testForCmdException(List arguments, String expectedMessage) // when CmdException actual = assertThrows(CmdException.class, ()->buildServer(buildCmdLineArguments(arguments))); // then - assertEquals("Expecting correct message", expectedMessage, actual.getMessage()); + assertEquals(expectedMessage, actual.getMessage(), "Expecting correct message"); } private static String[] buildCmdLineArguments(List listArgs) { diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiMainCmdCustomArguments.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiMainCmdCustomArguments.java index e2708b153db..aa64bc5f77b 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiMainCmdCustomArguments.java +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiMainCmdCustomArguments.java @@ -23,9 +23,9 @@ import java.util.function.Consumer; import org.junit.After; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; import org.apache.jena.atlas.logging.LogCtl; import org.apache.jena.cmd.ArgDecl; @@ -61,13 +61,13 @@ public class TestFusekiMainCmdCustomArguments { private static String level = null; - @BeforeClass public static void beforeClass() { + @BeforeAll public static void beforeClass() { FusekiLogging.setLogging(); level = LogCtl.getLevel(Fuseki.serverLog); LogCtl.setLevel(Fuseki.serverLog, "WARN"); } - @AfterClass public static void afterClass() { + @AfterAll public static void afterClass() { if ( level != null ) LogCtl.setLevel(Fuseki.serverLog, level); } @@ -85,10 +85,10 @@ public void test_custom_no_custom_args() { test(new ArgDecl(false, "special"), arguments, false, null); } - @Test(expected = CmdException.class) + @Test public void test_custom_no_custom_args_decl() { String[] arguments = {"--port=0", "--special", "--mem","/ds"}; - FusekiServer server = FusekiMain.build(arguments); + assertThrows(CmdException.class, ()->FusekiMain.build(arguments)); } @Test diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiServerBuild.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiServerBuild.java index 611ca3a80e0..6cd67861956 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiServerBuild.java +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiServerBuild.java @@ -22,7 +22,7 @@ import static org.apache.jena.fuseki.main.FusekiTestLib.expect404; import static org.apache.jena.fuseki.main.FusekiTestLib.expectQuery400; import static org.apache.jena.fuseki.main.FusekiTestLib.expectQuery404; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; import java.io.IOException; import java.util.function.Consumer; @@ -53,7 +53,7 @@ import org.apache.jena.sparql.sse.SSE; import org.apache.jena.system.Txn; import org.apache.jena.update.UpdateExecution; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.slf4j.Logger; public class TestFusekiServerBuild { diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiShaclValidation.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiShaclValidation.java index a4d09747aa1..4a62dc73f3f 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiShaclValidation.java +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiShaclValidation.java @@ -18,17 +18,18 @@ package org.apache.jena.fuseki.main; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; import org.apache.jena.graph.Graph; import org.apache.jena.http.HttpRDF; import org.apache.jena.rdfconnection.RDFConnection; import org.apache.jena.riot.RDFDataMgr; import org.apache.jena.shacl.ValidationReport; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; public class TestFusekiShaclValidation { // Fuseki Main server @@ -36,7 +37,7 @@ public class TestFusekiShaclValidation { private static String serverURL = null; private static final String DIR = "testing/ShaclValidation/"; - @BeforeClass + @BeforeAll public static void beforeClass() { FusekiServer server = FusekiServer.create() .port(0) @@ -46,7 +47,7 @@ public static void beforeClass() { serverURL = "http://localhost:"+server.getPort(); } - @AfterClass + @AfterAll public static void afterClass() { if ( server != null ) server.stop(); diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiStdReadOnlySetup.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiStdReadOnlySetup.java index fe16e1d5695..6c38f431384 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiStdReadOnlySetup.java +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiStdReadOnlySetup.java @@ -20,9 +20,9 @@ import java.util.function.Consumer; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; import org.apache.jena.atlas.lib.StrUtils; import org.apache.jena.atlas.web.HttpException; @@ -48,7 +48,7 @@ public class TestFusekiStdReadOnlySetup { private static String URL; - @BeforeClass + @BeforeAll public static void beforeClass() { data = SSE.parseGraph(StrUtils.strjoinNL ("(graph" @@ -68,7 +68,7 @@ public static void beforeClass() { URL = server.datasetURL("/ds"); } - @AfterClass + @AfterAll public static void afterClass() { if ( server != null ) server.stop(); diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiStdSetup.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiStdSetup.java index ecd71d6b50e..17d8c0f8e1b 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiStdSetup.java +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiStdSetup.java @@ -20,9 +20,9 @@ import java.util.function.Consumer; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; import org.apache.jena.atlas.lib.StrUtils; import org.apache.jena.atlas.web.HttpException; @@ -47,7 +47,7 @@ public class TestFusekiStdSetup { private static String URL; - @BeforeClass + @BeforeAll public static void beforeClass() { data = SSE.parseGraph(StrUtils.strjoinNL ("(graph" @@ -67,7 +67,7 @@ public static void beforeClass() { URL = server.datasetURL("/ds"); } - @AfterClass + @AfterAll public static void afterClass() { if ( server != null ) server.stop(); diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestHttpOperations.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestHttpOperations.java index 478a10c737c..bfb6dd43416 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestHttpOperations.java +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestHttpOperations.java @@ -18,8 +18,8 @@ package org.apache.jena.fuseki.main; - import static java.net.http.HttpRequest.BodyPublishers.ofString; +import static org.junit.jupiter.api.Assertions.*; import org.apache.jena.atlas.web.HttpException; import org.apache.jena.atlas.web.TypedInputStream; @@ -27,8 +27,7 @@ import org.apache.jena.riot.WebContent; import org.apache.jena.sparql.exec.http.Params; import org.apache.jena.sparql.util.Convert; -import org.junit.Assert; -import org.junit.Test; +import org.junit.jupiter.api.Test; /** Operation by HTTP - test dispatch - lower level than TestSPARQLProtocol */ public class TestHttpOperations extends AbstractFusekiTest { @@ -38,7 +37,7 @@ public void query_by_get_1() { String qs = Convert.encWWWForm("ASK{}"); String u = serviceQuery()+"?query=" + qs; try (TypedInputStream in = HttpOp.httpGet(u)) { - Assert.assertNotNull(in); + assertNotNull(in); } } @@ -48,7 +47,7 @@ public void query_by_post_1() { try (TypedInputStream in = HttpOp.httpPostStream(serviceQuery(), WebContent.contentTypeSPARQLQuery, ofString("ASK{}"), "*")) { - Assert.assertNotNull(in); + assertNotNull(in); } } @@ -57,7 +56,7 @@ public void query_by_post_2() { String qs = Convert.encWWWForm("ASK{}"); String u = serviceQuery()+"?query=" + qs; try (TypedInputStream in = HttpOp.httpPostStream(u)) { - Assert.assertNotNull(in); + assertNotNull(in); } } @@ -65,16 +64,18 @@ public void query_by_post_2() { public void query_by_form_1() { Params params = Params.create().add("query", "ASK{}"); try (TypedInputStream in = HttpOp.httpPostForm(serviceQuery(), params, "*") ) { - Assert.assertNotNull(in); + assertNotNull(in); } } - @Test(expected=HttpException.class) + @Test public void query_by_form_2() { Params params = Params.create().add("foobar", "ASK{}"); // Wrong. - try (TypedInputStream in = HttpOp.httpPostForm(serviceQuery(), params, "*") ) { - Assert.assertNotNull(in); - } + assertThrows(HttpException.class, ()->{ + try (TypedInputStream in = HttpOp.httpPostForm(serviceQuery(), params, "*") ) { + assertNotNull(in); + }; + }); } @Test @@ -88,7 +89,7 @@ public void update_by_post_1() { // String us = Convert.encWWWForm("INSERT DATA {}"); // String u = serviceUpdate+"?update=" + us; // try (TypedInputStream in = HttpOp.execHttpPostStream(u, null, null)) { -// Assert.assertNotNull(in); +// assertNotNull(in); // } // } @@ -96,16 +97,18 @@ public void update_by_post_1() { public void update_by_form_1() { Params params = Params.create().add("update", "INSERT DATA{}"); try (TypedInputStream in = HttpOp.httpPostForm(serviceUpdate(), params, "*") ) { - Assert.assertNotNull(in); + assertNotNull(in); } } - @Test(expected=HttpException.class) + @Test public void update_by_form_2() { Params params = Params.create().add("query", "INSERT DATA{}"); // Wrong paramater - try (TypedInputStream in = HttpOp.httpPostForm(serviceUpdate(), params, "*") ) { - Assert.assertNotNull(in); - } + assertThrows(HttpException.class, ()->{ + try (TypedInputStream in = HttpOp.httpPostForm(serviceUpdate(), params, "*") ) { + assertNotNull(in); + } + }); } // ---- Dataset direct, with content type. @@ -114,7 +117,7 @@ public void update_by_form_2() { public void ds_fetch_by_get_1() { String u = databaseURL(); try (TypedInputStream in = HttpOp.httpGet(u)) { - Assert.assertNotNull(in); + assertNotNull(in); } } @@ -122,7 +125,7 @@ public void ds_fetch_by_get_1() { public void ds_query_by_post_1() { String u = databaseURL(); try (TypedInputStream in = HttpOp.httpPostStream(u, WebContent.contentTypeSPARQLQuery, ofString("ASK{}"), "*")) { - Assert.assertNotNull(in); + assertNotNull(in); } } diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestHttpOptions.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestHttpOptions.java index 69af53e601d..cc0d1e4a87c 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestHttpOptions.java +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestHttpOptions.java @@ -20,7 +20,7 @@ import org.apache.jena.fuseki.test.FusekiTest; import org.apache.jena.http.HttpOp; -import org.junit.Test; +import org.junit.jupiter.api.Test; public class TestHttpOptions extends AbstractFusekiTest { diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestMetrics.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestMetrics.java index 841626938b9..c473c18aaa9 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestMetrics.java +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestMetrics.java @@ -18,7 +18,7 @@ package org.apache.jena.fuseki.main; import static org.apache.jena.http.HttpLib.handleResponseRtnString; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.InputStream; import java.net.http.HttpRequest; @@ -29,7 +29,7 @@ import org.apache.jena.http.HttpLib; import org.apache.jena.riot.WebContent; import org.apache.jena.riot.web.HttpNames; -import org.junit.Test; +import org.junit.jupiter.api.Test; public class TestMetrics extends AbstractFusekiTest { diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestMultipleEmbedded.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestMultipleEmbedded.java index d056a3c3c9f..92559b38aed 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestMultipleEmbedded.java +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestMultipleEmbedded.java @@ -18,8 +18,9 @@ package org.apache.jena.fuseki.main; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.function.Consumer; @@ -35,7 +36,7 @@ import org.apache.jena.sparql.sse.SSE; import org.apache.jena.sys.JenaSystem; import org.apache.jena.system.Txn; -import org.junit.Test; +import org.junit.jupiter.api.Test; public class TestMultipleEmbedded { @@ -45,7 +46,7 @@ public class TestMultipleEmbedded { static Quad q2 = SSE.parseQuad("(_ :s :p 2)"); // Two servers, same port -> bad. - @Test(expected=FusekiException.class) + @Test public void multiple_01() { DatasetGraph dsg = dataset(); @@ -54,18 +55,15 @@ public void multiple_01() { // Same port - Bad. FusekiServer server2 = FusekiServer.create().port(port).add("/ds2", dsg).build(); - server1.start(); - try { - server2.start(); - } catch (FusekiException ex) { + server1.start(); + FusekiException ex = assertThrows(FusekiException.class, ()->server2.start()); // Jetty 9.4.12 throws BindException // Jetty 9.4.26 throws IOException cause BindException Throwable cause = ex.getCause(); if ( cause instanceof java.io.IOException ) cause = cause.getCause(); assertTrue(cause instanceof java.net.BindException); - throw ex; } finally { try { server1.stop(); } catch (Exception ex) {} try { server2.stop(); } catch (Exception ex) {} diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestPatchFuseki.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestPatchFuseki.java index 503fecc157b..1bccb04f497 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestPatchFuseki.java +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestPatchFuseki.java @@ -17,9 +17,9 @@ package org.apache.jena.fuseki.main; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.net.http.HttpRequest.BodyPublishers; import java.util.function.BiConsumer; @@ -39,7 +39,7 @@ import org.apache.jena.sparql.exec.RowSetOps; import org.apache.jena.sparql.exec.http.QueryExecHTTP; import org.apache.jena.sparql.sse.SSE; -import org.junit.Test; +import org.junit.jupiter.api.Test; public class TestPatchFuseki { diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestPlainServer.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestPlainServer.java index 424cda10070..3831aca6195 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestPlainServer.java +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestPlainServer.java @@ -18,22 +18,22 @@ package org.apache.jena.fuseki.main; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.IOException; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; - import org.apache.jena.http.HttpOp; import org.apache.jena.sparql.core.DatasetGraphFactory; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; /** Test Fuseki with plain servlet and handling a file area */ public class TestPlainServer { @@ -43,7 +43,7 @@ public class TestPlainServer { private static String serverURL; - @BeforeClass + @BeforeAll public static void beforeClass() { FusekiServer server = FusekiServer.create() .port(0) @@ -76,7 +76,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO } } - @AfterClass + @AfterAll public static void afterClass() { if ( server != null ) server.stop(); diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestQuery.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestQuery.java index fe13606695c..60d4c392e3b 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestQuery.java +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestQuery.java @@ -18,9 +18,9 @@ package org.apache.jena.fuseki.main; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.*; import java.net.HttpURLConnection; @@ -28,9 +28,8 @@ import java.net.http.HttpClient; import java.util.Iterator; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.apache.jena.atlas.json.JsonArray; import org.apache.jena.atlas.web.AcceptList; @@ -62,7 +61,7 @@ public class TestQuery extends AbstractFusekiTest { private static final Graph graph1 = SSE.parseGraph("(base (graph (

      1)))"); private static final Graph graph2 = SSE.parseGraph("(base (graph (

      2)))"); - @Before + @BeforeEach public void before() { GSP.service(serviceGSP()).defaultGraph().PUT(graph1); GSP.service(serviceGSP()).graphName(gn1).PUT(graph2); @@ -89,7 +88,7 @@ public void query_recursive_01() { Var x = Var.alloc("x"); while (rs.hasNext()) { Binding b = rs.nextBinding(); - Assert.assertNotNull(b.get(x)); + assertNotNull(b.get(x)); } } finally { Fuseki.getContext().set(Service.httpServiceAllowed, serverSetting); @@ -101,7 +100,7 @@ public void query_with_params_01() { String query = "ASK { }"; try (QueryExecution qExec = QueryExecution.service(serviceQuery() + "?output=json", query)) { boolean result = qExec.execAsk(); - Assert.assertTrue(result); + assertTrue(result); } } @@ -110,7 +109,7 @@ public void request_id_header_01() throws IOException { String qs = Convert.encWWWForm("ASK{}"); URL u = new URL(serviceQuery() + "?query=" + qs); HttpURLConnection conn = (HttpURLConnection)u.openConnection(); - Assert.assertTrue(conn.getHeaderField(Fuseki.FusekiRequestIdHeader) != null); + assertTrue(conn.getHeaderField(Fuseki.FusekiRequestIdHeader) != null); } @Test @@ -155,8 +154,8 @@ public void query_construct_quad_01() try ( QueryExecutionHTTP qExec = QueryExecutionHTTP.service(serviceQuery(), query) ) { Iterator result = qExec.execConstructQuads(); - Assert.assertTrue(result.hasNext()); - Assert.assertEquals( "http://eg/g", result.next().getGraph().getURI()); + assertTrue(result.hasNext()); + assertEquals( "http://eg/g", result.next().getGraph().getURI()); } } @@ -169,8 +168,8 @@ public void query_construct_quad_02() try ( QueryExecution qExec = QueryExecution.service(serviceQuery(), query) ) { Dataset result = qExec.execConstructDataset(); - Assert.assertTrue(result.asDatasetGraph().find().hasNext()); - Assert.assertEquals( "http://eg/g", result.asDatasetGraph().find().next().getGraph().getURI()); + assertTrue(result.asDatasetGraph().find().hasNext()); + assertEquals( "http://eg/g", result.asDatasetGraph().find().next().getGraph().getURI()); } } @@ -180,7 +179,7 @@ public void query_construct_01() String query = " CONSTRUCT {?s ?p ?o} WHERE {?s ?p ?o}"; try ( QueryExecution qExec = QueryExecution.service(serviceQuery(), query) ) { Iterator result = qExec.execConstructTriples(); - Assert.assertTrue(result.hasNext()); + assertTrue(result.hasNext()); } } @@ -302,8 +301,8 @@ public void query_json_02() throws IOException { } result = sb.toString(); } - Assert.assertNotNull(result); - Assert.assertTrue(result.contains("http://example/x")); + assertNotNull(result); + assertTrue(result.contains("http://example/x")); } private void execQuery(String queryString, int exceptedRowCount) { @@ -318,7 +317,7 @@ private void execQuery(String queryString, ResultSet expectedResultSet) { try ( QueryExecution qExec = QueryExecution.service(serviceQuery(), queryString) ) { ResultSet rs = qExec.execSelect(); boolean b = ResultSetCompare.equalsByTerm(rs, expectedResultSet); - assertTrue("Result sets different", b); + assertTrue(b, "Result sets different"); } } diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestSPARQLProtocol.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestSPARQLProtocol.java index 109cc2fc1d5..fd27f70e1cb 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestSPARQLProtocol.java +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestSPARQLProtocol.java @@ -18,15 +18,16 @@ package org.apache.jena.fuseki.main; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertTrue; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.apache.jena.graph.Graph; import org.apache.jena.graph.Node; import org.apache.jena.graph.NodeFactory; -import org.apache.jena.query.*; +import org.apache.jena.query.Query; +import org.apache.jena.query.QueryFactory; import org.apache.jena.riot.WebContent; import org.apache.jena.sparql.exec.QueryExec; import org.apache.jena.sparql.exec.RowSet; @@ -50,7 +51,7 @@ public class TestSPARQLProtocol extends AbstractFusekiTest private static final Graph graph1 = SSE.parseGraph("(base (graph (

      1)))"); private static final Graph graph2 = SSE.parseGraph("(base (graph (

      2)))"); - @Before + @BeforeEach public void before() { GSP.service(serviceGSP()).defaultGraph().PUT(graph1); GSP.service(serviceGSP()).graphName(gn1).PUT(graph2); diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestSPARQLProtocolTimeout.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestSPARQLProtocolTimeout.java index ee80f161592..5a89f8ea9bc 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestSPARQLProtocolTimeout.java +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestSPARQLProtocolTimeout.java @@ -18,9 +18,14 @@ package org.apache.jena.fuseki.main; +import static org.junit.jupiter.api.Assertions.assertThrows; + import java.util.concurrent.TimeUnit; import java.util.stream.IntStream; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + import org.apache.jena.atlas.web.HttpException; import org.apache.jena.graph.Graph; import org.apache.jena.graph.NodeFactory; @@ -28,12 +33,10 @@ import org.apache.jena.sparql.graph.GraphFactory; import org.apache.jena.sparql.util.Convert; import org.apache.jena.update.UpdateExecution; -import org.junit.Before; -import org.junit.Test; public class TestSPARQLProtocolTimeout extends AbstractFusekiTest { - @Before + @BeforeEach public void before() { Graph graph = createTestGraph(); GSP.service(serviceGSP()).defaultGraph().PUT(graph); @@ -54,11 +57,13 @@ static String query(String base, String queryString) { /** If the HTTP client reaches its timeout and disconnects from the server then it is up * to the server whether it will cancel or complete the started SPARQL update execution. */ - @Test(expected = HttpException.class) + @Test public void update_timeout_01() { - UpdateExecution.service(serviceUpdate()) - .update("INSERT { } WHERE { ?a ?b ?c . ?d ?e ?f . ?g ?h ?i . }") - .timeout(500, TimeUnit.MILLISECONDS) - .execute(); + assertThrows(HttpException.class, ()-> + UpdateExecution.service(serviceUpdate()) + .update("INSERT { } WHERE { ?a ?b ?c . ?d ?e ?f . ?g ?h ?i . }") + .timeout(500, TimeUnit.MILLISECONDS) + .execute() + ); } } diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/AbstractTestFusekiSecurityAssembler.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/AbstractTestFusekiSecurityAssembler.java index ade33038b79..9d00a6f7429 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/AbstractTestFusekiSecurityAssembler.java +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/AbstractTestFusekiSecurityAssembler.java @@ -19,8 +19,8 @@ package org.apache.jena.fuseki.main.access; import static org.apache.jena.fuseki.main.access.AccessTestLib.assertSeen; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; import java.util.HashSet; import java.util.List; @@ -28,6 +28,10 @@ import java.util.Set; import java.util.concurrent.atomic.AtomicReference; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + import org.apache.jena.atlas.iterator.Iter; import org.apache.jena.atlas.lib.SetUtils; import org.apache.jena.atlas.lib.StrUtils; @@ -48,9 +52,6 @@ import org.apache.jena.sparql.core.Quad; import org.apache.jena.sparql.sse.SSE; import org.apache.jena.system.Txn; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.Test; /** * Tests on the assembler for data access control. @@ -78,13 +79,14 @@ private FusekiServer getServer() { return server; } - @AfterClass public static void afterClass() { + @AfterAll + public static void afterClass() { server.stop(); server = null; user.set(null); } - @Before + @BeforeEach public void before() { user.set(null); } diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/AbstractTestServiceDatasetAuth.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/AbstractTestServiceDatasetAuth.java index 4f03c952e6b..ac5d0fd3f7d 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/AbstractTestServiceDatasetAuth.java +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/AbstractTestServiceDatasetAuth.java @@ -23,7 +23,7 @@ import static org.apache.jena.fuseki.main.FusekiTestLib.expectQuery401; import static org.apache.jena.fuseki.main.FusekiTestLib.expectQuery403; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.apache.jena.atlas.web.WebLib; import org.apache.jena.fuseki.main.FusekiServer; diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/AccessTestLib.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/AccessTestLib.java index 4b32c450e25..91b65ce01ab 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/AccessTestLib.java +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/AccessTestLib.java @@ -18,7 +18,7 @@ package org.apache.jena.fuseki.main.access; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.Arrays; import java.util.Set; @@ -31,7 +31,6 @@ import org.apache.jena.sparql.sse.SSE; import org.apache.jena.system.Txn; - /** Some data and functions common to access control tests. */ public class AccessTestLib { private static String dataStr = StrUtils.strjoinNL diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TS_SecurityFuseki.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TS_SecurityFuseki.java index 0852f23f948..af2bf7f68c2 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TS_SecurityFuseki.java +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TS_SecurityFuseki.java @@ -18,11 +18,11 @@ package org.apache.jena.fuseki.main.access; -import org.junit.runner.RunWith; -import org.junit.runners.Suite; +import org.junit.platform.suite.api.SelectClasses; +import org.junit.platform.suite.api.Suite; -@RunWith(Suite.class) -@Suite.SuiteClasses( { +@Suite +@SelectClasses({ TestAuthorized.class , TestSimpleBearer.class diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TestAuthorized.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TestAuthorized.java index 752e4a08c49..43b93510acf 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TestAuthorized.java +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TestAuthorized.java @@ -18,12 +18,12 @@ package org.apache.jena.fuseki.main.access; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.apache.jena.atlas.logging.LogCtl; import org.apache.jena.fuseki.Fuseki; diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TestPasswdOnly.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TestPasswdOnly.java index 32436580ab6..c7db6978b95 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TestPasswdOnly.java +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TestPasswdOnly.java @@ -20,10 +20,11 @@ import static org.apache.jena.fuseki.main.FusekiTestLib.expectOK; import static org.apache.jena.fuseki.main.FusekiTestLib.expectQuery401; +import static org.junit.jupiter.api.Assertions.assertThrows; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; import org.apache.jena.atlas.web.WebLib; import org.apache.jena.fuseki.main.FusekiServer; @@ -44,7 +45,7 @@ public class TestPasswdOnly { protected static int port; private static AuthSetup auth1; - @BeforeClass public static void beforeClass () { + @BeforeAll public static void beforeClass () { port = WebLib.choosePort(); server = FusekiServer.create() //.verbose(true) @@ -59,18 +60,20 @@ public class TestPasswdOnly { auth1 = new AuthSetup("localhost", port, "user1", "pw1", null); } - @AfterClass public static void afterClass () { + @AfterAll public static void afterClass () { server.stop(); } // Bounced by Jetty. - @Test(expected=QueryExceptionHTTP.class) + @Test public void passwd_no_user_A() { - try (RDFLink conn = RDFLink.queryConnect("http://localhost:" + port + "/db")) { - try ( QueryExec qExec = conn.query("ASK{}") ) { - qExec.ask(); + assertThrows(QueryExceptionHTTP.class, () -> { + try (RDFLink conn = RDFLink.queryConnect("http://localhost:" + port + "/db")) { + try (QueryExec qExec = conn.query("ASK{}")) { + qExec.ask(); + } } - } + }); } @Test diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TestSecurityBuilderSetup.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TestSecurityBuilderSetup.java index ff7a425f735..4f806681e50 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TestSecurityBuilderSetup.java +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TestSecurityBuilderSetup.java @@ -18,12 +18,17 @@ package org.apache.jena.fuseki.main.access; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.fail; import java.net.http.HttpClient; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + import org.apache.jena.atlas.web.HttpException; import org.apache.jena.atlas.web.TypedInputStream; import org.apache.jena.atlas.web.WebLib; @@ -40,10 +45,6 @@ import org.apache.jena.web.HttpSC; import org.eclipse.jetty.ee10.servlet.security.ConstraintSecurityHandler; import org.eclipse.jetty.security.UserStore; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; /** * Tests for access to services using programmatic setup. @@ -62,7 +63,7 @@ public class TestSecurityBuilderSetup { // Not in the user store. private static AuthSetup authSetupX; - @BeforeClass + @BeforeAll public static void beforeClass() { int port = WebLib.choosePort(); @@ -109,14 +110,14 @@ public static void beforeClass() { } - @Before + @BeforeEach public void before() { // // Reset before every test and after the suite. // HttpClient hc = HttpOp.createDefaultHttpClient(); // HttpOp.setDefaultHttpClient(hc); } - @AfterClass + @AfterAll public static void afterClass() { fusekiServer.stop(); // HttpClient hc = HttpOp.createDefaultHttpClient(); diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TestSecurityConfig.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TestSecurityConfig.java index b68f9c00a1b..02377d44f25 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TestSecurityConfig.java +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TestSecurityConfig.java @@ -20,14 +20,16 @@ import static org.apache.jena.fuseki.test.HttpTest.expectQuery401; import static org.apache.jena.fuseki.test.HttpTest.expectQuery403; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; import java.io.IOException; import java.io.InputStream; import java.net.http.HttpClient; import java.util.function.Consumer; +import org.junit.jupiter.api.Test; + import org.apache.jena.atlas.io.IO; import org.apache.jena.atlas.web.HttpException; import org.apache.jena.atlas.web.TypedInputStream; @@ -39,7 +41,6 @@ import org.apache.jena.sparql.exec.http.QueryExecHTTP; import org.apache.jena.web.AuthSetup; import org.apache.jena.web.HttpSC; -import org.junit.Test; /** * Tests for security of the server, services and endpoints using configuration file setup. diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TestSecurityFilterFuseki.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TestSecurityFilterFuseki.java index 5bb7f2ae4f1..315e80c15e8 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TestSecurityFilterFuseki.java +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TestSecurityFilterFuseki.java @@ -19,17 +19,20 @@ package org.apache.jena.fuseki.main.access; import static org.apache.jena.fuseki.main.access.AccessTestLib.*; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; -import java.util.*; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Stream; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameters; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.apache.jena.atlas.iterator.Iter; import org.apache.jena.atlas.web.HttpException; @@ -57,18 +60,20 @@ import org.eclipse.jetty.util.security.Credential; import org.eclipse.jetty.util.security.Password; -@RunWith(Parameterized.class) public class TestSecurityFilterFuseki { - @Parameters(name = "{index}: {0}") - public static Iterable data() { - Object[] obj1 = { "TDB", "data1" }; - Object[] obj2 = { "TDB2", "data2" }; - Object[] obj3 = { "TIM", "data3" }; - return Arrays.asList(obj1, obj2, obj3); + private static Stream provideTestArgs() { + return Stream.of( +// Arguments.of("TDB", "data1"), +// Arguments.of("TDB2", "data2"), + Arguments.of("TIM", "data3") + ); + } + + private String baseUrl(String dsName) { + return fusekiServer.datasetURL(dsName); } - private final String baseUrl; private static DatasetGraph testdsg1 = TDB1Factory.createDatasetGraph(); private static DatasetGraph testdsg2 = DatabaseMgr.createDatasetGraph(); private static DatasetGraph testdsg3 = DatasetGraphFactory.createTxnMem(); @@ -76,7 +81,7 @@ public static Iterable data() { private static FusekiServer fusekiServer; // Set up Fuseki with two datasets, "data1" backed by TDB and "data2" backed by TDB2. - @BeforeClass public static void beforeClass() { + @BeforeAll public static void beforeClass() { addTestData(testdsg1); addTestData(testdsg2); addTestData(testdsg3); @@ -110,7 +115,7 @@ public static Iterable data() { fusekiServer.start(); } - @AfterClass public static void afterClass() { + @AfterAll public static void afterClass() { fusekiServer.stop(); } @@ -131,9 +136,7 @@ private static void addUserPassword(UserStore propertyUserStore, String user, St propertyUserStore.addUser(user, cred, roles); } - public TestSecurityFilterFuseki(String label, String dsName) { - baseUrl = fusekiServer.datasetURL(dsName); - } + public TestSecurityFilterFuseki() {} private static String queryAll = "SELECT * { { ?s ?p ?o } UNION { GRAPH ?g { ?s ?p ?o } } }"; private static String queryDft = "SELECT * { ?s ?p ?o }"; @@ -142,9 +145,9 @@ public TestSecurityFilterFuseki(String label, String dsName) { private static String queryG2 = "SELECT * { GRAPH { ?s ?p ?o } }"; private static String queryGraphNames = "SELECT * { GRAPH ?g { } }"; - private Set query(String user, String password, String queryString) { + private Set query(String user, String password, String dsName, String queryString) { Set results = new HashSet<>(); - try (RDFConnection conn = RDFConnection.connectPW(baseUrl, user, password)) { + try (RDFConnection conn = RDFConnection.connectPW(baseUrl(dsName), user, password)) { conn.queryResultSet(queryString, rs->{ List list = Iter.toList(rs); list.stream() @@ -157,17 +160,17 @@ private Set query(String user, String password, String queryString) { return results; } - private void query401(String user, String password, String queryString) { - queryHttp(401, user, password, queryString); + private void query401(String user, String password, String dsName, String queryString) { + queryHttp(401, user, password, dsName, queryString); } - private void query403(String user, String password, String queryString) { - queryHttp(403, user, password, queryString); + private void query403(String user, String password, String dsName, String queryString) { + queryHttp(403, user, password, dsName, queryString); } - private void queryHttp(int statusCode, String user, String password, String queryString) { + private void queryHttp(int statusCode, String user, String password, String dsName, String queryString) { try { - query(user, password, queryString); + query(user, password, dsName, queryString); if ( statusCode < 200 && statusCode > 299 ) fail("Should have responded with "+statusCode); } catch (QueryExceptionHTTP ex) { @@ -175,68 +178,91 @@ private void queryHttp(int statusCode, String user, String password, String quer } } - @Test public void query_userDft() { - Set results = query("userDft", "pwDft", queryAll); + @ParameterizedTest(name = "{index} {0}") + @MethodSource("provideTestArgs") + public void query_userDft(String label, String dsName) { + Set results = query("userDft", "pwDft", dsName, queryAll); assertSeen(results, s0); } - @Test public void query_userNone() { - Set results = query("userNone", "pwNone", queryAll); + @ParameterizedTest(name = "{index} {0}") + @MethodSource("provideTestArgs") + public void query_userNone(String label, String dsName) { + Set results = query("userNone", "pwNone", dsName, queryAll); assertSeen(results); } - @Test public void query_user0() { - Set results = query("user0", "pw0", queryAll); + @ParameterizedTest(name = "{index} {0}") + @MethodSource("provideTestArgs") + public void query_user0(String label, String dsName) { + Set results = query("user0", "pw0", dsName, queryAll); assertSeen(results, s0); } - @Test public void query_user1() { - Set results = query("user1", "pw1", queryAll); + @ParameterizedTest(name = "{index} {0}") + @MethodSource("provideTestArgs") + public void query_user1(String label, String dsName) { + Set results = query("user1", "pw1", dsName, queryAll); assertSeen(results, s0, s1); } - @Test public void query_bad_user() { - query401("userX", "pwX", queryAll); + @ParameterizedTest(name = "{index} {0}") + @MethodSource("provideTestArgs") + public void query_bad_user(String label, String dsName) { + query401("userX", "pwX", dsName, queryAll); } - @Test public void query_bad_password() { - query401("user0", "not-the-password", queryAll); + @ParameterizedTest(name = "{index} {0}") + @MethodSource("provideTestArgs") + public void query_bad_password(String label, String dsName) { + query401("user0", "not-the-password", dsName, queryAll); } // Visibility of data. - @Test public void query_dyn_1() { - Set results = query("user1", "pw1", "SELECT * FROM { ?s ?p ?o }"); + @ParameterizedTest(name = "{index} {0}") + @MethodSource("provideTestArgs") + public void query_dyn_1(String label, String dsName) { + Set results = query("user1", "pw1", dsName, "SELECT * FROM { ?s ?p ?o }"); assertSeen(results, s1); } - @Test public void query_dyn_2() { - Set results = query("user1", "pw1", "SELECT * FROM { ?s ?p ?o }"); + @ParameterizedTest(name = "{index} {0}") + @MethodSource("provideTestArgs") + public void query_dyn_2(String label, String dsName) { + Set results = query("user1", "pw1", dsName, "SELECT * FROM { ?s ?p ?o }"); assertSeen(results); } - @Test public void query_dyn_3() { - Set results = query("user1", "pw1", "SELECT * FROM FROM { ?s ?p ?o }"); + @ParameterizedTest(name = "{index} {0}") + @MethodSource("provideTestArgs") + public void query_dyn_3(String label, String dsName) { + Set results = query("user1", "pw1", dsName, "SELECT * FROM FROM { ?s ?p ?o }"); assertSeen(results,s1); } - @Test public void query_dyn_4() { - Set results = query("user3", "pw3", "SELECT * FROM <"+Quad.unionGraph.getURI()+"> { ?s ?p ?o }"); + @ParameterizedTest(name = "{index} {0}") + @MethodSource("provideTestArgs") + public void query_dyn_4(String label, String dsName) { + Set results = query("user3", "pw3", dsName, "SELECT * FROM <"+Quad.unionGraph.getURI()+"> { ?s ?p ?o }"); assertSeen(results, s2, s3); - Set results2 = query("user3", "pw3", "SELECT * { GRAPH <"+Quad.unionGraph.getURI()+"> { ?s ?p ?o } }"); + Set results2 = query("user3", "pw3", dsName, "SELECT * { GRAPH <"+Quad.unionGraph.getURI()+"> { ?s ?p ?o } }"); assertEquals(results, results2); } - @Test public void query_dyn_5() { - Set results = query("user3", "pw3", "SELECT * FROM NAMED { ?s ?p ?o }"); + @ParameterizedTest(name = "{index} {0}") + @MethodSource("provideTestArgs") + public void query_dyn_5(String label, String dsName) { + Set results = query("user3", "pw3", dsName, "SELECT * FROM NAMED { ?s ?p ?o }"); assertSeen(results); - Set results2 = query("user3", "pw3", "SELECT * { GRAPH { ?s ?p ?o } }"); + Set results2 = query("user3", "pw3", dsName, "SELECT * { GRAPH { ?s ?p ?o } }"); assertEquals(results, results2); } - private Set gsp(String user, String password, String graphName) { + private Set gsp(String user, String password, String dsName, String graphName) { Set results = new HashSet<>(); - try (RDFLink conn = RDFLink.connectPW(baseUrl, user, password)) { + String baseURL = baseUrl(dsName); + try (RDFLink conn = RDFLink.connectPW(baseUrl(dsName), user, password)) { Graph graph = (graphName == null) ? conn.get() : conn.get(graphName); // Extract subjects. Set seen = Iter.toSet(G.iterSubjects(graph)); @@ -244,21 +270,21 @@ private Set gsp(String user, String password, String graphName) { } } - private void gsp401(String user, String password, String graphName) { - gspHttp(401, user, password, graphName); + private void gsp401(String user, String password, String dsName, String graphName) { + gspHttp(401, user, password, dsName, graphName); } - private void gsp403(String user, String password, String graphName) { - gspHttp(403, user, password, graphName); + private void gsp403(String user, String password, String dsName, String graphName) { + gspHttp(403, user, password, dsName, graphName); } - private void gsp404(String user, String password, String graphName) { - gspHttp(404, user, password, graphName); + private void gsp404(String user, String password, String dsName, String graphName) { + gspHttp(404, user, password, dsName, graphName); } - private void gspHttp(int statusCode, String user, String password, String queryString) { + private void gspHttp(int statusCode, String user, String password, String dsName, String graphName) { try { - gsp(user, password, queryString); + gsp(user, password, dsName, graphName); if ( statusCode < 200 && statusCode > 299 ) fail("Should have responded with "+statusCode); } catch (HttpException ex) { @@ -269,78 +295,112 @@ private void gspHttp(int statusCode, String user, String password, String queryS // When a graph is not visible, it should return 404 except // for the default graph which should be empty. - @Test public void gsp_dft_userDft() { - Set results = gsp("userDft", "pwDft", null); + @ParameterizedTest(name = "{index} {0}") + @MethodSource("provideTestArgs") + public void gsp_dft_userDft(String label, String dsName) { + Set results = gsp("userDft", "pwDft", dsName, null); assertSeen(results, s0); } - @Test public void gsp_dft_userNone() { - Set results = gsp("userNone", "pwNone", null); + @ParameterizedTest(name = "{index} {0}") + @MethodSource("provideTestArgs") + public void gsp_dft_userNone(String label, String dsName) { + Set results = gsp("userNone", "pwNone", dsName, null); assertSeen(results); } - @Test public void gsp_dft_user0() { - Set results = gsp("user0", "pw0", null); + @ParameterizedTest(name = "{index} {0}") + @MethodSource("provideTestArgs") + public void gsp_dft_user0(String label, String dsName) { + Set results = gsp("user0", "pw0", dsName, null); assertSeen(results, s0); } - @Test public void gsp_dft_user1() { - Set results = gsp("user1", "pw1", null); + @ParameterizedTest(name = "{index} {0}") + @MethodSource("provideTestArgs") + public void gsp_dft_user1(String label, String dsName) { + Set results = gsp("user1", "pw1", dsName, null); assertSeen(results, s0); } - @Test public void gsp_dft_user2() { - Set results = gsp("user2", "pw2", null); + @ParameterizedTest(name = "{index} {0}") + @MethodSource("provideTestArgs") + public void gsp_dft_user2(String label, String dsName) { + Set results = gsp("user2", "pw2", dsName, null); assertSeen(results); } - @Test public void gsp_graph1_userDft() { - gsp404("userDft", "pwDft", "http://test/g1"); + @ParameterizedTest(name = "{index} {0}") + @MethodSource("provideTestArgs") + public void gsp_graph1_userDft(String label, String dsName) { + gsp404("userDft", "pwDft", dsName, "http://test/g1"); } - @Test public void gsp_graph1_userNone() { - gsp404("userNone", "pwNone", "http://test/g1"); + @ParameterizedTest(name = "{index} {0}") + @MethodSource("provideTestArgs") + public void gsp_graph1_userNone(String label, String dsName) { + gsp404("userNone", "pwNone", dsName, "http://test/g1"); } - @Test public void gsp_graph1_user0() { - gsp404("user0", "pw0", "http://test/g1"); + @ParameterizedTest(name = "{index} {0}") + @MethodSource("provideTestArgs") + public void gsp_graph1_user0(String label, String dsName) { + gsp404("user0", "pw0", dsName, "http://test/g1"); } - @Test public void gsp_graph1_user1() { - Set results = gsp("user1", "pw1", "http://test/g1"); + @ParameterizedTest(name = "{index} {0}") + @MethodSource("provideTestArgs") + public void gsp_graph1_user1(String label, String dsName) { + Set results = gsp("user1", "pw1", dsName, "http://test/g1"); assertSeen(results, s1); } - @Test public void gsp_graph1_user2() { - gsp404("user2", "pw2", "http://test/g1"); + @ParameterizedTest(name = "{index} {0}") + @MethodSource("provideTestArgs") + public void gsp_graph1_user2(String label, String dsName) { + gsp404("user2", "pw2", dsName, "http://test/g1"); } // No such graph. - @Test public void gsp_graphX_userDft() { - gsp404("userDft", "pwDft", "http://test/gX"); + @ParameterizedTest(name = "{index} {0}") + @MethodSource("provideTestArgs") + public void gsp_graphX_userDft(String label, String dsName) { + gsp404("userDft", "pwDft", dsName, "http://test/gX"); } - @Test public void gsp_graphX_userNone() { - gsp404("userNone", "pwNone", "http://test/gX"); + @ParameterizedTest(name = "{index} {0}") + @MethodSource("provideTestArgs") + public void gsp_graphX_userNone(String label, String dsName) { + gsp404("userNone", "pwNone", dsName, "http://test/gX"); } - @Test public void gsp_graphX_user0() { - gsp404("user0", "pw0", "http://test/gX"); + @ParameterizedTest(name = "{index} {0}") + @MethodSource("provideTestArgs") + public void gsp_graphX_user0(String label, String dsName) { + gsp404("user0", "pw0", dsName, "http://test/gX"); } - @Test public void gsp_graphX_user1() { - gsp404("user1", "pw1", "http://test/g1X"); + @ParameterizedTest(name = "{index} {0}") + @MethodSource("provideTestArgs") + public void gsp_graphX_user1(String label, String dsName) { + gsp404("user1", "pw1", dsName, "http://test/g1X"); } - @Test public void gsp_graphX_user2() { - gsp404("user2", "pw2", "http://test/gX"); + @ParameterizedTest(name = "{index} {0}") + @MethodSource("provideTestArgs") + public void gsp_graphX_user2(String label, String dsName) { + gsp404("user2", "pw2", dsName, "http://test/gX"); } - @Test public void gsp_bad_user() { - gsp401("userX", "pwX", null); + @ParameterizedTest(name = "{index} {0}") + @MethodSource("provideTestArgs") + public void gsp_bad_user(String label, String dsName) { + gsp401("userX", "pwX", dsName, null); } - @Test public void gsp_bad_password() { - gsp401("user0", "not-the-password", null); + @ParameterizedTest(name = "{index} {0}") + @MethodSource("provideTestArgs") + public void gsp_bad_password(String label, String dsName) { + gsp401("user0", "not-the-password", dsName, null); } } diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TestServiceDataAuthBuild.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TestServiceDataAuthBuild.java index 33c109e56a8..58a4be56db6 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TestServiceDataAuthBuild.java +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TestServiceDataAuthBuild.java @@ -18,9 +18,11 @@ package org.apache.jena.fuseki.main.access; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; + import org.apache.jena.fuseki.main.FusekiServer; -import org.junit.After; -import org.junit.Before; /** * AbstractTestServiceDatasetAuth with a configuration file. @@ -29,7 +31,7 @@ public class TestServiceDataAuthBuild extends AbstractTestServiceDatasetAuth { private FusekiServer server; - @Before public void before() { + @BeforeEach public void before() { server = FusekiServer.create() //.verbose(true) .port(port) @@ -38,7 +40,7 @@ public class TestServiceDataAuthBuild extends AbstractTestServiceDatasetAuth { server.start(); } - @After public void after () { + @AfterEach public void after () { server.stop(); } diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TestServiceDataAuthConfig.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TestServiceDataAuthConfig.java index 0a1768dbb1a..b54b8743b58 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TestServiceDataAuthConfig.java +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TestServiceDataAuthConfig.java @@ -18,6 +18,9 @@ package org.apache.jena.fuseki.main.access; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; + import org.apache.jena.fuseki.auth.Auth; import org.apache.jena.fuseki.auth.AuthPolicy; import org.apache.jena.fuseki.main.FusekiServer; @@ -26,8 +29,6 @@ import org.apache.jena.fuseki.server.Operation; import org.apache.jena.sparql.core.DatasetGraph; import org.apache.jena.sparql.core.DatasetGraphFactory; -import org.junit.After; -import org.junit.Before; /** * AbstractTestServiceDatasetAuth with a programmatically built server which should be @@ -36,12 +37,14 @@ public class TestServiceDataAuthConfig extends AbstractTestServiceDatasetAuth { private FusekiServer server; - @Before public void before() { + @BeforeEach + public void before() { server = build(port, null); server.start(); } - @After public void after () { + @AfterEach + public void after () { server.stop(); } diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TestSimpleBearer.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TestSimpleBearer.java index 87735ac5a28..d179095fd9e 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TestSimpleBearer.java +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/access/TestSimpleBearer.java @@ -18,10 +18,13 @@ package org.apache.jena.fuseki.main.access; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; import org.apache.jena.fuseki.main.auth.SimpleBearer; -import org.junit.Test; public class TestSimpleBearer { diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/prefixes/PrefixesServiceTests.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/prefixes/TS_PrefixesService.java similarity index 96% rename from jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/prefixes/PrefixesServiceTests.java rename to jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/prefixes/TS_PrefixesService.java index 20c45c9479f..5ad83830a03 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/prefixes/PrefixesServiceTests.java +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/prefixes/TS_PrefixesService.java @@ -30,4 +30,4 @@ , TestPrefixesActionResponse.class }) -public class PrefixesServiceTests {} +public class TS_PrefixesService {} diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/sys/TestFusekiModules.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/sys/TestFusekiModules.java index 75e48612cc4..fda3ebbd6cc 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/sys/TestFusekiModules.java +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/sys/TestFusekiModules.java @@ -18,26 +18,25 @@ package org.apache.jena.fuseki.main.sys; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.*; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; -import org.junit.BeforeClass; -import org.junit.Test; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; import org.apache.jena.fuseki.main.FusekiServer; import org.apache.jena.rdf.model.Model; import org.apache.jena.sys.JenaSystem; -/** Same packege for access */ +/** Same package for access */ public class TestFusekiModules { private static FusekiModules system = null; - @BeforeClass public static void beforeClass() { JenaSystem.init(); } + @BeforeAll + public static void beforeClass() { JenaSystem.init(); } @Test public void modules_0() { ModuleForTest module = new ModuleForTest(); @@ -77,28 +76,28 @@ private static void reset() { } private void lifecycle(FusekiServer.Builder builder, ModuleForTest module) { - assertEquals("prepare:", 0, module.countPrepared.get()); - assertEquals("configured:", 0, module.countConfiguration.get()); - assertEquals("server: ", 0, module.countServer.get()); - assertEquals("serverBefore: ", 0, module.countServerBeforeStarting.get()); - assertEquals("serverAfter: ", 0, module.countServerAfterStarting.get()); + assertEquals(0, module.countPrepared.get(), "prepare:"); + assertEquals(0, module.countConfiguration.get(), "configured:"); + assertEquals(0, module.countServer.get(), "server:"); + assertEquals(0, module.countServerBeforeStarting.get(), "serverBefore:"); + assertEquals(0, module.countServerAfterStarting.get(), "serverAfter:"); FusekiServer server = builder.build(); assertFalse(server.getModules().asList().isEmpty()); - assertEquals("prepare:", 1, module.countPrepared.getPlain()); - assertEquals("configured:", 1, module.countConfiguration.get()); - assertEquals("server: ", 1, module.countServer.get()); - assertEquals("serverBefore: ", 0, module.countServerBeforeStarting.get()); - assertEquals("serverAfter: ", 0, module.countServerAfterStarting.get()); + assertEquals(1, module.countPrepared.get(), "prepare:"); + assertEquals(1, module.countConfiguration.get(), "configured:"); + assertEquals(1, module.countServer.get(), "server:"); + assertEquals(0, module.countServerBeforeStarting.get(), "serverBefore:"); + assertEquals(0, module.countServerAfterStarting.get(), "serverAfter:"); server.start(); - assertEquals("prepare:", 1, module.countPrepared.get()); - assertEquals("configured:", 1, module.countConfiguration.get()); - assertEquals("server: ", 1, module.countServer.get()); - assertEquals("serverBefore: ", 1, module.countServerBeforeStarting.get()); - assertEquals("serverAfter: ", 1, module.countServerAfterStarting.get()); + assertEquals(1, module.countPrepared.get(), "prepare:"); + assertEquals(1, module.countConfiguration.get(), "configured:"); + assertEquals(1, module.countServer.get(), "server:"); + assertEquals(1, module.countServerBeforeStarting.get(), "serverBefore:"); + assertEquals(1, module.countServerAfterStarting.get(), "serverAfter:"); server.stop(); } diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/TC_FusekiMods.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/TS_FusekiMods.java similarity index 95% rename from jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/TC_FusekiMods.java rename to jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/TS_FusekiMods.java index d2fafda62f6..6771fa2f908 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/TC_FusekiMods.java +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/TS_FusekiMods.java @@ -35,6 +35,6 @@ // Apache Shiro TestModShiro.class }) -public class TC_FusekiMods { - public TC_FusekiMods() {} +public class TS_FusekiMods { + public TS_FusekiMods() {} } diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/admin/TestFusekiReload.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/admin/TestFusekiReload.java index ee68e96df1b..079a30e149c 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/admin/TestFusekiReload.java +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/admin/TestFusekiReload.java @@ -18,7 +18,7 @@ package org.apache.jena.fuseki.mod.admin; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.IOException; import java.io.UncheckedIOException; @@ -26,9 +26,9 @@ import java.nio.file.Path; import java.nio.file.StandardCopyOption; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.apache.jena.atlas.io.IOX; import org.apache.jena.atlas.logging.LogCtl; @@ -53,12 +53,12 @@ public class TestFusekiReload { private static Path fConfig1 = DIR.resolve("reload-config1.ttl"); private static Path fConfig2 = DIR.resolve("reload-config2.ttl"); - @Before public void before() { + @BeforeEach public void before() { // Initial state copyFile(fConfig1, fConfigServer); } - @AfterClass public static void after() { + @AfterAll public static void after() { try { Files.delete(fConfigServer); } catch (IOException ex) { @@ -138,9 +138,9 @@ private static void query(FusekiServer server, String datasetName, String queryS QueryExec qExec = QueryExecHTTP.service(server.datasetURL(datasetName)).query(queryString).build(); try { RowSetOps.consume(qExec.select()); - assertEquals(datasetName, expectedStatusCode, 200); + assertEquals(expectedStatusCode, 200, datasetName); } catch (QueryExceptionHTTP ex) { - assertEquals(datasetName, expectedStatusCode, ex.getStatusCode()); + assertEquals(expectedStatusCode, ex.getStatusCode(), datasetName); } } diff --git a/jena-fuseki2/jena-fuseki-main/src/test/resources/log4j2-test.properties b/jena-fuseki2/jena-fuseki-main/src/test/resources/log4j2-test.properties index 26b8c4b2e2d..fb3efdb6deb 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/resources/log4j2-test.properties +++ b/jena-fuseki2/jena-fuseki-main/src/test/resources/log4j2-test.properties @@ -25,8 +25,9 @@ logger.arq-exec.level = INFO logger.fuseki.name = org.apache.jena.fuseki logger.fuseki.level = WARN +## Some tests correctly log warnings. TS_PrefixesService logger.fuseki-fuseki.name = org.apache.jena.fuseki.Fuseki -logger.fuseki-fuseki.level = WARN +logger.fuseki-fuseki.level = ERROR logger.fuseki-autoload.name = org.apache.jena.fuseki.main.sys.FusekiAutoModules logger.fuseki-autoload.level = ERROR From 336080b1e1b2ca19f447597c3aed72ea2b32defa Mon Sep 17 00:00:00 2001 From: Andy Seaborne Date: Thu, 19 Dec 2024 10:44:12 +0000 Subject: [PATCH 3/8] GH-2902: FusekiServerCtl - Code for server on-disk state --- .../java/org/apache/jena/fuseki/Fuseki.java | 1 + .../jena/fuseki/build/FusekiConfig.java | 7 +- .../apache/jena/fuseki/ctl/ActionMetrics.java | 11 +- .../jena/fuseki/metrics/MetricsProvider.java | 35 ++- .../metrics/MetricsProviderRegistry.java | 68 ----- .../PrometheusMetricsProvider.java | 4 +- .../fuseki/metrics/SimpleMetricsProvider.java | 1 - .../metrics/prometheus/InitPrometheus.java | 38 --- .../jena/fuseki/system/DataUploader.java | 2 +- .../fuseki/system/UploadDetailsWithName.java | 32 --- .../jena/fuseki/system/spot/SpotTDB2.java | 1 - ...org.apache.jena.sys.JenaSubsystemLifecycle | 1 - .../apache/jena/fuseki/main/FusekiServer.java | 31 ++- .../jena/fuseki/main/cmds/FusekiMain.java | 27 +- .../fuseki/main/cmds/FusekiServerCmd.java | 5 +- .../fuseki/main/sys/FusekiAutoModules.java | 232 ++++++----------- .../jena/fuseki/main/sys/FusekiModules.java | 9 +- .../jena/fuseki/mgt/ActionBackupList.java | 6 +- .../jena/fuseki/mgt/ActionDatasets.java | 46 +--- .../org/apache/jena/fuseki/mgt/Backup.java | 2 +- .../{FusekiApp.java => FusekiServerCtl.java} | 241 ++++++++++-------- .../jena/fuseki/mgt/ServerMgtConst.java | 2 +- .../org/apache/jena/fuseki/mgt/Template.java | 2 +- .../jena/fuseki/mod/FusekiModServer.java | 90 +++++++ .../jena/fuseki/mod/admin/ArgModuleAdmin.java | 4 +- .../jena/fuseki/mod/admin/FMod_Admin.java | 31 ++- .../apache/jena/fuseki/mod/package-info.java | 2 - .../fuseki/mod/prometheus/ActionMetrics.java | 49 ---- .../mod/prometheus/FMod_Prometheus.java | 15 +- .../jena/fuseki/mod/shiro/FMod_Shiro.java | 10 +- .../apache/jena/fuseki/mod/ui/FMod_UI.java | 4 +- .../org/apache/jena/fuseki/server/config.ttl | 2 + .../apache/jena/fuseki/TC_FusekiServer.java | 4 +- .../jena/fuseki/main/TS_FusekiMain.java | 4 +- .../jena/fuseki/main/TestConfigFile.java | 1 - .../apache/jena/fuseki/main/TestMetrics.java | 8 +- ...Service.java => PrefixesServiceTests.java} | 3 +- .../jena/fuseki/main/sys/LegacyModule.java | 32 --- .../main/sys/ModuleByServiceLoader.java | 1 + .../jena/fuseki/main/sys/ModuleForTest.java | 1 + .../fuseki/main/sys/TestFusekiModules.java | 36 +-- .../apache/jena/fuseki/mod/TS_FusekiMods.java | 9 +- .../jena/fuseki/mod/TestFusekiServer.java | 1 - .../fuseki/mod/admin/TS_FusekiServerApp.java | 32 --- .../jena/fuseki/mod/admin/TestAdmin.java | 4 +- .../mod/admin/TestTemplateAddDataset.java | 6 +- .../fuseki/mod/metrics/TestModPrometheus.java | 16 +- .../jena/fuseki/mod/shiro/TestModShiro.java | 8 +- ...g.apache.jena.fuseki.main.sys.FusekiModule | 1 - jena-fuseki2/jena-fuseki-server/pom.xml | 10 +- .../fuseki/webapp/FusekiServerListener.java | 8 +- 51 files changed, 492 insertions(+), 704 deletions(-) delete mode 100644 jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/metrics/MetricsProviderRegistry.java rename jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/metrics/{prometheus => }/PrometheusMetricsProvider.java (93%) delete mode 100644 jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/metrics/prometheus/InitPrometheus.java delete mode 100644 jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/system/UploadDetailsWithName.java delete mode 100644 jena-fuseki2/jena-fuseki-core/src/main/resources/META-INF/services/org.apache.jena.sys.JenaSubsystemLifecycle rename jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/{FusekiApp.java => FusekiServerCtl.java} (77%) create mode 100644 jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/FusekiModServer.java delete mode 100644 jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/package-info.java delete mode 100644 jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/prometheus/ActionMetrics.java rename jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/prefixes/{TS_PrefixesService.java => PrefixesServiceTests.java} (96%) delete mode 100644 jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/sys/LegacyModule.java delete mode 100644 jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/admin/TS_FusekiServerApp.java delete mode 100644 jena-fuseki2/jena-fuseki-main/src/test/resources/META-INF/services/org.apache.jena.fuseki.main.sys.FusekiModule diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/Fuseki.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/Fuseki.java index e51b47bdc86..a8c4a0680c7 100644 --- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/Fuseki.java +++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/Fuseki.java @@ -183,6 +183,7 @@ public static void initConsts() {} public static final String attrOperationRegistry = "org.apache.jena.fuseki:OperationRegistry"; public static final String attrAuthorizationService = "org.apache.jena.fuseki:AuthorizationService"; public static final String attrFusekiServer = "org.apache.jena.fuseki:Server"; + public static final String attrMetricsProvider = "org.apache.jena.fuseki:MetricsProvider"; public static void setVerbose(ServletContext cxt, boolean verbose) { cxt.setAttribute(attrVerbose, Boolean.valueOf(verbose)); diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/FusekiConfig.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/FusekiConfig.java index 8496ef7a944..2e6ec3a8cae 100644 --- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/FusekiConfig.java +++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/FusekiConfig.java @@ -189,7 +189,6 @@ public static AuthPolicy allowedUsers(Graph graph, Node resource) { public static List processServerConfiguration(Graph configuration, Context context) { Node server = findServer(configuration); if ( server != null ) { - // XXX Temporary Resource rServer = resource(configuration, server); mergeContext(configuration,server, context); processLoadClass(configuration,server); @@ -213,8 +212,6 @@ public static List processServerConfiguration(Graph configurati public static List processServerConfiguration(Model configuration, Context context) { return processServerConfiguration(configuration.getGraph(), context); } - - // XXX Adapter /*package*/ static Resource resource(Graph graph, Node node) { Model m = ModelFactory.createModelForGraph(graph); RDFNode rNode = m.asRDFNode(node); @@ -271,7 +268,7 @@ private static void mergeContext(Graph configuration, Node resource, Context con } /** - * Process any {@code ja:loadClass} + * Legacy support for {@code ja:loadClass} */ public static void processLoadClass(Graph configuration, Node server) { if ( server == null ) @@ -633,7 +630,7 @@ else if ( isResource(ep) ) { if ( named.size() > 1 ) throw new FusekiConfigException("Multiple property values for <"+FusekiVocabG.pEndpointName+"> with <"+endpointProperty.getURI()+"> for "+BuildLib.displayStr(configuration, svc)); endpointName = named.get(0).getLiteralLexicalForm(); - // XXX Necessary? check + // Check for multiple List x = G.listSP(configuration, ep, FusekiVocabG.pAllowedUsers); if ( x.size() > 1 ) throw new FusekiConfigException("Multiple fuseki:"+FusekiVocabG.pAllowedUsers.getLocalName()+" for "+displayStr(configuration, ep)); diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/ActionMetrics.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/ActionMetrics.java index d488938e897..8565e8ae731 100644 --- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/ActionMetrics.java +++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/ctl/ActionMetrics.java @@ -17,7 +17,9 @@ */ package org.apache.jena.fuseki.ctl; -import org.apache.jena.fuseki.metrics.MetricsProviderRegistry; +import org.apache.jena.atlas.logging.Log; +import org.apache.jena.fuseki.Fuseki; +import org.apache.jena.fuseki.metrics.MetricsProvider; import org.apache.jena.fuseki.servlets.ActionLib; import org.apache.jena.fuseki.servlets.HttpAction; import org.apache.jena.fuseki.servlets.ServletOps; @@ -42,7 +44,12 @@ public void validate(HttpAction action) {} @Override public void execute(HttpAction action) { - MetricsProviderRegistry.get().scrape( action ); + MetricsProvider metricsProvider = MetricsProvider.getMetricsProvider(getServletContext()); + if ( metricsProvider == null ) { + Log.warn(Fuseki.actionLog, "No metrics provider"); + ServletOps.errorOccurred("No metrics provider"); + } + metricsProvider.scrape( action ); ServletOps.success(action); } } diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/metrics/MetricsProvider.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/metrics/MetricsProvider.java index 853ed89fd59..2dcd79cea93 100644 --- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/metrics/MetricsProvider.java +++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/metrics/MetricsProvider.java @@ -17,12 +17,43 @@ */ package org.apache.jena.fuseki.metrics; +import java.util.Objects; + import io.micrometer.core.instrument.MeterRegistry; +import jakarta.servlet.ServletContext; +import org.apache.jena.fuseki.Fuseki; +import org.apache.jena.fuseki.server.DataAccessPointRegistry; import org.apache.jena.fuseki.servlets.HttpAction; +/** Micrometer registry and output generator. */ public interface MetricsProvider { + public MeterRegistry getMeterRegistry(); + public void scrape(HttpAction action); + + /** Bind each data access point in a DataAccessPointRegistry to the system Micrometer {@link MeterRegistry}. */ + public default void dataAccessPointMetrics(MetricsProvider metricsProvider, DataAccessPointRegistry dapRegistry) { + try { + MeterRegistry meterRegistry = metricsProvider.getMeterRegistry(); + if (meterRegistry != null) { + dapRegistry.accessPoints().forEach(dap->{ + new FusekiRequestsMetrics( dap ).bindTo( meterRegistry ); + }); + } + } catch (Throwable th) { + Fuseki.configLog.error("Failed to bind all data access points to netrics provider", th); + } + } - MeterRegistry getMeterRegistry(); - void scrape(HttpAction action); + public static void setMetricsProvider(ServletContext servletContext, MetricsProvider provider) { + Objects.requireNonNull(servletContext); + if ( provider == null ) + servletContext.removeAttribute(Fuseki.attrMetricsProvider); + else + servletContext.setAttribute(Fuseki.attrMetricsProvider, provider); + } + public static MetricsProvider getMetricsProvider(ServletContext servletContext) { + Objects.requireNonNull(servletContext); + return (MetricsProvider)servletContext.getAttribute(Fuseki.attrMetricsProvider); + } } diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/metrics/MetricsProviderRegistry.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/metrics/MetricsProviderRegistry.java deleted file mode 100644 index cf37350da79..00000000000 --- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/metrics/MetricsProviderRegistry.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.jena.fuseki.metrics; - -import io.micrometer.core.instrument.MeterRegistry; -import org.apache.jena.fuseki.Fuseki; -import org.apache.jena.fuseki.server.DataAccessPointRegistry; - -public class MetricsProviderRegistry { - - private static int priority = Integer.MAX_VALUE; - private static MetricsProvider metricsProvider = new SimpleMetricsProvider(); - - public static MetricsProvider get() { - return metricsProvider; - } - - /** @deprecated Use {@link #set(MetricsProvider)} */ - @Deprecated(forRemoval = true) - public static void put(MetricsProvider metricsProvider, int priority) { - if (priority < MetricsProviderRegistry.priority) { - MetricsProviderRegistry.priority = priority; - MetricsProviderRegistry.metricsProvider = metricsProvider; - } - } - - public static void set(MetricsProvider metricsProvider) { - MetricsProviderRegistry.priority = Integer.MAX_VALUE; - MetricsProviderRegistry.metricsProvider = metricsProvider; - } - - /* - * @deprecated Use {@link #dataAccessPointMetrics}. - */ - @Deprecated(forRemoval = true) - public static void bindPrometheus(DataAccessPointRegistry dapRegistry) { - dataAccessPointMetrics(dapRegistry); - } - - /** Bind each data access point in a DataAccessPointRegistry to the system Micrometer {@link MeterRegistry}. */ - public static void dataAccessPointMetrics(DataAccessPointRegistry dapRegistry) { - try { - MeterRegistry meterRegistry = MetricsProviderRegistry.get().getMeterRegistry(); - if (meterRegistry != null) { - dapRegistry.accessPoints().forEach(dap->{ - new FusekiRequestsMetrics( dap ).bindTo( meterRegistry ); - }); - } - } catch (Throwable th) { - Fuseki.configLog.error("Failed to bind all data access points to Prometheus", th); - } - } -} diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/metrics/prometheus/PrometheusMetricsProvider.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/metrics/PrometheusMetricsProvider.java similarity index 93% rename from jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/metrics/prometheus/PrometheusMetricsProvider.java rename to jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/metrics/PrometheusMetricsProvider.java index c2cd09e4f23..fd55b74cbc4 100644 --- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/metrics/prometheus/PrometheusMetricsProvider.java +++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/metrics/PrometheusMetricsProvider.java @@ -15,14 +15,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.jena.fuseki.metrics.prometheus; +package org.apache.jena.fuseki.metrics; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.prometheusmetrics.PrometheusConfig; import io.micrometer.prometheusmetrics.PrometheusMeterRegistry; import jakarta.servlet.ServletOutputStream; -import org.apache.jena.fuseki.metrics.FusekiMetrics; -import org.apache.jena.fuseki.metrics.MetricsProvider; import org.apache.jena.fuseki.servlets.HttpAction; import org.apache.jena.fuseki.servlets.ServletOps; import org.apache.jena.riot.WebContent; diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/metrics/SimpleMetricsProvider.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/metrics/SimpleMetricsProvider.java index 6cd62f0699b..047ceb65107 100644 --- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/metrics/SimpleMetricsProvider.java +++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/metrics/SimpleMetricsProvider.java @@ -76,5 +76,4 @@ private void output(StringBuilder sbuff, String fmt, Object...args) { if ( ! str.endsWith("\n") ) sbuff.append("\n"); } - } diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/metrics/prometheus/InitPrometheus.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/metrics/prometheus/InitPrometheus.java deleted file mode 100644 index 8da041724ba..00000000000 --- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/metrics/prometheus/InitPrometheus.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.jena.fuseki.metrics.prometheus; - -import org.apache.jena.fuseki.metrics.MetricsProviderRegistry; -import org.apache.jena.sys.JenaSubsystemLifecycle; - -public class InitPrometheus implements JenaSubsystemLifecycle { - - @Override - public void start() { - MetricsProviderRegistry.set(new PrometheusMetricsProvider()); - } - - @Override - public void stop() { - } - - @Override - public int level() { - return 500; - } -} diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/system/DataUploader.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/system/DataUploader.java index df6ae9f57ea..33603fa9842 100644 --- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/system/DataUploader.java +++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/system/DataUploader.java @@ -105,7 +105,7 @@ public static UploadDetails incomingData(HttpAction action, StreamRDF dest) { // Previously, Jena has used HttpServletRequests.getParts. // Each application server (Tomcat and Jetty) has special configuration. - // Use Apache Commons FileUpload as the mulipart parser as it is portable. + // Use Apache Commons FileUpload as the multipart parser as it is portable. /** * Process an HTTP upload of RDF files (triples or quads) with content type diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/system/UploadDetailsWithName.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/system/UploadDetailsWithName.java deleted file mode 100644 index 3d60f77a277..00000000000 --- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/system/UploadDetailsWithName.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.jena.fuseki.system; - -import org.apache.jena.sparql.core.DatasetGraph; - -public class UploadDetailsWithName { - public final String graphName; - public final DatasetGraph data; - public final long count; - public UploadDetailsWithName(String gn, DatasetGraph dsg, long parserCount) { - this.graphName = gn; - this.data = dsg; - this.count = parserCount; - } -} diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/system/spot/SpotTDB2.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/system/spot/SpotTDB2.java index 9e52eb19072..9fc4e416f66 100644 --- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/system/spot/SpotTDB2.java +++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/system/spot/SpotTDB2.java @@ -172,7 +172,6 @@ private static void checkStorageArea(Location location) { /* * prefixes... */ - // XXX validateBPT(location, params.getPrefixTableBaseName()); validateBPT(location, params.getPrefixTableBaseName()); validateDAT(location, params.getPrefixTableBaseName()+"-data"); diff --git a/jena-fuseki2/jena-fuseki-core/src/main/resources/META-INF/services/org.apache.jena.sys.JenaSubsystemLifecycle b/jena-fuseki2/jena-fuseki-core/src/main/resources/META-INF/services/org.apache.jena.sys.JenaSubsystemLifecycle deleted file mode 100644 index ea05cfe5528..00000000000 --- a/jena-fuseki2/jena-fuseki-core/src/main/resources/META-INF/services/org.apache.jena.sys.JenaSubsystemLifecycle +++ /dev/null @@ -1 +0,0 @@ -org.apache.jena.fuseki.metrics.prometheus.InitPrometheus \ No newline at end of file diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/FusekiServer.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/FusekiServer.java index 313540ee0ea..ac7905a2162 100644 --- a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/FusekiServer.java +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/FusekiServer.java @@ -52,7 +52,8 @@ import org.apache.jena.fuseki.ctl.*; import org.apache.jena.fuseki.main.cmds.FusekiMain; import org.apache.jena.fuseki.main.sys.*; -import org.apache.jena.fuseki.metrics.MetricsProviderRegistry; +import org.apache.jena.fuseki.metrics.MetricsProvider; +import org.apache.jena.fuseki.mod.prometheus.PrometheusMetricsProvider; import org.apache.jena.fuseki.server.*; import org.apache.jena.fuseki.servlets.*; import org.apache.jena.graph.Graph; @@ -1406,17 +1407,19 @@ public FusekiServer build() { // FusekiModule call - inspect the DataAccessPointRegistry. FusekiModuleStep.configured(modules, this, dapRegistry, configModel); - // Setup Prometheus metrics. This will become a module. - bindPrometheus(dapRegistry); - - // Process the DataAccessPointRegistry for security. - buildSecurity(dapRegistry); - try { validate(); + // Process the DataAccessPointRegistry for security. + buildSecurity(dapRegistry); + // Build the ServletContextHandler - the Jetty server configuration. ServletContextHandler handler = buildFusekiServerContext(); + handler.getServletContext(); + + // Setup Prometheus metrics. + bindPrometheus(handler.getServletContext(), dapRegistry); + boolean hasFusekiSecurityHandler = applySecurityHandler(handler); // Prepare the DataAccessPointRegistry. // Put it in the servlet context. @@ -1427,8 +1430,6 @@ public FusekiServer build() { if ( hasFusekiSecurityHandler ) applyAccessControl(handler, dapRegistry); - - if ( jettyServerConfig != null ) { // Jetty server configuration provided. Server server = jettyServer(handler, jettyServerConfig); @@ -1487,9 +1488,15 @@ private DataAccessPointRegistry buildStart() { return dapRegistry; } - private void bindPrometheus(DataAccessPointRegistry dapRegistry) { - if ( withMetrics ) - MetricsProviderRegistry.dataAccessPointMetrics(dapRegistry); + private void bindPrometheus(ServletContext servletContext, DataAccessPointRegistry dapRegistry) { + if ( withMetrics ) { + MetricsProvider metricProvider = MetricsProvider.getMetricsProvider(servletContext); + if ( metricProvider == null ) { + metricProvider = new PrometheusMetricsProvider(); + MetricsProvider.setMetricsProvider(servletContext, metricProvider); + } + metricProvider.dataAccessPointMetrics(metricProvider, dapRegistry); + } } /** diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/FusekiMain.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/FusekiMain.java index a7a1789c442..bfcf5efda1b 100644 --- a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/FusekiMain.java +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/FusekiMain.java @@ -38,7 +38,6 @@ import org.apache.jena.fuseki.FusekiException; import org.apache.jena.fuseki.main.FusekiMainInfo; import org.apache.jena.fuseki.main.FusekiServer; -import org.apache.jena.fuseki.main.sys.FusekiAutoModules; import org.apache.jena.fuseki.main.sys.FusekiModules; import org.apache.jena.fuseki.main.sys.FusekiServerArgsCustomiser; import org.apache.jena.fuseki.main.sys.InitFusekiMain; @@ -333,7 +332,11 @@ protected void processModulesAndArgs() { private void processStdArguments(Logger log) { - // ---- Definition type + // ---- Command line definition of setup + // One dataset + // or a config file + // or a "standard setup" e.g.SPARQLer + // or empty allowed int numDefinitions = 0; SetupType setup = UNSET; @@ -572,22 +575,12 @@ private void processStdArguments(Logger log) { serverArgs.jettyConfigFile = jettyConfigFile; } - boolean withModules = hasValueOfTrue(argEnableModules); - if ( withModules ) { - // Use the discovered ones. - FusekiAutoModules.enable(true); + if ( serverArgs.fusekiModules == null ) { // Allows for external setting of serverArgs.fusekiModules - if ( serverArgs.fusekiModules == null ) { - FusekiAutoModules.setup(); - serverArgs.fusekiModules = FusekiModules.getSystemModules(); - } - } else { - // Disabled module discovery. - FusekiAutoModules.enable(false); - // Allows for external setting of serverArgs.fusekiModules - if ( serverArgs.fusekiModules == null ) { - serverArgs.fusekiModules = FusekiModules.empty(); - } + boolean withModules = hasValueOfTrue(argEnableModules); + serverArgs.fusekiModules = withModules + ? FusekiModules.getSystemModules() + : FusekiModules.empty(); } if ( contains(argCORS) ) { diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/FusekiServerCmd.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/FusekiServerCmd.java index 2c61c0ed09b..72c121a1c36 100644 --- a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/FusekiServerCmd.java +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/FusekiServerCmd.java @@ -18,7 +18,7 @@ package org.apache.jena.fuseki.main.cmds; -import org.apache.jena.fuseki.run.FusekiModServer; +import org.apache.jena.fuseki.mod.FusekiModServer; import org.apache.jena.fuseki.system.FusekiLogging; /** Fuseki command that runs a Fuseki server with the admin UI. @@ -45,9 +45,6 @@ public class FusekiServerCmd { * syntax but not start it. */ static public void main(String... args) { - // Fix up args - // --empty - // --modules=true FusekiModServer.runAsync(args).join(); } } diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/sys/FusekiAutoModules.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/sys/FusekiAutoModules.java index 5c94e9841b6..345755782bf 100644 --- a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/sys/FusekiAutoModules.java +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/sys/FusekiAutoModules.java @@ -23,12 +23,11 @@ import java.util.ServiceConfigurationError; import java.util.ServiceLoader; import java.util.function.Function; -import org.apache.jena.atlas.lib.Lib; + import org.apache.jena.atlas.lib.Version; import org.apache.jena.atlas.logging.FmtLog; import org.apache.jena.fuseki.Fuseki; import org.apache.jena.fuseki.FusekiConfigException; -import org.apache.jena.fuseki.main.FusekiServer; import org.slf4j.Logger; /** @@ -39,54 +38,19 @@ public class FusekiAutoModules { private static final Logger LOG = Fuseki.serverLog; private static final Object lock = new Object(); - public static final String logLoadingProperty = "fuseki.logLoading"; - public static final String envLogLoadingProperty = "FUSEKI_LOGLOADING"; - - private static boolean allowDiscovery = true; - private static boolean enabled = true; - - /*package*/ static boolean logModuleLoading() { - return Lib.isPropertyOrEnvVarSetToTrue(logLoadingProperty, envLogLoadingProperty); - } - - /*package*/ static Logger logger() { - return LOG; - } - - /** - * Enable/disable discovery of modules using the service loader. - * The default is 'enabled'. - */ - public static void enable(boolean setting) { - enabled = setting; - } - - /** Whether the system loaded modules are enabled. */ - public static boolean isEnabled() { - return enabled; - } - - /** - * Setup discovery of modules using the service loader. - * This replaces any current setup. - */ - public static void setup() { - if ( ! isEnabled() ) - return; - // Setup and discover now - autoModules = createServiceLoaderModules(); - } - + // Remember last loading. private static FusekiModules currentLoadedModules = null; + // ServiceLoader + private static ServiceLoader serviceLoader = null; + /** * Load FusekiAutoModules. This call reloads the modules every call. - * If disabled, return an empty {@link FusekiModules}. */ static FusekiModules load() { - if ( ! enabled ) - return FusekiModules.empty(); - currentLoadedModules = getServiceLoaderModules().load(); + if ( serviceLoader == null ) + serviceLoader = createServiceLoader(); + currentLoadedModules = loadAutoModules(serviceLoader); return get(); } @@ -101,131 +65,79 @@ static FusekiModules get() { // -- ServiceLoader machinery. - // testing - /*package*/ static void reset() { - load(); - } - - // Single auto-module controller. - private static FusekiServiceLoaderModules autoModules = null; - - private static FusekiServiceLoaderModules getServiceLoaderModules() { - // Load once. - if ( autoModules == null ) - setup(); - return autoModules; - } - - private static FusekiServiceLoaderModules createServiceLoaderModules() { - FusekiServiceLoaderModules newAutoModules = new FusekiServiceLoaderModules(); - newAutoModules.setDiscovery(); - return newAutoModules; - } +// // testing +// /*package*/ static void reset() { +// load(); +// } /** - * Use {@link java.util.ServiceLoader} to find {@link FusekiModule} - * available via the classpath or modules. - *

      - * These are the modules used when building a {@link FusekiServer} if - * {@link FusekiServer.Builder#setFusekiModules} is not used. + * Discover FusekiModules via {@link java.util.ServiceLoader}. + * This step does not create the module objects. */ - private static class FusekiServiceLoaderModules { - - // This keeps the list of discovered Fuseki modules. - private ServiceLoader serviceLoader = null; - - private FusekiServiceLoaderModules() { } - - private void setDiscovery() { - serviceLoader = discover(); - } - - /** - * Discover FusekiModules via {@link java.util.ServiceLoader}. - * This step does not create the module objects. - */ - private ServiceLoader discover() { - // Look for the 4.8.0 name (FusekiModule) which (4.9.0) is split into - // FusekiModule (interface) and FusekiAutoModule (this is loaded by ServiceLoader) - // Remove sometime! - discoveryWarnLegacy(); - - Class moduleClass = FusekiAutoModule.class; - ServiceLoader newServiceLoader = null; - synchronized (this) { - try { - newServiceLoader = ServiceLoader.load(moduleClass, this.getClass().getClassLoader()); - } catch (ServiceConfigurationError ex) { - FmtLog.error(LOG, ex, "Problem with service loading for %s", moduleClass.getName()); - throw ex; - } - if ( LOG.isDebugEnabled() ) { - newServiceLoader.stream().forEach(provider->{ - FmtLog.info(LOG, "Fuseki Module: %s", provider.type().getSimpleName()); - }); - } - } - return newServiceLoader; - } - - private void discoveryWarnLegacy() { - Class moduleClass = FusekiModule.class; + private static ServiceLoader createServiceLoader() { + Class moduleClass = FusekiAutoModule.class; + ServiceLoader newServiceLoader = null; + synchronized (lock) { try { - ServiceLoader newServiceLoader = ServiceLoader.load(moduleClass, this.getClass().getClassLoader()); + newServiceLoader = ServiceLoader.load(moduleClass, FusekiAutoModules.class.getClassLoader()); + } catch (ServiceConfigurationError ex) { + FmtLog.error(LOG, ex, "Problem with service loading for %s", moduleClass.getName()); + throw ex; + } + if ( LOG.isDebugEnabled() ) { newServiceLoader.stream().forEach(provider->{ - FmtLog.warn(FusekiAutoModules.class, "Ignored: \"%s\" : legacy use of interface FusekiModule which has changed to FusekiAutoModule", provider.type().getSimpleName()); + FmtLog.info(LOG, "Fuseki Module: %s", provider.type().getSimpleName()); }); - } catch (ServiceConfigurationError ex) { - // Ignore - we were only checking. } } + return newServiceLoader; + } - /** - * Instantiate modules found using the ServiceLoader. - * Each call to {@code load()} creates a new object for the FusekiModule. - * {@code start()} on each module has not been called. - */ - private FusekiModules load() { - if ( serviceLoader == null ) { - FmtLog.error(LOG, "Discovery step has not happened or it failed. Call FusekiSystemModules.discovery before FusekiSystemModules.load()"); - throw new FusekiConfigException("Discovery not performed"); - } - - Function, FusekiAutoModule> mapper = provider -> { - try { - FusekiAutoModule afmod = provider.get(); - return afmod; - } catch (ServiceConfigurationError ex) { - FmtLog.error(LOG, ex, - "Error instantiating class %s for %s", provider.type().getName(), FusekiModule.class.getName()); - return null; - } - }; - - // Create auto-module object, skip loads in error, sort auto-modules into level order. - List autoMods = serviceLoader.stream() - .map(mapper) - .filter(Objects::nonNull) - .sorted((x,y)-> Integer.compare(x.level(), y.level())) - .toList(); - // Start, and convert to FusekiModules (generics issue) - List fmods = autoMods.stream().map(afmod->{ - afmod.start(); - return (FusekiModule)afmod; - }).toList(); - - fmods.forEach(m->{ - String name = m.name(); - if ( name == null ) - name = m.getClass().getSimpleName(); - String verStr = Version.versionForClass(m.getClass()).orElse(null); - if ( verStr == null ) - FmtLog.info(LOG, "Module: %s", name); - else - FmtLog.info(LOG, "Module: %s (%s)", name, verStr); - }); - - return FusekiModules.create(fmods); + /** + * Instantiate modules found using a ServiceLoader. + * Each call to {@code load()} creates a new object for the FusekiModule. + * {@code start()} on each module has not been called. + */ + private static FusekiModules loadAutoModules(ServiceLoader serviceLoader) { + if ( serviceLoader == null ) { + FmtLog.error(LOG, "Discovery step has not happened or it failed. Call FusekiSystemModules.discovery before FusekiSystemModules.load()"); + throw new FusekiConfigException("Discovery not performed"); } + + Function, FusekiAutoModule> mapper = provider -> { + try { + FusekiAutoModule afmod = provider.get(); + return afmod; + } catch (ServiceConfigurationError ex) { + FmtLog.error(LOG, ex, + "Error instantiating class %s for %s", provider.type().getName(), FusekiModule.class.getName()); + return null; + } + }; + + // Create auto-module object, skip loads in error, sort auto-modules into level order. + List autoMods = serviceLoader.stream() + .map(mapper) + .filter(Objects::nonNull) + .sorted((x,y)-> Integer.compare(x.level(), y.level())) + .toList(); + // Start, and convert to FusekiModules (generics issue) + List fmods = autoMods.stream().map(afmod->{ + afmod.start(); + return (FusekiModule)afmod; + }).toList(); + + fmods.forEach(m->{ + String name = m.name(); + if ( name == null ) + name = m.getClass().getSimpleName(); + String verStr = Version.versionForClass(m.getClass()).orElse(null); + if ( verStr == null ) + FmtLog.info(LOG, "Module: %s", name); + else + FmtLog.info(LOG, "Module: %s (%s)", name, verStr); + }); + + return FusekiModules.create(fmods); } } diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/sys/FusekiModules.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/sys/FusekiModules.java index 8075d644d2b..5d0d524de80 100644 --- a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/sys/FusekiModules.java +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/sys/FusekiModules.java @@ -50,6 +50,11 @@ public static void restoreSystemDefault() { } public static FusekiModules getSystemModules() { + if ( systemFusekiModules == null ) { + if ( autoLoadedFusekiModules == null ) + autoLoadedFusekiModules = FusekiAutoModules.get(); + systemFusekiModules = autoLoadedFusekiModules; + } return systemFusekiModules; } @@ -78,10 +83,6 @@ public static FusekiModules create(List modules) { return new FusekiModules(modules); } -// public static FusekiModules autoloadedModules() { -// return FusekiAutoModules.load(); -// } - private final List modules; private FusekiModules(FusekiModule ... modules) { diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/ActionBackupList.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/ActionBackupList.java index 1e52a65b696..f7d0fc446a0 100644 --- a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/ActionBackupList.java +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/ActionBackupList.java @@ -66,11 +66,11 @@ public void execute(HttpAction action) { }; private JsonValue description(HttpAction action) { - if ( ! Files.isDirectory(FusekiApp.dirBackups) ) - ServletOps.errorOccurred(format("[%d] Backup area '%s' is not a directory", action.id, FusekiApp.dirBackups)); + if ( ! Files.isDirectory(FusekiServerCtl.dirBackups) ) + ServletOps.errorOccurred(format("[%d] Backup area '%s' is not a directory", action.id, FusekiServerCtl.dirBackups)); List paths = new ArrayList<>(); - try (DirectoryStream stream = Files.newDirectoryStream(FusekiApp.dirBackups, filterVisibleFiles)) { + try (DirectoryStream stream = Files.newDirectoryStream(FusekiServerCtl.dirBackups, filterVisibleFiles)) { stream.forEach(paths::add); } catch (IOException ex) { action.log.error(format("[%d] Backup file list :: IOException :: %s", action.id, ex.getMessage())); diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/ActionDatasets.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/ActionDatasets.java index 7630b8c6f02..977bcfea69f 100644 --- a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/ActionDatasets.java +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/ActionDatasets.java @@ -140,7 +140,7 @@ else if ( WebContent.isMultiPartForm(ct) ) // ---- // Keep a persistent copy immediately. This is not used for // anything other than being "for the record". - systemFileCopy = FusekiApp.dirSystemFileArea.resolve(uuid.toString()).toString(); + systemFileCopy = FusekiServerCtl.dirSystemFileArea.resolve(uuid.toString()).toString(); try ( OutputStream outCopy = IO.openOutputFile(systemFileCopy) ) { RDFDataMgr.write(outCopy, descriptionModel, Lang.TURTLE); } @@ -186,8 +186,8 @@ else if ( WebContent.isMultiPartForm(ct) ) action.log.info(format("[%d] Create database : name = %s", action.id, datasetPath)); - configFile = FusekiApp.generateConfigurationFilename(datasetPath); - List existing = FusekiApp.existingConfigurationFile(datasetPath); + configFile = FusekiServerCtl.generateConfigurationFilename(datasetPath); + List existing = FusekiServerCtl.existingConfigurationFile(datasetPath); if ( ! existing.isEmpty() ) ServletOps.error(HttpSC.CONFLICT_409, "Configuration file for '"+datasetPath+"' already exists"); @@ -318,7 +318,7 @@ protected void execDeleteItem(HttpAction action) { // Find the configuration. String filename = name.startsWith("/") ? name.substring(1) : name; - List configurationFiles = FusekiApp.existingConfigurationFile(filename); + List configurationFiles = FusekiServerCtl.existingConfigurationFile(filename); if ( configurationFiles.isEmpty() ) { // ---- Unmanaged @@ -352,7 +352,7 @@ protected void execDeleteItem(HttpAction action) { boolean isTDB1 = org.apache.jena.tdb1.sys.TDBInternal.isTDB1(dataService.getDataset()); boolean isTDB2 = org.apache.jena.tdb2.sys.TDBInternal.isTDB2(dataService.getDataset()); - // TODO This occasionally fails in tests due to outstanding transactions. + // This occasionally fails in tests due to outstanding transactions. try { dataService.shutdown(); } catch (JenaException ex) { @@ -363,7 +363,7 @@ protected void execDeleteItem(HttpAction action) { // Delete databases created by the UI, or the admin operation, which are // in predictable, unshared location on disk. // There may not be any database files, the in-memory case. - Path pDatabase = FusekiApp.dirDatabases.resolve(filename); + Path pDatabase = FusekiServerCtl.dirDatabases.resolve(filename); if ( Files.exists(pDatabase)) { try { if ( Files.isSymbolicLink(pDatabase)) { @@ -411,7 +411,7 @@ private static void assemblerFromForm(HttpAction action, StreamRDF dest) { params.put(Template.NAME, dbName.substring(1)); else params.put(Template.NAME, dbName); - FusekiApp.addGlobals(params); + FusekiServerCtl.addGlobals(params); //action.log.info(format("[%d] Create database : name = %s, type = %s", action.id, dbName, dbType )); @@ -430,36 +430,6 @@ private static void assemblerFromUpload(HttpAction action, StreamRDF dest) { DataUploader.incomingData(action, dest); } - // [ADMIN] -// // Persistent state change. -// private static void setDatasetState(String name, Resource newState) { -// boolean committed = false; -// system.begin(ReadWrite.WRITE); -// try { -// String dbName = name; -// if ( dbName.startsWith("/") ) -// dbName = dbName.substring(1); -// -// String update = StrUtils.strjoinNL -// (PREFIXES, -// "DELETE { GRAPH ?g { ?s fu:status ?state } }", -// "INSERT { GRAPH ?g { ?s fu:status "+FmtUtils.stringForRDFNode(newState)+" } }", -// "WHERE {", -// " GRAPH ?g { ?s fu:name '"+dbName+"'; ", -// " fu:status ?state .", -// " }", -// "}" -// ); -// UpdateRequest req = UpdateFactory.create(update); -// UpdateAction.execute(req, system); -// system.commit(); -// committed = true; -// } finally { -// if ( ! committed ) system.abort(); -// system.end(); -// } -// } - // ---- Auxiliary functions private static Quad getOne(DatasetGraph dsg, Node g, Node s, Node p, Node o) { @@ -482,8 +452,6 @@ private static Statement getOne(Model m, Resource s, Property p, RDFNode o) { return stmt; } - // TODO Merge with Upload.incomingData - private static void bodyAsGraph(HttpAction action, StreamRDF dest) { HttpServletRequest request = action.getRequest(); String base = ActionLib.wholeRequestURL(request); diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/Backup.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/Backup.java index 70ea5f51563..ea661c7ae6a 100644 --- a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/Backup.java +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/Backup.java @@ -54,7 +54,7 @@ public static String chooseFileName(String dsName) { String timestamp = DateTimeUtils.nowAsString("yyyy-MM-dd_HH-mm-ss"); String filename = ds + "_" + timestamp; - filename = FusekiApp.dirBackups.resolve(filename).toString(); + filename = FusekiServerCtl.dirBackups.resolve(filename).toString(); return filename; } diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/FusekiApp.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/FusekiServerCtl.java similarity index 77% rename from jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/FusekiApp.java rename to jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/FusekiServerCtl.java index d6cd0dc5708..37101571224 100644 --- a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/FusekiApp.java +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/FusekiServerCtl.java @@ -56,24 +56,33 @@ import org.apache.jena.sparql.core.assembler.AssemblerUtils; import org.apache.jena.system.G; -public class FusekiApp { +public class FusekiServerCtl { + /** + * Root of the varying files in this deployment. Often $PWD/run. + * This location must be writable. + */ + public static Path FUSEKI_BASE = null; + + public static final String DFT_SHIRO_INI = "shiro.ini"; + public static final String envFusekiBase = "FUSEKI_BASE"; + public static final String envFusekiShiro = "FUSEKI_SHIRO"; + // Relative names of directories in the FUSEKI_BASE area. - public static final String databasesLocationBase = "databases"; + private static final String databasesLocationBase = "databases"; // Place to put Lucene text and spatial indexes. - //private static final String databaseIndexesDir = "indexes"; + private static final String databaseIndexesDir = "text_indexes"; - public static final String backupDirNameBase = "backups"; - public static final String configDirNameBase = "configuration"; - public static final String logsNameBase = "logs"; - public static final String systemFileAreaBase = "system_files"; - public static final String templatesNameBase = "templates"; - public static final String DFT_SHIRO_INI = "shiro.ini"; - public static final String DFT_CONFIG = "config.ttl"; + private static final String backupDirNameBase = "backups"; + private static final String configDirNameBase = "configuration"; + private static final String logsNameBase = "logs"; + private static final String systemFileAreaBase = "system_files"; + private static final String templatesNameBase = "templates"; + private static final String DFT_CONFIG = "config.ttl"; - private static int BaseFusekiAutoModuleLevel = 500; - public static int levelFModAdmin = BaseFusekiAutoModuleLevel; - public static int levelFModUI = BaseFusekiAutoModuleLevel+10; - public static int levelFModShiro = BaseFusekiAutoModuleLevel+20; + private static int BaseFusekiAutoModuleLevel = 500; + public static int levelFModAdmin = BaseFusekiAutoModuleLevel; + public static int levelFModUI = BaseFusekiAutoModuleLevel+10; + public static int levelFModShiro = BaseFusekiAutoModuleLevel+20; /** Directory for TDB databases - this is known to the assembler templates */ @@ -88,9 +97,6 @@ public class FusekiApp { /** Directory for assembler files */ public static Path dirLogs = null; -// /** Directory for system database */ -// public static Path dirSystemDatabase = null; - /** Directory for files uploaded (e.g upload assembler descriptions); not data uploads. */ public static Path dirSystemFileArea = null; @@ -101,24 +107,51 @@ public class FusekiApp { // Marks the end of successful initialization. /*package*/static boolean serverInitialized = false; - - -// /** +// /** OLD // * Root of the Fuseki installation for fixed files. // * This may be null (e.g. running inside a web application container) // */ // public static Path FUSEKI_HOME = null; - /** - * Root of the varying files in this deployment. Often $PWD/run. - * This must be writable. - */ - public static Path FUSEKI_BASE = set_FUSEKI_BASE(); + // Too much is done with statics, assuming one server+admin process + // At the moment, it is one setup happening at a time. + // Once created, it is independent. + // Better, less statics, more FusekiServerApp instance object. + // Current limitation: FUSEKI_BASE is static and set in + // FusekiApp.java line 275 + // ArgModuleAdmin.java line 61 + // FMod_Admin.java line 117 + // FMod_UI.java line 133 + // Template.java line 27 + // ActionDadasets uses addGlobals. + + // Default - "run" in the current directory. + public static final String dftFusekiBase = "run"; + + public FusekiServerCtl(String location) { + if ( location == null ) { + FUSEKI_BASE = null; + return; + } + FUSEKI_BASE = Path.of(location); + } - public static String envFusekiBase = "FUSEKI_BASE"; - public static String envFusekiShiro = "FUSEKI_SHIRO"; + public Path setup() { + // Set the location of the BASE area + setFusekiBase(); + // Ensure the BASE area exists on disk. + setBaseAreaOnDisk(); + // Format the BASE area. + ensureBaseArea(FUSEKI_BASE); + return FUSEKI_BASE; + } - private static Path set_FUSEKI_BASE() { + private void setFusekiBase() { + if ( FUSEKI_BASE == null ) + FUSEKI_BASE = select_FUSEKI_BASE(); + } + + private Path select_FUSEKI_BASE() { // Does not guarantee existence Path setting = null; if ( FUSEKI_BASE == null ) @@ -127,20 +160,14 @@ private static Path set_FUSEKI_BASE() { return setting; } - private static Path calc_FUSEKI_BASE() { + private Path calc_FUSEKI_BASE() { String valueFusekiBase = getenv("FUSEKI_BASE"); if ( valueFusekiBase == null ) valueFusekiBase = dftFusekiBase; return Path.of(valueFusekiBase); } - // Default - "run" in the current directory. - public static final String dftFusekiBase = "run"; - - static void setEnvironment() { - if ( FUSEKI_BASE == null ) - FUSEKI_BASE = set_FUSEKI_BASE(); - + private void setBaseAreaOnDisk() { FmtLog.info(Fuseki.configLog, "FUSEKI_BASE=%s", FUSEKI_BASE); if ( ! Files.exists(FUSEKI_BASE) ) { try { @@ -152,96 +179,44 @@ static void setEnvironment() { // Further checks in ensureBaseArea } - public static Path setup() { - // Command line arguments "--base" ... - setEnvironment(); - // Format the BASE area. - FusekiApp.ensureBaseArea(FUSEKI_BASE); - return FUSEKI_BASE; - } - /** * Create directories if found to be missing. */ - public static void ensureBaseArea(Path FUSEKI_BASE) { - if ( Files.exists(FUSEKI_BASE) ) { - if ( ! Files.isDirectory(FUSEKI_BASE) ) - throw new FusekiConfigException("FUSEKI_BASE is not a directory: "+FUSEKI_BASE); - if ( ! Files.isWritable(FUSEKI_BASE) ) - throw new FusekiConfigException("FUSEKI_BASE is not writable: "+FUSEKI_BASE); + private void ensureBaseArea(Path baseArea) { + if ( Files.exists(baseArea) ) { + if ( ! Files.isDirectory(baseArea) ) + throw new FusekiConfigException("FUSEKI_BASE is not a directory: "+baseArea); + if ( ! Files.isWritable(baseArea) ) + throw new FusekiConfigException("FUSEKI_BASE is not writable: "+baseArea); } else { - ensureDir(FUSEKI_BASE); + ensureDir(baseArea); } // Ensure FUSEKI_BASE has the assumed directories. - dirTemplates = writeableDirectory(FUSEKI_BASE, templatesNameBase); - dirDatabases = writeableDirectory(FUSEKI_BASE, databasesLocationBase); - dirBackups = writeableDirectory(FUSEKI_BASE, backupDirNameBase); - dirConfiguration = writeableDirectory(FUSEKI_BASE, configDirNameBase); - dirLogs = writeableDirectory(FUSEKI_BASE, logsNameBase); - dirSystemFileArea = writeableDirectory(FUSEKI_BASE, systemFileAreaBase); + dirTemplates = writeableDirectory(baseArea, templatesNameBase); + dirDatabases = writeableDirectory(baseArea, databasesLocationBase); + dirBackups = writeableDirectory(baseArea, backupDirNameBase); + dirConfiguration = writeableDirectory(baseArea, configDirNameBase); + dirLogs = writeableDirectory(baseArea, logsNameBase); + dirSystemFileArea = writeableDirectory(baseArea, systemFileAreaBase); // ---- Initialize with files. // // Copy missing files into FUSEKI_BASE // Interacts with FMod_Shiro. - if ( Lib.getenv(FusekiApp.envFusekiShiro) == null ) { - copyFileIfMissing(null, DFT_SHIRO_INI, FUSEKI_BASE); - System.setProperty(FusekiApp.envFusekiShiro, FUSEKI_BASE.resolve(DFT_SHIRO_INI).toString()); + if ( Lib.getenv(FusekiServerCtl.envFusekiShiro) == null ) { + copyFileIfMissing(null, DFT_SHIRO_INI, baseArea); + System.setProperty(FusekiServerCtl.envFusekiShiro, baseArea.resolve(DFT_SHIRO_INI).toString()); } - copyFileIfMissing(null, DFT_CONFIG, FUSEKI_BASE); + copyFileIfMissing(null, DFT_CONFIG, baseArea); for ( String n : Template.templateNames ) { - copyFileIfMissing(null, n, FUSEKI_BASE); + copyFileIfMissing(null, n, baseArea); } serverInitialized = true; } - /** Copy a file from src to dst under name fn. - * If src is null, try as a classpath resource - * @param src Source directory, or null meaning use java resource. - * @param fn File name, a relative path. - * @param dst Destination directory. - * - */ - private static void copyFileIfMissing(Path src, String fn, Path dst) { - // fn may be a path. - Path dstFile = dst.resolve(fn); - if ( Files.exists(dstFile) ) - return; - if ( src != null ) { - Path srcFile = src.resolve(fn); - if ( ! Files.exists(dstFile) ) - throw new FusekiConfigException("File not found: "+srcFile); - try { - IOX.safeWrite(dstFile, output->Files.copy(srcFile, output)); - } catch (RuntimeIOException e) { - throw new FusekiConfigException("Failed to copy file "+srcFile+" to "+dstFile, e); - } - } else { - copyFileFromResource(fn, dstFile); - } - } - - private static void copyFileFromResource(String fn, Path dstFile) { - try { - // Get from the file from area "org/apache/jena/fuseki/server" - String absName = "org/apache/jena/fuseki/server/"+fn; - InputStream input = FusekiApp.class - // Else prepends classname as path - .getClassLoader() - .getResourceAsStream(absName); - - if ( input == null ) - throw new FusekiConfigException("Failed to find resource '"+absName+"'"); - IOX.safeWrite(dstFile, (output)-> input.transferTo(output)); - } - catch (RuntimeException e) { - throw new FusekiConfigException("Failed to copy "+fn+" to "+dstFile, e); - } - } - private static List processServerConfigFile(String configFilename) { if ( ! FileOps.exists(configFilename) ) { Fuseki.configLog.warn("Configuration file '" + configFilename+"' does not exist"); @@ -255,7 +230,7 @@ private static List processServerConfigFile(String configFilena return x; } - private static DataAccessPoint configFromTemplate(String templateFile, String datasetPath, + private DataAccessPoint configFromTemplate(String templateFile, String datasetPath, boolean allowUpdate, Map params) { // ---- Setup if ( params == null ) { @@ -308,6 +283,50 @@ public static void addGlobals(Map params) { // params.put("FUSEKI_HOME", pathStringOrElse(FusekiAppEnv.FUSEKI_HOME, "unset")); } + /** Copy a file from src to dst under name fn. + * If src is null, try as a classpath resource + * @param src Source directory, or null meaning use java resource. + * @param fn File name, a relative path. + * @param dst Destination directory. + * + */ + private static void copyFileIfMissing(Path src, String fn, Path dst) { + // fn may be a path. + Path dstFile = dst.resolve(fn); + if ( Files.exists(dstFile) ) + return; + if ( src != null ) { + Path srcFile = src.resolve(fn); + if ( ! Files.exists(dstFile) ) + throw new FusekiConfigException("File not found: "+srcFile); + try { + IOX.safeWrite(dstFile, output->Files.copy(srcFile, output)); + } catch (RuntimeIOException e) { + throw new FusekiConfigException("Failed to copy file "+srcFile+" to "+dstFile, e); + } + } else { + copyFileFromResource(fn, dstFile); + } + } + + private static void copyFileFromResource(String fn, Path dstFile) { + try { + // Get from the file from area "org/apache/jena/fuseki/server" + String absName = "org/apache/jena/fuseki/server/"+fn; + InputStream input = FusekiServerCtl.class + // Else prepends classname as path + .getClassLoader() + .getResourceAsStream(absName); + + if ( input == null ) + throw new FusekiConfigException("Failed to find resource '"+absName+"'"); + IOX.safeWrite(dstFile, (output)-> input.transferTo(output)); + } + catch (RuntimeException e) { + throw new FusekiConfigException("Failed to copy "+fn+" to "+dstFile, e); + } + } + private static String pathStringOrElse(Path path, String dft) { if ( path == null ) return dft; @@ -405,7 +424,7 @@ public static String generateConfigurationFilename(String dsName) { // Without "/" if ( filename.startsWith("/")) filename = filename.substring(1); - Path p = FusekiApp.dirConfiguration.resolve(filename+".ttl"); + Path p = FusekiServerCtl.dirConfiguration.resolve(filename+".ttl"); return p.toString(); } @@ -413,12 +432,12 @@ public static String generateConfigurationFilename(String dsName) { public static List existingConfigurationFile(String baseFilename) { try { List paths = new ArrayList<>(); - try (DirectoryStream stream = Files.newDirectoryStream(FusekiApp.dirConfiguration, baseFilename+".*") ) { - stream.forEach((p)-> paths.add(FusekiApp.dirConfiguration.resolve(p).toString() )); + try (DirectoryStream stream = Files.newDirectoryStream(FusekiServerCtl.dirConfiguration, baseFilename+".*") ) { + stream.forEach((p)-> paths.add(FusekiServerCtl.dirConfiguration.resolve(p).toString() )); } return paths; } catch (IOException ex) { - throw new InternalErrorException("Failed to read configuration directory "+FusekiApp.dirConfiguration); + throw new InternalErrorException("Failed to read configuration directory "+FusekiServerCtl.dirConfiguration); } } diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/ServerMgtConst.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/ServerMgtConst.java index 056b161470c..c67aa547d14 100644 --- a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/ServerMgtConst.java +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/ServerMgtConst.java @@ -20,7 +20,7 @@ /** * Various constants used in the management API functions and JSON responses in the - * webapp/full server. + * Fuseki server app. */ public class ServerMgtConst { public static final String opDatasets = "datasets"; diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/Template.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/Template.java index 2ef63652b8e..1266fb3faf4 100644 --- a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/Template.java +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/Template.java @@ -24,7 +24,7 @@ public class Template { public static Path getPath(String templateName) { - return FusekiApp.FUSEKI_BASE.resolve(templateName); + return FusekiServerCtl.FUSEKI_BASE.resolve(templateName); } public static final String templateDir = "templates"; diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/FusekiModServer.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/FusekiModServer.java new file mode 100644 index 00000000000..99bd550b4ff --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/FusekiModServer.java @@ -0,0 +1,90 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.mod; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.apache.jena.atlas.lib.FileOps; +import org.apache.jena.fuseki.main.FusekiServer; +import org.apache.jena.fuseki.main.cmds.FusekiMain; +import org.apache.jena.fuseki.main.sys.FusekiModule; +import org.apache.jena.fuseki.main.sys.FusekiModules; +import org.apache.jena.fuseki.mod.admin.FMod_Admin; +import org.apache.jena.fuseki.mod.prometheus.FMod_Prometheus; +import org.apache.jena.fuseki.mod.shiro.FMod_Shiro; +import org.apache.jena.fuseki.mod.ui.FMod_UI; + +public class FusekiModServer { + + public static void main(String... args) { + runAsync(args).join(); + } + + public static FusekiServer runAsync(String... args) { + return construct(args).start(); + } + + public static FusekiServer construct(String... args) { + // Order: FMod_Admin before FMod_Shiro + // These modules may have state that is carried across the build steps. + FusekiModule fmodShiro = FMod_Shiro.create(); + FusekiModule fmodAdmin = FMod_Admin.create(); + + FusekiModules serverModules = FusekiModules.create( fmodAdmin + , FMod_UI.get() + , fmodShiro + , FMod_Prometheus.get() ); + serverModules.forEach(FusekiMain::addCustomiser); + + System.setProperty("FUSEKI_BASE", "run"); + FileOps.ensureDir("run"); + + // Adjust args. + List argList = Arrays.asList(args); + // Ensure "--empty", "--modules=true" + // Better?: moded startup - i.e. setting defaults. + + if ( args.length == 0 ) { + String [] defaultArgs = { "--port=3030", "--empty" }; + args = defaultArgs; + } else { + List argsList = new ArrayList(Arrays.asList(args)); + if ( ! containsArg(argList, "--?empty") ) + argsList.add(0, "--empty"); // addFirst in java21 + if ( ! containsArg(argList, "--?modules") ) + argsList.add(0, "--modules=true"); + args = argsList.toArray(args); + } + + FusekiModules modules = serverModules; + FusekiModules.setSystemDefault(modules); + FusekiServer server = FusekiServer.construct(args); + return server; + } + + private static boolean containsArg(List argList, String argRegex) { + //Pattern pattern = Pattern.compile(argRegex); + + return argList.stream().anyMatch(arg->{ + return arg.matches(argRegex); + }); + } +} diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/admin/ArgModuleAdmin.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/admin/ArgModuleAdmin.java index 6cb3ed0b4e5..f1b9d6c751d 100644 --- a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/admin/ArgModuleAdmin.java +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/admin/ArgModuleAdmin.java @@ -26,7 +26,7 @@ import org.apache.jena.cmd.CmdArgModule; import org.apache.jena.cmd.CmdGeneral; import org.apache.jena.fuseki.FusekiConfigException; -import org.apache.jena.fuseki.mgt.FusekiApp; +import org.apache.jena.fuseki.mgt.FusekiServerCtl; public class ArgModuleAdmin implements ArgModuleGeneral { // Add a static of "extra command" @@ -58,7 +58,7 @@ public void processArgs(CmdArgModule cmdLine) { if ( ! Files.isWritable(directory) ) throw new FusekiConfigException("Not writable: "+dirStr); - FusekiApp.FUSEKI_BASE = directory; + FusekiServerCtl.FUSEKI_BASE = directory; } @Override diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/admin/FMod_Admin.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/admin/FMod_Admin.java index c48bd290bb3..d4ea62e6612 100644 --- a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/admin/FMod_Admin.java +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/admin/FMod_Admin.java @@ -38,7 +38,7 @@ import org.apache.jena.fuseki.mgt.ActionBackup; import org.apache.jena.fuseki.mgt.ActionBackupList; import org.apache.jena.fuseki.mgt.ActionDatasets; -import org.apache.jena.fuseki.mgt.FusekiApp; +import org.apache.jena.fuseki.mgt.FusekiServerCtl; import org.apache.jena.fuseki.server.DataAccessPoint; import org.apache.jena.rdf.model.Model; import org.slf4j.Logger; @@ -80,8 +80,8 @@ public void serverArgsModify(CmdGeneral fusekiCmd, ServerArgs serverArgs) { ArgModuleGeneral argModule = new ArgModuleGeneral() { @Override public void registerWith(CmdGeneral cmdLine) { - cmdLine.add(argAdmin, "--admin", "Enable server admin with user:password"); - cmdLine.add(argAdminArea,"--adminRun", "Directory for server configuration"); +// cmdLine.add(argAdmin, "--admin", "Enable server admin with user:password"); +// cmdLine.add(argAdminArea,"--adminRun", "Directory for server configuration"); } @Override public void processArgs(CmdArgModule cmdLine) {} @@ -101,6 +101,7 @@ public void serverArgsPrepare(CmdGeneral fusekiCmd, ServerArgs serverArgs) { if ( dirStr != null ) directory = Path.of(dirStr); + // Phase 2 if ( admin.equals("localhost") ) {} else { String pwFile = admin; @@ -113,7 +114,7 @@ public void serverArgsPrepare(CmdGeneral fusekiCmd, ServerArgs serverArgs) { if ( ! Files.isWritable(directory) ) throw new FusekiConfigException("Not writable: "+dirStr); } - FusekiApp.FUSEKI_BASE = directory; + FusekiServerCtl.FUSEKI_BASE = directory; } // @Override @@ -124,28 +125,26 @@ public void serverArgsPrepare(CmdGeneral fusekiCmd, ServerArgs serverArgs) { @Override public void prepare(FusekiServer.Builder builder, Set datasetNames, Model configModel) { - // Unpack + // Ensure the work area is setup - // XXX Do better! - FusekiApp.FUSEKI_BASE = null; - -// FusekiApp fusekiApp = new FusekiApp(); -// //fusekiApp.init(); -// String fusekiApp.FUSEKI_BASE - - Path path = FusekiApp.setup(); + Path path; + synchronized(FusekiServerCtl.class) { + // Temporary - one at a time because FUSEKI_BASE is static. + FusekiServerCtl app = new FusekiServerCtl(null); + path = app.setup(); + } FmtLog.info(LOG, "Fuseki Admin: %s", path); // Shiro. - Path shiroIni = path.resolve(FusekiApp.DFT_SHIRO_INI); + Path shiroIni = path.resolve(FusekiServerCtl.DFT_SHIRO_INI); if ( Files.exists(shiroIni) ) { - System.setProperty(FusekiApp.envFusekiShiro, shiroIni.toString()); + System.setProperty(FusekiServerCtl.envFusekiShiro, shiroIni.toString()); } else { FmtLog.info(LOG, "No shiro.ini: dir=%s", path); } - String configDir = FusekiApp.dirConfiguration.toString(); + String configDir = FusekiServerCtl.dirConfiguration.toString(); List directoryDatabases = FusekiConfig.readConfigurationDirectory(configDir); if ( directoryDatabases.isEmpty() ) diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/package-info.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/package-info.java deleted file mode 100644 index 7345d58a493..00000000000 --- a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -package org.apache.jena.fuseki.mod; - diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/prometheus/ActionMetrics.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/prometheus/ActionMetrics.java deleted file mode 100644 index 92164563a6a..00000000000 --- a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/prometheus/ActionMetrics.java +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.jena.fuseki.mod.prometheus; - -import org.apache.jena.fuseki.ctl.ActionCtl; -import org.apache.jena.fuseki.metrics.MetricsProviderRegistry; -import org.apache.jena.fuseki.servlets.ActionLib; -import org.apache.jena.fuseki.servlets.HttpAction; -import org.apache.jena.fuseki.servlets.ServletOps; - -public class ActionMetrics extends ActionCtl { - - public ActionMetrics() { super(); } - - @Override - public void execGet(HttpAction action) { - super.executeLifecycle(action); - } - - @Override - public void execOptions(HttpAction action) { - ActionLib.doOptionsGet(action); - ServletOps.success(action); - } - - @Override - public void validate(HttpAction action) {} - - @Override - public void execute(HttpAction action) { - MetricsProviderRegistry.get().scrape( action ); - ServletOps.success(action); - } -} diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/prometheus/FMod_Prometheus.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/prometheus/FMod_Prometheus.java index 0d51e0bd4fd..c61a45fc054 100644 --- a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/prometheus/FMod_Prometheus.java +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/prometheus/FMod_Prometheus.java @@ -20,9 +20,11 @@ import java.util.Set; +import org.apache.jena.fuseki.Fuseki; +import org.apache.jena.fuseki.ctl.ActionMetrics; import org.apache.jena.fuseki.main.FusekiServer; import org.apache.jena.fuseki.main.sys.FusekiModule; -import org.apache.jena.fuseki.metrics.MetricsProviderRegistry; +import org.apache.jena.fuseki.metrics.MetricsProvider; import org.apache.jena.rdf.model.Model; /** @@ -42,22 +44,19 @@ public FMod_Prometheus() {} // @Override // public int level() { // return 5000; -// } -// -// @Override public void start() { -// Fuseki.configLog.info("FMod Prometheus Metrics"); -// MetricsProviderRegistry.set(new PrometheusMetricsProvider()); // } @Override public String name() { return "FMod Prometheus Metrics"; } @Override public void prepare(FusekiServer.Builder serverBuilder, Set datasetNames, Model configModel) { - //MetricsProviderRegistry.set(new PrometheusMetricsProvider()); + MetricsProvider metricsProvider = new PrometheusMetricsProvider(); + serverBuilder.addServletAttribute(Fuseki.attrMetricsProvider, metricsProvider); serverBuilder.addServlet("/$/metrics", new ActionMetrics()); } @Override public void server(FusekiServer server) { - MetricsProviderRegistry.dataAccessPointMetrics(server.getDataAccessPointRegistry()); + MetricsProvider metricsProvider = MetricsProvider.getMetricsProvider(server.getServletContext()); + metricsProvider.dataAccessPointMetrics(metricsProvider, server.getDataAccessPointRegistry()); } } diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/shiro/FMod_Shiro.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/shiro/FMod_Shiro.java index e70a44d58d4..ff0acf2fc49 100644 --- a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/shiro/FMod_Shiro.java +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/shiro/FMod_Shiro.java @@ -34,7 +34,7 @@ import org.apache.jena.fuseki.main.FusekiServer; import org.apache.jena.fuseki.main.cmds.ServerArgs; import org.apache.jena.fuseki.main.sys.FusekiModule; -import org.apache.jena.fuseki.mgt.FusekiApp; +import org.apache.jena.fuseki.mgt.FusekiServerCtl; import org.apache.jena.rdf.model.Model; import org.apache.shiro.web.servlet.ShiroFilter; import org.eclipse.jetty.ee10.servlet.ServletContextHandler; @@ -45,8 +45,9 @@ /** * Fuseki Module for Apache Shiro. *

      - * TODO - * Configuration + * Looks for an argument {@code --shiro=file}, and + * in environment variable {@code FUSEKI_SHIRO} + * (including via system proprties). */ public class FMod_Shiro implements FusekiModule { @@ -79,7 +80,6 @@ public static FMod_Shiro create() { private static ArgDecl argShiroIni = new ArgDecl(true, "shiro", "shiro-ini"); - // XXX Should be a per build variable. private String shiroFile = null; public FMod_Shiro() { @@ -122,7 +122,7 @@ public void serverArgsPrepare(CmdGeneral fusekiCmd, ServerArgs serverArgs) { public void prepare(FusekiServer.Builder serverBuilder, Set datasetNames, Model configModel) { if ( shiroFile == null ) { // Environment variable: FUSEKI_SHIRO - shiroFile = Lib.getenv(FusekiApp.envFusekiShiro); + shiroFile = Lib.getenv(FusekiServerCtl.envFusekiShiro); } if ( shiroFile == null ) { diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/ui/FMod_UI.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/ui/FMod_UI.java index a46ec343aa5..51ad0fa32da 100644 --- a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/ui/FMod_UI.java +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/ui/FMod_UI.java @@ -34,7 +34,7 @@ import org.apache.jena.fuseki.main.FusekiServer; import org.apache.jena.fuseki.main.cmds.ServerArgs; import org.apache.jena.fuseki.main.sys.FusekiModule; -import org.apache.jena.fuseki.mgt.FusekiApp; +import org.apache.jena.fuseki.mgt.FusekiServerCtl; import org.apache.jena.fuseki.validation.DataValidator; import org.apache.jena.fuseki.validation.IRIValidator; import org.apache.jena.fuseki.validation.QueryValidator; @@ -130,7 +130,7 @@ private String findFusekiApp() { // 2:: $FUSEKI_BASE/webapp // If the FUSEKI_BASE does not exists, it is created later in FMod_admin.prepare // and does not include Fuseki app. - String x = fromPath(FusekiApp.FUSEKI_BASE, directoryNameUI); + String x = fromPath(FusekiServerCtl.FUSEKI_BASE, directoryNameUI); if ( x != null ) { LOG.info("Fuseki UI - path resource: "+x); return x; diff --git a/jena-fuseki2/jena-fuseki-main/src/main/resources/org/apache/jena/fuseki/server/config.ttl b/jena-fuseki2/jena-fuseki-main/src/main/resources/org/apache/jena/fuseki/server/config.ttl index e69de29bb2d..ef4f170a30d 100644 --- a/jena-fuseki2/jena-fuseki-main/src/main/resources/org/apache/jena/fuseki/server/config.ttl +++ b/jena-fuseki2/jena-fuseki-main/src/main/resources/org/apache/jena/fuseki/server/config.ttl @@ -0,0 +1,2 @@ +## Licensed under the terms of http://www.apache.org/licenses/LICENSE-2.0 +## Empty file \ No newline at end of file diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/TC_FusekiServer.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/TC_FusekiServer.java index 6b4d86c8fb6..8396f202fb2 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/TC_FusekiServer.java +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/TC_FusekiServer.java @@ -23,14 +23,12 @@ import org.apache.jena.fuseki.main.TS_FusekiMain; import org.apache.jena.fuseki.main.access.TS_SecurityFuseki; -import org.apache.jena.fuseki.main.prefixes.TS_PrefixesService; import org.apache.jena.fuseki.mod.TS_FusekiMods; @Suite @SelectClasses({ TS_FusekiMain.class, TS_SecurityFuseki.class, - TS_FusekiMods.class, - TS_PrefixesService.class + TS_FusekiMods.class }) public class TC_FusekiServer {} diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TS_FusekiMain.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TS_FusekiMain.java index f2ba8dda13c..7371b5e6745 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TS_FusekiMain.java +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TS_FusekiMain.java @@ -21,7 +21,7 @@ import org.junit.platform.suite.api.SelectClasses; import org.junit.platform.suite.api.Suite; -import org.apache.jena.fuseki.main.prefixes.TS_PrefixesService; +import org.apache.jena.fuseki.main.prefixes.PrefixesServiceTests; import org.apache.jena.fuseki.main.sys.TestFusekiModules; @Suite @@ -54,7 +54,7 @@ , TestPatchFuseki.class , TestFusekiCustomScriptFunc.class - , TS_PrefixesService.class + , PrefixesServiceTests.class , TestMetrics.class , TestFusekiShaclValidation.class diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestConfigFile.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestConfigFile.java index a0e8c176346..1711cb5d5af 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestConfigFile.java +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestConfigFile.java @@ -50,7 +50,6 @@ public class TestConfigFile { """; - private static RDFConnection namedServices(String baseURL) { return RDFConnectionRemote.newBuilder() .destination(baseURL) diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestMetrics.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestMetrics.java index c473c18aaa9..bc09f2a130c 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestMetrics.java +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestMetrics.java @@ -18,6 +18,7 @@ package org.apache.jena.fuseki.main; import static org.apache.jena.http.HttpLib.handleResponseRtnString; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.InputStream; @@ -25,11 +26,13 @@ import java.net.http.HttpResponse; import java.net.http.HttpResponse.BodyHandlers; +import org.junit.jupiter.api.Test; + import org.apache.jena.http.HttpEnv; import org.apache.jena.http.HttpLib; import org.apache.jena.riot.WebContent; import org.apache.jena.riot.web.HttpNames; -import org.junit.jupiter.api.Test; +import org.apache.jena.web.HttpSC; public class TestMetrics extends AbstractFusekiTest { @@ -38,8 +41,9 @@ public void can_retrieve_metrics() { String r = serverURL() + "$/metrics"; HttpRequest request = HttpRequest.newBuilder().uri(HttpLib.toRequestURI(r)).build(); HttpResponse response = HttpLib.executeJDK(HttpEnv.getDftHttpClient(), request, BodyHandlers.ofInputStream()); - String body = handleResponseRtnString(response); + assertEquals(HttpSC.OK_200, response.statusCode()); + String body = handleResponseRtnString(response); String ct = response.headers().firstValue(HttpNames.hContentType).orElse(null); assertTrue(ct.contains(WebContent.contentTypeTextPlain)); assertTrue(ct.contains(WebContent.charsetUTF8)); diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/prefixes/TS_PrefixesService.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/prefixes/PrefixesServiceTests.java similarity index 96% rename from jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/prefixes/TS_PrefixesService.java rename to jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/prefixes/PrefixesServiceTests.java index 5ad83830a03..815847c62f8 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/prefixes/TS_PrefixesService.java +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/prefixes/PrefixesServiceTests.java @@ -28,6 +28,5 @@ , TestPrefixesServiceRDF.class , TestPrefixesServicePrefixesMap.class , TestPrefixesActionResponse.class - }) -public class TS_PrefixesService {} +public class PrefixesServiceTests {} diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/sys/LegacyModule.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/sys/LegacyModule.java deleted file mode 100644 index d0c34040293..00000000000 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/sys/LegacyModule.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.jena.fuseki.main.sys; - -/** testing the legacy-discovery code */ -public class LegacyModule implements FusekiModule { - - public LegacyModule() { - } - - @Override - public String name() { - return "LegacyModule"; - } - -} diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/sys/ModuleByServiceLoader.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/sys/ModuleByServiceLoader.java index d7ccbfeff17..532ecc62aa1 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/sys/ModuleByServiceLoader.java +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/sys/ModuleByServiceLoader.java @@ -26,6 +26,7 @@ import org.apache.jena.fuseki.server.DataAccessPointRegistry; import org.apache.jena.rdf.model.Model; +// Must be in jena-fuseki-main/src/test/resources/META-INF/services/org.apache.jena.fuseki.main.sys.FusekiAutoModule public class ModuleByServiceLoader extends ModuleForTest implements FusekiAutoModule { private static ModuleByServiceLoader module = null; diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/sys/ModuleForTest.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/sys/ModuleForTest.java index a1b627911df..a87eaf45fa8 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/sys/ModuleForTest.java +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/sys/ModuleForTest.java @@ -26,6 +26,7 @@ import org.apache.jena.fuseki.server.DataAccessPointRegistry; import org.apache.jena.rdf.model.Model; +// Not autoloaded public class ModuleForTest implements FusekiModule { public AtomicInteger countPrepared = new AtomicInteger(0); diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/sys/TestFusekiModules.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/sys/TestFusekiModules.java index fda3ebbd6cc..28c020e2346 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/sys/TestFusekiModules.java +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/sys/TestFusekiModules.java @@ -45,20 +45,13 @@ public class TestFusekiModules { // Created, not loaded } - private static void reset() { - ModuleByServiceLoader.reset(); - FusekiModules.resetSystemDefault(); - } - @Test public void lifecycle_1() { ModuleForTest module = new ModuleForTest(); FusekiModules fmods = FusekiModules.create(module); - // Mock default set. FusekiModules.setSystemDefault(fmods); - - FusekiServer.Builder builder = FusekiServer.create().port(0); try { + FusekiServer.Builder builder = FusekiServer.create().port(0); lifecycle(builder, module); } finally { FusekiModules.setSystemDefault(null); @@ -66,7 +59,8 @@ private static void reset() { } @Test public void lifecycle_2() { - reset(); + ModuleByServiceLoader.reset(); + FusekiModules.resetSystemDefault(); ModuleForTest module = new ModuleForTest(); FusekiModules fmods = FusekiModules.create(module); @@ -103,18 +97,24 @@ private void lifecycle(FusekiServer.Builder builder, ModuleForTest module) { } @Test public void autoload_1() { - // Included reload. + FusekiModules systemModules = FusekiModules.getSystemModules(); ModuleByServiceLoader.reset(); - FusekiModules.resetSystemDefault(); + try { + FusekiModules loadedModules = FusekiAutoModules.load(); + FusekiModules.setSystemDefault(loadedModules); - // Reloaded by FusekiModules.resetSystemDefault - assertEquals(1, ModuleByServiceLoader.countLoads.get()); - assertEquals(1, ModuleByServiceLoader.countStart.get()); + // Reloaded by FusekiModules.resetSystemDefault + assertEquals(1, ModuleByServiceLoader.countLoads.get(),"countLoads:"); + assertEquals(1, ModuleByServiceLoader.countStart.get(), "countStart:"); - // Default : loaded FusekiModules - FusekiServer.Builder builder = FusekiServer.create().port(0); - ModuleForTest module = ModuleByServiceLoader.lastLoaded(); - lifecycle(builder, module); + // Default : loaded FusekiModules + FusekiServer.Builder builder = FusekiServer.create().port(0); + ModuleForTest module = ModuleByServiceLoader.lastLoaded(); + lifecycle(builder, module); + } finally { + ModuleByServiceLoader.reset(); + FusekiModules.setSystemDefault(systemModules); + } } @Test public void server_module_1() { diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/TS_FusekiMods.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/TS_FusekiMods.java index 6771fa2f908..f4f261e0e97 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/TS_FusekiMods.java +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/TS_FusekiMods.java @@ -21,13 +21,18 @@ import org.junit.platform.suite.api.SelectClasses; import org.junit.platform.suite.api.Suite; -import org.apache.jena.fuseki.mod.admin.TS_FusekiServerApp; +import org.apache.jena.fuseki.mod.admin.TestAdmin; +import org.apache.jena.fuseki.mod.admin.TestFusekiReload; +import org.apache.jena.fuseki.mod.admin.TestTemplateAddDataset; import org.apache.jena.fuseki.mod.metrics.TestModPrometheus; import org.apache.jena.fuseki.mod.shiro.TestModShiro; @Suite @SelectClasses({ - TS_FusekiServerApp.class, + // Admin + TestAdmin.class, + TestFusekiReload.class, + TestTemplateAddDataset.class, // UI // Prometheus diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/TestFusekiServer.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/TestFusekiServer.java index e0a6b2392df..c9d11ed6919 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/TestFusekiServer.java +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/TestFusekiServer.java @@ -23,7 +23,6 @@ import org.junit.jupiter.api.Test; import org.apache.jena.fuseki.main.FusekiServer; -import org.apache.jena.fuseki.run.FusekiModServer; /** * Test for the whole Fuseki server, not components. diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/admin/TS_FusekiServerApp.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/admin/TS_FusekiServerApp.java deleted file mode 100644 index b36360b8cba..00000000000 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/admin/TS_FusekiServerApp.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.jena.fuseki.mod.admin; - -import org.junit.platform.suite.api.SelectClasses; -import org.junit.platform.suite.api.Suite; - -@Suite -@SelectClasses({ - TestAdmin.class, - TestFusekiReload.class, - TestTemplateAddDataset.class, -}) -public class TS_FusekiServerApp { - -} diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/admin/TestAdmin.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/admin/TestAdmin.java index df0f6bafddd..396b14b2890 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/admin/TestAdmin.java +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/admin/TestAdmin.java @@ -56,7 +56,7 @@ import org.apache.jena.fuseki.ctl.JsonConstCtl; import org.apache.jena.fuseki.main.FusekiServer; import org.apache.jena.fuseki.main.sys.FusekiModules; -import org.apache.jena.fuseki.mgt.FusekiApp; +import org.apache.jena.fuseki.mgt.FusekiServerCtl; import org.apache.jena.fuseki.mgt.ServerMgtConst; import org.apache.jena.fuseki.server.ServerConst; import org.apache.jena.fuseki.test.HttpTest; @@ -121,7 +121,7 @@ private FusekiServer createServerForTest() { server.stop(); serverURL = null; // Clearup FMod_Shiro. - System.getProperties().remove(FusekiApp.envFusekiShiro); + System.getProperties().remove(FusekiServerCtl.envFusekiShiro); } protected String urlRoot() { diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/admin/TestTemplateAddDataset.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/admin/TestTemplateAddDataset.java index 975876095dc..9743802dcfa 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/admin/TestTemplateAddDataset.java +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/admin/TestTemplateAddDataset.java @@ -36,7 +36,7 @@ import org.apache.jena.fuseki.Fuseki; import org.apache.jena.fuseki.main.FusekiServer; import org.apache.jena.fuseki.main.sys.FusekiModules; -import org.apache.jena.fuseki.mgt.FusekiApp; +import org.apache.jena.fuseki.mgt.FusekiServerCtl; import org.apache.jena.http.HttpOp; import org.apache.jena.query.QueryExecution; import org.apache.jena.rdfconnection.RDFConnection; @@ -85,7 +85,7 @@ private static FusekiServer createServerForTest() { server.stop(); serverURL = null; // Clearup FMod_Shiro. - System.getProperties().remove(FusekiApp.envFusekiShiro); + System.getProperties().remove(FusekiServerCtl.envFusekiShiro); } protected String urlRoot() { @@ -143,7 +143,7 @@ private void testAddDelete(String dbName, String dbType, boolean alreadyExists, int x1 = count(conn); assertEquals(1, x1); - Path pathDB = FusekiApp.dirDatabases.resolve(dbName); + Path pathDB = FusekiServerCtl.dirDatabases.resolve(dbName); if ( hasFiles ) assertTrue(Files.exists(pathDB)); diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/metrics/TestModPrometheus.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/metrics/TestModPrometheus.java index ce3be524fb9..dbb475b002d 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/metrics/TestModPrometheus.java +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/metrics/TestModPrometheus.java @@ -27,13 +27,13 @@ import java.net.http.HttpResponse; import java.net.http.HttpResponse.BodyHandlers; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.*; import org.apache.jena.fuseki.main.FusekiServer; import org.apache.jena.fuseki.main.sys.FusekiModules; +import org.apache.jena.fuseki.metrics.MetricsProvider; import org.apache.jena.fuseki.mod.prometheus.FMod_Prometheus; +import org.apache.jena.fuseki.mod.prometheus.PrometheusMetricsProvider; import org.apache.jena.http.HttpEnv; import org.apache.jena.http.HttpLib; import org.apache.jena.riot.WebContent; @@ -41,6 +41,7 @@ import org.apache.jena.sparql.core.DatasetGraph; import org.apache.jena.sparql.core.DatasetGraphFactory; +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class TestModPrometheus { private FusekiServer testServer = null; @@ -62,6 +63,15 @@ public class TestModPrometheus { } @Test + @Order(1) + public void metrics_available() { + MetricsProvider metricsProvider = MetricsProvider.getMetricsProvider(testServer.getServletContext()); + assertNotNull(metricsProvider); + assertTrue(metricsProvider instanceof PrometheusMetricsProvider); + } + + @Test + @Order(2) public void can_retrieve_metrics() { String metricsURL = testServer.serverURL()+"$/metrics"; HttpRequest request = HttpRequest.newBuilder().uri(HttpLib.toRequestURI(metricsURL)).build(); diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/shiro/TestModShiro.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/shiro/TestModShiro.java index dafc636f78d..48f064f6a74 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/shiro/TestModShiro.java +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/shiro/TestModShiro.java @@ -37,7 +37,7 @@ import org.apache.jena.fuseki.main.cmds.FusekiMain; import org.apache.jena.fuseki.main.sys.FusekiModule; import org.apache.jena.fuseki.main.sys.FusekiModules; -import org.apache.jena.fuseki.mgt.FusekiApp; +import org.apache.jena.fuseki.mgt.FusekiServerCtl; import org.apache.jena.fuseki.system.FusekiLogging; import org.apache.jena.graph.Graph; import org.apache.jena.http.HttpEnv; @@ -62,7 +62,7 @@ public class TestModShiro { } @BeforeEach void before() { - System.getProperties().remove(FusekiApp.envFusekiShiro); + System.getProperties().remove(FusekiServerCtl.envFusekiShiro); AuthEnv.get().clearAuthEnv(); } @@ -71,7 +71,7 @@ public class TestModShiro { } @AfterAll static void afterAll() { - System.getProperties().remove(FusekiApp.envFusekiShiro); + System.getProperties().remove(FusekiServerCtl.envFusekiShiro); } private String unlocalhost(FusekiServer server, String dataset) { @@ -83,7 +83,7 @@ private String unlocalhost(FusekiServer server, String dataset) { /** Builder for a server with Shiro */ private FusekiServer.Builder serverBuilderWithShiro(String filename) { - System.getProperties().setProperty(FusekiApp.envFusekiShiro, filename); + System.getProperties().setProperty(FusekiServerCtl.envFusekiShiro, filename); FusekiModules modules = FusekiModules.create(FMod_Shiro.create()); return FusekiServer.create() .port(0) diff --git a/jena-fuseki2/jena-fuseki-main/src/test/resources/META-INF/services/org.apache.jena.fuseki.main.sys.FusekiModule b/jena-fuseki2/jena-fuseki-main/src/test/resources/META-INF/services/org.apache.jena.fuseki.main.sys.FusekiModule deleted file mode 100644 index fd5ce7ef2e4..00000000000 --- a/jena-fuseki2/jena-fuseki-main/src/test/resources/META-INF/services/org.apache.jena.fuseki.main.sys.FusekiModule +++ /dev/null @@ -1 +0,0 @@ -org.apache.jena.fuseki.main.sys.LegacyModule diff --git a/jena-fuseki2/jena-fuseki-server/pom.xml b/jena-fuseki2/jena-fuseki-server/pom.xml index e38fec0b688..28cc33f10ee 100644 --- a/jena-fuseki2/jena-fuseki-server/pom.xml +++ b/jena-fuseki2/jena-fuseki-server/pom.xml @@ -40,7 +40,13 @@ org.apache.jena jena-fuseki-main - ${project.version} + 5.3.0-SNAPSHOT + + + + org.apache.jena + jena-fuseki-ui + 5.3.0-SNAPSHOT @@ -99,7 +105,7 @@ - org.apache.jena.fuseki.main.cmds.FusekiMainCmd + org.apache.jena.fuseki.main.cmds.FusekiServerCmd true diff --git a/jena-fuseki2/jena-fuseki-webapp/src/main/java/org/apache/jena/fuseki/webapp/FusekiServerListener.java b/jena-fuseki2/jena-fuseki-webapp/src/main/java/org/apache/jena/fuseki/webapp/FusekiServerListener.java index 469e396f320..50357959a42 100644 --- a/jena-fuseki2/jena-fuseki-webapp/src/main/java/org/apache/jena/fuseki/webapp/FusekiServerListener.java +++ b/jena-fuseki2/jena-fuseki-webapp/src/main/java/org/apache/jena/fuseki/webapp/FusekiServerListener.java @@ -21,11 +21,11 @@ import jakarta.servlet.ServletContext; import jakarta.servlet.ServletContextEvent; import jakarta.servlet.ServletContextListener; - import org.apache.jena.fuseki.Fuseki; import org.apache.jena.fuseki.FusekiException; import org.apache.jena.fuseki.cmd.FusekiArgs; -import org.apache.jena.fuseki.metrics.MetricsProviderRegistry; +import org.apache.jena.fuseki.metrics.MetricsProvider; +import org.apache.jena.fuseki.metrics.PrometheusMetricsProvider; import org.apache.jena.fuseki.server.DataAccessPointRegistry; import org.apache.jena.fuseki.server.FusekiCoreInfo; import org.apache.jena.fuseki.server.OperationRegistry; @@ -102,7 +102,9 @@ private synchronized void serverInitialization(ServletContext servletContext) { //Fuseki.configLog.info("Register: "+dap.getName()); }); - MetricsProviderRegistry.dataAccessPointMetrics(dataAccessPointRegistry); + MetricsProvider metricsProvider = new PrometheusMetricsProvider(); + MetricsProvider.setMetricsProvider(servletContext, metricsProvider); + metricsProvider.dataAccessPointMetrics(metricsProvider, dataAccessPointRegistry); } catch (Throwable th) { Fuseki.serverLog.error("Exception in initialization: {}", th.getMessage()); From e3f8b39b5e8867e33a75f7a07215ee49a7bbe2e1 Mon Sep 17 00:00:00 2001 From: Andy Seaborne Date: Mon, 23 Dec 2024 22:11:08 +0000 Subject: [PATCH 4/8] GH-2046: Add runtime new datasets to metrics --- .../jena/fuseki/metrics/MetricsProvider.java | 21 +++++++++++++------ .../jena/fuseki/servlets/HttpAction.java | 19 +++++++++++++++++ .../jena/fuseki/main/cmds/FusekiMain.java | 16 +++++++------- .../jena/fuseki/main/sys/FusekiModules.java | 15 +++++-------- .../jena/fuseki/mgt/ActionDatasets.java | 9 +++++++- .../jena/fuseki/mod/FusekiModServer.java | 1 + 6 files changed, 56 insertions(+), 25 deletions(-) diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/metrics/MetricsProvider.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/metrics/MetricsProvider.java index 2dcd79cea93..c9ade6ee05d 100644 --- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/metrics/MetricsProvider.java +++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/metrics/MetricsProvider.java @@ -21,7 +21,9 @@ import io.micrometer.core.instrument.MeterRegistry; import jakarta.servlet.ServletContext; +import org.apache.jena.atlas.logging.Log; import org.apache.jena.fuseki.Fuseki; +import org.apache.jena.fuseki.server.DataAccessPoint; import org.apache.jena.fuseki.server.DataAccessPointRegistry; import org.apache.jena.fuseki.servlets.HttpAction; @@ -33,17 +35,24 @@ public interface MetricsProvider { /** Bind each data access point in a DataAccessPointRegistry to the system Micrometer {@link MeterRegistry}. */ public default void dataAccessPointMetrics(MetricsProvider metricsProvider, DataAccessPointRegistry dapRegistry) { try { - MeterRegistry meterRegistry = metricsProvider.getMeterRegistry(); - if (meterRegistry != null) { - dapRegistry.accessPoints().forEach(dap->{ - new FusekiRequestsMetrics( dap ).bindTo( meterRegistry ); - }); - } + dapRegistry.accessPoints().forEach(dap->addDataAccessPointMetrics(dap)); } catch (Throwable th) { Fuseki.configLog.error("Failed to bind all data access points to netrics provider", th); } } + public default void addDataAccessPointMetrics(DataAccessPoint dataAccessPoint) { + MeterRegistry meterRegistry = this.getMeterRegistry(); + if (meterRegistry != null ) + addDataAccessPointMetrics(meterRegistry, dataAccessPoint); + } + + private static void addDataAccessPointMetrics(MeterRegistry meterRegistry, DataAccessPoint dataAccessPoint) { + if ( dataAccessPoint == null ) + Log.warn(MetricsProvider.class, "addDataAccessPointMetrics: Null DataAccessPoint"); + new FusekiRequestsMetrics(dataAccessPoint).bindTo(meterRegistry); + } + public static void setMetricsProvider(ServletContext servletContext, MetricsProvider provider) { Objects.requireNonNull(servletContext); if ( provider == null ) diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/HttpAction.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/HttpAction.java index 9e35cd3310e..220625f9a3f 100644 --- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/HttpAction.java +++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/HttpAction.java @@ -32,6 +32,7 @@ import java.util.zip.DeflaterInputStream; import java.util.zip.GZIPInputStream; +import jakarta.servlet.ServletContext; import jakarta.servlet.ServletOutputStream; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @@ -41,6 +42,7 @@ import org.apache.jena.atlas.logging.Log; import org.apache.jena.fuseki.Fuseki; import org.apache.jena.fuseki.FusekiException; +import org.apache.jena.fuseki.metrics.MetricsProvider; import org.apache.jena.fuseki.server.*; import org.apache.jena.fuseki.system.ActionCategory; import org.apache.jena.query.ReadWrite; @@ -251,6 +253,23 @@ public DataAccessPointRegistry getDataAccessPointRegistry() { return dataAccessPointRegistry; } + /** + * Get {@link ServletContext} (may be null). + */ + public ServletContext getServletContext() { + return request.getServletContext(); + } + + /** + * Get the {@link MetricsProvider} for this action. + */ + public MetricsProvider getMetricsProvider() { + ServletContext servletContext = getServletContext(); + if ( servletContext == null ) + return null; + return MetricsProvider.getMetricsProvider(servletContext); + } + /** * Set the endpoint and endpoint name that this is an action for. * @param endpoint {@link Endpoint} diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/FusekiMain.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/FusekiMain.java index bfcf5efda1b..d902c9ce100 100644 --- a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/FusekiMain.java +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/FusekiMain.java @@ -97,8 +97,8 @@ public class FusekiMain extends CmdARQ { private static ArgDecl argWithMetrics = new ArgDecl(ArgDecl.NoValue, "withMetrics", "metrics"); private static ArgDecl argWithCompact = new ArgDecl(ArgDecl.NoValue, "withCompact", "compact"); - // Default is "true" and use modules found by the ServiceLoader. - private static ArgDecl argEnableModules = new ArgDecl(ArgDecl.HasValue, "modules", "fuseki-modules"); +// // Use modules found by the ServiceLoader. +// private static ArgDecl argEnableModules = new ArgDecl(ArgDecl.HasValue, "modules", "fuseki-modules"); private static ArgDecl argAuth = new ArgDecl(ArgDecl.HasValue, "auth"); @@ -306,7 +306,7 @@ private void argumentsSetup() { add(argWithMetrics, "--metrics", "Enable /$/metrics"); add(argWithCompact, "--compact", "Enable /$/compact/*"); - add(argEnableModules, "--modules=true|false", "Enable Fuseki modules"); + //add(argEnableModules, "--modules=true|false", "Enable Fuseki modules"); super.modVersion.addClass("Fuseki", Fuseki.class); @@ -575,12 +575,12 @@ private void processStdArguments(Logger log) { serverArgs.jettyConfigFile = jettyConfigFile; } + // Allows for external setting of serverArgs.fusekiModules if ( serverArgs.fusekiModules == null ) { - // Allows for external setting of serverArgs.fusekiModules - boolean withModules = hasValueOfTrue(argEnableModules); - serverArgs.fusekiModules = withModules - ? FusekiModules.getSystemModules() - : FusekiModules.empty(); + // Get modules from system-wide setup. + // This (Fuseki 5.3.0- defaults to an empty set of modules. +// boolean withModules = hasValueOfTrue(argEnableModules); + serverArgs.fusekiModules = FusekiModules.getSystemModules(); } if ( contains(argCORS) ) { diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/sys/FusekiModules.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/sys/FusekiModules.java index 5d0d524de80..e3603b4d6df 100644 --- a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/sys/FusekiModules.java +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/sys/FusekiModules.java @@ -32,9 +32,8 @@ */ public class FusekiModules { - private static FusekiModules autoLoadedFusekiModules = FusekiAutoModules.get(); // Never null, maybe empty - private static FusekiModules systemFusekiModules = autoLoadedFusekiModules; + private static FusekiModules systemFusekiModules = FusekiModules.create(); /** * There is a system wide set of modules used when no other modules are indicated. @@ -46,15 +45,12 @@ public static void setSystemDefault(FusekiModules fusekiModules) { /** Restore the original setting of the system default collection. */ public static void restoreSystemDefault() { - systemFusekiModules = autoLoadedFusekiModules; + systemFusekiModules = FusekiModules.create(); } public static FusekiModules getSystemModules() { - if ( systemFusekiModules == null ) { - if ( autoLoadedFusekiModules == null ) - autoLoadedFusekiModules = FusekiAutoModules.get(); - systemFusekiModules = autoLoadedFusekiModules; - } + if ( systemFusekiModules == null ) + systemFusekiModules = FusekiAutoModules.get(); return systemFusekiModules; } @@ -69,8 +65,7 @@ public static FusekiModules getSystemModules() { // Testing. /*package*/ static void resetSystemDefault() { // Reload, reset. Fresh objects. - autoLoadedFusekiModules = FusekiAutoModules.load(); - systemFusekiModules = autoLoadedFusekiModules; + systemFusekiModules = FusekiAutoModules.get(); } /** Create a collection of Fuseki modules */ diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/ActionDatasets.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/ActionDatasets.java index 977bcfea69f..27cfd888365 100644 --- a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/ActionDatasets.java +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/ActionDatasets.java @@ -42,6 +42,7 @@ import org.apache.jena.fuseki.build.FusekiConfig; import org.apache.jena.fuseki.ctl.ActionContainerItem; import org.apache.jena.fuseki.ctl.JsonDescription; +import org.apache.jena.fuseki.metrics.MetricsProvider; import org.apache.jena.fuseki.server.DataAccessPoint; import org.apache.jena.fuseki.server.DataService; import org.apache.jena.fuseki.server.FusekiVocab; @@ -107,6 +108,7 @@ protected JsonValue execGetItem(HttpAction action) { // ---- POST + /** Create dataset */ @Override protected JsonValue execPostContainer(HttpAction action) { UUID uuid = UUID.randomUUID(); @@ -208,8 +210,13 @@ else if ( WebContent.isMultiPartForm(ct) ) if ( ! datasetPath.equals(dataAccessPoint.getName()) ) FmtLog.warn(action.log, "Inconsistent names: datasetPath = %s; DataAccessPoint name = %s", datasetPath, dataAccessPoint); succeeded = true; - action.getDataAccessPointRegistry().register(dataAccessPoint); + + // Add to metrics + MetricsProvider metricProvider = action.getMetricsProvider(); + if ( metricProvider != null ) + action.getMetricsProvider().addDataAccessPointMetrics(dataAccessPoint); + action.setResponseContentType(WebContent.contentTypeTextPlain); ServletOps.success(action); } catch (IOException ex) { IO.exception(ex); } diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/FusekiModServer.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/FusekiModServer.java index 99bd550b4ff..84f0fb72f68 100644 --- a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/FusekiModServer.java +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/FusekiModServer.java @@ -75,6 +75,7 @@ public static FusekiServer construct(String... args) { } FusekiModules modules = serverModules; + // Set system modules - these are picked up in FusekiMain FusekiModules.setSystemDefault(modules); FusekiServer server = FusekiServer.construct(args); return server; From 1de553a30c0204e9481cc042c1449d66d107ae28 Mon Sep 17 00:00:00 2001 From: Andy Seaborne Date: Tue, 24 Dec 2024 08:39:38 +0000 Subject: [PATCH 5/8] GH-2902: Adjust command line args via customizer --- .../org/apache/jena/atlas/lib/FileOps.java | 3 +- .../java/org/apache/jena/fuseki/Fuseki.java | 3 + .../jena/fuseki/server/FusekiCoreInfo.java | 10 +- .../jena/fuseki/server/PlatformInfo.java | 18 +-- .../jena/fuseki/system/FusekiLogging.java | 7 +- jena-fuseki2/jena-fuseki-main/pom.xml | 16 +++ .../jena/fuseki/main/FusekiMainInfo.java | 61 --------- .../jena/fuseki/main/cmds/FusekiMain.java | 30 ++-- .../fuseki/main/cmds/FusekiServerCmd.java | 4 +- .../jena/fuseki/main/cmds/ServerArgs.java | 4 +- .../jena/fuseki/mgt/ActionDatasets.java | 13 +- .../jena/fuseki/mgt/FusekiServerCtl.java | 129 ++++++++---------- .../org/apache/jena/fuseki/mgt/Template.java | 7 - .../jena/fuseki/mgt/TemplateFunctions.java | 15 +- .../jena/fuseki/mod/FusekiModServer.java | 91 ------------ .../jena/fuseki/mod/FusekiServerRunner.java | 114 ++++++++++++++++ .../jena/fuseki/mod/admin/ArgModuleAdmin.java | 69 ---------- .../jena/fuseki/mod/admin/FMod_Admin.java | 58 ++++---- .../mod/prometheus/FMod_Prometheus.java | 9 +- .../jena/fuseki/mod/shiro/FMod_Shiro.java | 18 ++- .../jena/fuseki/mod/shiro/FusekiShiroLib.java | 5 +- .../shiro/ShiroEnvironmentLoaderListener.java | 4 +- .../apache/jena/fuseki/mod/ui/FMod_UI.java | 44 ++++-- .../apache/jena/fuseki/mod/TS_FusekiMods.java | 3 +- .../jena/fuseki/mod/TestFusekiServer.java | 68 ++++++++- .../jena/fuseki/mod/admin/TestAdmin.java | 20 +-- .../mod/admin/TestTemplateAddDataset.java | 2 +- .../fuseki/mod/metrics/TestModPrometheus.java | 2 +- .../jena/fuseki/mod/shiro/TestModShiro.java | 12 +- jena-fuseki2/pom.xml | 3 +- 30 files changed, 414 insertions(+), 428 deletions(-) delete mode 100644 jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/FusekiMainInfo.java delete mode 100644 jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/FusekiModServer.java create mode 100644 jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/FusekiServerRunner.java delete mode 100644 jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/admin/ArgModuleAdmin.java diff --git a/jena-base/src/main/java/org/apache/jena/atlas/lib/FileOps.java b/jena-base/src/main/java/org/apache/jena/atlas/lib/FileOps.java index 15d84eb7dd4..0e771185021 100644 --- a/jena-base/src/main/java/org/apache/jena/atlas/lib/FileOps.java +++ b/jena-base/src/main/java/org/apache/jena/atlas/lib/FileOps.java @@ -65,6 +65,7 @@ public static void delete(File f, boolean reportExistsAfter) { /** * Delete all files in a directory. + * Does not recurse in the direction. * Does nothing if the path name does not exist or is not a directory. * * @param dir @@ -84,7 +85,7 @@ public static void clearAll(String d) { clearAll(new File(d)) ; } - /** Delete all files and directories (recursively) in a directory */ + /** Delete all files and directories (recursively) in a directory; does not delete the directory argument. */ public static void clearAll(File d) { if ( ! d.exists() ) return ; diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/Fuseki.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/Fuseki.java index a8c4a0680c7..2c39ab04973 100644 --- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/Fuseki.java +++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/Fuseki.java @@ -182,7 +182,10 @@ public static void initConsts() {} public static final String attrNameRegistry = "org.apache.jena.fuseki:DataAccessPointRegistry"; public static final String attrOperationRegistry = "org.apache.jena.fuseki:OperationRegistry"; public static final String attrAuthorizationService = "org.apache.jena.fuseki:AuthorizationService"; + // The Fuseki Server public static final String attrFusekiServer = "org.apache.jena.fuseki:Server"; + // The FusekiServerCtl object for the admin area; may be null + public static final String attrFusekiServerCtl = "org.apache.jena.fuseki:ServerCtl"; public static final String attrMetricsProvider = "org.apache.jena.fuseki:MetricsProvider"; public static void setVerbose(ServletContext cxt, boolean verbose) { diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/FusekiCoreInfo.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/FusekiCoreInfo.java index 0b64600f252..e143ec9d8de 100644 --- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/FusekiCoreInfo.java +++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/FusekiCoreInfo.java @@ -37,7 +37,6 @@ public static void logCode(Logger log) { } /** Log details - this function is about command line details */ - // Shared between FusekiMain and Fuseki Webapp (currently). public static void logServerCmdSetup(Logger log, boolean verbose, DataAccessPointRegistry dapRegistry, String datasetPath, String datasetDescription, String serverConfigFile, String staticFiles) { if ( datasetPath != null ) @@ -46,16 +45,11 @@ public static void logServerCmdSetup(Logger log, boolean verbose, DataAccessPoin FmtLog.info(log, "Configuration file: %s", serverConfigFile); FusekiCoreInfo.logDataAccessPointRegistry(log, dapRegistry, verbose); - if ( staticFiles != null ) FmtLog.info(log, "Static files: %s", staticFiles); - - if ( verbose ) { - PlatformInfo.logDetailsSystem(log); + PlatformInfo.logDetailsSystem(log); + if ( verbose ) PlatformInfo.logDetailsJVM(log); - } - else - PlatformInfo.logDetailsSystemPlain(log); } /** Log a {@link DataAccessPointRegistry} */ diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/PlatformInfo.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/PlatformInfo.java index 7cd171ac33a..98414a16687 100644 --- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/PlatformInfo.java +++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/PlatformInfo.java @@ -24,7 +24,8 @@ import org.apache.jena.atlas.logging.FmtLog; import org.slf4j.Logger; -public class PlatformInfo { +/** Platform inforamtion - OS and JVM */ +/*package*/ class PlatformInfo { public static void main(String ...args) throws IOException { long maxMem = Runtime.getRuntime().maxMemory(); @@ -35,14 +36,8 @@ public static void main(String ...args) throws IOException { System.out.printf("max=%s total=%s used=%s free=%s\n", f.apply(maxMem), f.apply(totalMem), f.apply(usedMem), f.apply(freeMem)); } - /** System details section */ - public static void logDetailsSystem(Logger log) { - log.info("System"); - logDetailsSystemPlain(log); - } - - /** System details, no section header */ - public static void logDetailsSystemPlain(Logger log) { + /** System details */ + /*package*/ static void logDetailsSystem(Logger log) { String prefix = " "; long maxMem = Runtime.getRuntime().maxMemory(); long totalMem = Runtime.getRuntime().totalMemory(); @@ -60,19 +55,16 @@ public static void logDetailsSystemPlain(Logger log) { } /** JVM details section. */ - public static void logDetailsJVM(Logger log) { + /*package*/ static void logDetailsJVM(Logger log) { String prefix = " "; - log.info("Java"); logOne(log, prefix, "java.vendor"); logOne(log, prefix, "java.home"); logOne(log, prefix, "java.runtime.version"); logOne(log, prefix, "java.runtime.name"); - //logOne(log, "java.endorsed.dirs"); logOne(log, prefix, "user.language"); logOne(log, prefix, "user.timezone"); logOne(log, prefix, "user.country"); logOne(log, prefix, "user.dir"); - //logOne(log, prefix, "file.encoding"); } private static void logOne(Logger log, String prefix, String property) { diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/system/FusekiLogging.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/system/FusekiLogging.java index a6356b057e5..69f85a957e4 100644 --- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/system/FusekiLogging.java +++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/system/FusekiLogging.java @@ -232,8 +232,7 @@ private static void logLogging(String fmt, Object ... args) { } private static String log4j2setupFallback() { - // The logging file for Fuseki in Tomcat webapp is in "log4j2.properties" in the webapp root directory. - // This is used by command line Fuseki (full and main) + // This is the default // filters = threshold // filter.threshold.type = ThresholdFilter @@ -247,6 +246,7 @@ private static String log4j2setupFallback() { appender.console.name = OUT appender.console.target = SYSTEM_OUT appender.console.layout.type = PatternLayout + ##appender.console.layout.pattern = %d{HH:mm:ss} %-5p %-15c{1} :: %m%n appender.console.layout.pattern = [%d{yyyy-MM-dd HH:mm:ss}] %-10c{1} %-5p %m%n rootLogger.level = WARN @@ -297,8 +297,7 @@ private static String log4j2setupFallback() { logger.fuseki-request.additivity = false logger.fuseki-request.level = OFF logger.fuseki-request.appenderRef.plain.ref = PLAIN - ); - """; + """; } public static void resetLogging(String configString) { diff --git a/jena-fuseki2/jena-fuseki-main/pom.xml b/jena-fuseki2/jena-fuseki-main/pom.xml index b72e8613279..97afefa87c6 100644 --- a/jena-fuseki2/jena-fuseki-main/pom.xml +++ b/jena-fuseki2/jena-fuseki-main/pom.xml @@ -59,8 +59,24 @@ org.apache.jena jena-cmds + ${project.version} + + + + + org.apache.jena + jena-fuseki-ui 5.3.0-SNAPSHOT + test + + + org.eclipse.jetty.ee10 diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/FusekiMainInfo.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/FusekiMainInfo.java deleted file mode 100644 index bcdf758ddb6..00000000000 --- a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/FusekiMainInfo.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.jena.fuseki.main; - -import org.apache.jena.atlas.logging.FmtLog; -import org.apache.jena.fuseki.server.FusekiCoreInfo; -import org.slf4j.Logger; - -public class FusekiMainInfo { - - /** Details of the code version. */ - public static void logCode(Logger log) { - FusekiCoreInfo.logCode(log); - } - - /** Log server details. */ - public static void logServer(Logger log, FusekiServer server, boolean verbose) { - FusekiMainInfo.logServerConnections(log, server); - FusekiMainInfo.logServerDatasets(log, server, verbose); - if ( server.getStaticContentDir() != null ) - FmtLog.info(log, "Static files: %s", server.getStaticContentDir()); - } - - /** Log details about the code version */ - public static void logServerCode(Logger log) { - FusekiCoreInfo.logCode(log); - } - - /** The the server connection setup */ - public static void logServerConnections(Logger log, FusekiServer server) { - int httpsPort = server.getHttpsPort(); - int httpPort = server.getHttpPort(); - if ( httpsPort > 0 && httpPort > 0 ) - log.info("Ports: http="+httpPort+" https="+httpsPort); - else if ( httpsPort <= 0 ) - log.info("Port: http="+httpPort); - else if ( httpPort <= 0 ) - log.info("Port: https="+httpsPort); - } - - /** Log information about datasets in this server */ - public static void logServerDatasets(Logger log, FusekiServer server, boolean longForm) { - FusekiCoreInfo.logDataAccessPointRegistry(log, server.getDataAccessPointRegistry(), longForm); - } -} diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/FusekiMain.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/FusekiMain.java index d902c9ce100..6f4636b7303 100644 --- a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/FusekiMain.java +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/FusekiMain.java @@ -36,7 +36,6 @@ import org.apache.jena.cmd.*; import org.apache.jena.fuseki.Fuseki; import org.apache.jena.fuseki.FusekiException; -import org.apache.jena.fuseki.main.FusekiMainInfo; import org.apache.jena.fuseki.main.FusekiServer; import org.apache.jena.fuseki.main.sys.FusekiModules; import org.apache.jena.fuseki.main.sys.FusekiServerArgsCustomiser; @@ -56,6 +55,7 @@ import org.slf4j.Logger; public class FusekiMain extends CmdARQ { + /** Default HTTP port when running from the command line. */ public static int defaultPort = 3030; /** Default HTTPS port when running from the command line. */ @@ -97,8 +97,8 @@ public class FusekiMain extends CmdARQ { private static ArgDecl argWithMetrics = new ArgDecl(ArgDecl.NoValue, "withMetrics", "metrics"); private static ArgDecl argWithCompact = new ArgDecl(ArgDecl.NoValue, "withCompact", "compact"); -// // Use modules found by the ServiceLoader. -// private static ArgDecl argEnableModules = new ArgDecl(ArgDecl.HasValue, "modules", "fuseki-modules"); + // Use modules found by the ServiceLoader. Currently, no-op. + private static ArgDecl argEnableModules = new ArgDecl(ArgDecl.HasValue, "modules", "fuseki-modules"); private static ArgDecl argAuth = new ArgDecl(ArgDecl.HasValue, "auth"); @@ -196,6 +196,20 @@ public static void addCustomiser(FusekiServerArgsCustomiser customiser) { ArgCustomizers.addCustomiser(customiser); } + /** + * Registers CLI customisers. + *

      + * CLI customisers can add one/more custom arguments into the Fuseki Server CLI arguments and then can apply those + * to the Fuseki server being built during the processing of {@link #processModulesAndArgs()}. This allows for + * custom arguments that directly affect how the Fuseki server is built to be created. + *

      + * @see #addCustomiser(FusekiServerArgsCustomiser) + */ + public static void addCustomisers(FusekiModules customiserSet) { + Objects.requireNonNull(customiserSet); + customiserSet.forEach(customiser->ArgCustomizers.addCustomiser(customiser)); + } + /** * Resets any previously registered CLI customisers */ @@ -306,7 +320,7 @@ private void argumentsSetup() { add(argWithMetrics, "--metrics", "Enable /$/metrics"); add(argWithCompact, "--compact", "Enable /$/compact/*"); - //add(argEnableModules, "--modules=true|false", "Enable Fuseki modules"); + add(argEnableModules, "--modules=true|false", "Enable Fuseki autoloaded modules"); super.modVersion.addClass("Fuseki", Fuseki.class); @@ -578,9 +592,9 @@ private void processStdArguments(Logger log) { // Allows for external setting of serverArgs.fusekiModules if ( serverArgs.fusekiModules == null ) { // Get modules from system-wide setup. - // This (Fuseki 5.3.0- defaults to an empty set of modules. -// boolean withModules = hasValueOfTrue(argEnableModules); - serverArgs.fusekiModules = FusekiModules.getSystemModules(); + boolean withModules = hasValueOfTrue(argEnableModules); + if ( withModules ) + serverArgs.fusekiModules = FusekiModules.getSystemModules(); } if ( contains(argCORS) ) { @@ -630,7 +644,7 @@ protected void exec() { // Check for command line or config setup. try { Logger log = Fuseki.serverLog; - FusekiMainInfo.logServerCode(log); + FusekiCoreInfo.logCode(log); FusekiServer server = makeServer(serverArgs); infoCmd(server, log); try { diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/FusekiServerCmd.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/FusekiServerCmd.java index 72c121a1c36..1f4dc87fe77 100644 --- a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/FusekiServerCmd.java +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/FusekiServerCmd.java @@ -18,7 +18,7 @@ package org.apache.jena.fuseki.main.cmds; -import org.apache.jena.fuseki.mod.FusekiModServer; +import org.apache.jena.fuseki.mod.FusekiServerRunner; import org.apache.jena.fuseki.system.FusekiLogging; /** Fuseki command that runs a Fuseki server with the admin UI. @@ -45,7 +45,7 @@ public class FusekiServerCmd { * syntax but not start it. */ static public void main(String... args) { - FusekiModServer.runAsync(args).join(); + FusekiServerRunner.main(args); } } diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/ServerArgs.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/ServerArgs.java index d627adddc95..cad50c97f32 100644 --- a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/ServerArgs.java +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/ServerArgs.java @@ -52,7 +52,7 @@ public class ServerArgs { public boolean verboseLogging = false; /** - * FusekiModules to use during the server build * + * FusekiModules to use during the server build * Command line customisers are handled separately by FusekiMain. */ public FusekiModules fusekiModules = null; @@ -75,7 +75,7 @@ public class ServerArgs { public String serverConfigFile = null; public Model serverConfigModel = null; - /** Allow no datasets without it being an error. This is not an argument. */ + /** Allow no datasets without it being an error. This is not a command argument. */ public boolean allowEmpty = false; public SetupType setup = SetupType.UNSET; /** Start without a dataset or configuration (this is {@code --empty}) */ diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/ActionDatasets.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/ActionDatasets.java index 27cfd888365..3b30d82b7f2 100644 --- a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/ActionDatasets.java +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/ActionDatasets.java @@ -211,7 +211,7 @@ else if ( WebContent.isMultiPartForm(ct) ) FmtLog.warn(action.log, "Inconsistent names: datasetPath = %s; DataAccessPoint name = %s", datasetPath, dataAccessPoint); succeeded = true; action.getDataAccessPointRegistry().register(dataAccessPoint); - + // Add to metrics MetricsProvider metricProvider = action.getMetricsProvider(); if ( metricProvider != null ) @@ -418,7 +418,14 @@ private static void assemblerFromForm(HttpAction action, StreamRDF dest) { params.put(Template.NAME, dbName.substring(1)); else params.put(Template.NAME, dbName); - FusekiServerCtl.addGlobals(params); + + FusekiServerCtl serverCtl = FusekiServerCtl.get(action.getServletContext()); + if ( serverCtl != null ) + serverCtl.addGlobals(params); + else { + ServletOps.errorOccurred("No admin area"); + // No return. + } //action.log.info(format("[%d] Create database : name = %s, type = %s", action.id, dbName, dbType )); @@ -429,7 +436,7 @@ private static void assemblerFromForm(HttpAction action, StreamRDF dest) { ServletOps.errorBadRequest(format("dbType can be only one of %s", keys)); } - String instance = TemplateFunctions.templateFile(template, params, Lang.TTL); + String instance = TemplateFunctions.templateFile(serverCtl.getFusekiBase(), template, params, Lang.TTL); RDFParser.create().source(new StringReader(instance)).base("http://base/").lang(Lang.TTL).parse(dest); } diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/FusekiServerCtl.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/FusekiServerCtl.java index 37101571224..0407df4e04f 100644 --- a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/FusekiServerCtl.java +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/FusekiServerCtl.java @@ -29,18 +29,21 @@ import java.nio.file.Path; import java.util.*; +import jakarta.servlet.ServletContext; import org.apache.jena.atlas.RuntimeIOException; import org.apache.jena.atlas.io.IOX; import org.apache.jena.atlas.lib.FileOps; import org.apache.jena.atlas.lib.InternalErrorException; import org.apache.jena.atlas.lib.Lib; import org.apache.jena.atlas.logging.FmtLog; +import org.apache.jena.atlas.logging.Log; import org.apache.jena.cmd.CmdException; import org.apache.jena.dboe.sys.Names; import org.apache.jena.fuseki.Fuseki; import org.apache.jena.fuseki.FusekiConfigException; import org.apache.jena.fuseki.build.DatasetDescriptionMap; import org.apache.jena.fuseki.build.FusekiConfig; +import org.apache.jena.fuseki.main.cmds.FusekiMain; import org.apache.jena.fuseki.server.DataAccessPoint; import org.apache.jena.fuseki.server.DataService; import org.apache.jena.fuseki.server.FusekiVocabG; @@ -57,20 +60,23 @@ import org.apache.jena.system.G; public class FusekiServerCtl { - /** - * Root of the varying files in this deployment. Often $PWD/run. - * This location must be writable. - */ - public static Path FUSEKI_BASE = null; - - public static final String DFT_SHIRO_INI = "shiro.ini"; public static final String envFusekiBase = "FUSEKI_BASE"; public static final String envFusekiShiro = "FUSEKI_SHIRO"; + public static final String DFT_SHIRO_INI = "shiro.ini"; + + public static FusekiServerCtl get(ServletContext cxt) { + if ( cxt == null ) + return null; + FusekiServerCtl fusekiServerCtl = (FusekiServerCtl)cxt.getAttribute(Fuseki.attrFusekiServerCtl); + if ( fusekiServerCtl == null ) + Log.warn(FusekiServerCtl.class, "No FusekiServerCtl in ServletContext"); + return fusekiServerCtl; + } - // Relative names of directories in the FUSEKI_BASE area. + // Relative names of directories in the Fuseki base area. private static final String databasesLocationBase = "databases"; // Place to put Lucene text and spatial indexes. - private static final String databaseIndexesDir = "text_indexes"; + private static final String databaseIndexesDir = "text_indexes"; private static final String backupDirNameBase = "backups"; private static final String configDirNameBase = "configuration"; @@ -84,7 +90,6 @@ public class FusekiServerCtl { public static int levelFModUI = BaseFusekiAutoModuleLevel+10; public static int levelFModShiro = BaseFusekiAutoModuleLevel+20; - /** Directory for TDB databases - this is known to the assembler templates */ public static Path dirDatabases = null; @@ -107,73 +112,53 @@ public class FusekiServerCtl { // Marks the end of successful initialization. /*package*/static boolean serverInitialized = false; -// /** OLD -// * Root of the Fuseki installation for fixed files. -// * This may be null (e.g. running inside a web application container) -// */ -// public static Path FUSEKI_HOME = null; - - // Too much is done with statics, assuming one server+admin process - // At the moment, it is one setup happening at a time. - // Once created, it is independent. - // Better, less statics, more FusekiServerApp instance object. - // Current limitation: FUSEKI_BASE is static and set in - // FusekiApp.java line 275 - // ArgModuleAdmin.java line 61 - // FMod_Admin.java line 117 - // FMod_UI.java line 133 - // Template.java line 27 - // ActionDadasets uses addGlobals. - // Default - "run" in the current directory. public static final String dftFusekiBase = "run"; - public FusekiServerCtl(String location) { - if ( location == null ) { - FUSEKI_BASE = null; - return; - } - FUSEKI_BASE = Path.of(location); - } + private Path fusekiBase = null; - public Path setup() { - // Set the location of the BASE area - setFusekiBase(); - // Ensure the BASE area exists on disk. - setBaseAreaOnDisk(); - // Format the BASE area. - ensureBaseArea(FUSEKI_BASE); - return FUSEKI_BASE; + + public FusekiServerCtl(Path location) { + if ( location == null ) + location = envFusekiBase(); + + this.fusekiBase = location; } - private void setFusekiBase() { - if ( FUSEKI_BASE == null ) - FUSEKI_BASE = select_FUSEKI_BASE(); + public Path getFusekiBase() { + return fusekiBase; } - private Path select_FUSEKI_BASE() { + + private Path envFusekiBase() { // Does not guarantee existence - Path setting = null; - if ( FUSEKI_BASE == null ) - setting = calc_FUSEKI_BASE(); + if ( fusekiBase != null ) + return fusekiBase; + String valueFusekiBase = getenv("FUSEKI_BASE"); + if ( valueFusekiBase == null ) + valueFusekiBase = dftFusekiBase; + Path setting = Path.of(valueFusekiBase); setting = setting.toAbsolutePath(); return setting; } - private Path calc_FUSEKI_BASE() { - String valueFusekiBase = getenv("FUSEKI_BASE"); - if ( valueFusekiBase == null ) - valueFusekiBase = dftFusekiBase; - return Path.of(valueFusekiBase); + /** + * Set up the area if not already formatted. + */ + public void setup() { + // Ensure the BASE area exists on disk. + setBaseAreaOnDisk(); + // Format the BASE area. + ensureBaseArea(); } private void setBaseAreaOnDisk() { - FmtLog.info(Fuseki.configLog, "FUSEKI_BASE=%s", FUSEKI_BASE); - if ( ! Files.exists(FUSEKI_BASE) ) { + FmtLog.info(Fuseki.configLog, "Fuseki Base = %s", fusekiBase); + if ( ! Files.exists(fusekiBase) ) { try { - Files.createDirectories(FUSEKI_BASE); + Files.createDirectories(fusekiBase); } catch (IOException e) { - throw new FusekiConfigException("Failed to create FUSEKI_BASE: "+FUSEKI_BASE); + throw new FusekiConfigException("Failed to create Fuseki Base: "+fusekiBase); } } // Further checks in ensureBaseArea @@ -182,17 +167,18 @@ private void setBaseAreaOnDisk() { /** * Create directories if found to be missing. */ - private void ensureBaseArea(Path baseArea) { + private void ensureBaseArea() { + Path baseArea = fusekiBase; if ( Files.exists(baseArea) ) { if ( ! Files.isDirectory(baseArea) ) - throw new FusekiConfigException("FUSEKI_BASE is not a directory: "+baseArea); + throw new FusekiConfigException("Fuseki base is not a directory: "+baseArea); if ( ! Files.isWritable(baseArea) ) - throw new FusekiConfigException("FUSEKI_BASE is not writable: "+baseArea); + throw new FusekiConfigException("Fuseki base is not writable: "+baseArea); } else { ensureDir(baseArea); } - // Ensure FUSEKI_BASE has the assumed directories. + // Ensure the Fuseki base area has the assumed directories. dirTemplates = writeableDirectory(baseArea, templatesNameBase); dirDatabases = writeableDirectory(baseArea, databasesLocationBase); dirBackups = writeableDirectory(baseArea, backupDirNameBase); @@ -202,7 +188,7 @@ private void ensureBaseArea(Path baseArea) { // ---- Initialize with files. -// // Copy missing files into FUSEKI_BASE + // Copy missing files into the Fuseki base // Interacts with FMod_Shiro. if ( Lib.getenv(FusekiServerCtl.envFusekiShiro) == null ) { copyFileIfMissing(null, DFT_SHIRO_INI, baseArea); @@ -256,7 +242,7 @@ private DataAccessPoint configFromTemplate(String templateFile, String datasetPa // DRY -- ActionDatasets (and others?) addGlobals(params); - String str = TemplateFunctions.templateFile(templateFile, params, Lang.TTL); + String str = TemplateFunctions.templateFile(fusekiBase, templateFile, params, Lang.TTL); Lang lang = RDFLanguages.filenameToLang(str, Lang.TTL); Graph configuration = RDFParser.fromString(str, lang).toGraph(); @@ -271,16 +257,14 @@ private DataAccessPoint configFromTemplate(String templateFile, String datasetPa return dap; } - public static void addGlobals(Map params) { + public void addGlobals(Map params) { if ( params == null ) { Fuseki.configLog.warn("FusekiApp.addGlobals : params is null", new Throwable()); return; } if ( ! params.containsKey("FUSEKI_BASE") ) - params.put("FUSEKI_BASE", pathStringOrElse(FUSEKI_BASE, "unset")); -// if ( ! params.containsKey("FUSEKI_HOME") ) -// params.put("FUSEKI_HOME", pathStringOrElse(FusekiAppEnv.FUSEKI_HOME, "unset")); + params.put("FUSEKI_BASE", pathStringOrElse(fusekiBase, "unset")); } /** Copy a file from src to dst under name fn. @@ -391,6 +375,13 @@ private static Path writeableDirectory(Path root , String relName ) { return p; } + /** Running a full-features server sets some global state. Clear this up. (mainly for tests.)*/ + public static void clearUpSystemState() { + System.getProperties().remove(FusekiServerCtl.envFusekiShiro); + System.getProperties().remove(FusekiServerCtl.envFusekiBase); + FusekiMain.resetCustomisers(); + } + private static Path makePath(Path root , String relName ) { Path path = root.resolve(relName); // Must exist diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/Template.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/Template.java index 1266fb3faf4..04007dec8c9 100644 --- a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/Template.java +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/Template.java @@ -18,15 +18,8 @@ package org.apache.jena.fuseki.mgt; -import java.nio.file.Path; - - public class Template { - public static Path getPath(String templateName) { - return FusekiServerCtl.FUSEKI_BASE.resolve(templateName); - } - public static final String templateDir = "templates"; // These are used by the command line start up. diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/TemplateFunctions.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/TemplateFunctions.java index 009cc655ae6..a6b48c22c01 100644 --- a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/TemplateFunctions.java +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mgt/TemplateFunctions.java @@ -20,6 +20,7 @@ import java.io.IOException; import java.io.InputStream; +import java.nio.file.Path; import java.util.Map; import java.util.Map.Entry; @@ -31,13 +32,15 @@ public class TemplateFunctions { /** Read in a template from a file, substitute for {NAME} and return the string. */ - public static String templateFile(String templateName, Map params, Lang lang) { - String templateFilename = Template.getPath(templateName).toString(); + public static String templateFile(Path directory, String templateName, Map params, Lang lang) { + String templateFilename = directory.resolve(templateName).toString(); String template; - try { template = FileUtils.readWholeFileAsUTF8(templateFilename); } - catch (IOException ex) { + try { + template = FileUtils.readWholeFileAsUTF8(templateFilename); + } catch (IOException ex) { Fuseki.serverLog.error("File not found: "+templateFilename); - IO.exception(ex); return null; + IO.exception(ex); + return null; } return templateString(template, params, lang); } @@ -74,7 +77,7 @@ public static String templateString(String template, Map params, Lang.JSONLD.equals(lang) || Lang.RDFJSON.equals(lang) ) { - // Make safe for a RDF language ""-string - especially MS Windows \ path separators. + // Make safe for an RDF language ""-string - especially MS Windows \ path separators. x = x.replace("\\", "\\\\"); x = x.replace("\"", "\\\""); } diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/FusekiModServer.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/FusekiModServer.java deleted file mode 100644 index 84f0fb72f68..00000000000 --- a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/FusekiModServer.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.jena.fuseki.mod; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import org.apache.jena.atlas.lib.FileOps; -import org.apache.jena.fuseki.main.FusekiServer; -import org.apache.jena.fuseki.main.cmds.FusekiMain; -import org.apache.jena.fuseki.main.sys.FusekiModule; -import org.apache.jena.fuseki.main.sys.FusekiModules; -import org.apache.jena.fuseki.mod.admin.FMod_Admin; -import org.apache.jena.fuseki.mod.prometheus.FMod_Prometheus; -import org.apache.jena.fuseki.mod.shiro.FMod_Shiro; -import org.apache.jena.fuseki.mod.ui.FMod_UI; - -public class FusekiModServer { - - public static void main(String... args) { - runAsync(args).join(); - } - - public static FusekiServer runAsync(String... args) { - return construct(args).start(); - } - - public static FusekiServer construct(String... args) { - // Order: FMod_Admin before FMod_Shiro - // These modules may have state that is carried across the build steps. - FusekiModule fmodShiro = FMod_Shiro.create(); - FusekiModule fmodAdmin = FMod_Admin.create(); - - FusekiModules serverModules = FusekiModules.create( fmodAdmin - , FMod_UI.get() - , fmodShiro - , FMod_Prometheus.get() ); - serverModules.forEach(FusekiMain::addCustomiser); - - System.setProperty("FUSEKI_BASE", "run"); - FileOps.ensureDir("run"); - - // Adjust args. - List argList = Arrays.asList(args); - // Ensure "--empty", "--modules=true" - // Better?: moded startup - i.e. setting defaults. - - if ( args.length == 0 ) { - String [] defaultArgs = { "--port=3030", "--empty" }; - args = defaultArgs; - } else { - List argsList = new ArrayList(Arrays.asList(args)); - if ( ! containsArg(argList, "--?empty") ) - argsList.add(0, "--empty"); // addFirst in java21 - if ( ! containsArg(argList, "--?modules") ) - argsList.add(0, "--modules=true"); - args = argsList.toArray(args); - } - - FusekiModules modules = serverModules; - // Set system modules - these are picked up in FusekiMain - FusekiModules.setSystemDefault(modules); - FusekiServer server = FusekiServer.construct(args); - return server; - } - - private static boolean containsArg(List argList, String argRegex) { - //Pattern pattern = Pattern.compile(argRegex); - - return argList.stream().anyMatch(arg->{ - return arg.matches(argRegex); - }); - } -} diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/FusekiServerRunner.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/FusekiServerRunner.java new file mode 100644 index 00000000000..e86532594ba --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/FusekiServerRunner.java @@ -0,0 +1,114 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.mod; + +import java.net.BindException; + +import org.apache.jena.atlas.lib.FileOps; +import org.apache.jena.atlas.lib.Lib; +import org.apache.jena.cmd.CmdGeneral; +import org.apache.jena.fuseki.Fuseki; +import org.apache.jena.fuseki.FusekiException; +import org.apache.jena.fuseki.main.FusekiServer; +import org.apache.jena.fuseki.main.cmds.FusekiMain; +import org.apache.jena.fuseki.main.cmds.ServerArgs; +import org.apache.jena.fuseki.main.sys.FusekiModule; +import org.apache.jena.fuseki.main.sys.FusekiModules; +import org.apache.jena.fuseki.main.sys.FusekiServerArgsCustomiser; +import org.apache.jena.fuseki.mgt.FusekiServerCtl; +import org.apache.jena.fuseki.mod.admin.FMod_Admin; +import org.apache.jena.fuseki.mod.prometheus.FMod_Prometheus; +import org.apache.jena.fuseki.mod.shiro.FMod_Shiro; +import org.apache.jena.fuseki.mod.ui.FMod_UI; + +public class FusekiServerRunner { + + public static void main(String... args) { + //runAsync(args).join(); + prepareFusekiMain(); + FusekiMain.run(args); + } + + public static FusekiServer runAsync(String... args) { + FusekiServer server = construct(args); + try { + server.start(); + } catch (FusekiException ex) { + if ( ex.getCause() instanceof BindException ) { +// if ( serverArgs.jettyConfigFile == null ) +// Fuseki.serverLog.error("Failed to start server: "+ex.getCause().getMessage()+ ": port="+serverArgs.port); +// else +// Fuseki.serverLog.error("Failed to start server: "+ex.getCause().getMessage()+ ": port in use"); + Fuseki.serverLog.error("Failed to start server: "+ex.getCause().getMessage()+ ": port in use"); + System.exit(1); + } + throw ex; + } catch (Exception ex) { + throw new FusekiException("Failed to start server: " + ex.getMessage(), ex); + } + return server.start(); + } + + public static FusekiServer construct(String... args) { + prepareFusekiMain(); + // Make server + FusekiServer server = FusekiServer.construct(args); + resetFusekiMain(); + return server; + } + + private static void prepareFusekiMain() { + String fusekiBase = Lib.getenv(FusekiServerCtl.envFusekiBase); + if ( fusekiBase == null ) + fusekiBase = FusekiServerCtl.dftFusekiBase; + FileOps.ensureDir(fusekiBase); + + FusekiModules serverModules = serverModules(); + + // Adjust the default settings of ServerArgs + FusekiServerArgsCustomiser initializeServerArgs = new FusekiServerArgsCustomiser() { + @Override + public void serverArgsModify(CmdGeneral fusekiCmd, ServerArgs serverArgs) { + serverArgs.allowEmpty = true; + serverArgs.fusekiModules = serverModules; + } + }; + + FusekiMain.resetCustomisers(); + FusekiMain.addCustomiser(initializeServerArgs); + // They can also modify the argument processing. + serverModules.forEach(FusekiMain::addCustomiser); + } + + private static void resetFusekiMain() { + FusekiMain.resetCustomisers(); + } + + /** A use-once {@link FusekiModules} for the full-featured Fuseki server. */ + public static FusekiModules serverModules() { + // Modules may have state that is carried across the build steps or used for reload. + FusekiModule fmodShiro = FMod_Shiro.create(); + FusekiModule fmodAdmin = FMod_Admin.create(); + FusekiModule fmodUI = FMod_UI.create(); + FusekiModule fmodPrometheus = FMod_Prometheus.create(); + + FusekiModules serverModules = FusekiModules.create(fmodAdmin, fmodUI, fmodShiro, fmodPrometheus); + return serverModules; + } +} diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/admin/ArgModuleAdmin.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/admin/ArgModuleAdmin.java deleted file mode 100644 index f1b9d6c751d..00000000000 --- a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/admin/ArgModuleAdmin.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.jena.fuseki.mod.admin; - -import java.nio.file.Files; -import java.nio.file.Path; - -import org.apache.jena.cmd.ArgDecl; -import org.apache.jena.cmd.ArgModuleGeneral; -import org.apache.jena.cmd.CmdArgModule; -import org.apache.jena.cmd.CmdGeneral; -import org.apache.jena.fuseki.FusekiConfigException; -import org.apache.jena.fuseki.mgt.FusekiServerCtl; - -public class ArgModuleAdmin implements ArgModuleGeneral { - // Add a static of "extra command" - - private ArgDecl argAdmin = new ArgDecl(true, "admin"); - private ArgDecl argAdminArea = new ArgDecl(true, "adminArea", "adminBase"); - - public ArgModuleAdmin() { } - - @Override - public void processArgs(CmdArgModule cmdLine) { - System.out.println("ArgModuleAdmin"); - String admin = cmdLine.getValue(argAdmin); - if ( admin == null ) { - return; - } - - if ( admin.equals("localhost") ) {} - else { - String pwFile = admin; - } - - String dirStr = cmdLine.getValue(argAdminArea); - Path directory = Path.of(dirStr); - - if ( ! Files.isDirectory(directory) ) - throw new FusekiConfigException("Not a directory: "+dirStr); - - if ( ! Files.isWritable(directory) ) - throw new FusekiConfigException("Not writable: "+dirStr); - - FusekiServerCtl.FUSEKI_BASE = directory; - } - - @Override - public void registerWith(CmdGeneral cmdLine) { - cmdLine.add(argAdmin, "--admin=[UserPasswordFile|localhost]", "Enable the admin module"); - cmdLine.add(argAdminArea, "--run=DIR", "Admin state directory"); - } -} \ No newline at end of file diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/admin/FMod_Admin.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/admin/FMod_Admin.java index d4ea62e6612..c98e7b1628f 100644 --- a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/admin/FMod_Admin.java +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/admin/FMod_Admin.java @@ -69,8 +69,12 @@ public FMod_Admin() {} private static Logger LOG = Fuseki.configLog; - private ArgDecl argAdmin = new ArgDecl(true, "admin"); - private ArgDecl argAdminArea = new ArgDecl(true, "adminArea", "adminBase"); + private static ArgDecl argAdmin = new ArgDecl(true, "admin"); + private static ArgDecl argAdminArea = new ArgDecl(true, "adminArea", "adminBase"); + + // Module state. + private String admin = null; + private Path directory = null; @Override public void serverArgsModify(CmdGeneral fusekiCmd, ServerArgs serverArgs) { @@ -80,6 +84,7 @@ public void serverArgsModify(CmdGeneral fusekiCmd, ServerArgs serverArgs) { ArgModuleGeneral argModule = new ArgModuleGeneral() { @Override public void registerWith(CmdGeneral cmdLine) { + // Phase 2 // cmdLine.add(argAdmin, "--admin", "Enable server admin with user:password"); // cmdLine.add(argAdminArea,"--adminRun", "Directory for server configuration"); } @@ -91,30 +96,30 @@ public void processArgs(CmdArgModule cmdLine) {} @Override public void serverArgsPrepare(CmdGeneral fusekiCmd, ServerArgs serverArgs) { - String admin = fusekiCmd.getValue(argAdmin); - if ( admin == null ) { - return; - } - - Path directory = null; String dirStr = fusekiCmd.getValue(argAdminArea); if ( dirStr != null ) directory = Path.of(dirStr); // Phase 2 - if ( admin.equals("localhost") ) {} - else { - String pwFile = admin; - } +// String admin = fusekiCmd.getValue(argAdmin); +// if ( admin == null ) { +// return; +// } +// +// if ( admin.equals("localhost") ) {} +// else { +// String pwFile = admin; +// } if ( directory != null ) { if ( ! Files.isDirectory(directory) ) throw new FusekiConfigException("Not a directory: "+dirStr); - if ( ! Files.isWritable(directory) ) throw new FusekiConfigException("Not writable: "+dirStr); } - FusekiServerCtl.FUSEKI_BASE = directory; + + // "directory" null mean use the FUSEKI_BASE environment variable, done by "new FusekiServerCtl" + } // @Override @@ -127,29 +132,27 @@ public void serverArgsPrepare(CmdGeneral fusekiCmd, ServerArgs serverArgs) { public void prepare(FusekiServer.Builder builder, Set datasetNames, Model configModel) { // Ensure the work area is setup - Path path; - synchronized(FusekiServerCtl.class) { - // Temporary - one at a time because FUSEKI_BASE is static. - FusekiServerCtl app = new FusekiServerCtl(null); - path = app.setup(); - } - - FmtLog.info(LOG, "Fuseki Admin: %s", path); + FusekiServerCtl serverCtl = new FusekiServerCtl(directory); + serverCtl.setup(); + Path fusekiBase = serverCtl.getFusekiBase(); + builder.addServletAttribute(Fuseki.attrFusekiServerCtl, serverCtl); + FmtLog.info(LOG, "Fuseki Admin: %s", fusekiBase); // Shiro. - Path shiroIni = path.resolve(FusekiServerCtl.DFT_SHIRO_INI); + Path shiroIni = fusekiBase.resolve(FusekiServerCtl.DFT_SHIRO_INI); if ( Files.exists(shiroIni) ) { System.setProperty(FusekiServerCtl.envFusekiShiro, shiroIni.toString()); } else { - FmtLog.info(LOG, "No shiro.ini: dir=%s", path); + FmtLog.info(LOG, "No shiro.ini: dir=%s", fusekiBase); } String configDir = FusekiServerCtl.dirConfiguration.toString(); List directoryDatabases = FusekiConfig.readConfigurationDirectory(configDir); - if ( directoryDatabases.isEmpty() ) + if ( directoryDatabases.isEmpty() && datasetNames.isEmpty() ) FmtLog.info(LOG, "No databases: dir=%s", configDir); else { + datasetNames.forEach(n->FmtLog.info(Fuseki.configLog, "Database: %s", n)); directoryDatabases.forEach(dap -> FmtLog.info(Fuseki.configLog, "Database: %s", dap.getName())); } @@ -182,4 +185,9 @@ public void prepare(FusekiServer.Builder builder, Set datasetNames, Mode .enableCompact(true) ; } + + // Currently, the server admin area does not move during the run of a server. + /** {@inheritDoc} */ + @Override + public void serverReload(FusekiServer server) { } } diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/prometheus/FMod_Prometheus.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/prometheus/FMod_Prometheus.java index c61a45fc054..6aa7eb6c7b6 100644 --- a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/prometheus/FMod_Prometheus.java +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/prometheus/FMod_Prometheus.java @@ -34,11 +34,12 @@ */ public class FMod_Prometheus implements FusekiModule { - private static FusekiModule singleton = new FMod_Prometheus(); - public static FusekiModule get() { - return singleton; + public static FusekiModule create() { + return new FMod_Prometheus(); } + private MetricsProvider metricsProvider = null; + public FMod_Prometheus() {} // @Override @@ -50,7 +51,7 @@ public FMod_Prometheus() {} public String name() { return "FMod Prometheus Metrics"; } @Override public void prepare(FusekiServer.Builder serverBuilder, Set datasetNames, Model configModel) { - MetricsProvider metricsProvider = new PrometheusMetricsProvider(); + metricsProvider = new PrometheusMetricsProvider(); serverBuilder.addServletAttribute(Fuseki.attrMetricsProvider, metricsProvider); serverBuilder.addServlet("/$/metrics", new ActionMetrics()); } diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/shiro/FMod_Shiro.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/shiro/FMod_Shiro.java index ff0acf2fc49..646505c8b93 100644 --- a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/shiro/FMod_Shiro.java +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/shiro/FMod_Shiro.java @@ -80,6 +80,7 @@ public static FMod_Shiro create() { private static ArgDecl argShiroIni = new ArgDecl(true, "shiro", "shiro-ini"); + // Module state (for reload). private String shiroFile = null; public FMod_Shiro() { @@ -107,12 +108,7 @@ public void serverArgsPrepare(CmdGeneral fusekiCmd, ServerArgs serverArgs) { // The filter is added in prepare(). // This allows other Fuseki modules, such as FMod_Admin, to setup shiro.ini. - // FMod_Admin unpacks a default one to $FUSEKI_BASE/shiro.ini (usually "run/shiro.ini") - -// @Override -// public void serverArgsBuilder(FusekiServer.Builder serverBuilder, Model configModel) { -// //Add filter. -// } + // FMod_Admin unpacks a default one to FUSEKI_BASE/shiro.ini (usually "run/shiro.ini") /** * Determine the Shiro configuration file. @@ -181,8 +177,10 @@ public void serverBeforeStarting(FusekiServer server) { } } - @Override - public void serverAfterStarting(FusekiServer server) {} - -// @Override public void serverStopped(FusekiServer server) { } + // Later: + // Reload shirio.ini file and reset. +// // Currently, no actual - the server admin area does not move during the run of a server. +// /** {@inheritDoc} */ +// @Override +// public void serverReload(FusekiServer server) { } } diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/shiro/FusekiShiroLib.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/shiro/FusekiShiroLib.java index c963771efcb..a04b42589ae 100644 --- a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/shiro/FusekiShiroLib.java +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/shiro/FusekiShiroLib.java @@ -23,6 +23,7 @@ import java.util.List; import jakarta.servlet.ServletContext; +import org.apache.jena.atlas.lib.IRILib; import org.apache.jena.fuseki.FusekiConfigException; import org.apache.jena.rfc3986.URIScheme; import org.apache.shiro.lang.io.ResourceUtils; @@ -47,7 +48,9 @@ static String huntForShiroIni(List locations) { for ( String loc : locations ) { // If file:, look for that file. if ( loc.startsWith(fileSchemePrefix) ) { - Path p = Path.of(loc.substring(fileSchemePrefix.length())); + // Convert (back) to a filesystem path. + String fn = IRILib.IRIToFilename(loc); + Path p = Path.of(fn); if ( Files.exists(p) ) return loc; // Ignore. diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/shiro/ShiroEnvironmentLoaderListener.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/shiro/ShiroEnvironmentLoaderListener.java index c99f47aee71..13295bb6e13 100644 --- a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/shiro/ShiroEnvironmentLoaderListener.java +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/shiro/ShiroEnvironmentLoaderListener.java @@ -69,9 +69,9 @@ protected void customizeEnvironment(WebEnvironment environment) { shiroConfigLog.info(format("No Shiro file found (tried: %s)", locations)); return; } - shiroConfigLog.info("Shiro INI: "+loc); + shiroConfigLog.info("Shiro file: "+loc); String[] configLocationsHere = new String[] {loc}; env.setConfigLocations(configLocationsHere); } } -} \ No newline at end of file +} diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/ui/FMod_UI.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/ui/FMod_UI.java index 51ad0fa32da..2fb8b0de29d 100644 --- a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/ui/FMod_UI.java +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/mod/ui/FMod_UI.java @@ -46,10 +46,9 @@ public class FMod_UI implements FusekiModule { - // Only one module needed - it is stateless. private static FusekiModule singleton = new FMod_UI(); - public static FusekiModule get() { - return singleton; + public static FusekiModule create() { + return new FMod_UI(); } public FMod_UI() {} @@ -63,12 +62,15 @@ public FMod_UI() {} // } private static ArgDecl argUIFiles = new ArgDecl(true, "ui"); - private String uiAppLocation = null; + /** Java resource name used to find the UI files. */ private static String resourceNameUI = "webapp"; - /** Directory name of the root of UI files with {@code FUSEKI_BASE} */ + /** Directory name of the root of UI files */ private static String directoryNameUI = "webapp"; + // UI resources location. + private String uiAppLocation = null; + @Override public String name() { return "FMod UI"; @@ -95,8 +97,14 @@ public void prepare(FusekiServer.Builder builder, Set datasetNames, Mode return; } + FusekiServerCtl serverCtl = (FusekiServerCtl)builder.getServletAttribute(Fuseki.attrFusekiServerCtl); + if ( serverCtl == null ) { + LOG.warn("No server control"); + return; + } + if ( uiAppLocation == null ) { - uiAppLocation = findFusekiApp(); + uiAppLocation = findFusekiApp(serverCtl); if ( uiAppLocation == null ) { LOG.warn("No Static content location has been found"); return; @@ -114,26 +122,32 @@ public void prepare(FusekiServer.Builder builder, Set datasetNames, Mode // LOG.info("Fuseki UI loaded"); } + // Currently, fixed location during the run of a server. + /** {@inheritDoc} */ + @Override + public void serverReload(FusekiServer server) { } + /** * Locate the UI files. *
        *
      1. Command line name of a directory
      2. - *
      3. {@code $FUSEKI_BASE/webapp}
      4. + *
      5. {@code FusekiServerCtl.getFusekibase()/webapp}
      6. *
      7. Classpath java resource {@code webapp}
      8. *
          */ - private String findFusekiApp() { + private String findFusekiApp(FusekiServerCtl serverCtl) { // 1:: Command line setting. if ( uiAppLocation != null ) return uiAppLocation; - // 2:: $FUSEKI_BASE/webapp - // If the FUSEKI_BASE does not exists, it is created later in FMod_admin.prepare - // and does not include Fuseki app. - String x = fromPath(FusekiServerCtl.FUSEKI_BASE, directoryNameUI); - if ( x != null ) { - LOG.info("Fuseki UI - path resource: "+x); - return x; + // 2::FusekiServerCtl.getFusekibase()/webapp + if ( serverCtl != null ) { + Path fusekiBase = serverCtl.getFusekiBase(); + String x = fromPath(fusekiBase, directoryNameUI); + if ( x != null ) { + LOG.info("Fuseki UI - path resource: "+x); + return x; + } } // 3:: From a jar. diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/TS_FusekiMods.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/TS_FusekiMods.java index f4f261e0e97..982da56e2f7 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/TS_FusekiMods.java +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/TS_FusekiMods.java @@ -38,7 +38,8 @@ // Prometheus TestModPrometheus.class, // Apache Shiro - TestModShiro.class + TestModShiro.class, + TestFusekiServer.class }) public class TS_FusekiMods { public TS_FusekiMods() {} diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/TestFusekiServer.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/TestFusekiServer.java index c9d11ed6919..48140e0790e 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/TestFusekiServer.java +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/TestFusekiServer.java @@ -20,19 +20,77 @@ import static org.junit.jupiter.api.Assertions.assertNotEquals; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import org.apache.jena.atlas.lib.FileOps; import org.apache.jena.fuseki.main.FusekiServer; +import org.apache.jena.fuseki.main.cmds.FusekiMain; +import org.apache.jena.fuseki.main.sys.FusekiModules; +import org.apache.jena.fuseki.mgt.FusekiServerCtl; +import org.apache.jena.fuseki.system.FusekiLogging; +import org.apache.jena.http.HttpOp; /** * Test for the whole Fuseki server, not components. */ public class TestFusekiServer { - @Test public void run() { - // Setup - FusekiServer server = FusekiModServer.runAsync("--port=0", "--empty"); - int port = server.getPort(); - assertNotEquals(0, port, "Port is zero after async start"); + private static String serverBase = "target/runBase"; + + @BeforeAll static void beforeAll() { + FusekiModules.restoreSystemDefault(); + FusekiServerCtl.clearUpSystemState(); + FusekiLogging.setLogging(); + } + + @AfterEach void afterEach() { + FusekiModules.restoreSystemDefault(); + FusekiServerCtl.clearUpSystemState(); + } + + @Test + public void runCmdLine() { + String runBase = serverBase+"1"; + setup(runBase); + // Build-run command line + FusekiServer server = FusekiServerRunner.runAsync("--port=0", "--empty"); + try { + int port = server.getPort(); + assertNotEquals(0, port, "Port is zero after async start"); + } finally { + FusekiMain.resetCustomisers(); + server.stop(); + tearDown(runBase); + } + } + + @Test + public void buildRun() { + String runBase = serverBase+"2"; + setup(runBase); + // Build-run programmatically. + FusekiModules serverModules = FusekiServerRunner.serverModules(); + FusekiServer server = FusekiServer.create().port(0).fusekiModules(serverModules).build(); + server.start(); + try { + int port = server.getPort(); + assertNotEquals(0, port, "Port is zero after async start"); + // check it has a UI. + HttpOp.httpGetString(server.serverURL()+"#"); + } finally { + server.stop(); + tearDown(runBase); + } + } + + private void setup(String runBase) { + System.setProperty(FusekiServerCtl.envFusekiBase, runBase); + FileOps.clearAll(runBase); + } + + private void tearDown(String runBase) { + FileOps.clearAll(runBase); } } diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/admin/TestAdmin.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/admin/TestAdmin.java index 396b14b2890..fc73f767991 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/admin/TestAdmin.java +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/admin/TestAdmin.java @@ -37,6 +37,7 @@ import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -59,6 +60,7 @@ import org.apache.jena.fuseki.mgt.FusekiServerCtl; import org.apache.jena.fuseki.mgt.ServerMgtConst; import org.apache.jena.fuseki.server.ServerConst; +import org.apache.jena.fuseki.system.FusekiLogging; import org.apache.jena.fuseki.test.HttpTest; import org.apache.jena.riot.WebContent; import org.apache.jena.sparql.core.DatasetGraph; @@ -88,6 +90,10 @@ public class TestAdmin { private String serverURL = null; private FusekiServer server = null; + @BeforeAll public static void logging() { + FusekiLogging.setLogging(); + } + @BeforeEach public void startServer() { System.setProperty("FUSEKI_BASE", "target/run"); FileOps.clearAll("target/run"); @@ -120,8 +126,7 @@ private FusekiServer createServerForTest() { if ( server != null ) server.stop(); serverURL = null; - // Clearup FMod_Shiro. - System.getProperties().remove(FusekiServerCtl.envFusekiShiro); + FusekiServerCtl.clearUpSystemState(); } protected String urlRoot() { @@ -226,18 +231,17 @@ protected String datasetPath() { try { Path f = Path.of(fileBase+"config-ds-plain-1.ttl"); - { - httpPost(urlRoot()+"$/"+opDatasets, - WebContent.contentTypeTurtle+"; charset="+WebContent.charsetUTF8, - BodyPublishers.ofFile(f)); - } + httpPost(urlRoot()+"$/"+opDatasets, + WebContent.contentTypeTurtle+"; charset="+WebContent.charsetUTF8, + BodyPublishers.ofFile(f)); // Check exists. checkExists(dsTest); + // Try again. try { - } catch (HttpException ex) { httpPost(urlRoot()+"$/"+opDatasets, WebContent.contentTypeTurtle+"; charset="+WebContent.charsetUTF8, BodyPublishers.ofFile(f)); + } catch (HttpException ex) { assertEquals(HttpSC.CONFLICT_409, ex.getStatusCode()); } } catch (IOException ex) { IO.exception(ex); return; } diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/admin/TestTemplateAddDataset.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/admin/TestTemplateAddDataset.java index 9743802dcfa..2035ae581ca 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/admin/TestTemplateAddDataset.java +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/admin/TestTemplateAddDataset.java @@ -85,7 +85,7 @@ private static FusekiServer createServerForTest() { server.stop(); serverURL = null; // Clearup FMod_Shiro. - System.getProperties().remove(FusekiServerCtl.envFusekiShiro); + FusekiServerCtl.clearUpSystemState(); } protected String urlRoot() { diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/metrics/TestModPrometheus.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/metrics/TestModPrometheus.java index dbb475b002d..16bc61c017f 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/metrics/TestModPrometheus.java +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/metrics/TestModPrometheus.java @@ -48,7 +48,7 @@ public class TestModPrometheus { @BeforeEach void setupServer() { DatasetGraph dsg = DatasetGraphFactory.createTxnMem(); - FusekiModules fusekiModules = FusekiModules.create(FMod_Prometheus.get()); + FusekiModules fusekiModules = FusekiModules.create(FMod_Prometheus.create()); testServer = FusekiServer.create() .add("/ds", dsg) .enableMetrics(false) // N.B. false. Instead, use module to setup. diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/shiro/TestModShiro.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/shiro/TestModShiro.java index 48f064f6a74..3a740012a2c 100644 --- a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/shiro/TestModShiro.java +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/mod/shiro/TestModShiro.java @@ -26,7 +26,6 @@ import java.net.http.HttpClient; import java.util.regex.Pattern; -import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -55,23 +54,20 @@ public class TestModShiro { static { FusekiLogging.setLogging(); - // Incase it is finding file:log4j.properties first. + // In case it is finding file:log4j.properties first. // LogCtl.disable(Fuseki.serverLog); // LogCtl.disable(Fuseki.actionLog); // LogCtl.disable(FMod_Shiro.shiroConfigLog); } @BeforeEach void before() { - System.getProperties().remove(FusekiServerCtl.envFusekiShiro); + FusekiServerCtl.clearUpSystemState(); AuthEnv.get().clearAuthEnv(); } @AfterEach void after() { AuthEnv.get().clearAuthEnv(); - } - - @AfterAll static void afterAll() { - System.getProperties().remove(FusekiServerCtl.envFusekiShiro); + FusekiServerCtl.clearUpSystemState(); } private String unlocalhost(FusekiServer server, String dataset) { @@ -108,7 +104,6 @@ private FusekiServer.Builder serverBuilderWithShiro(String filename) { attemptByLocalhost(server, dsLocal); } finally { server.stop(); - AuthEnv.get().clearAuthEnv(); } } @@ -164,7 +159,6 @@ private FusekiServer.Builder serverBuilderWithShiro(String filename) { } finally { server.stop(); - AuthEnv.get().clearAuthEnv(); } } diff --git a/jena-fuseki2/pom.xml b/jena-fuseki2/pom.xml index 18844744cf8..8e2c5d75d41 100644 --- a/jena-fuseki2/pom.xml +++ b/jena-fuseki2/pom.xml @@ -57,8 +57,7 @@ jena-fuseki-main - - + jena-fuseki-server From db75cff8de8e8275fa5823f214239321c1a5dbc4 Mon Sep 17 00:00:00 2001 From: Andy Seaborne Date: Tue, 31 Dec 2024 13:46:06 +0000 Subject: [PATCH 6/8] GH-2902: Legacy entry points corresponding to jena-fuseki-fulljar --- .../org/apache/jena/fuseki/cmd/FusekiCmd.java | 32 +++++++++++++++++++ .../jena/fuseki/cmd/FusekiWebappCmd.java | 32 +++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/cmd/FusekiCmd.java create mode 100644 jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/cmd/FusekiWebappCmd.java diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/cmd/FusekiCmd.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/cmd/FusekiCmd.java new file mode 100644 index 00000000000..b3a434138ad --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/cmd/FusekiCmd.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.cmd; + +import org.apache.jena.fuseki.main.cmds.FusekiServerCmd; + +/** This duplicates an entry point of Fuseki/webapp (jena-fuseki-fulljar) */ +@Deprecated(forRemoval = true) +public class FusekiCmd { + /** This duplicates an entry point of Fuseki/webapp (jena-fuseki-fulljar) */ + public static void main(String... args) { + System.err.println("WARNING: Legacy entry point - use org.apache.jena.fuseki.main.cmds.FusekiServerCmd"); + FusekiServerCmd.main(args); + } + +} diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/cmd/FusekiWebappCmd.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/cmd/FusekiWebappCmd.java new file mode 100644 index 00000000000..a3b3b1df919 --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/cmd/FusekiWebappCmd.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.cmd; + +import org.apache.jena.fuseki.main.cmds.FusekiServerCmd; + +/** This duplicates an entry point of Fuseki/webapp (jena-fuseki-fulljar) */ +@Deprecated(forRemoval = true) +public class FusekiWebappCmd { + /** This duplicates an entry point of Fuseki/webapp (jena-fuseki-fulljar) */ + public static void main(String... args) { + System.err.println("WARNING: Legacy entry point - use org.apache.jena.fuseki.main.cmds.FusekiServerCmd"); + FusekiServerCmd.main(args); + } + +} From 8e18af1b4b06899585962d83999fedb8a13de6e7 Mon Sep 17 00:00:00 2001 From: Andy Seaborne Date: Fri, 27 Dec 2024 21:15:53 +0000 Subject: [PATCH 7/8] Fix warnings --- .../xerces/util/DatatypeMessageFormatter.java | 23 +++++++++---------- .../validation/event/ValidationEvent.java | 2 +- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/jena-core/src/main/java/org/apache/jena/ext/xerces/util/DatatypeMessageFormatter.java b/jena-core/src/main/java/org/apache/jena/ext/xerces/util/DatatypeMessageFormatter.java index 332947880d9..ee1c5b8ac41 100644 --- a/jena-core/src/main/java/org/apache/jena/ext/xerces/util/DatatypeMessageFormatter.java +++ b/jena-core/src/main/java/org/apache/jena/ext/xerces/util/DatatypeMessageFormatter.java @@ -5,9 +5,9 @@ * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -27,34 +27,33 @@ * @author Neeraj Bajaj, Sun Microsystems * @version $Id: DatatypeMessageFormatter.java 813087 2009-09-09 19:35:27Z mrglavas $ */ -@SuppressWarnings("unused") public class DatatypeMessageFormatter { - + private static final String BASE_NAME = "org.apache.jena.ext.xerces.impl.msg.DatatypeMessages"; - + /** * Formats a message with the specified arguments using the given * locale information. - * + * * @param locale The locale of the message. * @param key The message key. * @param arguments The message replacement text arguments. The order * of the arguments must match that of the placeholders * in the actual message. - * + * * @return the formatted message. * * @throws MissingResourceException Thrown if the message with the * specified key cannot be found. */ - public static String formatMessage(Locale locale, + public static String formatMessage(Locale locale, String key, Object[] arguments) throws MissingResourceException { if (locale == null) { locale = Locale.getDefault(); } - final ResourceBundle resourceBundle = + final ResourceBundle resourceBundle = ResourceBundle.getBundle(BASE_NAME, locale); // format message @@ -64,14 +63,14 @@ public static String formatMessage(Locale locale, if (arguments != null) { try { msg = java.text.MessageFormat.format(msg, arguments); - } + } catch (Exception e) { msg = resourceBundle.getString("FormatFailed"); msg += " " + resourceBundle.getString(key); } - } + } } - + // error catch (MissingResourceException e) { msg = resourceBundle.getString("BadMessageKey"); diff --git a/jena-shacl/src/main/java/org/apache/jena/shacl/validation/event/ValidationEvent.java b/jena-shacl/src/main/java/org/apache/jena/shacl/validation/event/ValidationEvent.java index 9bc3c802d12..b08ffd5c02c 100644 --- a/jena-shacl/src/main/java/org/apache/jena/shacl/validation/event/ValidationEvent.java +++ b/jena-shacl/src/main/java/org/apache/jena/shacl/validation/event/ValidationEvent.java @@ -21,7 +21,7 @@ import org.apache.jena.shacl.engine.ValidationContext; /** - * All events during SHACL validation implement this interface, providing access to the {@link org.apache.jena.ext.xerces.impl.dv.ValidationContext}. + * All events during SHACL validation implement this interface, providing access to the {@link ValidationContext}. */ public interface ValidationEvent { ValidationContext getValidationContext(); From 552496455a1efdbdcfd467b689291adf77d0b4c6 Mon Sep 17 00:00:00 2001 From: Andy Seaborne Date: Wed, 1 Jan 2025 10:02:55 +0000 Subject: [PATCH 8/8] Convert Fuseki-core tests to JUnit5 --- .../apache/jena/fuseki/TestValidators.java | 83 ++++++++++--------- .../server/TestDatasetDescriptionMap.java | 6 +- .../jena/fuseki/server/TestDispatchOnURI.java | 18 ++-- .../apache/jena/fuseki/test/FusekiTest.java | 6 +- .../org/apache/jena/fuseki/test/HttpTest.java | 18 ++-- jena-fuseki2/jena-fuseki-webapp/pom.xml | 18 ++++ 6 files changed, 85 insertions(+), 64 deletions(-) diff --git a/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/TestValidators.java b/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/TestValidators.java index e7c40f8a100..6259da5e9ac 100644 --- a/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/TestValidators.java +++ b/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/TestValidators.java @@ -18,8 +18,11 @@ package org.apache.jena.fuseki; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + import org.apache.jena.fuseki.server.Validators; -import org.junit.Test; public class TestValidators { @Test public void validator_service_1() { @@ -50,49 +53,49 @@ public class TestValidators { Validators.serviceName("/abc.def_ghi"); } - @Test(expected=FusekiConfigException.class) + @Test public void validator_service_bad_1() { - Validators.serviceName(null); + assertThrows(FusekiConfigException.class, ()->Validators.serviceName(null)); } - @Test(expected=FusekiConfigException.class) + @Test public void validator_service_bad_2() { - Validators.serviceName(" "); + assertThrows(FusekiConfigException.class, ()->Validators.serviceName(" ")); } - @Test(expected=FusekiConfigException.class) + @Test public void validator_service_bad_3() { - Validators.serviceName("\\"); + assertThrows(FusekiConfigException.class, ()->Validators.serviceName("\\")); } - @Test(expected=FusekiConfigException.class) + @Test public void validator_service_bad_4() { - Validators.serviceName("<"); + assertThrows(FusekiConfigException.class, ()->Validators.serviceName("<")); } - @Test(expected=FusekiConfigException.class) + @Test public void validator_service_bad_5() { - Validators.serviceName(">"); + assertThrows(FusekiConfigException.class, ()->Validators.serviceName(">")); } - @Test(expected=FusekiConfigException.class) + @Test public void validator_service_bad_6() { - Validators.serviceName("?"); + assertThrows(FusekiConfigException.class, ()->Validators.serviceName("?")); } - @Test(expected=FusekiConfigException.class) + @Test public void validator_service_bad_7() { - Validators.serviceName("#"); + assertThrows(FusekiConfigException.class, ()->Validators.serviceName("#")); } - @Test(expected=FusekiConfigException.class) + @Test public void validator_service_bad_8() { - Validators.serviceName("\""); + assertThrows(FusekiConfigException.class, ()->Validators.serviceName("\"")); } - @Test(expected=FusekiConfigException.class) + @Test public void validator_service_bad_20() { - Validators.serviceName(""); + assertThrows(FusekiConfigException.class, ()->Validators.serviceName("")); } @Test public void validator_endpoint_null() { @@ -127,49 +130,49 @@ public void validator_service_bad_20() { Validators.endpointName("/abc.def_ghi"); } -// @Test(expected=FusekiConfigException.class) +// @Test // public void validator_endpoint_bad_1() { // Validators.endpointName(null); // } - @Test(expected=FusekiConfigException.class) + @Test public void validator_endpoint_bad_2() { - Validators.endpointName(" "); + assertThrows(FusekiConfigException.class, ()->Validators.endpointName(" ")); } - @Test(expected=FusekiConfigException.class) + @Test public void validator_endpoint_bad_3() { - Validators.endpointName("\\"); + assertThrows(FusekiConfigException.class, ()->Validators.endpointName("\\")); } - @Test(expected=FusekiConfigException.class) + @Test public void validator_endpoint_bad_4() { - Validators.endpointName("<"); + assertThrows(FusekiConfigException.class, ()->Validators.endpointName("<")); } - @Test(expected=FusekiConfigException.class) + @Test public void validator_endpoint_bad_5() { - Validators.endpointName(">"); + assertThrows(FusekiConfigException.class, ()->Validators.endpointName(">")); } - @Test(expected=FusekiConfigException.class) + @Test public void validator_endpoint_bad_6() { - Validators.endpointName("?"); + assertThrows(FusekiConfigException.class, ()->Validators.endpointName("?")); } - @Test(expected=FusekiConfigException.class) + @Test public void validator_endpoint_bad_7() { - Validators.endpointName("#"); + assertThrows(FusekiConfigException.class, ()->Validators.endpointName("#")); } - @Test(expected=FusekiConfigException.class) + @Test public void validator_endpoint_bad_8() { - Validators.endpointName("\""); + assertThrows(FusekiConfigException.class, ()->Validators.endpointName("\"")); } - @Test(expected=FusekiConfigException.class) + @Test public void validator_endpoint_bad_20() { - Validators.endpointName(""); + assertThrows(FusekiConfigException.class, ()->Validators.endpointName(")")); } @Test public void validator_graph_1() { @@ -180,13 +183,13 @@ public void validator_endpoint_bad_20() { Validators.graphName("http://example/abc#def"); } - @Test(expected=FusekiConfigException.class) + @Test public void validator_graph_bad_1() { - Validators.graphName("abc"); + assertThrows(FusekiConfigException.class, ()->Validators.graphName("abc")); } - @Test(expected=FusekiConfigException.class) + @Test public void validator_graph_bad_2() { - Validators.graphName("#abc"); + assertThrows(FusekiConfigException.class, ()->Validators.graphName("#abc")); } } diff --git a/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/server/TestDatasetDescriptionMap.java b/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/server/TestDatasetDescriptionMap.java index 3c58c380844..706d66f7536 100644 --- a/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/server/TestDatasetDescriptionMap.java +++ b/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/server/TestDatasetDescriptionMap.java @@ -18,10 +18,10 @@ package org.apache.jena.fuseki.server; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.apache.jena.fuseki.build.DatasetDescriptionMap; import org.apache.jena.fuseki.build.FusekiConfig; diff --git a/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/server/TestDispatchOnURI.java b/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/server/TestDispatchOnURI.java index abad6c0f5d8..b505eab431b 100644 --- a/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/server/TestDispatchOnURI.java +++ b/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/server/TestDispatchOnURI.java @@ -18,14 +18,12 @@ package org.apache.jena.fuseki.server; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.fail; - import org.apache.jena.fuseki.servlets.ActionLib; -import org.junit.BeforeClass; -import org.junit.Test; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; /* * Test the request URI part of dispatch. @@ -38,7 +36,7 @@ public class TestDispatchOnURI { private static DataAccessPointRegistry registryNoRoot; private static DataAccessPointRegistry registryWithRoot; - @BeforeClass public static void beforeClass() { + @BeforeAll public static void beforeClass() { registryNoRoot = new DataAccessPointRegistry(); DataService dSrv1 = DataService.newBuilder() .addEndpoint(Operation.Query) @@ -119,7 +117,7 @@ public class TestDispatchOnURI { private void testNoDispatch(String requestURI, DataAccessPointRegistry registry) { DataAccessPoint dap = Dispatcher.locateDataAccessPoint(requestURI, registry); - assertNull("Expect no dispatch for "+requestURI, dap); + assertNull(dap, "Expected no dispatch for "+requestURI); } private void testDispatch(String requestURI, DataAccessPointRegistry registry, String expectedDataset, String expectedEndpoint) { @@ -132,6 +130,6 @@ private void testDispatch(String requestURI, DataAccessPointRegistry registry, S // The request URI part of dispatch choice in Dispatcher.chooseProcessor(HttpAction action) String ep = ActionLib.mapRequestToEndpointName(requestURI, dap); assertNotNull(ep); - assertEquals("Endpoint", expectedEndpoint, ep); + assertEquals(expectedEndpoint, ep); } } diff --git a/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/test/FusekiTest.java b/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/test/FusekiTest.java index ab2775b5358..be737bae671 100644 --- a/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/test/FusekiTest.java +++ b/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/test/FusekiTest.java @@ -20,7 +20,7 @@ import java.util.Objects; -import org.junit.Assert; +import org.junit.jupiter.api.Assertions; public class FusekiTest { @@ -29,10 +29,10 @@ public static void assertStringList(String str, String... expected) { str = str.replace(" ", ""); String[] x = str.split(","); for ( String ex : expected ) { - Assert.assertTrue("Got: "+str+" - Does not contain "+ex, containsStr(ex, x)); + Assertions.assertTrue(containsStr(ex, x), "Got: "+str+" - Does not contain "+ex); } for ( String s : x ) { - Assert.assertTrue("Got: "+str+" - Not expected "+s, containsStr(s, expected)); + Assertions.assertTrue(containsStr(s, expected), "Got: "+str+" - Not expected "+s); } } diff --git a/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/test/HttpTest.java b/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/test/HttpTest.java index 06d88dfc92d..4026519bd09 100644 --- a/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/test/HttpTest.java +++ b/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/test/HttpTest.java @@ -18,24 +18,27 @@ package org.apache.jena.fuseki.test; +import static org.junit.jupiter.api.Assertions.fail; + +import org.junit.jupiter.api.Assertions; + import org.apache.jena.atlas.web.HttpException; import org.apache.jena.sparql.engine.http.QueryExceptionHTTP; import org.apache.jena.web.HttpSC; -import org.junit.Assert; public class HttpTest { public static void expect4xx(Runnable action) { try { action.run(); - Assert.fail("Expected HttpException"); + Assertions.fail("Expected HttpException"); } catch (QueryExceptionHTTP ex) { if ( ex.getStatusCode() < 400 || ex.getStatusCode() > 499 ) - Assert.fail(ex.getMessage()); + Assertions.fail(ex.getMessage()); } catch (HttpException ex) { // -1 : any status code in HttpException if ( ex.getStatusCode() < 400 || ex.getStatusCode() > 499 ) - Assert.fail(ex.getMessage()); + Assertions.fail(ex.getMessage()); } } @@ -63,17 +66,16 @@ public static void expect415(Runnable action) { execWithHttpException(HttpSC.UNSUPPORTED_MEDIA_TYPE_415, action); } - public static void execWithHttpException(int expectedStatusCode, Runnable action) { try { action.run(); - Assert.fail("Expected HttpException "+expectedStatusCode); + fail("Expected HttpException "+expectedStatusCode); } catch (QueryExceptionHTTP ex) { if ( expectedStatusCode > 0 ) - Assert.assertEquals(ex.getMessage()+" ::", expectedStatusCode, ex.getStatusCode()); + Assertions.assertEquals(expectedStatusCode, ex.getStatusCode(), ex.getMessage()+" ::"); } catch (HttpException ex) { if ( expectedStatusCode > 0 ) - Assert.assertEquals(ex.getMessage()+" ::", expectedStatusCode, ex.getStatusCode()); + Assertions.assertEquals(expectedStatusCode, ex.getStatusCode(), ex.getMessage()+" ::"); } } diff --git a/jena-fuseki2/jena-fuseki-webapp/pom.xml b/jena-fuseki2/jena-fuseki-webapp/pom.xml index d5f30151b7c..f56d9913970 100644 --- a/jena-fuseki2/jena-fuseki-webapp/pom.xml +++ b/jena-fuseki2/jena-fuseki-webapp/pom.xml @@ -134,6 +134,24 @@ test + + org.junit.jupiter + junit-jupiter + test + + + + org.junit.jupiter + junit-jupiter-params + test + + + + org.junit.platform + junit-platform-suite + test + + org.awaitility awaitility