This is an automated email from the ASF dual-hosted git repository. andy pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/jena.git
commit 17cb4d1b007d2b568820f54cdca197dd88ec1003 Author: Andy Seaborne <[email protected]> AuthorDate: Wed Nov 13 17:04:05 2024 +0000 GH-2753: Custom arguments for FusekiMain CLI --- .../org/apache/jena/fuseki/main/FusekiServer.java | 7 +- .../org/apache/jena/fuseki/main/cmds/DSGSetup.java | 30 +- .../apache/jena/fuseki/main/cmds/FusekiMain.java | 345 ++++++++++++--------- .../cmds/{ServerConfig.java => ServerArgs.java} | 10 +- .../apache/jena/fuseki/main/sys/FusekiModule.java | 4 +- .../main/sys/FusekiServerArgsCustomiser.java | 113 +++++++ .../org/apache/jena/fuseki/main/TS_FusekiMain.java | 1 + .../fuseki/main/TestFusekiMainCmdArguments.java | 24 +- .../main/TestFusekiMainCmdCustomArguments.java | 227 ++++++++++++++ 9 files changed, 594 insertions(+), 167 deletions(-) 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 3e00e103d5..ef3c4622bd 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 @@ -800,6 +800,11 @@ public class FusekiServer { return this; } + /** + * Return the config model + */ + public Model configModel() { return configModel; } + /** * Configure using a Fuseki services/datasets assembler in a {@link Graph}. * <p> @@ -849,7 +854,7 @@ public class FusekiServer { public Builder jettyServerConfig(String filename) { requireNonNull(filename, "filename"); if ( ! FileOps.exists(filename) ) - throw new FusekiConfigException("File no found: "+filename); + throw new FusekiConfigException("File not found: "+filename); this.jettyServerConfig = filename; return this; } diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/DSGSetup.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/DSGSetup.java index 4610bcfa14..87a11e5e45 100644 --- a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/DSGSetup.java +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/DSGSetup.java @@ -33,7 +33,7 @@ import org.apache.jena.tdb2.DatabaseMgr; * Given a path name and a preference of TDB1/TDB2 for new databases, return * details of the setup to use. */ - /*package*/ static void setupTDB(String directory, boolean useTDB2, ServerConfig serverConfig) { + /*package*/ static void setupTDB(String directory, boolean useTDB2, ServerArgs serverArgs) { File dir = Path.of(directory).toFile(); if ( ! dir.exists() ) throw new CmdException("Directory does not exist: " + directory); @@ -46,40 +46,40 @@ import org.apache.jena.tdb2.DatabaseMgr; if ( IO.isEmptyDirectory(directory) ) { if ( useTDB2 ) - setupTDB2(directory, serverConfig); + setupTDB2(directory, serverArgs); else - setupTDB1(directory, serverConfig); + setupTDB1(directory, serverArgs); return; } // Exists, not empty or does not exist if ( TDBOps.isTDB1(directory) ) { - setupTDB1(directory, serverConfig); + setupTDB1(directory, serverArgs); return; } else if ( TDBOps.isTDB2(directory) ) { - setupTDB2(directory, serverConfig); + setupTDB2(directory, serverArgs); return; } else throw new CmdException("Directory not a database: " + directory); } - private static void setupTDB1(String directory, ServerConfig serverConfig) { - serverConfig.datasetDescription = "TDB1 dataset: location="+directory; - serverConfig.dsg = TDB1Factory.createDatasetGraph(directory); + private static void setupTDB1(String directory, ServerArgs serverArgs) { + serverArgs.datasetDescription = "TDB1 dataset: location="+directory; + serverArgs.dsg = TDB1Factory.createDatasetGraph(directory); } - private static void setupTDB2(String directory, ServerConfig serverConfig) { - serverConfig.datasetDescription = "TDB2 dataset: location="+directory; - serverConfig.dsg = DatabaseMgr.connectDatasetGraph(directory); + private static void setupTDB2(String directory, ServerArgs serverArgs) { + serverArgs.datasetDescription = "TDB2 dataset: location="+directory; + serverArgs.dsg = DatabaseMgr.connectDatasetGraph(directory); } - public static void setupMemTDB(boolean useTDB2, ServerConfig serverConfig) { + public static void setupMemTDB(boolean useTDB2, ServerArgs serverArgs) { String tag = useTDB2 ? "TDB2" : "TDB1"; - serverConfig.datasetDescription = tag+" dataset in-memory"; - serverConfig.dsg = useTDB2 + serverArgs.datasetDescription = tag+" dataset in-memory"; + serverArgs.dsg = useTDB2 ? DatabaseMgr.createDatasetGraph() : TDB1Factory.createDatasetGraph(); - serverConfig.allowUpdate = true; + serverArgs.allowUpdate = true; } } 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 998c79b979..181756c244 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 @@ -25,6 +25,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import arq.cmdline.CmdARQ; import arq.cmdline.ModDatasetAssembler; @@ -40,8 +41,9 @@ 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.FusekiAutoModules; +import org.apache.jena.fuseki.main.sys.FusekiServerArgsCustomiser; +import org.apache.jena.fuseki.main.sys.FusekiModules; import org.apache.jena.fuseki.server.DataAccessPoint; import org.apache.jena.fuseki.server.DataAccessPointRegistry; import org.apache.jena.fuseki.server.FusekiCoreInfo; @@ -53,10 +55,7 @@ import org.apache.jena.fuseki.validation.UpdateValidator; import org.apache.jena.query.ARQ; import org.apache.jena.query.Dataset; import org.apache.jena.rdfs.RDFSFactory; -import org.apache.jena.riot.Lang; -import org.apache.jena.riot.RDFDataMgr; -import org.apache.jena.riot.RDFLanguages; -import org.apache.jena.riot.RiotException; +import org.apache.jena.riot.*; import org.apache.jena.sparql.core.DatasetGraphFactory; import org.apache.jena.sys.JenaSystem; import org.apache.jena.system.Txn; @@ -123,22 +122,15 @@ public class FusekiMain extends CmdARQ { private static ArgDecl argValidators = new ArgDecl(ArgDecl.NoValue, "validators"); private static List<ArgModuleGeneral> additionalArgs = new ArrayList<>(); - public static void addArgModule(ArgModuleGeneral argModule) { additionalArgs.add(argModule); } // private static ModLocation modLocation = new ModLocation(); private static ModDatasetAssembler modDataset = new ModDatasetAssembler(); - private final ServerConfig serverConfig = new ServerConfig(); + private final ServerArgs serverArgs = new ServerArgs(); // Default private boolean useTDB2 = true; - // -- Programmatic ways to create a server - - /** Build, but do not start, a server based on command line syntax. */ - public static FusekiServer build(String... args) { - FusekiServer.Builder builder = builder(args); - return builder.build(); - } + // -- Programmatic ways to create a server using command line syntax. /** * Create a {@link org.apache.jena.fuseki.main.FusekiServer.Builder} which has @@ -147,25 +139,68 @@ public class FusekiMain extends CmdARQ { */ public static FusekiServer.Builder builder(String... args) { // Parses command line, sets arguments. - FusekiMain inner = new FusekiMain(args); + FusekiMain fusekiMain = new FusekiMain(args); // Process command line args according to the argument specified. - inner.process(); - // Apply command line/serverConfig to a builder. - FusekiServer.Builder builder = inner.builder(); - applyServerArgs(builder, inner.serverConfig); + fusekiMain.process(); + // Apply command line/serverArgs to a builder. + FusekiServer.Builder builder = fusekiMain.builder(); + applyServerArgs(builder, fusekiMain.serverArgs); return builder; } + /** + * Build, but do not start, a server based on command line syntax. + */ + public static FusekiServer build(String... args) { + FusekiServer.Builder builder = builder(args); + return builder.build(); + } + /** * Create a server and run, within the same JVM. * This is the command line entry point. */ - static void run(String... argv) { JenaSystem.init(); new FusekiMain(argv).mainRun(); } + /** + * Registers a custom arguments module + * <p> + * This approach is useful when your custom arguments affect global server/runtime setup and don't need to directly + * impact Fuseki Server building. If you need to impact server building then use + * {@link #addCustomiser(FusekiServerArgsCustomiser)} instead + * </p> + * @param argModule Arguments module + * @deprecated Register a {@link org.apache.jena.fuseki.main.sys.FusekiServerArgsCustomiser} via + * {@link #addCustomiser(FusekiServerArgsCustomiser)} instead. + */ + @Deprecated(forRemoval = true) + public static void addArgModule(ArgModuleGeneral argModule) { additionalArgs.add(argModule); } + + private static final List<FusekiServerArgsCustomiser> customiseServerArgs = new ArrayList<>(); + /** + * Registers a CLI customiser + * <p> + * A CLI customiser 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. + * </p> + * @param customiser CLI customiser + */ + public static void addCustomiser(FusekiServerArgsCustomiser customiser) { + Objects.requireNonNull(customiser); + customiseServerArgs.add(customiser); + } + + /** + * Resets any previously registered CLI customisers + */ + public static void resetCustomisers() { + customiseServerArgs.clear(); + } + // -- protected FusekiMain(String... argv) { @@ -245,6 +280,10 @@ public class FusekiMain extends CmdARQ { add(argEnableModules, "--modules=true|false", "Enable Fuseki modules"); super.modVersion.addClass("Fuseki", Fuseki.class); + + for (FusekiServerArgsCustomiser customiser : customiseServerArgs) { + customiser.serverArgsModify(this); + } } static String argUsage = "[--config=FILE] [--mem|--desc=AssemblerFile|--file=FILE] [--port PORT] /DatasetPathName"; @@ -258,7 +297,7 @@ public class FusekiMain extends CmdARQ { protected void processModulesAndArgs() { Logger log = Fuseki.serverLog; - serverConfig.verboseLogging = super.isVerbose(); + serverArgs.verboseLogging = super.isVerbose(); boolean allowEmpty = contains(argEmpty) || contains(argSparqler); @@ -299,32 +338,32 @@ public class FusekiMain extends CmdARQ { if ( getPositional().size() > 1 ) throw new CmdException("Multiple dataset path names given"); if ( getPositional().size() != 0 ) - serverConfig.datasetPath = DataAccessPoint.canonical(getPositionalArg(0)); + serverArgs.datasetPath = DataAccessPoint.canonical(getPositionalArg(0)); } - serverConfig.datasetDescription = "<unset>"; + serverArgs.datasetDescription = "<unset>"; // ---- check: Invalid: --update + --conf if ( contains(argUpdate) && contains(argConfig) ) throw new CmdException("--update and a configuration file does not make sense (control using the configuration file only)"); boolean allowUpdate = contains(argUpdate); - serverConfig.allowUpdate = allowUpdate; + serverArgs.allowUpdate = allowUpdate; boolean hasJettyConfigFile = contains(argJettyConfig); // ---- Port - serverConfig.port = defaultPort; + serverArgs.port = defaultPort; if ( contains(argPort) ) { if ( hasJettyConfigFile ) throw new CmdException("Cannot specify the port and also provide a Jetty configuration file"); - serverConfig.port = portNumber(argPort); + serverArgs.port = portNumber(argPort); } if ( contains(argLocalhost) ) { if ( hasJettyConfigFile ) throw new CmdException("Cannot specify 'localhost' and also provide a Jetty configuration file"); - serverConfig.loopback = true; + serverArgs.loopback = true; } // ---- Dataset @@ -337,41 +376,30 @@ public class FusekiMain extends CmdARQ { useTDB2 = true; if ( allowEmpty ) { - serverConfig.empty = true; - serverConfig.datasetDescription = "No dataset"; + serverArgs.empty = true; + serverArgs.datasetDescription = "No dataset"; } // Fuseki config file - if ( contains(argConfig) ) { - String file = getValue(argConfig); - if ( file.startsWith("file:") ) - file = file.substring("file:".length()); - - Path path = Path.of(file); - if ( ! Files.exists(path) ) - throw new CmdException("File not found: "+file); - if ( Files.isDirectory(path) ) - throw new CmdException("Is a directory: "+file); - serverConfig.datasetDescription = "Configuration: "+path.toAbsolutePath(); - serverConfig.serverConfig = getValue(argConfig); - } + if ( contains(argConfig) ) + serverArgs.serverConfigFile = getValue(argConfig); // Ways to set up a dataset. if ( contains(argMem) ) { - serverConfig.datasetDescription = "in-memory"; + serverArgs.datasetDescription = "in-memory"; // Only one setup should be called by the test above but to be safe // and in case of future changes, clear the configuration. - serverConfig.dsg = DatasetGraphFactory.createTxnMem(); + serverArgs.dsg = DatasetGraphFactory.createTxnMem(); // Always allow, else you can't do very much! - serverConfig.allowUpdate = true; + serverArgs.allowUpdate = true; } if ( contains(argFile) ) { List<String> filenames = getValues(argFile); - serverConfig.datasetDescription = "in-memory, with files loaded"; + serverArgs.datasetDescription = "in-memory, with files loaded"; // Update is not enabled by default for --file - serverConfig.allowUpdate = contains(argUpdate); - serverConfig.dsg = DatasetGraphFactory.createTxnMem(); + serverArgs.allowUpdate = contains(argUpdate); + serverArgs.dsg = DatasetGraphFactory.createTxnMem(); for(String filename : filenames ) { String pathname = filename; @@ -384,10 +412,10 @@ public class FusekiMain extends CmdARQ { Lang language = RDFLanguages.filenameToLang(filename); if ( language == null ) throw new CmdException("Cannot guess language for file: " + filename); - Txn.executeWrite(serverConfig.dsg, ()-> { + Txn.executeWrite(serverArgs.dsg, ()-> { try { log.info("Dataset: in-memory: load file: " + filename); - RDFDataMgr.read(serverConfig.dsg, filename); + RDFDataMgr.read(serverArgs.dsg, filename); } catch (RiotException ex) { throw new CmdException("Failed to load file: " + filename); } @@ -396,28 +424,28 @@ public class FusekiMain extends CmdARQ { } if ( contains(argMemTDB) ) { - DSGSetup.setupMemTDB(useTDB2, serverConfig); + DSGSetup.setupMemTDB(useTDB2, serverArgs); } if ( contains(argTDB) ) { String directory = getValue(argTDB); - DSGSetup.setupTDB(directory, useTDB2, serverConfig); + DSGSetup.setupTDB(directory, useTDB2, serverArgs); } if ( contains(assemblerDescDecl) ) { - serverConfig.datasetDescription = "Assembler: "+ getValue(assemblerDescDecl); + serverArgs.datasetDescription = "Assembler: "+ getValue(assemblerDescDecl); // Need to add service details. Dataset ds = modDataset.createDataset(); - serverConfig.dsg = ds.asDatasetGraph(); + serverArgs.dsg = ds.asDatasetGraph(); } if ( contains(argRDFS) ) { String rdfsVocab = getValue(argRDFS); if ( !FileOps.exists(rdfsVocab) ) throw new CmdException("No such file for RDFS: "+rdfsVocab); - serverConfig.rdfsGraph = RDFDataMgr.loadGraph(rdfsVocab); - serverConfig.datasetDescription = serverConfig.datasetDescription+ " (with RDFS)"; - serverConfig.dsg = RDFSFactory.datasetRDFS(serverConfig.dsg, serverConfig.rdfsGraph); + serverArgs.rdfsGraph = RDFDataMgr.loadGraph(rdfsVocab); + serverArgs.datasetDescription = serverArgs.datasetDescription+ " (with RDFS)"; + serverArgs.dsg = RDFSFactory.datasetRDFS(serverArgs.dsg, serverArgs.rdfsGraph); } // ---- Misc features. @@ -430,21 +458,21 @@ public class FusekiMain extends CmdARQ { String filebase = getValue(argSparqler); if ( ! FileOps.exists(filebase) ) throw new CmdException("File area not found: "+filebase); - serverConfig.contentDirectory = filebase; - serverConfig.addGeneral = "/sparql"; - serverConfig.empty = true; - serverConfig.validators = true; + serverArgs.contentDirectory = filebase; + serverArgs.addGeneral = "/sparql"; + serverArgs.empty = true; + serverArgs.validators = true; } if ( contains(argGeneralQuerySvc) ) { String z = getValue(argGeneralQuerySvc); if ( ! z.startsWith("/") ) z = "/"+z; - serverConfig.addGeneral = z; + serverArgs.addGeneral = z; } if ( contains(argValidators) ) { - serverConfig.validators = true; + serverArgs.validators = true; } // -- Server setup. @@ -453,7 +481,7 @@ public class FusekiMain extends CmdARQ { String contextPath = getValue(argContextPath); contextPath = sanitizeContextPath(contextPath); if ( contextPath != null ) - serverConfig.servletContextPath = contextPath; + serverArgs.servletContextPath = contextPath; } if ( contains(argBase) ) { @@ -463,17 +491,17 @@ public class FusekiMain extends CmdARQ { throw new CmdException("File area not found: "+filebase); //FmtLog.warn(Fuseki.configLog, "File area not found: "+filebase); } - serverConfig.contentDirectory = filebase; + serverArgs.contentDirectory = filebase; } if ( contains(argPasswdFile) ) { if ( hasJettyConfigFile ) throw new CmdException("Can't specify a password file and also provide a Jetty configuration file"); - serverConfig.passwdFile = getValue(argPasswdFile); + serverArgs.passwdFile = getValue(argPasswdFile); } if ( contains(argRealm) ) - serverConfig.realm = getValue(argRealm); + serverArgs.realm = getValue(argRealm); if ( contains(argHttpsPort) && ! contains(argHttps) ) throw new CmdException("https port given but not certificate details via --"+argHttps.getKeyName()); @@ -481,19 +509,19 @@ public class FusekiMain extends CmdARQ { if ( contains(argHttps) ) { if ( hasJettyConfigFile ) throw new CmdException("Can't specify \"https\" and also provide a Jetty configuration file"); - serverConfig.httpsPort = defaultHttpsPort; + serverArgs.httpsPort = defaultHttpsPort; if ( contains(argHttpsPort) ) - serverConfig.httpsPort = portNumber(argHttpsPort); + serverArgs.httpsPort = portNumber(argHttpsPort); String httpsSetup = getValue(argHttps); // The details go in a separate file that can be secured. - serverConfig.httpsKeysDetails = httpsSetup; + serverArgs.httpsKeysDetails = httpsSetup; } if ( contains(argAuth) ) { if ( hasJettyConfigFile ) throw new CmdException("Can't specify authentication and also provide a Jetty configuration file"); String schemeStr = getValue(argAuth); - serverConfig.authScheme = AuthScheme.scheme(schemeStr); + serverArgs.authScheme = AuthScheme.scheme(schemeStr); } // Jetty server : this will be the server configuration regardless of other settings. @@ -501,24 +529,24 @@ public class FusekiMain extends CmdARQ { String jettyConfigFile = getValue(argJettyConfig); if ( ! FileOps.exists(jettyConfigFile) ) throw new CmdException("Jetty config file not found: "+jettyConfigFile); - serverConfig.jettyConfigFile = jettyConfigFile; + serverArgs.jettyConfigFile = jettyConfigFile; } boolean withModules = hasValueOfTrue(argEnableModules); if ( withModules ) { // Use the discovered ones. FusekiAutoModules.enable(true); - // Allows for external setting of serverConfig.fusekiModules - if ( serverConfig.fusekiModules == null ) { + // Allows for external setting of serverArgs.fusekiModules + if ( serverArgs.fusekiModules == null ) { FusekiAutoModules.setup(); - serverConfig.fusekiModules = FusekiAutoModules.load(); + serverArgs.fusekiModules = FusekiAutoModules.load(); } } else { // Disabled module discovery. FusekiAutoModules.enable(false); - // Allows for external setting of serverConfig.fusekiModules - if ( serverConfig.fusekiModules == null ) { - serverConfig.fusekiModules = FusekiModules.empty(); + // Allows for external setting of serverArgs.fusekiModules + if ( serverArgs.fusekiModules == null ) { + serverArgs.fusekiModules = FusekiModules.empty(); } } @@ -526,15 +554,20 @@ public class FusekiMain extends CmdARQ { String corsConfigFile = getValue(argCORS); if ( ! FileOps.exists(corsConfigFile) ) throw new CmdException("CORS config file not found: "+corsConfigFile); - serverConfig.corsConfigFile = corsConfigFile; + serverArgs.corsConfigFile = corsConfigFile; } else if (contains(argNoCORS)) { - serverConfig.withCORS = ! contains(argNoCORS); + serverArgs.withCORS = ! contains(argNoCORS); } - serverConfig.withPing = contains(argWithPing); - serverConfig.withStats = contains(argWithStats); - serverConfig.withMetrics = contains(argWithMetrics); - serverConfig.withCompact = contains(argWithCompact); + serverArgs.withPing = contains(argWithPing); + serverArgs.withStats = contains(argWithStats); + serverArgs.withMetrics = contains(argWithMetrics); + serverArgs.withCompact = contains(argWithCompact); + + // Deal with any customisers + for (FusekiServerArgsCustomiser customiser : customiseServerArgs) { + customiser.serverArgsPrepare(this, serverArgs); + } } private int portNumber(ArgDecl arg) { @@ -568,14 +601,14 @@ public class FusekiMain extends CmdARQ { try { Logger log = Fuseki.serverLog; FusekiMainInfo.logServerCode(log); - FusekiServer server = makeServer(serverConfig); + FusekiServer server = makeServer(serverArgs); infoCmd(server, log); try { server.start(); } catch (FusekiException ex) { if ( ex.getCause() instanceof BindException ) { - if ( serverConfig.jettyConfigFile == null ) - Fuseki.serverLog.error("Failed to start server: "+ex.getCause().getMessage()+ ": port="+serverConfig.port); + 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"); System.exit(1); @@ -584,6 +617,7 @@ public class FusekiMain extends CmdARQ { } catch (Exception ex) { throw new FusekiException("Failed to start server: " + ex.getMessage(), ex); } + // This does not normally return. server.join(); System.exit(0); } @@ -597,13 +631,12 @@ public class FusekiMain extends CmdARQ { } /** - * Take a {@link ServerConfig} and make a {@Link FusekiServer}. + * Take a {@link ServerArgs} and make a {@Link FusekiServer}. * The server has not been started. */ - private FusekiServer makeServer(ServerConfig serverConfig) { + private FusekiServer makeServer(ServerArgs serverArgs) { FusekiServer.Builder builder = builder(); - applyServerArgs(builder, serverConfig); - return builder.build(); + return buildServer(builder, serverArgs); } protected FusekiServer.Builder builder() { @@ -611,27 +644,27 @@ public class FusekiMain extends CmdARQ { } /** - * Process {@link ServerConfig} and build a server. + * Process {@link ServerArgs} and build a server. * The server has not been started. */ - private static FusekiServer buildServer(FusekiServer.Builder builder, ServerConfig serverConfig) { - applyServerArgs(builder, serverConfig); + private static FusekiServer buildServer(FusekiServer.Builder builder, ServerArgs serverArgs) { + applyServerArgs(builder, serverArgs); return builder.build(); } - /** Apply {@link ServerConfig} to a {@link FusekiServer.Builder}. */ - private static void applyServerArgs(FusekiServer.Builder builder, ServerConfig serverConfig) { - if ( serverConfig.jettyConfigFile != null ) - builder.jettyServerConfig(serverConfig.jettyConfigFile); - builder.port(serverConfig.port); - builder.loopback(serverConfig.loopback); - builder.verbose(serverConfig.verboseLogging); + /** Apply {@link ServerArgs} to a {@link FusekiServer.Builder}. */ + private static void applyServerArgs(FusekiServer.Builder builder, ServerArgs serverArgs) { + if ( serverArgs.jettyConfigFile != null ) + builder.jettyServerConfig(serverArgs.jettyConfigFile); + builder.port(serverArgs.port); + builder.loopback(serverArgs.loopback); + builder.verbose(serverArgs.verboseLogging); - if ( serverConfig.addGeneral != null ) + if ( serverArgs.addGeneral != null ) // Add SPARQL_QueryGeneral as a general servlet, not reached by the service router. - builder.addServlet(serverConfig.addGeneral, new SPARQL_QueryGeneral()); + builder.addServlet(serverArgs.addGeneral, new SPARQL_QueryGeneral()); - if ( serverConfig.validators ) { + if ( serverArgs.validators ) { // Validators. builder.addServlet("/$/validate/query", new QueryValidator()); builder.addServlet("/$/validate/update", new UpdateValidator()); @@ -639,50 +672,84 @@ public class FusekiMain extends CmdARQ { builder.addServlet("/$/validate/data", new DataValidator()); } - if ( ! serverConfig.empty ) { - if ( serverConfig.serverConfig != null ) - // Config file. - builder.parseConfigFile(serverConfig.serverConfig); - else - // One dataset. - builder.add(serverConfig.datasetPath, serverConfig.dsg, serverConfig.allowUpdate); +// if ( ! serverArgs.empty ) { +// if ( serverArgs.serverConfig != null ) +// // Config file. +// builder.parseConfigFile(serverArgs.serverConfig); +// else +// // One dataset. +// builder.add(serverArgs.datasetPath, serverArgs.dsg, serverArgs.allowUpdate); +// } + + if ( ! serverArgs.empty ) { + // A CLI customiser may have already set the model. + if (serverArgs.serverConfigModel != null ) { + builder.parseConfig(serverArgs.serverConfigModel); + serverArgs.datasetDescription = "Configuration: provided"; + } else if ( serverArgs.serverConfigFile != null ) { + // if there is a configuration file, read it. + // Read the model. + String file = serverArgs.serverConfigFile; + if ( file.startsWith("file:") ) + file = file.substring("file:".length()); + Path path = Path.of(file); + if ( ! Files.exists(path) ) + throw new CmdException("File not found: "+file); + if ( Files.isDirectory(path) ) + throw new CmdException("Is a directory: "+file); + serverArgs.datasetDescription = "Configuration: "+path.toAbsolutePath(); + serverArgs.serverConfigModel = RDFParser.source(path).toModel(); + builder.parseConfig(serverArgs.serverConfigModel); + } else { + // One dataset up by command line arguments. + if ( serverArgs.dsg == null || serverArgs.datasetPath == null ) { + // Internal error: should have happened during checking earlier. + throw new CmdException("Failed to set the dataset service"); + } + builder.add(serverArgs.datasetPath, serverArgs.dsg, serverArgs.allowUpdate); + } } - if ( serverConfig.fusekiModules != null ) - builder.fusekiModules(serverConfig.fusekiModules); + if ( serverArgs.fusekiModules != null ) + builder.fusekiModules(serverArgs.fusekiModules); - if ( serverConfig.servletContextPath != null ) - builder.contextPath(serverConfig.servletContextPath); + if ( serverArgs.servletContextPath != null ) + builder.contextPath(serverArgs.servletContextPath); - if ( serverConfig.contentDirectory != null ) - builder.staticFileBase(serverConfig.contentDirectory); + if ( serverArgs.contentDirectory != null ) + builder.staticFileBase(serverArgs.contentDirectory); - if ( serverConfig.passwdFile != null ) - builder.passwordFile(serverConfig.passwdFile); + if ( serverArgs.passwdFile != null ) + builder.passwordFile(serverArgs.passwdFile); - if ( serverConfig.realm != null ) - builder.realm(serverConfig.realm); + if ( serverArgs.realm != null ) + builder.realm(serverArgs.realm); - if ( serverConfig.httpsKeysDetails != null) - builder.https(serverConfig.httpsPort, serverConfig.httpsKeysDetails); + if ( serverArgs.httpsKeysDetails != null) + builder.https(serverArgs.httpsPort, serverArgs.httpsKeysDetails); - if ( serverConfig.authScheme != null ) - builder.auth(serverConfig.authScheme); + if ( serverArgs.authScheme != null ) + builder.auth(serverArgs.authScheme); - if ( serverConfig.withCORS ) - builder.enableCors(true, serverConfig.corsConfigFile); + if ( serverArgs.withCORS ) + builder.enableCors(true, serverArgs.corsConfigFile); - if ( serverConfig.withPing ) + if ( serverArgs.withPing ) builder.enablePing(true); - if ( serverConfig.withStats ) + if ( serverArgs.withStats ) builder.enableStats(true); - if ( serverConfig.withMetrics ) + if ( serverArgs.withMetrics ) builder.enableMetrics(true); - if ( serverConfig.withCompact ) + if ( serverArgs.withCompact ) builder.enableCompact(true); + + // Allow customisers to process their own arguments. + for (FusekiServerArgsCustomiser customiser : customiseServerArgs) { + customiser.serverArgsBuilder(builder, builder.configModel()); + } } /** Information from the command line setup */ @@ -690,25 +757,25 @@ public class FusekiMain extends CmdARQ { if ( super.isQuiet() ) return; - if ( serverConfig.empty ) { + if ( serverArgs.empty ) { FmtLog.info(log, "No SPARQL datasets services"); } else { - if ( serverConfig.datasetPath == null && serverConfig.serverConfig == null ) + if ( serverArgs.datasetPath == null && serverArgs.serverConfigModel == null ) log.error("No dataset path nor server configuration file"); } DataAccessPointRegistry dapRegistry = DataAccessPointRegistry.get(server.getServletContext()); - if ( serverConfig.datasetPath != null ) { + if ( serverArgs.datasetPath != null ) { if ( dapRegistry.size() != 1 ) log.error("Expected only one dataset in the DataAccessPointRegistry"); } // Log details on startup. - String datasetPath = serverConfig.datasetPath; - String datasetDescription = serverConfig.datasetDescription; - String serverConfigFile = serverConfig.serverConfig; - String staticFiles = serverConfig.contentDirectory; - boolean verbose = serverConfig.verboseLogging; + String datasetPath = serverArgs.datasetPath; + String datasetDescription = serverArgs.datasetDescription; + String serverConfigFile = serverArgs.serverConfigFile; + String staticFiles = serverArgs.contentDirectory; + boolean verbose = serverArgs.verboseLogging; if ( ! super.isQuiet() ) FusekiCoreInfo.logServerCmdSetup(log, verbose, dapRegistry, diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/ServerConfig.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/ServerArgs.java similarity index 91% rename from jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/ServerConfig.java rename to jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/ServerArgs.java index 5e1ac68fd1..62dc3f3261 100644 --- a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/ServerConfig.java +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/ServerArgs.java @@ -21,14 +21,15 @@ package org.apache.jena.fuseki.main.cmds; import org.apache.jena.atlas.web.AuthScheme; import org.apache.jena.fuseki.main.sys.FusekiModules; import org.apache.jena.graph.Graph; +import org.apache.jena.rdf.model.Model; import org.apache.jena.sparql.core.DatasetGraph; /** * Setup details (command line, config file) from command line processing. - * This is built by {@link FusekiMain#exec}. - * This is processed by {@link FusekiMain#buildServer}. + * This is built by {@link FusekiMain#processModulesAndArgs}. + * This is processed by {@link FusekiMain#applyServerArgs}. */ -class ServerConfig { +public class ServerArgs { /** Server port. This is the http port when both http and https are active. */ public int port = -1; /** Loopback */ @@ -64,7 +65,8 @@ class ServerConfig { public Graph rdfsGraph = null; // ... or this. - public String serverConfig = null; + public String serverConfigFile = null; + public Model serverConfigModel = null; /** No registered datasets without it being an error. */ public boolean empty = false; diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/sys/FusekiModule.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/sys/FusekiModule.java index d34292e272..63196f2b31 100644 --- a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/sys/FusekiModule.java +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/sys/FusekiModule.java @@ -56,7 +56,7 @@ import org.apache.jena.rdf.model.Model; * Modules must not rely on a call to {@code serverStopped} happening.</li> * </ul> */ -public interface FusekiModule extends FusekiBuildCycle, FusekiStartStop, FusekiActionCycle { +public interface FusekiModule extends FusekiServerArgsCustomiser, FusekiBuildCycle, FusekiStartStop, FusekiActionCycle { // Gather all interface method together. // Inherited javadoc. @@ -65,9 +65,11 @@ public interface FusekiModule extends FusekiBuildCycle, FusekiStartStop, FusekiA // ---- Build cycle + /** {@inheritDoc} */ @Override public default void prepare(FusekiServer.Builder serverBuilder, Set<String> datasetNames, Model configModel) { } + /** {@inheritDoc} */ @Override public default void configured(FusekiServer.Builder serverBuilder, DataAccessPointRegistry dapRegistry, Model configModel) { dapRegistry.accessPoints().forEach(accessPoint->configDataAccessPoint(accessPoint, configModel)); diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/sys/FusekiServerArgsCustomiser.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/sys/FusekiServerArgsCustomiser.java new file mode 100644 index 0000000000..a43ff2d972 --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/sys/FusekiServerArgsCustomiser.java @@ -0,0 +1,113 @@ +/* + * 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; + +import org.apache.jena.cmd.CmdException; +import org.apache.jena.cmd.CmdGeneral; +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.rdf.model.Model; + + + +/** + * Interface to implement for extending the CLI argument parsing portion of a + * {@link FusekiServer}. + * <p> + * Customisation code is registered by calling {@link FusekiMain#addCustomiser} + * before invoking {@link FusekiMain#build}. This can be done from Java code, or + * during {@link FusekiAutoModule#start()} for dynamically loaded code. + * <p> + * The customiser modifies the Fuseki arguments setup after the standard Fuseki main + * arguments have been registered. The customiser is then called + * after the standard arguments have been used to produce the {@link ServerArgs}. + * <p> + * The lifecycle for command line argument processing is: + * <ul> + * <li> + * Add customisers by calling {@link FusekiMain#addCustomiser} from + * {@link FusekiAutoModule#start()} or from Java application code. + * </li> + * <li> + * {@link #serverArgsModify} — Register or modify the argument setup to be + * used to parse the command line. + * </li> + * <li> + * {@link #serverArgsPrepare} — Called after parsing the command line and + * recoding the command line settings in {@link ServerArgs}. Customisers can record + * argument values and flags. + * </li> + * <li> + * {@link #serverArgsBuilder} — Called after the {@link ServerArgs} have + * been used to construct a server builder. + * </li> + * </ul> + * Following command like processing, server construction proceeds with the {@link FusekiBuildCycle}, + * the first step of which is {@link FusekiBuildCycle#prepare}. + */ +public interface FusekiServerArgsCustomiser { + + /** + * Called after the standard Fuseki main arguments have been added + * and before argument processing of the command line. + * This allows a Fuseki module to add custom arguments via + * {@link CmdGeneral#addArg(String, String)} and + * {@link CmdGeneral#addModule(org.apache.jena.cmd.ArgModuleGeneral)}. + * <p> + * This method can throw {@link CmdException} to indicate errors. + * This will cause a error message to be printed, without the stack trace. + * The server construction is aborted. + * + * @param fusekiCmd Fuseki Main command line arguments + */ + public default void serverArgsModify(CmdGeneral fusekiCmd) { } + + /** + * Called at the end command line argument processing. + * <p> + * This allows a Fuseki module to pull out custom arguments it has added and + * process them appropriately, including validating or modifying the + * {@link ServerArgs} that will be used to build the server. + * <p> + * This method can throw {@link CmdException} to indicate errors. + * This will cause a error message to be printed, without the stack trace. + * The server construction is aborted. + * + * @param fusekiCmd Fuseki Main + * @param serverArgs Standard server argument settings, before building the + * server. + */ + public default void serverArgsPrepare(CmdGeneral fusekiCmd, ServerArgs serverArgs) { } + + /** + * Called at the end of applying the {@link ServerArgs} to the builder. + * <p> + * The configuration model (if any) has been setup. This step can do validation + * and argument processing dependent on the configuration model. + * <p> + * This method can throw {@link CmdException} to indicate errors. + * This will cause a error message to be printed, without the stack trace. + * The server construction is aborted. + * + * @param serverBuilder The server builder. + * @param configModel The configuration model; this may be null. + */ + public default void serverArgsBuilder(FusekiServer.Builder serverBuilder, Model configModel) {} +} 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 78b902b670..e0fa22d5e8 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 @@ -35,6 +35,7 @@ import org.junit.platform.suite.api.Suite; , TestFusekiCustomOperation.class , TestFusekiMainCmd.class , TestFusekiMainCmdArguments.class + , TestFusekiMainCmdCustomArguments.class , TestFusekiStdSetup.class , TestFusekiStdReadOnlySetup.class , TestConfigFile.class 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 082475bdd9..9992139d88 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 @@ -23,6 +23,9 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; import java.util.List; import org.apache.jena.atlas.logging.LogCtl; @@ -43,17 +46,24 @@ import org.junit.Test; public class TestFusekiMainCmdArguments { private static String level = null; + private static File jettyConfigFile; + private static String jettyConfigFilename; - @BeforeClass public static void beforeClass() { + @BeforeClass public static void beforeClass() throws IOException { // This is not reset by each running server. FusekiLogging.setLogging(); level = LogCtl.getLevel(Fuseki.serverLog); LogCtl.setLevel(Fuseki.serverLog, "WARN"); + + // Create a fake Jetty config file just to avoid File Not Found error + jettyConfigFile = Files.createTempFile("jetty-config",".xml").toFile(); + jettyConfigFilename = jettyConfigFile.getAbsolutePath(); } @AfterClass public static void afterClass() { if ( level != null ) LogCtl.setLevel(Fuseki.serverLog, level); + jettyConfigFile.delete(); } private FusekiServer server = null; @@ -272,7 +282,7 @@ public class TestFusekiMainCmdArguments { @Test public void test_error_jettyConfigFileAndPort() { // given - List<String> arguments = List.of("--mem", "--jetty=file", "--port=99", "/path"); + List<String> arguments = List.of("--mem", "--jetty=" + jettyConfigFilename, "--port=99", "/path"); String expectedMessage = "Cannot specify the port and also provide a Jetty configuration file"; // when, then testForCmdException(arguments, expectedMessage); @@ -281,7 +291,7 @@ public class TestFusekiMainCmdArguments { @Test public void test_error_jettyConfigFileAndLocalHost() { // given - List<String> arguments = List.of("--mem", "--jetty=file", "--localhost", "/path"); + List<String> arguments = List.of("--mem", "--jetty=" + jettyConfigFilename, "--localhost", "/path"); String expectedMessage = "Cannot specify 'localhost' and also provide a Jetty configuration file"; // when, then testForCmdException(arguments, expectedMessage); @@ -357,7 +367,7 @@ public class TestFusekiMainCmdArguments { @Test public void test_error_passwdFile_withJetty() { // given - List<String> arguments = List.of("--mem", "--passwd=missing_file", "--jetty=file", "/dataset"); + List<String> arguments = List.of("--mem", "--passwd=missing_file", "--jetty=" + jettyConfigFilename, "/dataset"); String expectedMessage = "Can't specify a password file and also provide a Jetty configuration file"; // when, then testForCmdException(arguments, expectedMessage); @@ -375,7 +385,7 @@ public class TestFusekiMainCmdArguments { @Test public void test_error_httpsConfig_withJetty() { // given - List<String> arguments = List.of("--mem", "--httpsPort=12345", "--https=something", "--jetty=file", "/dataset"); + List<String> arguments = List.of("--mem", "--httpsPort=12345", "--https=something", "--jetty=" + jettyConfigFilename, "/dataset"); String expectedMessage = "Can't specify \"https\" and also provide a Jetty configuration file"; // when, then testForCmdException(arguments, expectedMessage); @@ -384,7 +394,7 @@ public class TestFusekiMainCmdArguments { @Test public void test_error_auth_withJetty() { // given - List<String> arguments = List.of("--mem", "--auth=12345", "--jetty=file", "/dataset"); + List<String> arguments = List.of("--mem", "--auth=12345", "--jetty=" + jettyConfigFilename, "/dataset"); String expectedMessage = "Can't specify authentication and also provide a Jetty configuration file"; // when, then testForCmdException(arguments, expectedMessage); @@ -440,7 +450,7 @@ public class TestFusekiMainCmdArguments { // Build and set the server private void buildServer(String... cmdline) { if ( server != null ) - fail("Bad test - a server has aleardy been created"); + fail("Bad test - a server has already been created"); server = FusekiMain.build(cmdline); server.start(); } 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 new file mode 100644 index 0000000000..cae8c05e03 --- /dev/null +++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestFusekiMainCmdCustomArguments.java @@ -0,0 +1,227 @@ +/* + * 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 static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.util.Objects; +import java.util.function.Consumer; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import org.apache.jena.atlas.logging.LogCtl; +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.main.cmds.FusekiMain; +import org.apache.jena.fuseki.main.cmds.ServerArgs; +import org.apache.jena.fuseki.main.sys.FusekiServerArgsCustomiser; +import org.apache.jena.fuseki.server.DataAccessPoint; +import org.apache.jena.fuseki.system.FusekiLogging; +import org.apache.jena.rdf.model.Model; +import org.apache.jena.riot.Lang; +import org.apache.jena.riot.RDFParser; + +public class TestFusekiMainCmdCustomArguments { + + static { FusekiLogging.setLogging(); } + + private static String confFixedStr = """ + PREFIX : <http://example/base/> + PREFIX fuseki: <http://jena.apache.org/fuseki#> + PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> + PREFIX ja: <http://jena.hpl.hp.com/2005/11/Assembler#> + + :service rdf:type fuseki:Service ; + fuseki:name "dataset" ; + fuseki:endpoint [ fuseki:operation fuseki:query ; ] ; + fuseki:dataset [ rdf:type ja:MemoryDataset ] + . + """; + private static Model confFixed = RDFParser.fromString(confFixedStr, Lang.TTL).toModel(); + + private static String level = null; + + @BeforeClass public static void beforeClass() { + FusekiLogging.setLogging(); + level = LogCtl.getLevel(Fuseki.serverLog); + LogCtl.setLevel(Fuseki.serverLog, "WARN"); + } + + @AfterClass public static void afterClass() { + if ( level != null ) + LogCtl.setLevel(Fuseki.serverLog, level); + } + + private FusekiServer server = null; + + @After public void after() { + if ( server != null ) + server.stop(); + } + + @Test + public void test_custom_no_custom_args() { + String[] arguments = {"--port=0", "--mem","/ds"}; + test(new ArgDecl(false, "special"), arguments, false, null); + } + + @Test(expected = CmdException.class) + public void test_custom_no_custom_args_decl() { + String[] arguments = {"--port=0", "--special", "--mem","/ds"}; + FusekiServer server = FusekiMain.build(arguments); + } + + @Test + public void test_custom_flag() { + String[] arguments = {"--port=0", "--mem", "--custom-flag", "/ds"}; + test(new ArgDecl(false, "custom-flag"), arguments, true, null); + } + + @Test + public void test_custom_arg() { + String[] arguments = {"--port=0", "--mem", "--custom-arg", "test", "/ds"}; + test(new ArgDecl(true, "custom-arg"), arguments, true, "test"); + } + + @Test + public void test_custom_confModel_noFlag() { + String[] arguments = {"--port=0", "--mem", "/ds"}; + FusekiCliCustomModel customiser = new FusekiCliCustomModel(new ArgDecl(false, "fixed"), confFixed); + test(customiser, arguments, server->{ + assertSame(null, customiser.notedServerConfigModel); + DataAccessPoint dap1 = server.getDataAccessPointRegistry().get("/ds"); + assertNotNull(dap1); + DataAccessPoint dap2 = server.getDataAccessPointRegistry().get("/dataset"); + assertNull(dap2); + }); + } + + @Test + public void test_custom_confModel_withFlag() { + // Ignores command line. + String[] arguments = {"--port=0", "--mem", "--fixed", "/ds"}; + FusekiCliCustomModel customiser = new FusekiCliCustomModel(new ArgDecl(false, "fixed"), confFixed); + FusekiServer server = test(customiser, arguments, serv->{ + DataAccessPoint dap1 = serv.getDataAccessPointRegistry().get("/ds"); + assertNull(dap1); + DataAccessPoint dap2 = serv.getDataAccessPointRegistry().get("/dataset"); + assertNotNull(dap2); + }); + assertTrue(server.getDataAccessPointRegistry().isRegistered("/dataset")); + assertFalse(server.getDataAccessPointRegistry().isRegistered("/ds")); + } + + // ---- + + private void test(ArgDecl argDecl, String[] arguments, boolean seen, String value) { + FusekiCliCustomArg customiser = new FusekiCliCustomArg(argDecl); + test(customiser, arguments, (server)->{ + assertEquals(seen, customiser.argSeen); + assertEquals(value, customiser.argValue); + }); + } + + private FusekiServer test(FusekiServerArgsCustomiser customiser, String[] arguments, Consumer<FusekiServer> checker) { + FusekiMain.resetCustomisers(); + FusekiMain.addCustomiser(customiser); + FusekiServer server = FusekiMain.build(arguments); + if ( checker != null ) + checker.accept(server); + return server; + } + + // ---- Test FusekiCliCustomisers + + static class FusekiCliCustomArg implements FusekiServerArgsCustomiser { + final ArgDecl argDecl; + String argValue = null; + boolean argSeen = false; + + FusekiCliCustomArg(ArgDecl argDecl) { + this.argDecl = argDecl; + } + + @Override + public void serverArgsModify(CmdGeneral fusekiCmd) { + fusekiCmd.add(argDecl); + } + + @Override + public void serverArgsPrepare(CmdGeneral fusekiCmd, ServerArgs serverArgs) { + argSeen = fusekiCmd.contains(argDecl); + argValue = fusekiCmd.getValue(argDecl); + } + }; + + static class FusekiCliCustomModel implements FusekiServerArgsCustomiser { + final ArgDecl argDecl; + final Model fixedModel; + Model notedServerConfigModel = null; + boolean argSeen = false; + + FusekiCliCustomModel(ArgDecl argDecl, Model conf) { + this.argDecl = argDecl; + this.fixedModel = conf; + } + + @Override + public void serverArgsModify(CmdGeneral fusekiCmd) { + fusekiCmd.add(argDecl); + } + + @Override + public void serverArgsPrepare(CmdGeneral fusekiCmd, ServerArgs serverArgs) { + argSeen = fusekiCmd.contains(argDecl); + if ( argSeen ) { + serverArgs.serverConfigModel = fixedModel; + notedServerConfigModel = fixedModel; + serverArgs.dsg = null; + } + } + + @Override + public void serverArgsBuilder(FusekiServer.Builder serverBuilder, Model configModel) { + if ( argSeen ) + assertSame(notedServerConfigModel, configModel); + else + assertNull(configModel); + } + }; + + static class FusekiCliCustomConfiguration implements FusekiServerArgsCustomiser { + @Override public void serverArgsModify(CmdGeneral fusekiCmd) { } + @Override public void serverArgsPrepare(CmdGeneral fusekiCmd, ServerArgs serverArgs) { + if ( Objects.equals(serverArgs.serverConfigFile, "placeholder") ) { + serverArgs.serverConfigModel = confFixed; + } + + //serverArgs.serverConfigFile; + //serverArgs.serverConfigModel; + } + + @Override public void serverArgsBuilder(FusekiServer.Builder serverBuilder, Model configModel) {} + } + +}
