You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jena.apache.org by an...@apache.org on 2018/09/21 10:16:07 UTC
[56/70] [abbrv] jena git commit: JENA-1597: Modules jena-fuseki-main
and jena-fuseki-server
http://git-wip-us.apache.org/repos/asf/jena/blob/7e6d03af/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/JettyServer.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/JettyServer.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/JettyServer.java
new file mode 100644
index 0000000..61a2e11
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/JettyServer.java
@@ -0,0 +1,369 @@
+/*
+ * 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 java.lang.String.format;
+import static java.util.Objects.requireNonNull;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.Filter;
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.jena.atlas.lib.Pair;
+import org.apache.jena.fuseki.Fuseki;
+import org.apache.jena.fuseki.server.DataAccessPointRegistry;
+import org.apache.jena.fuseki.servlets.ActionBase;
+import org.apache.jena.fuseki.servlets.ServiceDispatchRegistry;
+import org.apache.jena.riot.web.HttpNames;
+import org.apache.jena.web.HttpSC;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.security.SecurityHandler;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.handler.ErrorHandler;
+import org.eclipse.jetty.servlet.DefaultServlet;
+import org.eclipse.jetty.servlet.FilterHolder;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Jetty server for servlets, including being able to run Fuseki {@link ActionBase} derived servlets.
+ * Static RDF types by file extension can be enabled.
+ */
+public class JettyServer {
+ // Use this for the super class of FusekiServer or as implementation inheritance.
+ // Caution : there are small differences e.g. in building where order matters.
+
+ private static Logger LOG = LoggerFactory.getLogger("HTTP");
+
+ protected final Server server;
+ protected int port;
+
+ public static Builder create() {
+ return new Builder();
+ }
+
+ protected JettyServer(int port, Server server) {
+ this.server = server;
+ this.port = port;
+ }
+
+ /**
+ * Return the port begin used.
+ * This will be the give port, which defaults to 3330, or
+ * the one actually allocated if the port was 0 ("choose a free port").
+ */
+ public int getPort() {
+ return port;
+ }
+
+ /** Get the underlying Jetty server which has also been set up. */
+ public Server getJettyServer() {
+ return server;
+ }
+
+ /** Get the {@link ServletContext}.
+ * Adding new servlets is possible with care.
+ */
+ public ServletContext getServletContext() {
+ return ((ServletContextHandler)server.getHandler()).getServletContext();
+ }
+
+ /** Start the server - the server continues to run after this call returns.
+ * To synchronise with the server stopping, call {@link #join}.
+ */
+ public JettyServer start() {
+ try { server.start(); }
+ catch (Exception e) { throw new RuntimeException(e); }
+ if ( port == 0 )
+ port = ((ServerConnector)server.getConnectors()[0]).getLocalPort();
+ logStart();
+ return this;
+ }
+
+ protected void logStart() {
+ LOG.info("Start (port="+port+")");
+ }
+
+ /** Stop the server. */
+ public void stop() {
+ logStop();
+ try { server.stop(); }
+ catch (Exception e) { throw new RuntimeException(e); }
+ }
+
+ protected void logStop() {
+ LOG.info("Stop (port="+port+")");
+ }
+
+ /** Wait for the server to exit. This call is blocking. */
+ public void join() {
+ try { server.join(); }
+ catch (Exception e) { throw new RuntimeException(e); }
+ }
+
+ /** One line error handler */
+ public static class PlainErrorHandler extends ErrorHandler {
+ // c.f. FusekiErrorHandler1
+ public PlainErrorHandler() {}
+
+ @Override
+ public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
+ {
+ String method = request.getMethod();
+
+ if ( !method.equals(HttpMethod.GET.asString())
+ && !method.equals(HttpMethod.POST.asString())
+ && !method.equals(HttpMethod.HEAD.asString()) )
+ return ;
+
+ response.setContentType(MimeTypes.Type.TEXT_PLAIN_UTF_8.asString()) ;
+ response.setHeader(HttpNames.hCacheControl, "must-revalidate,no-cache,no-store");
+ response.setHeader(HttpNames.hPragma, "no-cache");
+ int code = response.getStatus() ;
+ String message=(response instanceof Response)?((Response)response).getReason(): HttpSC.getMessage(code) ;
+ response.getOutputStream().print(format("Error %d: %s\n", code, message)) ;
+ }
+ }
+
+ protected static class Builder {
+ private int port = -1;
+ private boolean loopback = false;
+ protected boolean verbose = false;
+ // Other servlets to add.
+ private List<Pair<String, HttpServlet>> servlets = new ArrayList<>();
+ private List<Pair<String, Filter>> filters = new ArrayList<>();
+
+ private String contextPath = "/";
+ private String servletContextName = "Jetty";
+ private String staticContentDir = null;
+ private SecurityHandler securityHandler = null;
+ private ErrorHandler errorHandler = new PlainErrorHandler();
+ private Map<String, Object> servletAttr = new HashMap<>();
+
+ /** Set the port to run on. */
+ public Builder port(int port) {
+ if ( port < 0 )
+ throw new IllegalArgumentException("Illegal port="+port+" : Port must be greater than or equal to zero.");
+ this.port = port;
+ return this;
+ }
+
+ /**
+ * Context path. If it's "/" then Server URL will look like
+ * "http://host:port/" else "http://host:port/path/"
+ * (or no port if :80).
+ */
+ public Builder contextPath(String path) {
+ requireNonNull(path, "path");
+ this.contextPath = path;
+ return this;
+ }
+
+ /**
+ * ServletContextName.
+ */
+ public Builder servletContextName(String name) {
+ requireNonNull(name, "name");
+ this.servletContextName = name;
+ return this;
+ }
+
+ /** Restrict the server to only responding to the localhost interface. */
+ public Builder loopback(boolean loopback) {
+ this.loopback = loopback;
+ return this;
+ }
+
+ /** Set the location (filing system directory) to serve static file from. */
+ public Builder staticFileBase(String directory) {
+ requireNonNull(directory, "directory");
+ this.staticContentDir = directory;
+ return this;
+ }
+
+ /** Set a Jetty SecurityHandler.
+ * <p>
+ * By default, the server runs with no security.
+ * This is more for using the basic server for testing.
+ * The full Fuseki server provides security with Apache Shiro
+ * and a defensive reverse proxy (e.g. Apache httpd) in front of the Jetty server
+ * can also be used, which provides a wide varity of proven security options.
+ */
+ public Builder securityHandler(SecurityHandler securityHandler) {
+ requireNonNull(securityHandler, "securityHandler");
+ this.securityHandler = securityHandler;
+ return this;
+ }
+
+ /** Set an {@link ErrorHandler}.
+ * <p>
+ * By default, the server runs with error handle that prints the code and message.
+ */
+ public Builder errorHandler(ErrorHandler errorHandler) {
+ requireNonNull(errorHandler, "securityHandler");
+ this.errorHandler = errorHandler;
+ return this;
+ }
+
+ /** Set verbose logging */
+ public Builder verbose(boolean verbose) {
+ this.verbose = verbose;
+ return this;
+ }
+
+ /**
+ * Add the given servlet with the pathSpec. These are added so that they are
+ * before the static content handler (which is the last servlet)
+ * used for {@link #staticFileBase(String)}.
+ */
+ public Builder addServlet(String pathSpec, HttpServlet servlet) {
+ requireNonNull(pathSpec, "pathSpec");
+ requireNonNull(servlet, "servlet");
+ servlets.add(Pair.create(pathSpec, servlet));
+ return this;
+ }
+
+ /**
+ * Add a servlet attribute. Pass a value of null to remove any existing binding.
+ */
+ public Builder addServletAttribute(String attrName, Object value) {
+ requireNonNull(attrName, "attrName");
+ if ( value != null )
+ servletAttr.put(attrName, value);
+ else
+ servletAttr.remove(attrName);
+ return this;
+ }
+
+ /**
+ * Add the given filter with the pathSpec.
+ * It is applied to all dispatch types.
+ */
+ public Builder addFilter(String pathSpec, Filter filter) {
+ requireNonNull(pathSpec, "pathSpec");
+ requireNonNull(filter, "filter");
+ filters.add(Pair.create(pathSpec, filter));
+ return this;
+ }
+
+ /**
+ * Build a server according to the current description.
+ */
+ public JettyServer build() {
+ ServletContextHandler handler = buildServletContext();
+ // Use HandlerCollection for several ServletContextHandlers and thus several ServletContext.
+ Server server = jettyServer(port, loopback);
+ server.setHandler(handler);
+ return new JettyServer(port, server);
+ }
+
+ /** Build a ServletContextHandler : one servlet context */
+ private ServletContextHandler buildServletContext() {
+ ServletContextHandler handler = buildServletContext(contextPath);
+ ServletContext cxt = handler.getServletContext();
+ adjustForFuseki(cxt);
+ servletAttr.forEach((n,v)->cxt.setAttribute(n, v));
+ servletsAndFilters(handler);
+ return handler;
+ }
+
+ private void adjustForFuseki(ServletContext cxt) {
+ // For Fuseki servlets added directly.
+ // This enables servlets inheriting from {@link ActionBase} to work in the
+ // plain Jetty server, e.g. to use Fuseki logging.
+ try {
+ Fuseki.setVerbose(cxt, verbose);
+ ServiceDispatchRegistry.set(cxt, new ServiceDispatchRegistry(false));
+ DataAccessPointRegistry.set(cxt, new DataAccessPointRegistry());
+ } catch (NoClassDefFoundError err) {
+ LOG.info("Fuseki classes not found");
+ }
+ }
+
+ /** Build a ServletContextHandler. */
+ private ServletContextHandler buildServletContext(String contextPath) {
+ if ( contextPath == null || contextPath.isEmpty() )
+ contextPath = "/";
+ else if ( !contextPath.startsWith("/") )
+ contextPath = "/" + contextPath;
+ ServletContextHandler context = new ServletContextHandler();
+ context.setDisplayName(servletContextName);
+ context.setErrorHandler(errorHandler);
+ context.setContextPath(contextPath);
+ if ( securityHandler != null )
+ context.setSecurityHandler(securityHandler);
+
+ return context;
+ }
+
+ /** Add servlets and servlet filters */
+ private void servletsAndFilters(ServletContextHandler context) {
+ servlets.forEach(p-> addServlet(context, p.getLeft(), p.getRight()) );
+ filters.forEach (p-> addFilter (context, p.getLeft(), p.getRight()) );
+
+ if ( staticContentDir != null ) {
+ DefaultServlet staticServlet = new DefaultServlet();
+ ServletHolder staticContent = new ServletHolder(staticServlet);
+ staticContent.setInitParameter("resourceBase", staticContentDir);
+ context.addServlet(staticContent, "/");
+ }
+ }
+
+ protected static void addServlet(ServletContextHandler context, String pathspec, HttpServlet httpServlet) {
+ ServletHolder sh = new ServletHolder(httpServlet);
+ context.addServlet(sh, pathspec);
+ }
+
+ protected void addFilter(ServletContextHandler context, String pathspec, Filter filter) {
+ FilterHolder h = new FilterHolder(filter);
+ context.addFilter(h, pathspec, null);
+ }
+
+ /** Jetty server */
+ private static Server jettyServer(int port, boolean loopback) {
+ Server server = new Server();
+ HttpConnectionFactory f1 = new HttpConnectionFactory();
+
+ //f1.getHttpConfiguration().setRequestHeaderSize(512 * 1024);
+ //f1.getHttpConfiguration().setOutputBufferSize(1024 * 1024);
+ f1.getHttpConfiguration().setSendServerVersion(false);
+ ServerConnector connector = new ServerConnector(server, f1);
+ connector.setPort(port);
+ server.addConnector(connector);
+ if ( loopback )
+ connector.setHost("localhost");
+ return server;
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/jena/blob/7e6d03af/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/FusekiMain.java
----------------------------------------------------------------------
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
new file mode 100644
index 0000000..702b4f6
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/FusekiMain.java
@@ -0,0 +1,498 @@
+/*
+ * 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 java.net.BindException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import arq.cmdline.CmdARQ;
+import arq.cmdline.ModAssembler;
+import arq.cmdline.ModDatasetAssembler;
+import jena.cmd.ArgDecl;
+import jena.cmd.CmdException;
+import org.apache.jena.assembler.exceptions.AssemblerException;
+import org.apache.jena.atlas.lib.DateTimeUtils;
+import org.apache.jena.atlas.lib.FileOps;
+import org.apache.jena.atlas.logging.FmtLog;
+import org.apache.jena.fuseki.Fuseki;
+import org.apache.jena.fuseki.FusekiException;
+import org.apache.jena.fuseki.main.FusekiServer;
+import org.apache.jena.fuseki.server.DataAccessPoint;
+import org.apache.jena.fuseki.server.DataAccessPointRegistry;
+import org.apache.jena.fuseki.server.DataService;
+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.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.sys.JenaSystem;
+import org.apache.jena.system.Txn;
+import org.apache.jena.tdb.TDB;
+import org.apache.jena.tdb.TDBFactory;
+import org.apache.jena.tdb.transaction.TransactionManager;
+import org.apache.jena.tdb2.DatabaseMgr;
+import org.slf4j.Logger;
+
+public class FusekiMain extends CmdARQ {
+ private static int defaultPort = 3030;
+
+ 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 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");
+
+ // No SPARQL dataset or services
+ private static ArgDecl argEmpty = new ArgDecl(ArgDecl.NoValue, "empty", "no-dataset");
+ 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 argGZip = new ArgDecl(ArgDecl.HasValue, "gzip");
+ private static ArgDecl argBase = new ArgDecl(ArgDecl.HasValue, "base", "files");
+ private static ArgDecl argGeneralQuerySvc = new ArgDecl(ArgDecl.HasValue, "general");
+
+ // 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 ModLocation modLocation = new ModLocation();
+ private static ModDatasetAssembler modDataset = new ModDatasetAssembler();
+
+ private final ServerConfig serverConfig = new ServerConfig();
+ private boolean useTDB2;
+
+ /** Build, but do not start, a server based on command line syntax. */
+ public static FusekiServer build(String... argv) {
+ FusekiMain inner = new FusekiMain(argv);
+ inner.process();
+ return inner.buildServer();
+ }
+
+ static void innerMain(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");
+ 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(argTDB2mode, "--tdb2",
+ "Create command line persistent datasets with 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 (validators only)");
+ 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(argConfig, "--config=",
+ "Use a configuration file to determine the services");
+ add(argGZip, "--gzip=on|off",
+ "Enable GZip compression (HTTP Accept-Encoding) if request header set");
+ add(argBase, "--base=DIR",
+ "Directory for static content");
+ add(argSparqler, "--sparqler=DIR",
+ "Run with SPARQLer services Directory for static content");
+ add(argValidators, "--validators", "Install validators");
+
+ 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() {
+ boolean allowEmpty = contains(argEmpty) || contains(argSparqler);
+
+ // ---- Checking consistency
+ int numDefinitions = 0;
+
+ if ( contains(argMem) )
+ numDefinitions++;
+ if ( contains(argFile) )
+ numDefinitions++;
+ if ( contains(ModAssembler.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 --desc");
+
+ 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 configutation file and a service name");
+ } else if ( ! allowEmpty ) {
+ if ( getPositional().size() == 0 )
+ throw new CmdException("Missing service name");
+ if ( getPositional().size() > 1 )
+ throw new CmdException("Multiple dataset path names given");
+ 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;
+
+ // ---- Port
+ serverConfig.port = defaultPort;
+
+ if ( contains(argPort) ) {
+ String portStr = getValue(argPort);
+ try {
+ int port = Integer.parseInt(portStr);
+ serverConfig.port = port;
+ } catch (NumberFormatException ex) {
+ throw new CmdException(argPort.getKeyName() + " : bad port number: " + portStr);
+ }
+ }
+ if ( contains(argLocalhost) )
+ serverConfig.loopback = true;
+
+ // ---- Dataset
+ // Only one of these is choose from the checking above.
+
+ // Which TDB to use to create a command line TDB database.
+ useTDB2 = contains(argTDB2mode);
+ String tag = useTDB2 ? "TDB2" : "TDB";
+
+ 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 = Paths.get(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 setup 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) ) {
+ String filename = getValue(argFile);
+ String pathname = filename;
+ if ( filename.startsWith("file:") )
+ pathname = filename.substring("file:".length());
+
+ serverConfig.datasetDescription = "file:"+filename;
+ if ( !FileOps.exists(pathname) )
+ throw new CmdException("File not found: " + filename);
+ serverConfig.dsg = DatasetGraphFactory.createTxnMem();
+
+ // INITIAL DATA.
+ Lang language = RDFLanguages.filenameToLang(filename);
+ if ( language == null )
+ throw new CmdException("Can't guess language for file: " + filename);
+ Txn.executeWrite(serverConfig.dsg, ()->RDFDataMgr.read(serverConfig.dsg, filename));
+ }
+
+ if ( contains(argMemTDB) ) {
+ serverConfig.datasetDescription = tag+" dataset in-memory";
+ serverConfig.dsg =
+ useTDB2
+ ? DatabaseMgr.createDatasetGraph()
+ : TDBFactory.createDatasetGraph();
+ serverConfig.allowUpdate = true;
+ }
+
+ if ( contains(argTDB) ) {
+ String dir = getValue(argTDB);
+ serverConfig.datasetDescription = tag+" dataset: "+dir;
+ serverConfig.dsg =
+ useTDB2
+ ? DatabaseMgr.connectDatasetGraph(dir)
+ : TDBFactory.createDatasetGraph(dir);
+ }
+
+ if ( contains(ModAssembler.assemblerDescDecl) ) {
+ serverConfig.datasetDescription = "Assembler: "+ getValue(ModAssembler.assemblerDescDecl);
+ // Need to add service details.
+ Dataset ds = modDataset.createDataset();
+ serverConfig.dsg = ds.asDatasetGraph();
+ }
+
+ // ---- 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;
+ }
+
+ 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(argGZip) ) {
+// if ( !hasValueOfTrue(argGZip) && !hasValueOfFalse(argGZip) )
+// throw new CmdException(argGZip.getNames().get(0) + ": Not understood: " + getValue(argGZip));
+// jettyServerConfig.enableCompression = super.hasValueOfTrue(argGZip);
+// }
+ }
+
+ @Override
+ protected void exec() {
+ try {
+ FusekiServer server = buildServer(serverConfig);
+ info(server, serverConfig);
+ try {
+ server.start();
+ } catch (FusekiException ex) {
+ if ( ex.getCause() instanceof BindException ) {
+ Fuseki.serverLog.error("Failed to start server: "+ex.getCause().getMessage()+ ": port="+serverConfig.port) ;
+ 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 ex) {
+ if ( ex.getCause() != null )
+ System.err.println(ex.getCause().getMessage());
+ else
+ System.err.println(ex.getMessage());
+ }
+ }
+
+ private FusekiServer buildServer() {
+ return buildServer(serverConfig);
+ }
+
+ // ServerConfig -> Setup the builder.
+ private FusekiServer buildServer(ServerConfig serverConfig) {
+ FusekiServer.Builder builder = builder();
+ return buildServer(builder, serverConfig);
+ }
+
+ protected FusekiServer.Builder builder() {
+ return FusekiServer.create();
+ }
+
+ private static FusekiServer buildServer(FusekiServer.Builder builder, ServerConfig serverConfig) {
+ builder.port(serverConfig.port);
+ builder.loopback(serverConfig.loopback);
+
+ if ( serverConfig.addGeneral != null )
+ 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.contentDirectory != null )
+ builder.staticFileBase(serverConfig.contentDirectory) ;
+
+ return builder.build();
+ }
+
+ private void info(FusekiServer server, ServerConfig serverConfig) {
+ if ( super.isQuiet() )
+ return;
+
+ Logger log = Fuseki.serverLog;
+
+ String version = Fuseki.VERSION;
+ String buildDate = Fuseki.BUILD_DATE ;
+
+ if ( version != null && version.equals("${project.version}") )
+ version = null ;
+ if ( buildDate != null && buildDate.equals("${build.time.xsd}") )
+ buildDate = DateTimeUtils.nowAsXSDDateTimeString() ;
+
+ String name = Fuseki.NAME;
+ name = name +" (basic server)";
+
+ if ( version != null ) {
+ if ( Fuseki.developmentMode && buildDate != null )
+ FmtLog.info(log, "%s %s %s", name, version, buildDate) ;
+ else
+ FmtLog.info(log, "%s %s", name, version);
+ }
+
+ // Dataset -> Endpoints
+ Map<String, List<String>> mapDatasetEndpoints = description(DataAccessPointRegistry.get(server.getServletContext()));
+
+ 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");
+ }
+
+ if ( serverConfig.datasetPath != null ) {
+ if ( mapDatasetEndpoints.size() != 1 )
+ log.error("Expected only one dataset");
+ List<String> endpoints = mapDatasetEndpoints.get(serverConfig.datasetPath);
+ FmtLog.info(log, "Dataset Type = %s", serverConfig.datasetDescription);
+ FmtLog.info(log, "Path = %s; Services = %s", serverConfig.datasetPath, endpoints);
+ }
+ if ( serverConfig.serverConfig != null ) {
+ // May be many datasets and services.
+ FmtLog.info(log, "Configuration file %s", serverConfig.serverConfig);
+ mapDatasetEndpoints.forEach((path, endpoints)->{
+ FmtLog.info(log, "Path = %s; Services = %s", path, endpoints);
+ });
+ }
+
+ if ( serverConfig.contentDirectory != null )
+ FmtLog.info(log, "Static files = %s", serverConfig.contentDirectory);
+
+ if ( super.isVerbose() )
+ PlatformInfo.logDetailsVerbose(log);
+ else if ( !super.isQuiet() )
+ PlatformInfo.logDetails(log);
+ }
+
+ private static Map<String, List<String>> description(DataAccessPointRegistry reg) {
+ Map<String, List<String>> desc = new LinkedHashMap<>();
+ reg.forEach((ds,dap)->{
+ List<String> endpoints = new ArrayList<>();
+ desc.put(ds, endpoints);
+ DataService dSrv = dap.getDataService();
+ dSrv.getOperations().forEach((op)->{
+ dSrv.getEndpoints(op).forEach(ep-> {
+ String x = ep.getEndpoint();
+ if ( x.isEmpty() )
+ x = "quads";
+ endpoints.add(x);
+ });
+ });
+ });
+ return desc;
+ }
+
+ @Override
+ protected String getCommandName() {
+ return "fuseki";
+ }
+ }
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/jena/blob/7e6d03af/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/FusekiMainCmd.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/FusekiMainCmd.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/FusekiMainCmd.java
new file mode 100644
index 0000000..a7ae54b
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/FusekiMainCmd.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.main.cmds;
+
+import org.apache.jena.fuseki.system.FusekiLogging;
+
+/** Fuseki command that runs a Fuseki server without the admin UI, just SPARQL services.
+ * <p>
+ * Use {@code --conf=} for multiple datasets and specific service names.
+ * <p>
+ * The command line dataset setup only supports a single dataset.
+ */
+
+public class FusekiMainCmd {
+ // This class wraps FusekiBasicMain so that it can take control of logging setup.
+ // It does not depend via inheritance on any Jena code - FusekiBasicMain does.
+ // 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... argv) {
+ FusekiMain.innerMain(argv);
+ }
+}
http://git-wip-us.apache.org/repos/asf/jena/blob/7e6d03af/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/PlatformInfo.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/PlatformInfo.java b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/PlatformInfo.java
new file mode 100644
index 0000000..143a43d
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/PlatformInfo.java
@@ -0,0 +1,135 @@
+/*
+ * 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 java.io.IOException;
+import java.util.function.Function;
+
+import org.apache.jena.atlas.logging.FmtLog;
+import org.slf4j.Logger;
+
+public class PlatformInfo {
+
+ public static void main(String ...args) throws IOException {
+ long maxMem = Runtime.getRuntime().maxMemory();
+ long totalMem = Runtime.getRuntime().totalMemory();
+ long freeMem = Runtime.getRuntime().freeMemory();
+ long usedMem = totalMem - freeMem;
+ Function<Long, String> f = PlatformInfo::strNumMixed;
+
+ System.out.printf("max=%s total=%s used=%s free=%s\n", f.apply(maxMem), f.apply(totalMem), f.apply(usedMem), f.apply(freeMem));
+ }
+
+ /** Essential information about the runtime environment. */
+
+ public static void logDetails(Logger log) {
+ logDetails(log, " ");
+ }
+
+ /**
+ * Essential information about the runtime environment
+ * @param log
+ * @param prefix String to add at the start of the log message.
+ */
+ public static void logDetails(Logger log, String prefix) {
+ if ( prefix == null )
+ prefix = "";
+ long maxMem = Runtime.getRuntime().maxMemory();
+ long totalMem = Runtime.getRuntime().totalMemory();
+ long freeMem = Runtime.getRuntime().freeMemory();
+ long usedMem = totalMem - freeMem;
+ Function<Long, String> f = PlatformInfo::strNum2;
+
+ long pid = getProcessId();
+ FmtLog.info(log, "%sMemory: %s", prefix, f.apply(maxMem));
+ //FmtLog.info(log, "%sMemory: max=%s total=%s used=%s free=%s", prefix, f.apply(maxMem), f.apply(totalMem), f.apply(usedMem), f.apply(freeMem));
+ FmtLog.info(log, "%sJava: %s", prefix, System.getProperty("java.version"));
+ FmtLog.info(log, "%sOS: %s %s %s", prefix, System.getProperty("os.name"), System.getProperty("os.version"), System.getProperty("os.arch"));
+ if ( pid != -1)
+ FmtLog.info(log, "%sPID: %s", prefix, pid);
+ }
+
+ public static void logDetailsVerbose(Logger log) {
+ logDetails(log);
+ logOne(log, "java.vendor");
+ logOne(log, "java.home");
+ logOne(log, "java.runtime.version");
+ logOne(log, "java.runtime.name");
+ //logOne(log, "java.endorsed.dirs");
+ logOne(log, "user.language");
+ logOne(log, "user.timezone");
+ logOne(log, "user.country");
+ logOne(log, "user.dir");
+ //logOne(log, "file.encoding");
+ }
+
+ private static void logOne(Logger log, String property) {
+ FmtLog.info(log, " %-20s = %s", property, System.getProperty(property));
+ }
+
+ /** Create a human-friendly string for a number based on Kilo/Mega/Giga/Tera (powers of 2) */
+ public static String strNumMixed(long x) {
+ // https://en.wikipedia.org/wiki/Kibibyte
+ if ( x < 1024 )
+ return Long.toString(x);
+ if ( x < 1024*1024 )
+ return String.format("%.1fK", x/1024.0);
+ if ( x < 1024*1024*1024 )
+ return String.format("%.1fM", x/(1024.0*1024));
+ if ( x < 1024L*1024*1024*1024 )
+ return String.format("%.1fG", x/(1024.0*1024*1024));
+ return String.format("%.1fT", x/(1024.0*1024*1024*1024));
+ }
+
+ private static long getProcessId() {
+ // Java9
+ //long pid = ProcessHandle.current().getPid();
+ try {
+ String x = java.lang.management.ManagementFactory.getRuntimeMXBean().getName().split("@")[0];
+ return Long.parseLong(x);
+ } catch (NumberFormatException ex) { return -1 ; }
+ }
+
+ /** Create a human-friendly string for a number based on Kilo/Mega/Giga/Tera (powers of 10) */
+ public static String strNum10(long x) {
+ if ( x < 1_000 )
+ return Long.toString(x);
+ if ( x < 1_000_000 )
+ return String.format("%.1fK", x/1000.0);
+ if ( x < 1_000_000_000 )
+ return String.format("%.1fM", x/(1000.0*1000));
+ if ( x < 1_000_000_000_000L )
+ return String.format("%.1fG", x/(1000.0*1000*1000));
+ return String.format("%.1fT", x/(1000.0*1000*1000*1000));
+ }
+
+ /** Create a human-friendly string for a number based on Kibi/Mebi/Gibi/Tebi (powers of 2) */
+ public static String strNum2(long x) {
+ // https://en.wikipedia.org/wiki/Kibibyte
+ if ( x < 1024 )
+ return Long.toString(x);
+ if ( x < 1024*1024 )
+ return String.format("%.1f KiB", x/1024.0);
+ if ( x < 1024*1024*1024 )
+ return String.format("%.1f MiB", x/(1024.0*1024));
+ if ( x < 1024L*1024*1024*1024 )
+ return String.format("%.1f GiB", x/(1024.0*1024*1024));
+ return String.format("%.1fTiB", x/(1024.0*1024*1024*1024));
+ }
+}
http://git-wip-us.apache.org/repos/asf/jena/blob/7e6d03af/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/ServerConfig.java
----------------------------------------------------------------------
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/ServerConfig.java
new file mode 100644
index 0000000..2376a50
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-main/src/main/java/org/apache/jena/fuseki/main/cmds/ServerConfig.java
@@ -0,0 +1,51 @@
+/*
+ * 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.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}.
+ */
+class ServerConfig {
+ /** Server port */
+ public int port;
+ /** Loopback */
+ public boolean loopback = false;
+ /** The dataset name */
+ public String datasetPath = null;
+ /** Allow update */
+ public boolean allowUpdate = false;
+
+ // This is set ...
+ public DatasetGraph dsg = null;
+ // ... or this.
+ public String serverConfig = null;
+
+ /** No registered datasets without it being an error. */
+ public boolean empty = false ;
+ /** General query processor servlet */
+ public String addGeneral = null ;
+
+ public boolean validators = false ;
+ /** An informative label */
+ public String datasetDescription;
+ public String contentDirectory = null;
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/jena/blob/7e6d03af/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/CustomService.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/CustomService.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/CustomService.java
new file mode 100644
index 0000000..94eec39
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/CustomService.java
@@ -0,0 +1,81 @@
+/*
+ * 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 java.io.IOException;
+
+import org.apache.jena.fuseki.servlets.ActionREST;
+import org.apache.jena.fuseki.servlets.HttpAction;
+import org.apache.jena.fuseki.servlets.ServletOps;
+import org.apache.jena.riot.WebContent;
+import org.apache.jena.web.HttpSC;
+
+public class CustomService extends ActionREST {
+
+ // do* -- the operations to accept
+
+ @Override
+ protected void doGet(HttpAction action) {
+ action.response.setStatus(HttpSC.OK_200);
+ try {
+ action.response.setContentType(WebContent.contentTypeTextPlain);
+ action.response.getOutputStream().println(" ** Hello world (GET) **");
+ }
+ catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ protected void doHead(HttpAction action) {
+ action.response.setStatus(HttpSC.OK_200);
+ action.response.setContentType(WebContent.contentTypeTextPlain);
+ }
+
+ @Override
+ protected void doPost(HttpAction action) {
+ action.response.setStatus(HttpSC.OK_200);
+ try {
+ action.response.setContentType(WebContent.contentTypeTextPlain);
+ action.response.getOutputStream().println(" ** Hello world (POST) **");
+ }
+ catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ protected void doPatch(HttpAction action) { notSupported(action); }
+
+ @Override
+ protected void doDelete(HttpAction action) { notSupported(action); }
+
+ @Override
+ protected void doPut(HttpAction action) { notSupported(action); }
+
+ @Override
+ protected void doOptions(HttpAction action) { notSupported(action); }
+
+ @Override
+ protected void validate(HttpAction action) { }
+
+ private void notSupported(HttpAction action) {
+ ServletOps.errorMethodNotAllowed(action.getMethod()+" "+action.getDatasetName());
+ }
+}
http://git-wip-us.apache.org/repos/asf/jena/blob/7e6d03af/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/FusekiTestAuth.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/FusekiTestAuth.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/FusekiTestAuth.java
new file mode 100644
index 0000000..8342571
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/FusekiTestAuth.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.main;
+
+import java.util.Objects;
+
+import org.apache.jena.atlas.web.HttpException;
+import org.apache.jena.fuseki.FusekiLib;
+import org.apache.jena.fuseki.jetty.JettyLib;
+import org.apache.jena.sparql.core.DatasetGraph ;
+import org.apache.jena.sparql.core.DatasetGraphFactory;
+import org.apache.jena.web.HttpSC;
+import org.eclipse.jetty.security.*;
+import org.eclipse.jetty.security.authentication.BasicAuthenticator;
+import org.eclipse.jetty.util.security.Constraint;
+import org.junit.Assert;
+
+/**
+ * Testing HTTP athentication.
+ * <p>
+ * {@code FusekiTestAuth} provides helper code for before/after (any of suite/class/test).
+ * The pattern of usage is:
+ * <pre>
+ *
+ * @BeforeClass
+ * public static void beforeClassAuth() {
+ * SecurityHandler sh = FusekiTestAuth.makeSimpleSecurityHandler("/*", "USER", "PASSWORD");
+ * FusekiTestAuth.setupServer(true, sh);
+ * }
+ *
+ * @AfterClass
+ * public static void afterClassAuth() {
+ * FusekiTestAuth.teardownServer();
+ * // Clear up any pooled connections.
+ * HttpOp.setDefaultHttpClient(HttpOp.createPoolingHttpClient());
+ * }
+ *
+ * @Test
+ * public void myAuthTest() {
+ * BasicCredentialsProvider credsProvider = new BasicCredentialsProvider();
+ * Credentials credentials = new UsernamePasswordCredentials("USER", "PASSWORD");
+ * credsProvider.setCredentials(AuthScope.ANY, credentials);
+ * HttpClient client = HttpClients.custom().setDefaultCredentialsProvider(credsProvider).build();
+ * try (TypedInputStream in = HttpOp.execHttpGet(ServerCtl.urlDataset(), "* /*", client, null)) {}
+ * }
+ *
+ * @Test
+ * public void myAuthTestRejected() {
+ * BasicCredentialsProvider credsProvider = new BasicCredentialsProvider();
+ * Credentials credentials = new UsernamePasswordCredentials("USER", "PASSWORD");
+ * credsProvider.setCredentials(AuthScope.ANY, credentials);
+ * HttpClient client = HttpClients.custom().setDefaultCredentialsProvider(credsProvider).build();
+ * try (TypedInputStream in = HttpOp.execHttpGet(ServerCtl.urlDataset(), "* /*", client, null)) {}
+ * catch (HttpException ex) {
+ * throw assertAuthHttpException(ex);
+ * }
+ * }
+ * </pre>
+ *
+ * {@code @BeforeClass} can be {@code @Before} but server stop-start is expensive so a
+ * large test suite may end up quite slow.
+ */
+public class FusekiTestAuth {
+ private static int currentPort = FusekiLib.choosePort() ;
+
+ public static int port() {
+ return currentPort ;
+ }
+
+ static boolean CLEAR_DSG_DIRECTLY = true ;
+ static private DatasetGraph dsgTesting ;
+
+ // Abstraction that runs a SPARQL server for tests.
+ public static final String urlRoot() { return "http://localhost:"+port()+"/" ; }
+ public static final String datasetPath() { return "/dataset" ; }
+ public static final String urlDataset() { return "http://localhost:"+port()+datasetPath() ; }
+ public static final DatasetGraph getDataset() { return dsgTesting ; }
+
+ public static final String serviceUpdate() { return "http://localhost:"+port()+datasetPath()+"/update" ; }
+ public static final String serviceQuery() { return "http://localhost:"+port()+datasetPath()+"/query" ; }
+ public static final String serviceGSP() { return "http://localhost:"+port()+datasetPath()+"/data" ; }
+
+ private static FusekiServer server ;
+
+ /** Setup a testing server, using the given Jetty {@link SecurityHandler} for authentication.
+ * The server will have an empty, in-emory transactional dataset.
+ */
+ public static void setupServer(boolean updateable, SecurityHandler sh) {
+ setupServer(updateable, sh, DatasetGraphFactory.createTxnMem());
+ }
+
+ /** Setup a testing server, using the given Jetty {@link SecurityHandler} for authentication.
+ */
+ public static void setupServer(boolean updateable, SecurityHandler sh, DatasetGraph dsg) {
+ dsgTesting = dsg;
+ server = FusekiServer.create()
+ .add(datasetPath(), dsgTesting)
+ .port(port())
+ .securityHandler(sh)
+ .build()
+ .start();
+ }
+
+ /** Shutdown the server.*/
+ public static void teardownServer() {
+ if ( server != null ) {
+ server.stop() ;
+ server = null ;
+ dsgTesting = null;
+ }
+ }
+
+ /** Create a Jetty {@link SecurityHandler} for basic authentication, one user/password/role. */
+ public static SecurityHandler makeSimpleSecurityHandler(String pathSpec, String user, String password) {
+ return makeSimpleSecurityHandler(pathSpec, null, user, password, "FusekiTestRole");
+ }
+
+ /** Create a Jetty {@link SecurityHandler} for basic authentication, one user/password/role. */
+ public static SecurityHandler makeSimpleSecurityHandler(String pathSpec, String realm, String user, String password, String role) {
+ Objects.requireNonNull(user);
+ Objects.requireNonNull(password);
+ Objects.requireNonNull(role);
+
+ Constraint constraint = new Constraint() ;
+ constraint.setName(Constraint.__BASIC_AUTH) ;
+ String[] roles = new String[]{role};
+ constraint.setRoles(roles) ;
+ constraint.setAuthenticate(true) ;
+
+ ConstraintMapping mapping = new ConstraintMapping() ;
+ mapping.setConstraint(constraint) ;
+ mapping.setPathSpec("/*") ;
+
+ IdentityService identService = new DefaultIdentityService() ;
+
+ ConstraintSecurityHandler securityHandler = new ConstraintSecurityHandler() ;
+ securityHandler.addConstraintMapping(mapping) ;
+ securityHandler.setIdentityService(identService) ;
+
+ UserStore userStore = JettyLib.makeUserStore(user, password, role);
+
+ HashLoginService loginService = new HashLoginService("Fuseki Authentication") ;
+ loginService.setUserStore(userStore);
+ loginService.setIdentityService(identService) ;
+
+ securityHandler.setLoginService(loginService) ;
+ securityHandler.setAuthenticator(new BasicAuthenticator()) ;
+ if ( realm != null )
+ securityHandler.setRealmName(realm);
+
+ return securityHandler;
+ }
+
+ /** Assert that an {@code HttpException} ias an authorization failure.
+ * This is normally 403. 401 indicates no retryu with credentials.
+ */
+ public static HttpException assertAuthHttpException(HttpException ex) {
+ int rc = ex.getResponseCode();
+ Assert.assertTrue(rc == HttpSC.FORBIDDEN_403 || rc == HttpSC.UNAUTHORIZED_401 );
+ return ex;
+ }
+}
http://git-wip-us.apache.org/repos/asf/jena/blob/7e6d03af/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/FusekiTestServer.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/FusekiTestServer.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/FusekiTestServer.java
new file mode 100644
index 0000000..58983c9
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/FusekiTestServer.java
@@ -0,0 +1,261 @@
+/**
+ * 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.apache.jena.fuseki.main.FusekiTestServer.ServerScope.CLASS;
+import static org.apache.jena.fuseki.main.FusekiTestServer.ServerScope.SUITE;
+import static org.apache.jena.fuseki.main.FusekiTestServer.ServerScope.TEST;
+
+import java.util.concurrent.atomic.AtomicInteger ;
+
+import org.apache.http.client.HttpClient ;
+import org.apache.http.impl.client.CloseableHttpClient ;
+import org.apache.jena.atlas.io.IO ;
+import org.apache.jena.fuseki.FusekiLib;
+import org.apache.jena.riot.web.HttpOp ;
+import org.apache.jena.sparql.core.DatasetGraph ;
+import org.apache.jena.sparql.core.DatasetGraphFactory;
+import org.apache.jena.sparql.modify.request.Target ;
+import org.apache.jena.sparql.modify.request.UpdateDrop ;
+import org.apache.jena.system.Txn ;
+import org.apache.jena.update.Update ;
+import org.apache.jena.update.UpdateExecutionFactory ;
+import org.apache.jena.update.UpdateProcessor ;
+
+/**
+ * Manage a single server for use with tests. It supports three modes:
+ * <ul>
+ * <li>One server for a whole test suite
+ * <li>One server per test class
+ * <li>One server per individual test
+ * </ul>
+ * One server per individual test can be troublesome due to connections not closing down
+ * fast enough (left in TCP state {@code TIME_WAIT} which is 2 minutes) and also can be
+ * slow. One server per test class is a good compromise.
+ * <p>
+ * The data in the server is always reseet between tests.
+ * <p>
+ * Using a connection pooling HttpClient (see {@link HttpOp#createPoolingHttpClient()}) is
+ * important, both for test performance and for reducing the TCP connection load on the
+ * operating system.
+ * <p>
+ * Usage:
+ * </p>
+ * <p>
+ * In the test suite, put:
+ *
+ * <pre>
+ * {@literal @BeforeClass} static public void beforeSuiteClass() { FusekiTestServer.ctlBeforeTestSuite(); }
+ * {@literal @AfterClass} static public void afterSuiteClass() { FusekiTestServer.ctlAfterTestSuite(); }
+ * </pre>
+ * <p>
+ * In the test class, put:
+ *
+ * <pre>
+ * {@literal @BeforeClass} public static void ctlBeforeClass() { FusekiTestServer.ctlBeforeClass(); }
+ * {@literal @AfterClass} public static void ctlAfterClass() { FusekiTestServer.ctlAfterClass(); }
+ * {@literal @Before} public void ctlBeforeTest() { FusekiTestServer.ctlBeforeTest(); }
+ * {@literal @After} public void ctlAfterTest() { FusekiTestServer.ctlAfterTest(); }
+ * </pre>
+ *
+ * Much of this machinery is unnessecary for just running a sever in the background:
+ *
+ * <pre>
+ * private static FusekiServer server ;
+ * private static DatasetGraph serverdsg = DatasetGraphFactory.createTxnMem() ;
+ *
+ * @BeforeClass
+ * public static void beforeClass() {
+ * server = FusekiServer.create()
+ * .setPort(....)
+ * .add("/ds", serverdsg)
+ * .build()
+ * .start();
+ * }
+ *
+ * @Before
+ * public void beforeTest() {
+ * // Clear up data in server servers
+ * Txn.executeWrite(serverdsg, ()->serverdsg.clear()) ;
+ * }
+ *
+ * @AfterClass
+ * public static void afterClass() {
+ * server.stop();
+ * }
+ * </pre>
+ */
+public class FusekiTestServer {
+ /* Cut&Paste versions:
+
+ Test suite (TS_*)
+ @BeforeClass static public void beforeSuiteClass() { FusekiTestServer.ctlBeforeTestSuite(); }
+ @AfterClass static public void afterSuiteClass() { FusekiTestServer.ctlAfterTestSuite(); }
+
+ Test class (Test*)
+ @BeforeClass public static void ctlBeforeClass() { FusekiTestServer.ctlBeforeClass(); }
+ @AfterClass public static void ctlAfterClass() { FusekiTestServer.ctlAfterClass(); }
+ @Before public void ctlBeforeTest() { FusekiTestServer.ctlBeforeTest(); }
+ @After public void ctlAfterTest() { FusekiTestServer.ctlAfterTest(); }
+
+ */
+
+ // Note: it is important to cleanly close a PoolingHttpClient across server restarts
+ // otherwise the pooled connections remain for the old server.
+
+ /*package : for import static */ enum ServerScope { SUITE, CLASS, TEST }
+ private static ServerScope serverScope = ServerScope.CLASS ;
+ private static int currentPort = FusekiLib.choosePort() ;
+
+ public static int port() {
+ return currentPort ;
+ }
+
+ // Whether to use a transaction on the dataset or to use SPARQL Update.
+ static boolean CLEAR_DSG_DIRECTLY = true ;
+ static private DatasetGraph dsgTesting ;
+
+ // Abstraction that runs a SPARQL server for tests.
+ public static final String urlRoot() { return "http://localhost:"+port()+"/" ; }
+ public static final String datasetPath() { return "/dataset" ; }
+ public static final String urlDataset() { return "http://localhost:"+port()+datasetPath() ; }
+
+ public static final String serviceUpdate() { return "http://localhost:"+port()+datasetPath()+"/update" ; }
+ public static final String serviceQuery() { return "http://localhost:"+port()+datasetPath()+"/query" ; }
+ public static final String serviceGSP() { return "http://localhost:"+port()+datasetPath()+"/data" ; }
+
+ public static void ctlBeforeTestSuite() {
+ if ( serverScope == SUITE ) {
+ setPoolingHttpClient() ;
+ allocServer();
+ }
+ }
+
+ public static void ctlAfterTestSuite() {
+ if ( serverScope == SUITE ) {
+ freeServer();
+ resetDefaultHttpClient() ;
+ }
+ }
+
+ /**
+ * Setup for the tests by allocating a Fuseki instance to work with
+ */
+ public static void ctlBeforeClass() {
+ if ( serverScope == CLASS ) {
+ setPoolingHttpClient() ;
+ allocServer();
+ }
+ }
+
+ /**
+ * Clean up after tests by de-allocating the Fuseki instance
+ */
+ public static void ctlAfterClass() {
+ if ( serverScope == CLASS ) {
+ freeServer();
+ resetDefaultHttpClient() ;
+ }
+ }
+
+ /**
+ * Placeholder.
+ */
+ public static void ctlBeforeTest() {
+ if ( serverScope == TEST ) {
+ setPoolingHttpClient() ;
+ allocServer();
+ }
+ }
+
+ /**
+ * Clean up after each test by resetting the Fuseki dataset
+ */
+ public static void ctlAfterTest() {
+ if ( serverScope == TEST ) {
+ freeServer();
+ resetDefaultHttpClient() ;
+ } else
+ resetServer();
+ }
+
+ /** Set a PoolingHttpClient */
+ public static void setPoolingHttpClient() {
+ setHttpClient(HttpOp.createPoolingHttpClient()) ;
+ }
+
+ /** Restore the original setup */
+ private static void resetDefaultHttpClient() {
+ setHttpClient(HttpOp.createDefaultHttpClient());
+ }
+
+ /** Set the HttpClient - close the old one if appropriate */
+ public static void setHttpClient(HttpClient newHttpClient) {
+ HttpClient hc = HttpOp.getDefaultHttpClient() ;
+ if ( hc instanceof CloseableHttpClient )
+ IO.close((CloseableHttpClient)hc) ;
+ HttpOp.setDefaultHttpClient(newHttpClient) ;
+ }
+
+ // reference count of start/stop server
+ private static AtomicInteger countServer = new AtomicInteger() ;
+ private static FusekiServer server = null ;
+
+ /*package*/ static void allocServer() {
+ if ( countServer.getAndIncrement() == 0 )
+ setupServer(true) ;
+ }
+
+ /*package*/ static void freeServer() {
+ if ( countServer.decrementAndGet() == 0 )
+ teardownServer() ;
+ }
+
+ /*package*/ static void setupServer(boolean updateable) {
+ dsgTesting = DatasetGraphFactory.createTxnMem();
+ server = FusekiServer.create()
+ .add(datasetPath(), dsgTesting)
+ .port(port())
+ .build()
+ .start();
+ }
+
+ /*package*/ static void teardownServer() {
+ if ( server != null ) {
+ server.stop() ;
+ server = null ;
+ }
+ }
+
+ /*package*/ static void resetServer() {
+ if (countServer.get() == 0)
+ throw new RuntimeException("No server started!");
+ if ( CLEAR_DSG_DIRECTLY ) {
+ Txn.executeWrite(dsgTesting, ()->dsgTesting.clear()) ;
+ } else {
+ Update clearRequest = new UpdateDrop(Target.ALL) ;
+ UpdateProcessor proc = UpdateExecutionFactory.createRemote(clearRequest, serviceUpdate()) ;
+ try {proc.execute() ; }
+ catch (Throwable e) {e.printStackTrace(); throw e;}
+ }
+ }
+
+ // ---- Helper code.
+
+}
http://git-wip-us.apache.org/repos/asf/jena/blob/7e6d03af/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TS_EmbeddedFuseki.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TS_EmbeddedFuseki.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TS_EmbeddedFuseki.java
new file mode 100644
index 0000000..c10c8d5
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TS_EmbeddedFuseki.java
@@ -0,0 +1,51 @@
+/*
+ * 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.LogCtl ;
+import org.apache.jena.fuseki.Fuseki ;
+import org.junit.BeforeClass ;
+import org.junit.runner.RunWith ;
+import org.junit.runners.Suite ;
+import org.junit.runners.Suite.SuiteClasses ;
+
+@RunWith(Suite.class)
+@SuiteClasses({
+ TestEmbeddedFuseki.class
+ , TestMultipleEmbedded.class
+ , TestFusekiTestServer.class
+ , TestFusekiTestAuth.class
+ , TestFusekiCustomOperation.class
+})
+public class TS_EmbeddedFuseki {
+ @BeforeClass public static void setupForFusekiServer() {
+ 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");
+
+ // Shouldn't see these in the embedded server.
+// LogCtl.setLevel("org.apache.shiro", "WARN") ;
+// LogCtl.setLevel(Fuseki.configLogName, "WARN");
+
+// LogCtl.setLevel(Fuseki.builderLogName, "WARN");
+// LogCtl.setLevel(Fuseki.servletRequestLogName,"WARN");
+ }
+}
http://git-wip-us.apache.org/repos/asf/jena/blob/7e6d03af/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestEmbeddedFuseki.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestEmbeddedFuseki.java b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestEmbeddedFuseki.java
new file mode 100644
index 0000000..6499444
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-main/src/test/java/org/apache/jena/fuseki/main/TestEmbeddedFuseki.java
@@ -0,0 +1,321 @@
+/*
+ * 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.Assert.assertEquals ;
+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 java.io.OutputStream ;
+import java.util.function.Consumer ;
+
+import org.apache.http.HttpEntity ;
+import org.apache.http.entity.ContentProducer ;
+import org.apache.http.entity.EntityTemplate ;
+import org.apache.jena.atlas.web.ContentType ;
+import org.apache.jena.atlas.web.HttpException ;
+import org.apache.jena.atlas.web.TypedInputStream ;
+import org.apache.jena.fuseki.FusekiLib;
+import org.apache.jena.fuseki.server.DataAccessPointRegistry ;
+import org.apache.jena.fuseki.server.DataService ;
+import org.apache.jena.fuseki.server.Operation ;
+import org.apache.jena.graph.Graph ;
+import org.apache.jena.query.QueryExecution;
+import org.apache.jena.query.QueryExecutionFactory;
+import org.apache.jena.query.ResultSet;
+import org.apache.jena.query.ResultSetFormatter;
+import org.apache.jena.riot.RDFDataMgr ;
+import org.apache.jena.riot.RDFFormat ;
+import org.apache.jena.riot.RDFLanguages ;
+import org.apache.jena.riot.web.HttpOp ;
+import org.apache.jena.sparql.core.DatasetGraph ;
+import org.apache.jena.sparql.core.DatasetGraphFactory ;
+import org.apache.jena.sparql.core.Quad ;
+import org.apache.jena.sparql.graph.GraphFactory ;
+import org.apache.jena.sparql.sse.SSE ;
+import org.apache.jena.system.Txn ;
+import org.apache.jena.update.UpdateExecutionFactory ;
+import org.apache.jena.update.UpdateFactory ;
+import org.apache.jena.update.UpdateRequest ;
+import org.apache.jena.web.HttpSC ;
+import org.junit.Test ;
+
+public class TestEmbeddedFuseki {
+
+ private static final String DIR = "testing/FusekiEmbedded/" ;
+
+ // Test - build on default port.
+ @Test public void embedded_01() {
+ DatasetGraph dsg = dataset() ;
+ int port = 3330 ; // Default port.
+ FusekiServer server = FusekiServer.create().add("/ds", dsg).build() ;
+ assertTrue(server.getDataAccessPointRegistry().isRegistered("/ds")) ;
+ server.start() ;
+ query("http://localhost:"+port+"/ds/query", "SELECT * { ?s ?p ?o}", qExec-> {
+ ResultSet rs = qExec.execSelect() ;
+ assertFalse(rs.hasNext()) ;
+ }) ;
+ server.stop() ;
+ }
+
+ // Different dataset name.
+ @Test public void embedded_02() {
+ DatasetGraph dsg = dataset() ;
+ int port = 0 ;//FusekiEnv.choosePort() ;
+ FusekiServer server = FusekiServer.make(port, "/ds2", dsg) ;
+ DataAccessPointRegistry registry = server.getDataAccessPointRegistry() ;
+ // But no /ds
+ assertEquals(1, registry.size()) ;
+ assertTrue(registry.isRegistered("/ds2")) ;
+ assertFalse(registry.isRegistered("/ds")) ;
+ try {
+ server.start() ;
+ } finally { server.stop() ; }
+ }
+
+ // Different dataset name.
+ @Test public void embedded_03() {
+ DatasetGraph dsg = dataset() ;
+ int port = FusekiLib.choosePort() ;
+ FusekiServer server = FusekiServer.create()
+ .port(port)
+ .add("/ds1", dsg)
+ .build() ;
+ server.start() ;
+ try {
+ // Add while live.
+ Txn.executeWrite(dsg, ()->{
+ Quad q = SSE.parseQuad("(_ :s :p _:b)") ;
+ dsg.add(q);
+ }) ;
+ query("http://localhost:"+port+"/ds1/query", "SELECT * { ?s ?p ?o}", qExec->{
+ ResultSet rs = qExec.execSelect() ;
+ int x = ResultSetFormatter.consume(rs) ;
+ assertEquals(1, x) ;
+ }) ;
+ } finally { server.stop() ; }
+ }
+
+
+ @Test public void embedded_04() {
+ DatasetGraph dsg = dataset() ;
+ Txn.executeWrite(dsg, ()->{
+ Quad q = SSE.parseQuad("(_ :s :p _:b)") ;
+ dsg.add(q);
+ }) ;
+
+ // A service with just being able to do quads operations
+ // That is, GET, POST, PUT on "/data" in N-quads and TriG.
+ DataService dataService = new DataService(dsg) ;
+ dataService.addEndpoint(Operation.Quads_RW, "");
+ dataService.addEndpoint(Operation.Query, "");
+ dataService.addEndpoint(Operation.Update, "");
+ int port = FusekiLib.choosePort() ;
+
+ FusekiServer server = FusekiServer.create()
+ .port(port)
+ .add("/data", dataService)
+ .build() ;
+ server.start() ;
+ try {
+ // Put data in.
+ String data = "(graph (:s :p 1) (:s :p 2) (:s :p 3))" ;
+ Graph g = SSE.parseGraph(data) ;
+ HttpEntity e = graphToHttpEntity(g) ;
+ HttpOp.execHttpPut("http://localhost:"+port+"/data", e) ;
+
+ // Get data out.
+ try ( TypedInputStream in = HttpOp.execHttpGet("http://localhost:"+port+"/data") ) {
+ Graph g2 = GraphFactory.createDefaultGraph() ;
+ RDFDataMgr.read(g2, in, RDFLanguages.contentTypeToLang(in.getContentType())) ;
+ assertTrue(g.isIsomorphicWith(g2)) ;
+ }
+ // Query.
+ query("http://localhost:"+port+"/data", "SELECT * { ?s ?p ?o}", qExec->{
+ ResultSet rs = qExec.execSelect() ;
+ int x = ResultSetFormatter.consume(rs) ;
+ assertEquals(3, x) ;
+ }) ;
+ // Update
+ UpdateRequest req = UpdateFactory.create("CLEAR DEFAULT") ;
+ UpdateExecutionFactory.createRemote(req, "http://localhost:"+port+"/data").execute();
+ // Query again.
+ query("http://localhost:"+port+"/data", "SELECT * { ?s ?p ?o}", qExec-> {
+ ResultSet rs = qExec.execSelect() ;
+ int x = ResultSetFormatter.consume(rs) ;
+ assertEquals(0, x) ;
+ }) ;
+ } finally { server.stop() ; }
+ }
+
+ @Test public void embedded_05() {
+ DatasetGraph dsg = dataset() ;
+ int port = FusekiLib.choosePort() ;
+ FusekiServer server = FusekiServer.create()
+ .port(port)
+ .add("/ds0", dsg)
+ .build() ;
+ server.start() ;
+ try {
+ // No stats
+ String x = HttpOp.execHttpGetString("http://localhost:"+port+"/$/stats") ;
+ assertNull(x) ;
+ } finally { server.stop() ; }
+ }
+
+ @Test public void embedded_06() {
+ DatasetGraph dsg = dataset() ;
+ int port = FusekiLib.choosePort() ;
+ FusekiServer server = FusekiServer.create()
+ .port(port)
+ .add("/ds0", dsg)
+ .enableStats(true)
+ .build() ;
+ server.start() ;
+ // No stats
+ String x = HttpOp.execHttpGetString("http://localhost:"+port+"/$/stats") ;
+ assertNotNull(x) ;
+ server.stop() ;
+ }
+
+ // Context path.
+ @Test public void embedded_07() {
+ DatasetGraph dsg = dataset() ;
+ int port = FusekiLib.choosePort() ;
+
+ FusekiServer server = FusekiServer.create()
+ .port(port)
+ .contextPath("/ABC")
+ .add("/ds", dsg)
+ .build() ;
+ server.start() ;
+ try {
+ String x1 = HttpOp.execHttpGetString("http://localhost:"+port+"/ds") ;
+ assertNull(x1) ;
+ String x2 = HttpOp.execHttpGetString("http://localhost:"+port+"/ABC/ds") ;
+ assertNotNull(x2) ;
+ } finally { server.stop() ; }
+ }
+
+ @Test public void embedded_08() {
+ DatasetGraph dsg = dataset() ;
+ int port = FusekiLib.choosePort() ;
+
+ FusekiServer server = FusekiServer.create()
+ .port(port)
+ .parseConfigFile(DIR+"config.ttl")
+ .build() ;
+ server.start() ;
+ try {
+ query("http://localhost:"+port+"/FuTest", "SELECT * {}", x->{}) ;
+ } finally { server.stop() ; }
+ }
+
+ @Test public void embedded_09() {
+ DatasetGraph dsg = dataset() ;
+ int port = FusekiLib.choosePort() ;
+
+ FusekiServer server = FusekiServer.create()
+ .port(port)
+ .contextPath("/ABC")
+ .parseConfigFile(DIR+"config.ttl")
+ .build() ;
+ server.start() ;
+ try {
+ try {
+ query("http://localhost:"+port+"/FuTest", "ASK{}", x->{}) ;
+ } catch (HttpException ex) {
+ assertEquals(HttpSC.METHOD_NOT_ALLOWED_405, ex.getResponseCode()) ;
+ }
+
+ query("http://localhost:"+port+"/ABC/FuTest","ASK{}",x->{}) ;
+ } finally { server.stop() ; }
+ }
+
+ @Test public void embedded_20() {
+ DatasetGraph dsg = dataset() ;
+ int port = FusekiLib.choosePort() ;
+
+ DataService dSrv = new DataService(dsg) ;
+ dSrv.addEndpoint(Operation.Query, "q") ;
+ dSrv.addEndpoint(Operation.GSP_R, "gsp") ;
+ FusekiServer server = FusekiServer.create()
+ .add("/dsrv1", dSrv)
+ .port(port)
+ .build() ;
+ server.start() ;
+ try {
+ query("http://localhost:"+port+"/dsrv1/q","ASK{}",x->{}) ;
+ String x1 = HttpOp.execHttpGetString("http://localhost:"+port+"/dsrv1/gsp") ;
+ assertNotNull(x1) ;
+ } finally { server.stop() ; }
+ }
+
+ @Test public void embedded_21() {
+ DatasetGraph dsg = dataset() ;
+ int port = FusekiLib.choosePort() ;
+
+ DataService dSrv = new DataService(dsg) ;
+ dSrv.addEndpoint(Operation.Query, "q") ;
+ dSrv.addEndpoint(Operation.GSP_R, "gsp") ;
+ FusekiServer server = FusekiServer.create()
+ .add("/dsrv1", dSrv)
+ .staticFileBase(DIR)
+ .port(port)
+ .build() ;
+ server.start() ;
+
+ try {
+ query("http://localhost:"+port+"/dsrv1/q","ASK{}",x->{}) ;
+ String x1 = HttpOp.execHttpGetString("http://localhost:"+port+"/dsrv1/gsp") ;
+ assertNotNull(x1) ;
+ // Static
+ String x2 = HttpOp.execHttpGetString("http://localhost:"+port+"/test.txt");
+ assertNotNull(x2) ;
+ } finally { server.stop() ; }
+ }
+
+
+ /** Create an HttpEntity for the graph */
+ protected static HttpEntity graphToHttpEntity(final Graph graph) {
+ final RDFFormat syntax = RDFFormat.TURTLE_BLOCKS ;
+ ContentProducer producer = new ContentProducer() {
+ @Override
+ public void writeTo(OutputStream out) {
+ RDFDataMgr.write(out, graph, syntax) ;
+ }
+ } ;
+ EntityTemplate entity = new EntityTemplate(producer) ;
+ ContentType ct = syntax.getLang().getContentType() ;
+ entity.setContentType(ct.getContentType()) ;
+ return entity ;
+ }
+
+ /*package*/ static DatasetGraph dataset() {
+ return DatasetGraphFactory.createTxnMem() ;
+ }
+
+ /*package*/ static void query(String URL, String query, Consumer<QueryExecution> body) {
+ try (QueryExecution qExec = QueryExecutionFactory.sparqlService(URL, query) ) {
+ body.accept(qExec);
+ }
+ }
+}