/* * 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 java.nio.file.Files ; import java.nio.file.Path ; import arq.cmdline.CmdARQ ; import arq.cmdline.ModDatasetAssembler ; import jena.cmd.ArgDecl ; import jena.cmd.CmdException ; import org.apache.jena.atlas.lib.FileOps ; import org.apache.jena.fuseki.Fuseki ; import org.apache.jena.fuseki.FusekiLogging ; import org.apache.jena.fuseki.build.Template ; import org.apache.jena.fuseki.jetty.JettyFuseki ; import org.apache.jena.fuseki.jetty.JettyServerConfig ; import org.apache.jena.fuseki.server.FusekiEnv ; import org.apache.jena.fuseki.server.FusekiServer ; import org.apache.jena.fuseki.server.FusekiServerListener ; import org.apache.jena.fuseki.server.ServerInitialConfig ; import org.apache.jena.query.ARQ ; import org.apache.jena.query.Dataset ; import org.apache.jena.riot.Lang ; import org.apache.jena.riot.RDFDataMgr ; import org.apache.jena.riot.RDFLanguages ; import org.apache.jena.sparql.core.DatasetGraphFactory ; import org.apache.jena.system.JenaSystem ; import org.apache.jena.system.Txn ; import org.apache.jena.tdb.TDB ; import org.apache.jena.tdb.sys.Names ; import org.slf4j.Logger ; /** * Handles the fuseki command, used to start a Fuseki server. */ public class FusekiCmd { // This allows us to set logging before calling FusekiCmdInner // FusekiCmdInner inherits from CmdMain which statically sets logging. // By java classloading, super class statics run before the // statics of a class are run. static { FusekiEnv.mode = FusekiEnv.INIT.STANDALONE ; FusekiLogging.setLogging() ; } static public void main(String... argv) { FusekiCmdInner.innerMain(argv); } static class FusekiCmdInner extends CmdARQ { // --mgt. --mgtPort :: Legacy. private static ArgDecl argMgt = new ArgDecl(ArgDecl.NoValue, "mgt") ; private static ArgDecl argMgtPort = new ArgDecl(ArgDecl.HasValue, "mgtPort", "mgtport") ; // --home :: Legacy - do not use. private static ArgDecl argHome = new ArgDecl(ArgDecl.HasValue, "home") ; // --pages :: Legacy - do not use. private static ArgDecl argPages = new ArgDecl(ArgDecl.HasValue, "pages") ; private static ArgDecl argMem = new ArgDecl(ArgDecl.NoValue, "mem") ; // This does not apply to empty in-memory setups. private static ArgDecl argUpdate = new ArgDecl(ArgDecl.NoValue, "update", "allowUpdate") ; private static ArgDecl argFile = new ArgDecl(ArgDecl.HasValue, "file") ; private static ArgDecl argMemTDB = new ArgDecl(ArgDecl.NoValue, "memtdb", "memTDB", "tdbmem") ; private static ArgDecl argTDB = new ArgDecl(ArgDecl.HasValue, "loc", "location", "tdb") ; 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 argFusekiConfig = new ArgDecl(ArgDecl.HasValue, "config", "conf") ; private static ArgDecl argJettyConfig = new ArgDecl(ArgDecl.HasValue, "jetty-config") ; private static ArgDecl argGZip = new ArgDecl(ArgDecl.HasValue, "gzip") ; // Deprecated. Use shiro. private static ArgDecl argBasicAuth = new ArgDecl(ArgDecl.HasValue, "basic-auth") ; // private static ModLocation modLocation = new ModLocation() ; private static ModDatasetAssembler modDataset = new ModDatasetAssembler() ; static public void innerMain(String... argv) { JenaSystem.init() ; // Do explicitly so it happens after subsystem initialization. Fuseki.init() ; new FusekiCmdInner(argv).mainRun() ; } private JettyServerConfig jettyServerConfig = new JettyServerConfig() ; { jettyServerConfig.port = 3030 ; jettyServerConfig.contextPath = "/" ; jettyServerConfig.jettyConfigFile = null ; jettyServerConfig.enableCompression = true ; jettyServerConfig.verboseLogging = false ; } private final ServerInitialConfig cmdLineConfig = new ServerInitialConfig() ; public FusekiCmdInner(String... argv) { super(argv) ; getUsage().startCategory("Fuseki") ; addModule(modDataset) ; 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(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(argPort, "--port", "Listen on this port number") ; // Set via jetty config file. 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(argFusekiConfig, "--config=", "Use a configuration file to determine the services") ; add(argJettyConfig, "--jetty-config=FILE", "Set up the server (not services) with a Jetty XML file") ; add(argBasicAuth) ; add(argPages) ; add(argMgt) ; // Legacy add(argMgtPort) ; // Legacy add(argGZip, "--gzip=on|off", "Enable GZip compression (HTTP Accept-Encoding) if request header set") ; super.modVersion.addClass(TDB.class) ; super.modVersion.addClass(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() { if ( super.isVerbose() || super.isDebug() ) { jettyServerConfig.verboseLogging = true ; // Output is still at level INFO (currently) } // Any final tinkering with FUSEKI_HOME and FUSEKI_BASE, e.g. arguments like --home, --base, then .... FusekiEnv.resetEnvironment() ; Logger log = Fuseki.serverLog ; if ( contains(argFusekiConfig) ) cmdLineConfig.fusekiCmdLineConfigFile = getValue(argFusekiConfig) ; ArgDecl assemblerDescDecl = new ArgDecl(ArgDecl.HasValue, "desc", "dataset") ; // ---- Datasets // Check one and only way is defined. int x = 0 ; if ( contains(argMem) ) x++ ; if ( contains(argFile) ) x++ ; if ( contains(assemblerDescDecl) ) x++ ; if ( contains(argTDB) ) x++ ; if ( contains(argMemTDB) ) x++ ; if ( cmdLineConfig.fusekiCmdLineConfigFile != null ) { if ( x >= 1 ) throw new CmdException("Dataset specified on the command line but a configuration file also given.") ; } else { // No configuration file. 0 or 1 legal. if ( x > 1 ) throw new CmdException("Multiple ways providing a dataset. Only one of --mem, --file, --loc or --desc") ; } boolean cmdlineConfigPresent = ( x != 0 ) ; if ( cmdlineConfigPresent && getPositional().size() == 0 ) throw new CmdException("Missing service name") ; if ( cmdLineConfig.fusekiCmdLineConfigFile != null && getPositional().size() > 0 ) throw new CmdException("Service name will come from --conf; no command line service name allowed") ; if ( !cmdlineConfigPresent && getPositional().size() > 0 ) throw new CmdException("Service name given but no configuration argument to match (e.g. --mem, --loc/--tdb, --file)") ; if ( cmdlineConfigPresent && getPositional().size() > 1 ) throw new CmdException("Multiple dataset path names given") ; if ( ! cmdlineConfigPresent && cmdLineConfig.fusekiCmdLineConfigFile == null ) { // Turn command line argument into an absolute file name. FusekiEnv.setEnvironment(); Path cfg = FusekiEnv.FUSEKI_BASE.resolve(FusekiServer.DFT_CONFIG).toAbsolutePath() ; if ( Files.exists(cfg) ) cmdLineConfig.fusekiServerConfigFile = cfg.toString() ; } cmdLineConfig.allowUpdate = contains(argUpdate) ; if ( contains(argMem) ) { log.info("Dataset: 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. cmdLineConfig.reset(); cmdLineConfig.argTemplateFile = Template.templateMemFN ; // Always allow. cmdLineConfig.allowUpdate = true ; } if ( contains(argFile) ) { String filename = getValue(argFile) ; log.info("Dataset: in-memory: load file: " + filename) ; if ( !FileOps.exists(filename) ) throw new CmdException("File not found: " + filename) ; // Directly populate the dataset. cmdLineConfig.reset(); cmdLineConfig.dsg = DatasetGraphFactory.createTxnMem() ; Lang language = RDFLanguages.filenameToLang(filename) ; if ( language == null ) throw new CmdException("Can't guess language for file: " + filename) ; Txn.executeWrite(cmdLineConfig.dsg, ()->RDFDataMgr.read(cmdLineConfig.dsg, filename)) ; } if ( contains(argMemTDB) ) { //log.info("TDB dataset: in-memory") ; cmdLineConfig.reset(); cmdLineConfig.argTemplateFile = Template.templateTDBMemFN ; cmdLineConfig.params.put(Template.DIR, Names.memName) ; // Always allow. cmdLineConfig.allowUpdate = true ; } if ( contains(argTDB) ) { cmdLineConfig.reset(); cmdLineConfig.argTemplateFile = Template.templateTDBDirFN ; String dir = getValue(argTDB) ; cmdLineConfig.params.put(Template.DIR, dir) ; } // Otherwise if ( contains(assemblerDescDecl) ) { log.info("Dataset from assembler") ; // Need to add service details. Dataset ds = modDataset.createDataset() ; //cmdLineDataset.dsg = ds.asDatasetGraph() ; } if ( cmdlineConfigPresent ) { cmdLineConfig.datasetPath = getPositionalArg(0) ; if ( cmdLineConfig.datasetPath.length() > 0 && !cmdLineConfig.datasetPath.startsWith("/") ) throw new CmdException("Dataset path name must begin with a /: " + cmdLineConfig.datasetPath) ; if ( ! cmdLineConfig.allowUpdate ) Fuseki.serverLog.info("Running in read-only mode for "+cmdLineConfig.datasetPath) ; // Include the dataset name as NAME for any templates. cmdLineConfig.params.put(Template.NAME, cmdLineConfig.datasetPath) ; } // ---- Jetty server if ( contains(argBasicAuth) ) Fuseki.configLog.warn("--basic-auth ignored: Use Apache Shiro security - see shiro.ini") ; if ( contains(argPort) ) { String portStr = getValue(argPort) ; try { jettyServerConfig.port = Integer.parseInt(portStr) ; } catch (NumberFormatException ex) { throw new CmdException(argPort.getKeyName() + " : bad port number: " + portStr) ; } } if ( contains(argMgt) ) Fuseki.configLog.warn("Fuseki v2: Management functions are always enabled. --mgt not needed.") ; if ( contains(argMgtPort) ) Fuseki.configLog.warn("Fuseki v2: Management functions are always on the same port as the server. --mgtPort ignored.") ; if ( contains(argLocalhost) ) jettyServerConfig.loopback = true ; if ( contains(argTimeout) ) { String str = getValue(argTimeout) ; ARQ.getContext().set(ARQ.queryTimeout, str) ; } if ( contains(argJettyConfig) ) { jettyServerConfig.jettyConfigFile = getValue(argJettyConfig) ; if ( !FileOps.exists(jettyServerConfig.jettyConfigFile) ) throw new CmdException("No such file: " + jettyServerConfig.jettyConfigFile) ; } if ( contains(argBasicAuth) ) Fuseki.configLog.warn("--basic-auth ignored (use Shiro setup instead)") ; if ( contains(argHome) ) Fuseki.configLog.warn("--home ignored (use enviroment variables $FUSEKI_HOME and $FUSEKI_BASE)") ; if ( contains(argPages) ) Fuseki.configLog.warn("--pages ignored (enviroment variables $FUSEKI_HOME to provide the webapp)") ; if ( contains(argGZip) ) { if ( !hasValueOfTrue(argGZip) && !hasValueOfFalse(argGZip) ) throw new CmdException(argGZip.getNames().get(0) + ": Not understood: " + getValue(argGZip)) ; jettyServerConfig.enableCompression = super.hasValueOfTrue(argGZip) ; } } private static String sort_out_dir(String path) { path.replace('\\', '/') ; if ( !path.endsWith("/") ) path = path + "/" ; return path ; } @Override protected void exec() { runFuseki(cmdLineConfig, jettyServerConfig) ; } @Override protected String getCommandName() { return "fuseki" ; } } /** Configure and run a Fuseki server - this function does not return */ public static void runFuseki(ServerInitialConfig serverConfig, JettyServerConfig jettyConfig) { FusekiServerListener.initialSetup = serverConfig ; JettyFuseki.initializeServer(jettyConfig) ; JettyFuseki.instance.start() ; JettyFuseki.instance.join() ; System.exit(0) ; } }