| /* |
| * 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 static arq.cmdline.ModAssembler.assemblerDescDecl; |
| |
| import java.net.BindException; |
| import java.nio.file.Files; |
| import java.nio.file.Path; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import arq.cmdline.CmdARQ; |
| import arq.cmdline.ModDatasetAssembler; |
| import org.apache.jena.assembler.exceptions.AssemblerException; |
| import org.apache.jena.atlas.lib.FileOps; |
| import org.apache.jena.atlas.logging.FmtLog; |
| import org.apache.jena.atlas.web.AuthScheme; |
| import org.apache.jena.cmd.ArgDecl; |
| import org.apache.jena.cmd.ArgModuleGeneral; |
| import org.apache.jena.cmd.CmdException; |
| import org.apache.jena.cmd.TerminationException; |
| 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.server.DataAccessPoint; |
| import org.apache.jena.fuseki.server.DataAccessPointRegistry; |
| import org.apache.jena.fuseki.server.FusekiCoreInfo; |
| import org.apache.jena.fuseki.servlets.SPARQL_QueryGeneral; |
| 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.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.sparql.core.DatasetGraphFactory; |
| import org.apache.jena.sys.JenaSystem; |
| import org.apache.jena.system.Txn; |
| import org.apache.jena.tdb1.transaction.TransactionManager; |
| 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. */ |
| public static int defaultHttpsPort = 3043; |
| |
| private static ArgDecl argMem = new ArgDecl(ArgDecl.NoValue, "mem"); |
| private static ArgDecl argUpdate = new ArgDecl(ArgDecl.NoValue, "update", "allowUpdate"); |
| private static ArgDecl argFile = new ArgDecl(ArgDecl.HasValue, "file"); |
| |
| private static ArgDecl argTDB1mode = new ArgDecl(ArgDecl.NoValue, "tdb1"); |
| private static ArgDecl argTDB2mode = new ArgDecl(ArgDecl.NoValue, "tdb2"); |
| private static ArgDecl argMemTDB = new ArgDecl(ArgDecl.NoValue, "memtdb", "memTDB", "tdbmem"); |
| private static ArgDecl argTDB = new ArgDecl(ArgDecl.HasValue, "loc", "location", "tdb"); |
| |
| // RDFS vocabulary applied to command line defined dataset. |
| private static ArgDecl argRDFS = new ArgDecl(ArgDecl.HasValue, "rdfs"); |
| |
| // No SPARQL dataset or services |
| private static ArgDecl argEmpty = new ArgDecl(ArgDecl.NoValue, "empty", "no-dataset"); |
| private static ArgDecl argGeneralQuerySvc = new ArgDecl(ArgDecl.HasValue, "general"); |
| |
| private static ArgDecl argPort = new ArgDecl(ArgDecl.HasValue, "port"); |
| private static ArgDecl argLocalhost = new ArgDecl(ArgDecl.NoValue, "localhost", "local"); |
| private static ArgDecl argTimeout = new ArgDecl(ArgDecl.HasValue, "timeout"); |
| private static ArgDecl argConfig = new ArgDecl(ArgDecl.HasValue, "config", "conf"); |
| |
| private static ArgDecl argJettyConfig = new ArgDecl(ArgDecl.HasValue, "jetty-config", "jetty"); |
| private static ArgDecl argGZip = new ArgDecl(ArgDecl.HasValue, "gzip"); |
| // Set the servlet context path (the initial path for URLs.) for any datasets. |
| // A context of "/path" and a dataset name of "/ds", service "sparql" is accessed as "/path/ds/sparql" |
| private static ArgDecl argPathBase = new ArgDecl(ArgDecl.HasValue, "pathBase", "contextPath"); |
| // Static files. URLs are affected by argPathBase |
| private static ArgDecl argBase = new ArgDecl(ArgDecl.HasValue, "base", "files"); |
| |
| private static ArgDecl argCORS = new ArgDecl(ArgDecl.HasValue, "withCORS", "cors", "CORS", "cors-config"); |
| private static ArgDecl argNoCORS = new ArgDecl(ArgDecl.NoValue, "noCORS", "no-cors"); |
| private static ArgDecl argWithPing = new ArgDecl(ArgDecl.NoValue, "withPing", "ping"); |
| private static ArgDecl argWithStats = new ArgDecl(ArgDecl.NoValue, "withStats", "stats"); |
| 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"); |
| |
| private static ArgDecl argAuth = new ArgDecl(ArgDecl.HasValue, "auth"); |
| |
| private static ArgDecl argHttps = new ArgDecl(ArgDecl.HasValue, "https"); |
| private static ArgDecl argHttpsPort = new ArgDecl(ArgDecl.HasValue, "httpsPort", "httpsport", "sport"); |
| |
| private static ArgDecl argPasswdFile = new ArgDecl(ArgDecl.HasValue, "passwd"); |
| private static ArgDecl argRealm = new ArgDecl(ArgDecl.HasValue, "realm"); |
| |
| // Same as --empty --validators --general=/sparql, --files=ARG |
| |
| private static ArgDecl argSparqler = new ArgDecl(ArgDecl.HasValue, "sparqler"); |
| |
| 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(); |
| // 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(); |
| } |
| |
| /** |
| * Create a {@link org.apache.jena.fuseki.main.FusekiServer.Builder} which has |
| * been setup according to the command line arguments. |
| * The builder can be further modified. |
| */ |
| public static FusekiServer.Builder builder(String... args) { |
| // Parses command line, sets arguments. |
| FusekiMain inner = 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); |
| return builder; |
| } |
| |
| /** |
| * 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(); |
| } |
| |
| // -- |
| |
| protected FusekiMain(String... argv) { |
| super(argv); |
| |
| if ( false ) |
| // Consider ... |
| TransactionManager.QueueBatchSize = TransactionManager.QueueBatchSize / 2; |
| |
| getUsage().startCategory("Fuseki Main"); |
| |
| additionalArgs.forEach(aMod->super.addModule(aMod)); |
| |
| // Control the order! |
| add(argMem, "--mem", |
| "Create an in-memory, non-persistent dataset for the server"); |
| add(argFile, "--file=FILE", |
| "Create an in-memory, non-persistent dataset for the server, initialised with the contents of the file"); |
| add(argTDB2mode, "--tdb2", |
| "Use TDB2 for command line persistent datasets"); |
| add(argTDB1mode, "--tdb1", |
| "Use TDB1 for command line persistent datasets (default is TDB2)"); |
| add(argTDB, "--loc=DIR", |
| "Use an existing TDB database (or create if does not exist)"); |
| add(argMemTDB, "--memTDB", |
| "Create an in-memory, non-persistent dataset using TDB (testing only)"); |
| // add(argEmpty, "--empty", |
| // "Run with no datasets and services"); |
| add(argRDFS, "--rdfs=FILE", |
| "Apply RDFS on top of the dataset"); |
| add(argConfig, "--config=FILE", |
| "Use a configuration file to determine the services"); |
| addModule(modDataset); |
| add(argEmpty); // Hidden for now. |
| add(argPort, "--port", |
| "Listen on this port number"); |
| add(argLocalhost, "--localhost", |
| "Listen only on the localhost interface"); |
| add(argTimeout, "--timeout=", |
| "Global timeout applied to queries (value in ms) -- format is X[,Y] "); |
| add(argUpdate, "--update", |
| "Allow updates (via SPARQL Update and SPARQL HTTP Update)"); |
| add(argGZip, "--gzip=on|off", |
| "Enable GZip compression (HTTP Accept-Encoding) if request header set"); |
| add(argBase, "--base=DIR", |
| "Directory for static content"); |
| add(argPathBase, "--pathBase=DIR", |
| "Context path for datasets"); |
| add(argSparqler, "--sparqler=DIR", |
| "Run with SPARQLer services Directory for static content"); |
| add(argValidators, "--validators", |
| "Install validators"); |
| add(argGeneralQuerySvc, "--general=PATH", |
| "Add a general SPARQL endpoint (without a dataset) at /PATH"); |
| |
| add(argAuth, "--auth=[basic|digest]", |
| "Run the server using basic or digest authentication"); |
| add(argHttps, "--https=CONF", |
| "https certificate access details. JSON file { \"cert\":FILE , \"passwd\"; SECRET } "); |
| add(argHttpsPort, "--httpsPort=NUM", |
| "https port (default port is 3043)"); |
| |
| add(argPasswdFile, "--passwd=FILE", |
| "Password file"); |
| add(argJettyConfig, "--jetty=FILE", |
| "jetty.xml server configuration"); |
| add(argCORS, "--cors=FILE", "Configure CORS settings from file"); |
| add(argNoCORS, "--no-cors", "Disable CORS"); |
| // put in the configuration file |
| // add(argRealm, "--realm=REALM", "Realm name"); |
| |
| add(argWithPing, "--ping", "Enable /$/ping"); |
| add(argWithStats, "--stats", "Enable /$/stats"); |
| add(argWithMetrics, "--metrics", "Enable /$/metrics"); |
| add(argWithCompact, "--compact", "Enable /$/compact/*"); |
| |
| add(argEnableModules, "--modules=true|false", "Enable Fuseki modules"); |
| |
| super.modVersion.addClass("Fuseki", Fuseki.class); |
| } |
| |
| static String argUsage = "[--config=FILE] [--mem|--desc=AssemblerFile|--file=FILE] [--port PORT] /DatasetPathName"; |
| |
| @Override |
| protected String getSummary() { |
| return getCommandName() + " " + argUsage; |
| } |
| |
| @Override |
| protected void processModulesAndArgs() { |
| Logger log = Fuseki.serverLog; |
| |
| serverConfig.verboseLogging = super.isVerbose(); |
| |
| boolean allowEmpty = contains(argEmpty) || contains(argSparqler); |
| |
| // ---- Checking consistency |
| int numDefinitions = 0; |
| |
| if ( contains(argMem) ) |
| numDefinitions++; |
| if ( contains(argFile) ) |
| numDefinitions++; |
| if ( contains(assemblerDescDecl) ) |
| numDefinitions++; |
| if ( contains(argTDB) ) |
| numDefinitions++; |
| if ( contains(argMemTDB) ) |
| numDefinitions++; |
| if ( contains(argConfig) ) |
| numDefinitions++; |
| |
| if ( numDefinitions == 0 && ! allowEmpty ) |
| throw new CmdException("No dataset specified on the command line."); |
| |
| if ( numDefinitions > 1 ) |
| throw new CmdException("Multiple ways providing a dataset. Only one of --mem, --file, --loc or --conf"); |
| |
| if ( numDefinitions > 0 && allowEmpty ) |
| throw new CmdException("Dataset provided but 'no dataset' flag given"); |
| |
| //---- check: Invalid: --conf + service name. |
| if ( contains(argConfig) ) { |
| if ( getPositional().size() != 0 ) |
| throw new CmdException("Can't have both a configuration file and a service name"); |
| if ( contains(argRDFS) ) |
| throw new CmdException("Need to define RDFS setup in the configuration file."); |
| } else { |
| if ( ! allowEmpty && getPositional().size() == 0 ) |
| throw new CmdException("Missing service name"); |
| if ( getPositional().size() > 1 ) |
| throw new CmdException("Multiple dataset path names given"); |
| if ( getPositional().size() != 0 ) |
| serverConfig.datasetPath = DataAccessPoint.canonical(getPositionalArg(0)); |
| } |
| |
| serverConfig.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; |
| |
| boolean hasJettyConfigFile = contains(argJettyConfig); |
| |
| // ---- Port |
| serverConfig.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); |
| } |
| |
| if ( contains(argLocalhost) ) { |
| if ( hasJettyConfigFile ) |
| throw new CmdException("Cannot specify 'localhost' and also provide a Jetty configuration file"); |
| serverConfig.loopback = true; |
| } |
| |
| // ---- Dataset |
| // Only one of these is chosen from the checking above. |
| |
| // Which TDB to use to create a command line TDB database. |
| if ( contains(argTDB1mode) ) |
| useTDB2 = false; |
| if ( contains(argTDB2mode) ) |
| useTDB2 = true; |
| |
| if ( allowEmpty ) { |
| serverConfig.empty = true; |
| serverConfig.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); |
| } |
| |
| // Ways to set up a dataset. |
| if ( contains(argMem) ) { |
| serverConfig.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(); |
| // Always allow, else you can't do very much! |
| serverConfig.allowUpdate = true; |
| } |
| |
| if ( contains(argFile) ) { |
| List<String> filenames = getValues(argFile); |
| serverConfig.datasetDescription = "in-memory, with files loaded"; |
| // Update is not enabled by default for --file |
| serverConfig.allowUpdate = contains(argUpdate); |
| serverConfig.dsg = DatasetGraphFactory.createTxnMem(); |
| |
| for(String filename : filenames ) { |
| String pathname = filename; |
| if ( filename.startsWith("file:") ) |
| pathname = filename.substring("file:".length()); |
| if ( !FileOps.exists(pathname) ) |
| throw new CmdException("File not found: " + filename); |
| |
| // INITIAL DATA. |
| Lang language = RDFLanguages.filenameToLang(filename); |
| if ( language == null ) |
| throw new CmdException("Cannot guess language for file: " + filename); |
| Txn.executeWrite(serverConfig.dsg, ()-> { |
| try { |
| log.info("Dataset: in-memory: load file: " + filename); |
| RDFDataMgr.read(serverConfig.dsg, filename); |
| } catch (RiotException ex) { |
| throw new CmdException("Failed to load file: " + filename); |
| } |
| }); |
| } |
| } |
| |
| if ( contains(argMemTDB) ) { |
| DSGSetup.setupMemTDB(useTDB2, serverConfig); |
| } |
| |
| if ( contains(argTDB) ) { |
| String directory = getValue(argTDB); |
| DSGSetup.setupTDB(directory, useTDB2, serverConfig); |
| } |
| |
| if ( contains(assemblerDescDecl) ) { |
| serverConfig.datasetDescription = "Assembler: "+ getValue(assemblerDescDecl); |
| // Need to add service details. |
| Dataset ds = modDataset.createDataset(); |
| serverConfig.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); |
| } |
| |
| // ---- Misc features. |
| if ( contains(argTimeout) ) { |
| String str = getValue(argTimeout); |
| ARQ.getContext().set(ARQ.queryTimeout, str); |
| } |
| |
| if ( contains(argSparqler) ) { |
| 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; |
| } |
| |
| if ( contains(argGeneralQuerySvc) ) { |
| String z = getValue(argGeneralQuerySvc); |
| if ( ! z.startsWith("/") ) |
| z = "/"+z; |
| serverConfig.addGeneral = z; |
| } |
| |
| if ( contains(argValidators) ) { |
| serverConfig.validators = true; |
| } |
| |
| // -- Server setup. |
| |
| if ( contains(argPathBase) ) { |
| // Static files. |
| String servletContextPath = getValue(argPathBase); |
| |
| if ( ! servletContextPath.equals("/") && servletContextPath.endsWith("/") ) |
| throw new CmdException("Path base must not end with \"/\": '"+servletContextPath+"'"); |
| serverConfig.servletContextPath = servletContextPath; |
| } |
| |
| if ( contains(argBase) ) { |
| // Static files. |
| String filebase = getValue(argBase); |
| if ( ! FileOps.exists(filebase) ) { |
| throw new CmdException("File area not found: "+filebase); |
| //FmtLog.warn(Fuseki.configLog, "File area not found: "+filebase); |
| } |
| serverConfig.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); |
| } |
| |
| if ( contains(argRealm) ) |
| serverConfig.realm = getValue(argRealm); |
| |
| if ( contains(argHttpsPort) && ! contains(argHttps) ) |
| throw new CmdException("https port given but not certificate details via --"+argHttps.getKeyName()); |
| |
| if ( contains(argHttps) ) { |
| if ( hasJettyConfigFile ) |
| throw new CmdException("Can't specify \"https\" and also provide a Jetty configuration file"); |
| serverConfig.httpsPort = defaultHttpsPort; |
| if ( contains(argHttpsPort) ) |
| serverConfig.httpsPort = portNumber(argHttpsPort); |
| String httpsSetup = getValue(argHttps); |
| // The details go in a separate file that can be secured. |
| serverConfig.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); |
| } |
| |
| // Jetty server : this will be the server configuration regardless of other settings. |
| if ( contains(argJettyConfig) ) { |
| String jettyConfigFile = getValue(argJettyConfig); |
| if ( ! FileOps.exists(jettyConfigFile) ) |
| throw new CmdException("Jetty config file not found: "+jettyConfigFile); |
| serverConfig.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 ) { |
| FusekiAutoModules.setup(); |
| serverConfig.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(); |
| } |
| } |
| |
| if ( contains(argCORS) ) { |
| String corsConfigFile = getValue(argCORS); |
| if ( ! FileOps.exists(corsConfigFile) ) |
| throw new CmdException("CORS config file not found: "+corsConfigFile); |
| serverConfig.corsConfigFile = corsConfigFile; |
| } else if (contains(argNoCORS)) { |
| serverConfig.withCORS = ! contains(argNoCORS); |
| } |
| |
| serverConfig.withPing = contains(argWithPing); |
| serverConfig.withStats = contains(argWithStats); |
| serverConfig.withMetrics = contains(argWithMetrics); |
| serverConfig.withCompact = contains(argWithCompact); |
| } |
| |
| private int portNumber(ArgDecl arg) { |
| String portStr = getValue(arg); |
| if ( portStr.isEmpty() ) |
| return -1; |
| try { |
| int port = Integer.parseInt(portStr); |
| return port; |
| } catch (NumberFormatException ex) { |
| throw new CmdException(argPort.getKeyName() + " : bad port number: '" + portStr+"'"); |
| } |
| } |
| |
| @Override |
| protected void exec() { |
| try { |
| Logger log = Fuseki.serverLog; |
| FusekiMainInfo.logServerCode(log); |
| FusekiServer server = makeServer(serverConfig); |
| 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); |
| else |
| 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); |
| } |
| server.join(); |
| System.exit(0); |
| } |
| catch (AssemblerException | FusekiException ex) { |
| if ( ex.getCause() != null ) |
| System.err.println(ex.getCause().getMessage()); |
| else |
| System.err.println(ex.getMessage()); |
| throw new TerminationException(1); |
| } |
| } |
| |
| /** |
| * Take a {@link ServerConfig} and make a {@Link FusekiServer}. |
| * The server has not been started. |
| */ |
| private FusekiServer makeServer(ServerConfig serverConfig) { |
| FusekiServer.Builder builder = builder(); |
| applyServerArgs(builder, serverConfig); |
| return builder.build(); |
| } |
| |
| protected FusekiServer.Builder builder() { |
| return FusekiServer.create(); |
| } |
| |
| /** |
| * Process {@link ServerConfig} and build a server. |
| * The server has not been started. |
| */ |
| private static FusekiServer buildServer(FusekiServer.Builder builder, ServerConfig serverConfig) { |
| applyServerArgs(builder, serverConfig); |
| 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); |
| |
| if ( serverConfig.addGeneral != null ) |
| // Add SPARQL_QueryGeneral as a general servlet, not reached by the service router. |
| builder.addServlet(serverConfig.addGeneral, new SPARQL_QueryGeneral()); |
| |
| if ( serverConfig.validators ) { |
| // Validators. |
| builder.addServlet("/$/validate/query", new QueryValidator()); |
| builder.addServlet("/$/validate/update", new UpdateValidator()); |
| builder.addServlet("/$/validate/iri", new IRIValidator()); |
| 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 ( serverConfig.fusekiModules != null ) |
| builder.fusekiModules(serverConfig.fusekiModules); |
| |
| if ( serverConfig.servletContextPath != null ) |
| builder.contextPath(serverConfig.servletContextPath); |
| |
| if ( serverConfig.contentDirectory != null ) |
| builder.staticFileBase(serverConfig.contentDirectory); |
| |
| if ( serverConfig.passwdFile != null ) |
| builder.passwordFile(serverConfig.passwdFile); |
| |
| if ( serverConfig.realm != null ) |
| builder.realm(serverConfig.realm); |
| |
| if ( serverConfig.httpsKeysDetails != null) |
| builder.https(serverConfig.httpsPort, serverConfig.httpsKeysDetails); |
| |
| if ( serverConfig.authScheme != null ) |
| builder.auth(serverConfig.authScheme); |
| |
| if ( serverConfig.withCORS ) |
| builder.enableCors(true, serverConfig.corsConfigFile); |
| |
| if ( serverConfig.withPing ) |
| builder.enablePing(true); |
| |
| if ( serverConfig.withStats ) |
| builder.enableStats(true); |
| |
| if ( serverConfig.withMetrics ) |
| builder.enableMetrics(true); |
| |
| if ( serverConfig.withCompact ) |
| builder.enableCompact(true); |
| } |
| |
| /** Information from the command line setup */ |
| private void infoCmd(FusekiServer server, Logger log) { |
| if ( super.isQuiet() ) |
| return; |
| |
| if ( serverConfig.empty ) { |
| FmtLog.info(log, "No SPARQL datasets services"); |
| } else { |
| if ( serverConfig.datasetPath == null && serverConfig.serverConfig == null ) |
| log.error("No dataset path nor server configuration file"); |
| } |
| |
| DataAccessPointRegistry dapRegistry = DataAccessPointRegistry.get(server.getServletContext()); |
| if ( serverConfig.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; |
| |
| if ( ! super.isQuiet() ) |
| FusekiCoreInfo.logServerCmdSetup(log, verbose, dapRegistry, |
| datasetPath, datasetDescription, serverConfigFile, staticFiles); |
| } |
| |
| @Override |
| protected String getCommandName() { |
| return "fuseki"; |
| } |
| } |