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:15:27 UTC

[16/70] [abbrv] [partial] jena git commit: JENA-1597: separate jena-fuseki-webapp module

http://git-wip-us.apache.org/repos/asf/jena/blob/e8abcbb6/jena-fuseki2/jena-fuseki-webapp/fuseki-dev
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-webapp/fuseki-dev b/jena-fuseki2/jena-fuseki-webapp/fuseki-dev
new file mode 100755
index 0000000..365a33a
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-webapp/fuseki-dev
@@ -0,0 +1,105 @@
+#!/usr/bin/env bash
+
+# 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.
+
+## Run Fuseki, include development code if it looks like it's available.
+
+function check_dir() {
+    local NAME="$1"
+    local DIR="$2"
+    if [ ! -e "$DIR" ]
+    then
+	echo "$NAME: '$DIR' does not exist" 1>&2
+	exit 1
+    fi
+    if [ ! -d "$DIR" ]
+    then
+	echo "$NAME: '$DIR' exists but is not a directory" 1>&2
+    exit 1
+    fi
+}
+
+export FUSEKI_HOME="${FUSEKI_HOME:-$PWD}"
+check_dir "FUSEKI_HOME" "$FUSEKI_HOME"
+
+export FUSEKI_BASE="${FUSEKI_BASE:-$FUSEKI_HOME/run}"
+check_dir "FUSEKI_BASE" "$FUSEKI_BASE"
+
+CPF="$FUSEKI_HOME/fuseki.classpath"
+if [ ! -e "$CPF" ]; then
+    echo "Need to create Fuseki classpath file"
+    echo "Ensure maven is upto date with the latest snapshots and then run"
+    echo -e "( cd $FUSEKI_HOME ; \n  mvn dependency:build-classpath -Dmdep.outputFile=fuseki.classpath )"
+    exit 1
+fi
+CP="$(cat $CPF)"
+
+# Add development directories.
+if [ -e "$FUSEKI_HOME/classes" ]
+then
+    CP="$FUSEKI_HOME/classes:$CP"
+elif [ -e "$FUSEKI_HOME/target/classes" ]
+then
+    CP="$FUSEKI_HOME/target/classes:$CP"
+fi
+
+# Prepend any development directories here
+DEVDIRS="jena-core jena-tdb jena-arq jena-text"
+for X in $DEVDIRS
+do
+    CPX="$FUSEKI_HOME/../../$X/target/classes"
+    if [ -e "$CPX" ]
+    then
+	CP="$CPX:$CP"
+    fi
+done
+
+## echo "$CP"
+## exit
+
+FUSEKI_LOG="" #${FUSEKI_LOG:-}
+
+if [ -z "$JAVA" ]
+then
+    if [ -z "$JAVA_HOME" ]
+    then
+       JAVA=$(which java)
+    else
+        JAVA=$JAVA_HOME/bin/java
+    fi
+fi
+
+if [ -z "$JAVA" ]
+then
+    (
+	echo "Cannot find a Java JDK."
+	echo "Please set either set JAVA or JAVA_HOME and put java (>=1.8) in your PATH."
+    ) 1>&2
+  exit 1
+fi
+
+JVM_ARGS=${JVM_ARGS:--Xmx1200M}
+
+# Debug assistence
+## echo "++++ Classpath:"
+## echo "$(echo $CP | sed -e "s/:/\n/g")"
+## exit
+
+exec "$JAVA" -cp "$CP" $JVM_ARGS $FUSEKI_LOG org.apache.jena.fuseki.cmd.FusekiCmd "$@"
+
+# Run as war file.
+# java -jar jetty-runner.jar fuseki-server.war

http://git-wip-us.apache.org/repos/asf/jena/blob/e8abcbb6/jena-fuseki2/jena-fuseki-webapp/pom.xml
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-webapp/pom.xml b/jena-fuseki2/jena-fuseki-webapp/pom.xml
new file mode 100644
index 0000000..436e420
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-webapp/pom.xml
@@ -0,0 +1,198 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+   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.
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.apache.jena</groupId>
+    <artifactId>jena-fuseki</artifactId>
+    <version>3.9.0-SNAPSHOT</version>
+    <relativePath>../</relativePath>
+  </parent>
+
+  <name>Apache Jena - Fuseki Webapp</name>
+  <artifactId>jena-fuseki-webapp</artifactId>
+  <packaging>jar</packaging>
+
+  <dependencies>
+
+    <dependency>
+      <groupId>org.apache.jena</groupId>
+      <artifactId>jena-fuseki-core</artifactId>
+      <version>3.9.0-SNAPSHOT</version>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.jena</groupId>
+      <artifactId>jena-cmds</artifactId>
+      <version>3.9.0-SNAPSHOT</version>
+    </dependency>
+   
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-webapp</artifactId>
+      <version>${ver.jetty}</version>
+    </dependency>
+
+    <!--
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-runner</artifactId>
+      <version>${ver.jetty}</version>
+    </dependency>
+    -->
+
+    <!-- Text and spatial -->
+    <dependency>
+      <groupId>org.apache.jena</groupId>
+      <artifactId>jena-text</artifactId>
+      <version>3.9.0-SNAPSHOT</version>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.jena</groupId>
+      <artifactId>jena-spatial</artifactId>
+      <version>3.9.0-SNAPSHOT</version>
+    </dependency>
+
+    <!-- Apache Shiro -->
+    <dependency>
+      <groupId>org.apache.shiro</groupId>
+      <artifactId>shiro-core</artifactId>
+      <version>${ver.shiro}</version>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.shiro</groupId>
+      <artifactId>shiro-web</artifactId>
+      <version>${ver.shiro}</version>
+    </dependency>
+
+    <!-- Logging :  Needed because the Fuseki command line manages logging -->
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-api</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-log4j12</artifactId>
+      <optional>true</optional>
+    </dependency>
+
+    <dependency>
+      <groupId>log4j</groupId>
+      <artifactId>log4j</artifactId>
+      <optional>true</optional>
+    </dependency>
+
+    <!-- Testing -->
+    <dependency>
+      <groupId>org.apache.jena</groupId>
+      <artifactId>jena-arq</artifactId>
+      <version>3.9.0-SNAPSHOT</version>
+      <classifier>tests</classifier>
+      <scope>test</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.jena</groupId>
+      <artifactId>jena-base</artifactId>
+      <version>3.9.0-SNAPSHOT</version>
+      <classifier>tests</classifier>
+      <scope>test</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <scope>test</scope>
+    </dependency>
+
+  </dependencies>
+
+  <build>
+    <resources>
+      <resource>
+        <filtering>false</filtering>
+        <directory>src/main/resources</directory>
+        <excludes>
+          <exclude>org/apache/jena/fuseki/fuseki-properties.xml</exclude>
+        </excludes>
+      </resource>
+      <resource>
+        <filtering>true</filtering>
+        <directory>src/main/resources</directory>
+        <includes>
+          <include>org/apache/jena/fuseki/fuseki-properties.xml</include>
+        </includes>
+      </resource>
+    </resources>
+
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-compiler-plugin</artifactId>
+      </plugin>
+
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-source-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>attach-sources</id>
+            <phase>package</phase>
+            <goals>
+              <goal>jar-no-fork</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-surefire-plugin</artifactId>
+        <configuration>
+          <includes>
+            <include>**/TS_*.java</include>
+          </includes>
+        </configuration>
+      </plugin>
+
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-dependency-plugin</artifactId>
+        <configuration>
+          <overWriteReleases>false</overWriteReleases>
+          <overWriteIfNewer>true</overWriteIfNewer>
+        </configuration>
+      </plugin>
+
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-resources-plugin</artifactId>
+        <configuration>
+          <encoding>UTF-8</encoding>
+        </configuration>
+      </plugin>
+
+    </plugins>
+
+  </build>
+
+</project>

http://git-wip-us.apache.org/repos/asf/jena/blob/e8abcbb6/jena-fuseki2/jena-fuseki-webapp/src/main/java/org/apache/jena/fuseki/authz/AuthorizationFilter403.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-webapp/src/main/java/org/apache/jena/fuseki/authz/AuthorizationFilter403.java b/jena-fuseki2/jena-fuseki-webapp/src/main/java/org/apache/jena/fuseki/authz/AuthorizationFilter403.java
new file mode 100644
index 0000000..ca4cd2e
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-webapp/src/main/java/org/apache/jena/fuseki/authz/AuthorizationFilter403.java
@@ -0,0 +1,59 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.fuseki.authz;
+
+import java.io.IOException ;
+
+import javax.servlet.ServletRequest ;
+import javax.servlet.ServletResponse ;
+import javax.servlet.http.HttpServletResponse ;
+
+import org.apache.jena.web.HttpSC ;
+import org.apache.shiro.web.filter.authz.AuthorizationFilter ;
+import org.apache.shiro.web.util.WebUtils ;
+
+/** Specialise AuthorizationFilter to yield HTTP 403 on access denied */ 
+public abstract class AuthorizationFilter403 extends AuthorizationFilter
+{    
+    private String message ;
+
+    protected AuthorizationFilter403(String text)   { setMessage(text) ; }
+    protected AuthorizationFilter403()              { this(null) ; }
+    
+    /** Set the message used in HTTP 403 responses */
+    public void setMessage(String msg) { message = msg ; }
+    
+    public String getMessage() { return message ; }
+
+    @Override
+    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException {
+        HttpServletResponse httpResponse ;
+        try { httpResponse = WebUtils.toHttp(response); }
+        catch (ClassCastException ex) { 
+            // Not a HTTP Servlet operation
+            return super.onAccessDenied(request, response) ;
+        }
+        if ( message == null )
+            httpResponse.sendError(HttpSC.FORBIDDEN_403) ;
+        else
+            httpResponse.sendError(HttpSC.FORBIDDEN_403, message) ;
+        return false ;  // No further processing.
+    }
+}
+

http://git-wip-us.apache.org/repos/asf/jena/blob/e8abcbb6/jena-fuseki2/jena-fuseki-webapp/src/main/java/org/apache/jena/fuseki/authz/DenyFilter.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-webapp/src/main/java/org/apache/jena/fuseki/authz/DenyFilter.java b/jena-fuseki2/jena-fuseki-webapp/src/main/java/org/apache/jena/fuseki/authz/DenyFilter.java
new file mode 100644
index 0000000..aac7ecd
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-webapp/src/main/java/org/apache/jena/fuseki/authz/DenyFilter.java
@@ -0,0 +1,33 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.fuseki.authz;
+
+import javax.servlet.ServletRequest ;
+import javax.servlet.ServletResponse ;
+
+/** An authorization filter that always denies access and sends back HTTP 403 */
+public class DenyFilter extends AuthorizationFilter403 {
+
+    public DenyFilter() { super("Access denied") ; }
+
+    @Override
+    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
+        return false ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/e8abcbb6/jena-fuseki2/jena-fuseki-webapp/src/main/java/org/apache/jena/fuseki/authz/LocalhostFilter.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-webapp/src/main/java/org/apache/jena/fuseki/authz/LocalhostFilter.java b/jena-fuseki2/jena-fuseki-webapp/src/main/java/org/apache/jena/fuseki/authz/LocalhostFilter.java
new file mode 100644
index 0000000..71de761
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-webapp/src/main/java/org/apache/jena/fuseki/authz/LocalhostFilter.java
@@ -0,0 +1,62 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.fuseki.authz;
+
+import javax.servlet.ServletRequest ;
+import javax.servlet.ServletResponse ;
+
+import org.apache.shiro.web.filter.authz.PortFilter ;
+
+/**
+ * A Filter that can allow or deny access based on whether the
+ * the host that sent the request is the loopback address (AKA localhost).
+ * Use of the external IP address of the local machine does not permit access,
+ * only the loopback interface is authorized.
+ * Responds with HTTP 403 on any denied request.
+ * 
+ * Example:
+ * <pre>
+ * [main]
+ * localhost=org.apache.jena.fuseki.authz.LocalhostFilter
+ * ...
+ * [urls]
+ * /LocalFilesForLocalPeople/** = localhost
+ * </pre>
+ * @see PortFilter
+ */
+
+public class LocalhostFilter extends AuthorizationFilter403 {
+    
+    private static final String message = "Access denied : only localhost access allowed" ;   
+    
+    public LocalhostFilter() { super(message); } 
+
+    private static String LOCALHOST_IpV6 =  "0:0:0:0:0:0:0:1" ;
+    private static String LOCALHOST_IpV4 =  "127.0.0.1" ;   // Strictly, 127.*.*.*
+    
+    @Override
+    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
+        String remoteAddr = request.getRemoteAddr() ;
+        if ( LOCALHOST_IpV6.equals(remoteAddr) || LOCALHOST_IpV4.equals(remoteAddr) )
+            return true ;
+        return false ;
+    }
+}
+
+

http://git-wip-us.apache.org/repos/asf/jena/blob/e8abcbb6/jena-fuseki2/jena-fuseki-webapp/src/main/java/org/apache/jena/fuseki/cmd/FusekiCmd.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-webapp/src/main/java/org/apache/jena/fuseki/cmd/FusekiCmd.java b/jena-fuseki2/jena-fuseki-webapp/src/main/java/org/apache/jena/fuseki/cmd/FusekiCmd.java
new file mode 100644
index 0000000..249e050
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-webapp/src/main/java/org/apache/jena/fuseki/cmd/FusekiCmd.java
@@ -0,0 +1,377 @@
+/*
+ * 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 jena.cmd.TerminationException;
+import org.apache.jena.atlas.lib.FileOps ;
+import org.apache.jena.fuseki.Fuseki ;
+import org.apache.jena.fuseki.FusekiException;
+import org.apache.jena.fuseki.jetty.JettyServerConfig ;
+import org.apache.jena.fuseki.mgt.Template;
+import org.apache.jena.fuseki.server.FusekiInitialConfig ;
+import org.apache.jena.fuseki.system.FusekiLogging;
+import org.apache.jena.fuseki.webapp.FusekiEnv;
+import org.apache.jena.fuseki.webapp.FusekiServerListener;
+import org.apache.jena.fuseki.webapp.FusekiSystem;
+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.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;
+        FusekiEnv.setEnvironment();
+        FusekiLogging.setLogging(FusekiEnv.FUSEKI_BASE);
+    }
+
+    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  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") ;
+        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 FusekiInitialConfig cmdLineConfig  = new FusekiInitialConfig() ;
+        private boolean useTDB2;
+
+        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(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(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) 
+            }
+            cmdLineConfig.quiet = super.isQuiet();
+            cmdLineConfig.verbose = super.isVerbose();
+            
+            // 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) ;
+                cmdLineConfig.datasetDescription = "Configuration: "+cmdLineConfig.fusekiCmdLineConfigFile;
+            }
+
+            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(FusekiSystem.DFT_CONFIG).toAbsolutePath() ;
+                if ( Files.exists(cfg) )
+                    cmdLineConfig.fusekiServerConfigFile = cfg.toString() ;
+            }
+
+            // Which TDB to use to create a command line TDB database. 
+            useTDB2 = contains(argTDB2mode);
+            
+            cmdLineConfig.allowUpdate = contains(argUpdate) ; 
+
+            if ( contains(argMem) ) {
+                log.info("Dataset: in-memory") ;
+                cmdLineConfig.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.  
+                cmdLineConfig.reset();
+                cmdLineConfig.argTemplateFile = Template.templateTIM_MemFN ;
+                // Always allow.
+                cmdLineConfig.allowUpdate = true ;
+            }
+
+            if ( contains(argFile) ) {
+                String filename = getValue(argFile) ;
+                log.info("Dataset: in-memory: load file: " + filename) ;
+                String pathname = filename;
+                if ( filename.startsWith("file:") )
+                    pathname = filename.substring("file:".length());
+                if ( !FileOps.exists(filename) )
+                    throw new CmdException("File not found: " + filename) ;
+                cmdLineConfig.datasetDescription = "file: "+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 = useTDB2 ? Template.templateTDB2_MemFN : Template.templateTDB1_MemFN ;
+                cmdLineConfig.params.put(Template.DIR, Names.memName) ;
+                // Always allow.
+                cmdLineConfig.allowUpdate = true ;
+                cmdLineConfig.datasetDescription = useTDB2 ? "TDB2 dataset (in-memory)" : "TDB dataset (in-memory)";
+            }
+
+            if ( contains(argTDB) ) {
+                cmdLineConfig.reset();
+                cmdLineConfig.argTemplateFile = 
+                    useTDB2 ? Template.templateTDB2_DirFN : Template.templateTDB1_DirFN;
+                String dir = getValue(argTDB) ;
+                cmdLineConfig.params.put(Template.DIR, dir) ;
+                cmdLineConfig.datasetDescription = useTDB2 ? "TDB2 dataset: "+dir : "TDB dataset: "+dir;
+            }
+
+            // Otherwise
+            if ( contains(assemblerDescDecl) ) {
+                log.info("Dataset from assembler") ;
+                cmdLineConfig.datasetDescription = "Assembler: "+ modDataset.getAssemblerFile();
+                // 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) ;
+            }
+        }
+
+        @Override
+        protected void exec() {
+            try {
+                runFuseki(cmdLineConfig, jettyServerConfig) ;
+            } catch (FusekiException ex) {
+                throw new TerminationException(1) ;
+            }
+        }
+
+        @Override
+        protected String getCommandName() {
+            return "fuseki" ;
+        }
+    }
+    
+    /** Configure and run a Fuseki server - this function does not return except for error starting up*/  
+    public static void runFuseki(FusekiInitialConfig serverConfig, JettyServerConfig jettyConfig) {
+        FusekiServerListener.initialSetup = serverConfig ;
+        JettyFuseki.initializeServer(jettyConfig) ;
+        JettyFuseki.instance.start() ;
+        JettyFuseki.instance.join() ;
+    }
+    
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/jena/blob/e8abcbb6/jena-fuseki2/jena-fuseki-webapp/src/main/java/org/apache/jena/fuseki/cmd/JettyFuseki.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-webapp/src/main/java/org/apache/jena/fuseki/cmd/JettyFuseki.java b/jena-fuseki2/jena-fuseki-webapp/src/main/java/org/apache/jena/fuseki/cmd/JettyFuseki.java
new file mode 100644
index 0000000..29608d0
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-webapp/src/main/java/org/apache/jena/fuseki/cmd/JettyFuseki.java
@@ -0,0 +1,325 @@
+/*
+ * 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 static java.lang.String.format ;
+import static org.apache.jena.fuseki.Fuseki.serverLog ;
+
+import java.io.FileInputStream ;
+
+import javax.servlet.ServletContext ;
+
+import org.apache.jena.atlas.lib.DateTimeUtils ;
+import org.apache.jena.atlas.lib.FileOps ;
+import org.apache.jena.fuseki.Fuseki ;
+import org.apache.jena.fuseki.FusekiException ;
+import org.apache.jena.fuseki.jetty.FusekiErrorHandler;
+import org.apache.jena.fuseki.jetty.JettyServerConfig;
+import org.apache.jena.fuseki.server.DataAccessPointRegistry ;
+import org.apache.jena.fuseki.webapp.FusekiEnv;
+import org.eclipse.jetty.security.* ;
+import org.eclipse.jetty.security.authentication.BasicAuthenticator ;
+import org.eclipse.jetty.server.HttpConnectionFactory ;
+import org.eclipse.jetty.server.Server ;
+import org.eclipse.jetty.server.ServerConnector ;
+import org.eclipse.jetty.server.handler.AllowSymLinkAliasChecker ;
+import org.eclipse.jetty.server.handler.ContextHandler ;
+import org.eclipse.jetty.server.handler.gzip.GzipHandler ;
+import org.eclipse.jetty.servlet.ServletContextHandler ;
+import org.eclipse.jetty.util.security.Constraint ;
+import org.eclipse.jetty.webapp.WebAppContext ;
+import org.eclipse.jetty.xml.XmlConfiguration ;
+
+/** Standalone full server, not run as a WAR file.
+ * Used in testing and development.
+ * 
+ * SPARQLServer is the Jena server instance which wraps/utilizes 
+ * {@link org.eclipse.jetty.server.Server}. This class provides
+ * immediate access to the {@link org.eclipse.jetty.server.Server#start()} and 
+ * {@link org.eclipse.jetty.server.Server#stop()} commands as well as obtaining
+ * instances of the server and server configuration. Finally we can obtain 
+ * instances of {@link org.apache.jena.fuseki.jetty.JettyServerConfig}.
+ */
+public class JettyFuseki {
+    // Jetty specific.
+    // This class is becoming less important - it now sets up a Jetty server for in-process use
+    // either for the command line in development  
+    // and in testing but not direct webapp deployments. 
+    static { Fuseki.init() ; }
+
+    public static JettyFuseki  instance    = null ;
+
+    private ServerConnector serverConnector = null ;
+    // If a separate ...
+    private ServerConnector mgtConnector    = null ;
+    
+    private JettyServerConfig serverConfig ;
+
+    // The jetty server.
+    
+    private Server              server         = null ;
+    private ServletContext      servletContext = null ;
+    
+    // webapp setup - standard maven layout
+    public static       String contextpath     = "/" ;
+    // Standalone jar
+    public static final String resourceBase1   = "webapp" ;
+    // Development
+    public static final String resourceBase2   = "src/main/webapp" ;
+
+    /**
+     * Default setup which requires a {@link org.apache.jena.fuseki.jetty.JettyServerConfig}
+     * object as input.  We use this config to pass in the command line arguments for dataset, 
+     * name etc. 
+     * @param config
+     */
+    
+    public static void initializeServer(JettyServerConfig config) {
+        instance = new JettyFuseki(config) ;
+    }
+    
+    private JettyFuseki(JettyServerConfig config) {
+        this.serverConfig = config ;
+        buildServerWebapp(serverConfig.contextPath, serverConfig.jettyConfigFile) ;
+        if ( mgtConnector == null )
+            mgtConnector = serverConnector ;
+
+        if ( config.enableCompression ) {
+            GzipHandler gzipHandler = new GzipHandler();
+            gzipHandler.setHandler(server.getHandler());
+            server.setHandler(gzipHandler); 
+        }
+    }
+
+    /**
+     * Initialize the {@link JettyFuseki} instance.
+     */
+    public void start() {
+        
+        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() ;
+        
+        if ( version != null ) {
+            if ( Fuseki.developmentMode && buildDate != null )
+                serverLog.info(format("%s %s %s", Fuseki.NAME, version, buildDate)) ;
+            else
+                serverLog.info(format("%s %s", Fuseki.NAME, version)) ;
+        }
+        // This does not get set usefully for Jetty as we use it.
+        // String jettyVersion = org.eclipse.jetty.server.Server.getVersion() ;
+        // serverLog.info(format("Jetty %s",jettyVersion)) ;
+        
+        String host = serverConnector.getHost() ;
+        if ( host != null )
+            serverLog.info("Incoming connections limited to " + host) ;
+
+        try {
+            server.start() ;
+        } catch (java.net.BindException ex) {
+            serverLog.error("SPARQLServer (port="+serverConnector.getPort()+"): Failed to start server: " + ex.getMessage()) ;
+            throw new FusekiException("BindException: port="+serverConnector.getPort()+": Failed to start server: " + ex.getMessage(), ex) ;
+        } catch (Exception ex) {
+            serverLog.error("SPARQLServer: Failed to start server: " + ex.getMessage(), ex) ;
+            throw new FusekiException("Failed to start server: " + ex.getMessage(), ex) ;
+        }
+        String now = DateTimeUtils.nowAsString() ;
+        serverLog.info(format("Started %s on port %d", now, serverConnector.getPort())) ;
+    }
+
+    /**
+     * Sync with the {@link JettyFuseki} instance.
+     * Returns only if the server exits cleanly 
+     */
+    public void join() {
+        try {
+            server.join() ;
+        } catch (InterruptedException ex) { }
+    }
+
+        /**
+     * Stop the {@link JettyFuseki} instance.
+     */
+    public void stop() {
+        String now = DateTimeUtils.nowAsString() ;
+        serverLog.info(format("Stopped %s on port %d", now, serverConnector.getPort())) ;
+        try {
+            server.stop() ;
+        } catch (Exception ex) {
+            Fuseki.serverLog.warn("SPARQLServer: Exception while stopping server: " + ex.getMessage(), ex) ;
+        }
+    }
+
+    public static WebAppContext createWebApp(String contextPath) {
+        FusekiEnv.setEnvironment();
+        WebAppContext webapp = new WebAppContext();
+        webapp.getServletContext().getContextHandler().setMaxFormContentSize(10 * 1000 * 1000) ;
+
+        // Hunt for the webapp for the standalone jar (or development system). 
+        // Note that Path FUSEKI_HOME is not initialized until the webapp starts
+        // so it is not available here.
+
+        String resourceBase3 = null ;
+        String resourceBase4 = null ;
+        if ( FusekiEnv.FUSEKI_HOME != null ) {
+            String HOME = FusekiEnv.FUSEKI_HOME.toString() ;
+            resourceBase3 = HOME+"/"+resourceBase1 ;
+            resourceBase4 = HOME+"/"+resourceBase2 ;
+        }
+
+        String resourceBase = tryResourceBase(resourceBase1, null) ;
+        resourceBase = tryResourceBase(resourceBase2, resourceBase) ;
+        resourceBase = tryResourceBase(resourceBase3, resourceBase) ;
+        resourceBase = tryResourceBase(resourceBase4, resourceBase) ;
+
+        if ( resourceBase == null ) {
+            if ( resourceBase3 == null )
+                Fuseki.serverLog.error("Can't find resourceBase (tried "+resourceBase1+" and "+resourceBase2+")") ;
+            else
+                Fuseki.serverLog.error("Can't find resourceBase (tried "+resourceBase1+", "+resourceBase2+", "+resourceBase3+" and "+resourceBase4+")") ;
+            Fuseki.serverLog.error("Failed to start") ;
+            throw new FusekiException("Failed to start") ;
+        }
+
+        webapp.setDescriptor(resourceBase+"/WEB-INF/web.xml");
+        webapp.setResourceBase(resourceBase);
+        webapp.setContextPath(contextPath);
+
+        //-- Jetty setup for the ServletContext logger.
+        // The name of the Jetty-allocated slf4j/log4j logger is
+        // the display name or, if null, the context path name.   
+        // It is set, without checking for a previous call of setLogger in "doStart"
+        // which happens during server startup. 
+        // This the name of the ServletContext logger as well
+        webapp.setDisplayName(Fuseki.servletRequestLogName);  
+        webapp.setParentLoaderPriority(true);  // Normal Java classloader behaviour.
+        webapp.setErrorHandler(new FusekiErrorHandler()) ;
+        return webapp ;
+    }
+
+    public static String getenv(String name) {
+        String x = System.getenv(name) ;
+        if ( x == null )
+            x = System.getProperty(name) ;
+        return x ;
+    }
+    
+    public DataAccessPointRegistry getDataAccessPointRegistry() {
+        return DataAccessPointRegistry.get(servletContext) ;
+    }
+
+    private static String tryResourceBase(String maybeResourceBase, String currentResourceBase) {
+        if ( currentResourceBase != null )
+            return currentResourceBase ;
+        if ( maybeResourceBase != null && FileOps.exists(maybeResourceBase) )
+            return maybeResourceBase ;
+        return currentResourceBase ;
+    }
+    
+    private void buildServerWebapp(String contextPath, String jettyConfig) {
+        if ( jettyConfig != null )
+            // --jetty-config=jetty-fuseki.xml
+            // for detailed configuration of the server using Jetty features.
+            configServer(jettyConfig) ;
+        else
+            defaultServerConfig(serverConfig.port, serverConfig.loopback) ;
+
+        WebAppContext webapp = createWebApp(contextPath) ;
+        if ( false /*enable symbolic links */ ) {
+            // See http://www.eclipse.org/jetty/documentation/current/serving-aliased-files.html
+            // Record what would be needed:
+            // 1 - Allow all symbolic links without checking
+            webapp.addAliasCheck(new ContextHandler.ApproveAliases());
+            // 2 - Check links are to valid resources. But default for Unix?
+            webapp.addAliasCheck(new AllowSymLinkAliasChecker()) ;
+        }
+        servletContext = webapp.getServletContext() ;
+        server.setHandler(webapp) ;
+        // Replaced by Shiro.
+        if ( jettyConfig == null && serverConfig.authConfigFile != null )
+            security(webapp, serverConfig.authConfigFile) ;
+    }
+    
+    // This is now provided by Shiro.
+    private static void security(ServletContextHandler context, String authfile) {
+        Constraint constraint = new Constraint() ;
+        constraint.setName(Constraint.__BASIC_AUTH) ;
+        constraint.setRoles(new String[]{"fuseki"}) ;
+        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) ;
+
+        HashLoginService loginService = new HashLoginService("Fuseki Authentication", authfile) ;
+        loginService.setIdentityService(identService) ;
+
+        securityHandler.setLoginService(loginService) ;
+        securityHandler.setAuthenticator(new BasicAuthenticator()) ;
+
+        context.setSecurityHandler(securityHandler) ;
+
+        serverLog.debug("Basic Auth Configuration = " + authfile) ;
+    }
+
+    private void configServer(String jettyConfig) {
+        try {
+            serverLog.info("Jetty server config file = " + jettyConfig) ;
+            server = new Server() ;
+            XmlConfiguration configuration = new XmlConfiguration(new FileInputStream(jettyConfig)) ;
+            configuration.configure(server) ;
+            serverConnector = (ServerConnector)server.getConnectors()[0] ;
+        } catch (Exception ex) {
+            serverLog.error("SPARQLServer: Failed to configure server: " + ex.getMessage(), ex) ;
+            throw new FusekiException("Failed to configure a server using configuration file '" + jettyConfig + "'") ;
+        }
+    }
+
+    private void defaultServerConfig(int port, boolean loopback) {
+        server = new Server() ;
+        HttpConnectionFactory f1 = new HttpConnectionFactory() ;
+        // Some people do try very large operations ... really, should use POST.
+        f1.getHttpConfiguration().setRequestHeaderSize(512 * 1024);
+        f1.getHttpConfiguration().setOutputBufferSize(5 * 1024 * 1024) ;
+        // Do not add "Server: Jetty(....) when not a development system.
+        if ( ! Fuseki.outputJettyServerHeader )
+            f1.getHttpConfiguration().setSendServerVersion(false) ;
+
+        // https is better done with a Jetty configuration file
+        // because there are several things to configure. 
+        // See "examples/fuseki-jetty-https.xml"
+
+        ServerConnector connector = new ServerConnector(server, f1) ;
+        connector.setPort(port) ;
+        server.addConnector(connector);
+        if ( loopback )
+            connector.setHost("localhost");
+        serverConnector = connector ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/e8abcbb6/jena-fuseki2/jena-fuseki-webapp/src/main/java/org/apache/jena/fuseki/mgt/ActionBackup.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-webapp/src/main/java/org/apache/jena/fuseki/mgt/ActionBackup.java b/jena-fuseki2/jena-fuseki-webapp/src/main/java/org/apache/jena/fuseki/mgt/ActionBackup.java
new file mode 100644
index 0000000..d732c55
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-webapp/src/main/java/org/apache/jena/fuseki/mgt/ActionBackup.java
@@ -0,0 +1,67 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.fuseki.mgt;
+
+import static java.lang.String.format ;
+
+import org.apache.jena.fuseki.ctl.ActionAsyncTask;
+import org.apache.jena.fuseki.ctl.TaskBase;
+import org.apache.jena.fuseki.servlets.HttpAction ;
+import org.apache.jena.fuseki.servlets.ServletOps ;
+import org.slf4j.Logger ;
+import org.slf4j.LoggerFactory ;
+
+public class ActionBackup extends ActionAsyncTask
+{
+    public ActionBackup() { super() ; }
+
+    @Override
+    protected Runnable createRunnable(HttpAction action) {
+        String name = action.getDatasetName() ;
+        if ( name == null ) {
+            action.log.error("Null for dataset name in item request") ;  
+            ServletOps.errorOccurred("Null for dataset name in item request");
+            return null ;
+        }
+        
+        action.log.info(format("[%d] Backup dataset %s", action.id, name)) ;
+        return new BackupTask(action) ;
+    }
+
+    static class BackupTask extends TaskBase {
+        static private Logger log = LoggerFactory.getLogger("Backup") ;
+        
+        public BackupTask(HttpAction action) {
+            super(action) ;
+        }
+
+        @Override
+        public void run() {
+            try {
+                String backupFilename = Backup.chooseFileName(datasetName) ;
+                log.info(format("[%d] >>>> Start backup %s -> %s", actionId, datasetName, backupFilename)) ;
+                Backup.backup(transactional, dataset, backupFilename) ;
+                log.info(format("[%d] <<<< Finish backup %s -> %s", actionId, datasetName, backupFilename)) ;
+            } catch (Exception ex) {
+                log.info(format("[%d] **** Exception in backup", actionId), ex) ;
+            }
+        }
+    }
+}
+    
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/jena/blob/e8abcbb6/jena-fuseki2/jena-fuseki-webapp/src/main/java/org/apache/jena/fuseki/mgt/ActionBackupList.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-webapp/src/main/java/org/apache/jena/fuseki/mgt/ActionBackupList.java b/jena-fuseki2/jena-fuseki-webapp/src/main/java/org/apache/jena/fuseki/mgt/ActionBackupList.java
new file mode 100644
index 0000000..0dc540d
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-webapp/src/main/java/org/apache/jena/fuseki/mgt/ActionBackupList.java
@@ -0,0 +1,95 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.fuseki.mgt;
+
+import static java.lang.String.format ;
+
+import java.io.File ;
+import java.io.IOException ;
+import java.nio.file.DirectoryStream ;
+import java.nio.file.Files ;
+import java.nio.file.Path ;
+import java.util.ArrayList ;
+import java.util.List ;
+import java.util.stream.Collectors ;
+
+import javax.servlet.http.HttpServletRequest ;
+import javax.servlet.http.HttpServletResponse ;
+
+import org.apache.jena.atlas.json.JsonBuilder ;
+import org.apache.jena.atlas.json.JsonValue ;
+import org.apache.jena.fuseki.ctl.ActionCtl;
+import org.apache.jena.fuseki.servlets.HttpAction ;
+import org.apache.jena.fuseki.servlets.ServletOps ;
+import org.apache.jena.fuseki.webapp.FusekiSystem;
+
+/**
+ * A JSON API to list all the backups in the backup directory
+ */
+public class ActionBackupList extends ActionCtl {
+
+    @Override
+    protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
+        doCommon(req, resp);
+    }
+
+    @Override
+    protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
+        doCommon(req, resp);
+    }
+
+    @Override
+    protected void perform(HttpAction action) {
+        JsonValue result = description(action) ;
+        ServletOps.setNoCache(action.response) ;
+        ServletOps.sendJsonReponse(action, result);
+    }
+        
+    private static DirectoryStream.Filter<Path> filterVisibleFiles = (entry) -> {
+        File f = entry.toFile() ;
+        return f.isFile() && !f.isHidden() ;
+    } ;
+
+    private JsonValue description(HttpAction action) {
+        if ( ! Files.isDirectory(FusekiSystem.dirBackups) )
+            ServletOps.errorOccurred(format("[%d] Backup area '%s' is not a directory", action.id, FusekiSystem.dirBackups)) ;
+        
+        List<Path> paths = new ArrayList<>() ;
+        try (DirectoryStream<Path> stream = Files.newDirectoryStream(FusekiSystem.dirBackups, filterVisibleFiles)) {
+            stream.forEach(paths::add) ;
+        } catch (IOException ex) {
+            action.log.error(format("[%d] Backup file list :: IOException :: %s", action.id, ex.getMessage())) ;
+            ServletOps.errorOccurred(ex);
+        }
+
+        List<String> fileNames = paths.stream().map((p)->p.getFileName().toString()).sorted().collect(Collectors.toList()) ;
+
+        JsonBuilder builder = new JsonBuilder() ;
+        builder.startObject("top") ;
+        builder.key("backups") ;
+
+        builder.startArray() ;
+        fileNames.forEach(builder::value) ;
+        builder.finishArray() ;
+
+        builder.finishObject("top") ;
+        return builder.build() ; 
+        
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/e8abcbb6/jena-fuseki2/jena-fuseki-webapp/src/main/java/org/apache/jena/fuseki/mgt/ActionDatasets.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-webapp/src/main/java/org/apache/jena/fuseki/mgt/ActionDatasets.java b/jena-fuseki2/jena-fuseki-webapp/src/main/java/org/apache/jena/fuseki/mgt/ActionDatasets.java
new file mode 100644
index 0000000..b016c3b
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-webapp/src/main/java/org/apache/jena/fuseki/mgt/ActionDatasets.java
@@ -0,0 +1,515 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.fuseki.mgt;
+
+import static java.lang.String.format ;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.StringReader;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.*;
+
+import javax.servlet.http.HttpServletRequest ;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.jena.atlas.RuntimeIOException;
+import org.apache.jena.atlas.io.IO ;
+import org.apache.jena.atlas.json.JsonBuilder ;
+import org.apache.jena.atlas.json.JsonValue ;
+import org.apache.jena.atlas.lib.FileOps ;
+import org.apache.jena.atlas.lib.InternalErrorException ;
+import org.apache.jena.atlas.lib.StrUtils ;
+import org.apache.jena.atlas.logging.FmtLog;
+import org.apache.jena.atlas.web.ContentType ;
+import org.apache.jena.datatypes.xsd.XSDDatatype ;
+import org.apache.jena.fuseki.FusekiLib ;
+import org.apache.jena.fuseki.build.DatasetDescriptionRegistry;
+import org.apache.jena.fuseki.build.FusekiBuilder;
+import org.apache.jena.fuseki.build.FusekiConst;
+import org.apache.jena.fuseki.ctl.ActionContainerItem;
+import org.apache.jena.fuseki.ctl.JsonDescription;
+import org.apache.jena.fuseki.server.DataAccessPoint;
+import org.apache.jena.fuseki.server.DataService;
+import org.apache.jena.fuseki.server.FusekiVocab;
+import org.apache.jena.fuseki.server.ServerConst;
+import org.apache.jena.fuseki.servlets.ActionLib;
+import org.apache.jena.fuseki.servlets.HttpAction;
+import org.apache.jena.fuseki.servlets.ServletOps;
+import org.apache.jena.fuseki.system.Upload;
+import org.apache.jena.fuseki.webapp.FusekiSystem;
+import org.apache.jena.fuseki.webapp.SystemState;
+import org.apache.jena.graph.Node ;
+import org.apache.jena.graph.NodeFactory ;
+import org.apache.jena.query.Dataset;
+import org.apache.jena.query.ReadWrite;
+import org.apache.jena.rdf.model.* ;
+import org.apache.jena.riot.* ;
+import org.apache.jena.riot.system.StreamRDF ;
+import org.apache.jena.riot.system.StreamRDFLib ;
+import org.apache.jena.shared.uuid.JenaUUID ;
+import org.apache.jena.sparql.core.DatasetGraph ;
+import org.apache.jena.sparql.core.Quad ;
+import org.apache.jena.sparql.util.FmtUtils ;
+import org.apache.jena.tdb.transaction.DatasetGraphTransaction ;
+import org.apache.jena.update.UpdateAction ;
+import org.apache.jena.update.UpdateFactory ;
+import org.apache.jena.update.UpdateRequest ;
+import org.apache.jena.web.HttpSC ;
+
+public class ActionDatasets extends ActionContainerItem {
+    
+    private static Dataset system = SystemState.getDataset() ;
+    private static DatasetGraphTransaction systemDSG = SystemState.getDatasetGraph() ; 
+    
+    static private Property pServiceName = FusekiVocab.pServiceName ;
+    static private Property pStatus = FusekiVocab.pStatus ;
+
+    private static final String paramDatasetName    = "dbName" ;
+    private static final String paramDatasetType    = "dbType" ;
+    private static final String tDatabaseTDB        = "tdb" ;
+    private static final String tDatabaseTDB2       = "tdb2" ;
+    private static final String tDatabaseMem        = "mem" ;
+
+    public ActionDatasets() { super() ; }
+    
+    // ---- GET : return details of dataset or datasets.
+    @Override
+    protected JsonValue execGetContainer(HttpAction action) { 
+        action.log.info(format("[%d] GET datasets", action.id)) ;
+        JsonBuilder builder = new JsonBuilder() ;
+        builder.startObject("D") ;
+        builder.key(ServerConst.datasets) ;
+        JsonDescription.arrayDatasets(builder, action.getDataAccessPointRegistry());
+        builder.finishObject("D") ;
+        return builder.build() ;
+    }
+
+    @Override
+    protected JsonValue execGetItem(HttpAction action) {
+        action.log.info(format("[%d] GET dataset %s", action.id, action.getDatasetName())) ;
+        JsonBuilder builder = new JsonBuilder() ;
+        DataAccessPoint dsDesc = action.getDataAccessPointRegistry().get(action.getDatasetName()) ;
+        if ( dsDesc == null )
+            ServletOps.errorNotFound("Not found: dataset "+action.getDatasetName());
+        JsonDescription.describe(builder, dsDesc) ;
+        return builder.build() ;
+    }
+    
+    // ---- POST 
+    
+    @Override
+    protected JsonValue execPostContainer(HttpAction action) {
+        JenaUUID uuid = JenaUUID.generate() ;
+        DatasetDescriptionRegistry registry = new DatasetDescriptionRegistry() ;
+        
+        ContentType ct = FusekiLib.getContentType(action) ;
+        
+        boolean hasParams = action.request.getParameterNames().hasMoreElements();
+        
+        if ( ct == null && ! hasParams ) {
+            ServletOps.errorBadRequest("Bad request - Content-Type or both parameters dbName and dbType required");
+            // Or do "GET over POST"
+            //return execGetContainer(action);
+        }
+        
+        boolean committed = false ;
+        // Also acts as a concurrency lock
+        system.begin(ReadWrite.WRITE) ;
+        String systemFileCopy = null ;
+        String configFile = null ;
+            
+        try {
+            // Where to build the templated service/database. 
+            Model model = ModelFactory.createDefaultModel() ;
+            StreamRDF dest = StreamRDFLib.graph(model.getGraph()) ;
+    
+            if ( hasParams || WebContent.isHtmlForm(ct) )
+                assemblerFromForm(action, dest) ;
+            else if ( WebContent.isMultiPartForm(ct) )
+                assemblerFromUpload(action, dest) ;
+            else
+                assemblerFromBody(action, dest) ;
+            
+            // ----
+            // Keep a persistent copy immediately.  This is not used for
+            // anything other than being "for the record".
+            systemFileCopy = FusekiSystem.dirFileArea.resolve(uuid.asString()).toString() ;
+            try ( OutputStream outCopy = IO.openOutputFile(systemFileCopy) ) {
+                RDFDataMgr.write(outCopy, model, Lang.TURTLE) ;
+            }
+            // ----
+
+            // Process configuration.
+            Statement stmt = getOne(model, null, pServiceName, null) ;
+            if ( stmt == null ) {
+                StmtIterator sIter = model.listStatements(null, pServiceName, (RDFNode)null ) ;
+                if ( ! sIter.hasNext() )
+                    ServletOps.errorBadRequest("No name given in description of Fuseki service") ;
+                sIter.next() ;
+                if ( sIter.hasNext() )
+                    ServletOps.errorBadRequest("Multiple names given in description of Fuseki service") ;
+                throw new InternalErrorException("Inconsistent: getOne didn't fail the second time") ;
+            }
+                
+            if ( ! stmt.getObject().isLiteral() )
+                ServletOps.errorBadRequest("Found "+FmtUtils.stringForRDFNode(stmt.getObject())+" : Service names are strings, then used to build the external URI") ;
+
+            Resource subject = stmt.getSubject() ;
+            Literal object = stmt.getObject().asLiteral() ;
+            
+            if ( object.getDatatype() != null && ! object.getDatatype().equals(XSDDatatype.XSDstring) )
+                action.log.warn(format("[%d] Service name '%s' is not a string", action.id, FmtUtils.stringForRDFNode(object)));
+            
+            String datasetPath ;
+            {   // Check the name provided.
+                String datasetName = object.getLexicalForm() ;
+                // This duplicates the code FusekiBuilder.buildDataAccessPoint to give better error messages and HTTP status code."
+                
+                // ---- Check and canonicalize name.
+                if ( datasetName.isEmpty() )
+                    ServletOps.error(HttpSC.BAD_REQUEST_400, "Empty dataset name") ;
+                if ( StringUtils.isBlank(datasetName) )
+                    ServletOps.error(HttpSC.BAD_REQUEST_400, format("Whitespace dataset name: '%s'", datasetName)) ;
+                if ( datasetName.contains(" ") )
+                    ServletOps.error(HttpSC.BAD_REQUEST_400, format("Bad dataset name (contains spaces) '%s'",datasetName)) ;
+                if ( datasetName.equals("/") )
+                    ServletOps.error(HttpSC.BAD_REQUEST_400, format("Bad dataset name '%s'",datasetName)) ;
+                datasetPath = DataAccessPoint.canonical(datasetName) ;
+                // ---- Check whether it already exists 
+                if ( action.getDataAccessPointRegistry().isRegistered(datasetPath) )
+                    // And abort.
+                    ServletOps.error(HttpSC.CONFLICT_409, "Name already registered "+datasetPath) ;
+            }
+            
+            action.log.info(format("[%d] Create database : name = %s", action.id, datasetPath)) ;
+
+            configFile = FusekiSystem.generateConfigurationFilename(datasetPath) ;
+            List<String> existing = FusekiSystem.existingConfigurationFile(datasetPath) ;
+            if ( ! existing.isEmpty() )
+                ServletOps.error(HttpSC.CONFLICT_409, "Configuration file for '"+datasetPath+"' already exists") ;
+
+            // Write to configuration directory.
+            try ( OutputStream outCopy = IO.openOutputFile(configFile) ) {
+                RDFDataMgr.write(outCopy, model, Lang.TURTLE) ;
+            }
+
+            // Currently do nothing with the system database.
+            // In the future ... maybe ...
+//            Model modelSys = system.getNamedModel(gn.getURI()) ;
+//            modelSys.removeAll(null, pStatus, null) ;
+//            modelSys.add(subject, pStatus, FusekiVocab.stateActive) ;
+            
+            // Need to be in Resource space at this point.
+            DataAccessPoint dataAccessPoint = FusekiBuilder.buildDataAccessPoint(subject, registry) ;
+            dataAccessPoint.getDataService().goActive();
+            if ( ! datasetPath.equals(dataAccessPoint.getName()) )
+                FmtLog.warn(action.log, "Inconsistent names: datasetPath = %s; DataAccessPoint name = %s", datasetPath, dataAccessPoint);
+            
+            action.getDataAccessPointRegistry().register(dataAccessPoint) ;
+            action.getResponse().setContentType(WebContent.contentTypeTextPlain); 
+            ServletOps.success(action) ;
+            system.commit();
+            committed = true ;
+            
+        } catch (IOException ex) { IO.exception(ex); }
+        finally { 
+            if ( ! committed ) {
+                if ( systemFileCopy != null ) FileOps.deleteSilent(systemFileCopy);
+                if ( configFile != null ) FileOps.deleteSilent(configFile);
+                system.abort() ; 
+            }
+            system.end() ; 
+        }
+        return null ;
+    }
+
+    @Override
+    protected JsonValue execPostItem(HttpAction action) {
+        String name = action.getDatasetName() ;
+        if ( name == null )
+            name = "''" ;
+        action.log.info(format("[%d] POST dataset %s", action.id, name)) ;
+        
+        if ( action.getDataAccessPoint() == null )
+            ServletOps.errorNotFound("Not found: dataset "+action.getDatasetName());
+        
+        DataService dSrv = action.getDataService() ;
+        if ( dSrv == null )
+            // If not set explicitly, take from DataAccessPoint
+            dSrv = action.getDataAccessPoint().getDataService() ;
+        
+        String s = action.request.getParameter("state") ;
+        if ( s == null || s.isEmpty() )
+            ServletOps.errorBadRequest("No state change given") ;
+
+        // setDatasetState is a transaction on the persistent state of the server. 
+        if ( s.equalsIgnoreCase("active") ) {
+            action.log.info(format("[%d] REBUILD DATASET %s", action.id, name)) ;
+            setDatasetState(name, FusekiVocab.stateActive) ;
+            dSrv.goActive() ; 
+            // DatasetGraph dsg = ???? ;
+            //dSrv.activate(dsg) ; 
+            //dSrv.activate() ;
+        } else if ( s.equalsIgnoreCase("offline") ) {
+            action.log.info(format("[%d] OFFLINE DATASET %s", action.id, name)) ;
+            //DataAccessPoint access = action.getDataAccessPoint() ;
+            //access.goOffline() ;
+            dSrv.goOffline() ;  // Affects the target of the name. 
+            setDatasetState(name, FusekiVocab.stateOffline) ;  
+            //dSrv.offline() ;
+        } else if ( s.equalsIgnoreCase("unlink") ) {
+            action.log.info(format("[%d] UNLINK ACCESS NAME %s", action.id, name)) ;
+            //DataAccessPoint access = action.getDataAccessPoint() ;
+            ServletOps.errorNotImplemented("unlink: dataset"+action.getDatasetName());
+            //access.goOffline() ;
+            // Registry?
+        }
+        else
+            ServletOps.errorBadRequest("State change operation '"+s+"' not recognized");
+        return null ;
+    }
+
+    // ---- DELETE
+    
+    @Override
+    protected void execDeleteItem(HttpAction action) {
+        // Does not exist?
+        String name = action.getDatasetName() ;
+        if ( name == null )
+            name = "" ;
+        action.log.info(format("[%d] DELETE ds=%s", action.id, name)) ;
+
+        if ( ! action.getDataAccessPointRegistry().isRegistered(name) )
+            ServletOps.errorNotFound("No such dataset registered: "+name);
+
+        // This acts as a lock. 
+        systemDSG.begin(ReadWrite.WRITE) ;
+        boolean committed = false ;
+
+        try {
+            // Here, go offline.
+            // Need to reference count operations when they drop to zero
+            // or a timer goes off, we delete the dataset.
+
+            DataAccessPoint ref = action.getDataAccessPointRegistry().get(name) ;
+            
+            // Redo check inside transaction.
+            if ( ref == null )
+                ServletOps.errorNotFound("No such dataset registered: "+name);
+            
+            String filename = name.startsWith("/") ? name.substring(1) : name;
+            List<String> configurationFiles = FusekiSystem.existingConfigurationFile(filename);
+            if  ( configurationFiles.size() != 1 ) {
+                // This should not happen.
+                action.log.warn(format("[%d] There are %d configuration files, not one.", action.id, configurationFiles.size()));
+                ServletOps.errorOccurred(
+                    format(
+                        "There are %d configuration files, not one. Delete not performed; clearup of the filesystem needed.",
+                        action.id, configurationFiles.size()));
+            }
+            
+            String cfgPathname = configurationFiles.get(0);
+            
+            // Delete configuration file.
+            // Once deleted, server restart will not have the database. 
+            FileOps.deleteSilent(cfgPathname);
+
+            // Get before removing.
+            DataService dataService = ref.getDataService();
+            
+            // Make it invisible in this running server.
+            action.getDataAccessPointRegistry().remove(name);
+
+            // Delete the database for real only when it is in the server "run/databases"
+            // area. Don't delete databases that reside elsewhere. We do delete the
+            // configuration file, so the databases will not be associated with the server
+            // anymore.
+            
+            // JENA-1586: Remove from current running Fuseki server.
+
+            boolean isTDB1 = org.apache.jena.tdb.sys.TDBInternal.isTDB1(dataService.getDataset());
+            boolean isTDB2 = org.apache.jena.tdb2.sys.TDBInternal.isTDB2(dataService.getDataset());
+
+            dataService.shutdown();
+            // JENA-1481: Really delete files.
+            if ( ( isTDB1 || isTDB2 ) ) {
+                // Delete databases created by the UI, or the admin operation, which are
+                // in predictable, unshared location on disk.
+                // There may not be any database files, the in-memory case.
+                Path pDatabase = FusekiSystem.dirDatabases.resolve(filename);
+                if ( Files.exists(pDatabase)) {
+                    try {
+                        if ( Files.isSymbolicLink(pDatabase)) {
+                            action.log.info(format("[%d] Database is a symbolic link, not removing files", action.id, pDatabase));
+                        } else {
+                            IO.deleteAll(pDatabase);
+                            action.log.info(format("[%d] Deleted database files %s", action.id, pDatabase));
+                        }
+                    } catch (RuntimeIOException ex) {
+                        action.log.error(format("[%d] Error while deleting database files %s: %s", action.id, pDatabase, ex.getMessage()), ex);
+                        // But we have managed to remove it from the running server, and removed its configuration, so declare victory. 
+                    }
+                }
+            }
+            
+            // -- System database
+            // Find graph associated with this dataset name.
+            // (Statically configured databases aren't in the system database.)
+            Node n = NodeFactory.createLiteral(DataAccessPoint.canonical(name)) ;
+            Quad q = getOne(systemDSG, null, null, pServiceName.asNode(), n) ;
+//            if ( q == null )
+//                ServletOps.errorBadRequest("Failed to find dataset for '"+name+"'");
+            if ( q != null ) {
+                Node gn = q.getGraph() ;
+                //action.log.info("SHUTDOWN NEEDED"); // To ensure it goes away?
+                systemDSG.deleteAny(gn, null, null, null) ;
+            }
+            systemDSG.commit() ;
+            committed = true ;
+            ServletOps.success(action) ;
+        } finally { 
+            if ( ! committed ) systemDSG.abort() ; 
+            systemDSG.end() ; 
+        }
+    }
+
+    private static void assemblerFromBody(HttpAction action, StreamRDF dest) {
+        bodyAsGraph(action, dest) ;
+    }
+
+    private static Map<String, String> dbTypeToTemplate = new HashMap<>();
+    static {
+        dbTypeToTemplate.put(tDatabaseTDB,  Template.templateTDB1_FN);
+        dbTypeToTemplate.put(tDatabaseTDB2, Template.templateTDB2_FN);
+        dbTypeToTemplate.put(tDatabaseMem,  Template.templateTIM_MemFN);
+    }
+    
+    private static void assemblerFromForm(HttpAction action, StreamRDF dest) {
+        String dbType = action.getRequest().getParameter(paramDatasetType) ;
+        String dbName = action.getRequest().getParameter(paramDatasetName) ;
+        if ( StringUtils.isBlank(dbType) || StringUtils.isBlank(dbName) )
+            ServletOps.errorBadRequest("Required parameters: dbName and dbType");
+        
+        Map<String, String> params = new HashMap<>() ;
+        
+        if ( dbName.startsWith("/") )
+            params.put(Template.NAME, dbName.substring(1)) ;
+        else
+            params.put(Template.NAME, dbName) ;
+        FusekiSystem.addGlobals(params); 
+        
+        //action.log.info(format("[%d] Create database : name = %s, type = %s", action.id, dbName, dbType )) ;
+        
+        String template = dbTypeToTemplate.get(dbType.toLowerCase(Locale.ROOT));
+        if ( template == null )
+                ServletOps.errorBadRequest(format("dbType can be only '%s', '%s' or '%s'", tDatabaseTDB, tDatabaseTDB2, tDatabaseMem)) ;
+        
+        String syntax =  TemplateFunctions.templateFile(template, params, Lang.TTL) ;
+        RDFParser.create().source(new StringReader(syntax)).base("http://base/").lang(Lang.TTL).parse(dest);
+    }
+
+    private static void assemblerFromUpload(HttpAction action, StreamRDF dest) {
+        Upload.fileUploadWorker(action, dest);
+    }
+
+    // Persistent state change.
+    private static void setDatasetState(String name, Resource newState) {
+        boolean committed = false ;
+        system.begin(ReadWrite.WRITE) ;
+        try {
+            String dbName = name ;
+            if ( dbName.startsWith("/") )
+                dbName = dbName.substring(1) ;
+            
+            String update =  StrUtils.strjoinNL
+                (FusekiConst.PREFIXES,
+                 "DELETE { GRAPH ?g { ?s fu:status ?state } }",
+                 "INSERT { GRAPH ?g { ?s fu:status "+FmtUtils.stringForRDFNode(newState)+" } }",
+                 "WHERE {",
+                 "   GRAPH ?g { ?s fu:name '"+dbName+"' ; ",
+                 "                 fu:status ?state .",
+                 "   }",
+                 "}"
+                 ) ;
+            UpdateRequest req =  UpdateFactory.create(update) ;
+            UpdateAction.execute(req, system);
+            system.commit();
+            committed = true ;
+        } finally { 
+            if ( ! committed ) system.abort() ; 
+            system.end() ; 
+        }
+    }
+    
+    // ---- Auxiliary functions
+
+    private static Quad getOne(DatasetGraph dsg, Node g, Node s, Node p, Node o) {
+        Iterator<Quad> iter = dsg.findNG(g, s, p, o) ;
+        if ( ! iter.hasNext() )
+            return null ;
+        Quad q = iter.next() ;
+        if ( iter.hasNext() )
+            return null ;
+        return q ;
+    }
+    
+    private static Statement getOne(Model m, Resource s, Property p, RDFNode o) {
+        StmtIterator iter = m.listStatements(s, p, o) ;
+        if ( ! iter.hasNext() )
+            return null ;
+        Statement stmt = iter.next() ;
+        if ( iter.hasNext() )
+            return null ;
+        return stmt ;
+    }
+    
+    // XXX Merge with Upload.incomingData
+    
+    private static void bodyAsGraph(HttpAction action, StreamRDF dest) {
+        HttpServletRequest request = action.request ;
+        String base = ActionLib.wholeRequestURL(request) ;
+        ContentType ct = FusekiLib.getContentType(request) ;
+        Lang lang = RDFLanguages.contentTypeToLang(ct.getContentType()) ;
+        if ( lang == null ) {
+            ServletOps.errorBadRequest("Unknown content type for triples: " + ct) ;
+            return ;
+        }
+        InputStream input = null ;
+        try { input = request.getInputStream() ; } 
+        catch (IOException ex) { IO.exception(ex) ; }
+
+        // Don't log - assemblers are typically small.
+        // Adding this to the log confuses things.
+        // Reserve logging for data uploads. 
+//        long len = request.getContentLengthLong() ;
+//        if ( action.verbose ) {
+//            if ( len >= 0 )
+//                alog.info(format("[%d]   Body: Content-Length=%d, Content-Type=%s, Charset=%s => %s", action.id, len,
+//                                ct.getContentType(), ct.getCharset(), lang.getName())) ;
+//            else
+//                alog.info(format("[%d]   Body: Content-Type=%s, Charset=%s => %s", action.id, ct.getContentType(),
+//                                ct.getCharset(), lang.getName())) ;
+//        }
+        dest.prefix("root", base+"#");
+        ActionLib.parse(action, dest, input, lang, base) ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/e8abcbb6/jena-fuseki2/jena-fuseki-webapp/src/main/java/org/apache/jena/fuseki/mgt/ActionLogs.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-webapp/src/main/java/org/apache/jena/fuseki/mgt/ActionLogs.java b/jena-fuseki2/jena-fuseki-webapp/src/main/java/org/apache/jena/fuseki/mgt/ActionLogs.java
new file mode 100644
index 0000000..aeb83f4
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-webapp/src/main/java/org/apache/jena/fuseki/mgt/ActionLogs.java
@@ -0,0 +1,60 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.fuseki.mgt;
+
+import static org.apache.jena.riot.WebContent.charsetUTF8 ;
+import static org.apache.jena.riot.WebContent.contentTypeTextPlain ;
+
+import java.io.IOException ;
+
+import javax.servlet.ServletOutputStream ;
+import javax.servlet.http.HttpServletRequest ;
+import javax.servlet.http.HttpServletResponse ;
+
+import org.apache.jena.fuseki.ctl.ActionCtl;
+import org.apache.jena.fuseki.servlets.HttpAction ;
+import org.apache.jena.fuseki.servlets.ServletOps ;
+
+public class ActionLogs extends ActionCtl
+{
+    public ActionLogs() { super() ; } 
+    
+    @Override
+    protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
+        doCommon(req, resp); 
+    }
+    
+    @Override
+    protected void perform(HttpAction action) {
+        execGet(action) ;
+    }
+
+    protected void execGet(HttpAction action) {
+        try {
+            HttpServletResponse response = action.response ;
+            ServletOutputStream out = response.getOutputStream() ;
+            response.setContentType(contentTypeTextPlain) ;
+            response.setCharacterEncoding(charsetUTF8) ;
+            out.println("Not implemented yet") ;
+            out.println() ; 
+            out.flush() ;
+            ServletOps.success(action);
+        } catch (IOException ex) { ServletOps.errorOccurred(ex) ; }
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/e8abcbb6/jena-fuseki2/jena-fuseki-webapp/src/main/java/org/apache/jena/fuseki/mgt/ActionServerStatus.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-webapp/src/main/java/org/apache/jena/fuseki/mgt/ActionServerStatus.java b/jena-fuseki2/jena-fuseki-webapp/src/main/java/org/apache/jena/fuseki/mgt/ActionServerStatus.java
new file mode 100644
index 0000000..d3c0873
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-webapp/src/main/java/org/apache/jena/fuseki/mgt/ActionServerStatus.java
@@ -0,0 +1,117 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.fuseki.mgt;
+
+import static org.apache.jena.riot.WebContent.charsetUTF8 ;
+import static org.apache.jena.riot.WebContent.contentTypeJSON ;
+
+import java.io.IOException ;
+
+import javax.servlet.ServletOutputStream ;
+import javax.servlet.http.HttpServletRequest ;
+import javax.servlet.http.HttpServletResponse ;
+
+import org.apache.jena.atlas.json.JSON ;
+import org.apache.jena.atlas.json.JsonBuilder ;
+import org.apache.jena.atlas.json.JsonValue ;
+import org.apache.jena.fuseki.Fuseki ;
+import org.apache.jena.fuseki.ctl.ActionCtl;
+import org.apache.jena.fuseki.ctl.JsonDescription;
+import org.apache.jena.fuseki.server.DataAccessPointRegistry ;
+import org.apache.jena.fuseki.server.ServerConst;
+import org.apache.jena.fuseki.servlets.HttpAction ;
+import org.apache.jena.fuseki.servlets.ServletOps ;
+
+/** Description of datasets for a server */ 
+public class ActionServerStatus extends ActionCtl
+{
+    public ActionServerStatus() { super() ; }
+    
+    @Override
+    protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
+        doCommon(req, resp) ;
+    }
+
+    @Override
+    protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
+        doCommon(req, resp) ;
+    }
+
+    @Override
+    protected void perform(HttpAction action) {
+        try {
+            description(action) ;
+            ServletOps.success(action) ;
+        } catch (IOException e) {
+            ServletOps.errorOccurred(e) ;
+        }
+    }
+    
+    private void description(HttpAction action) throws IOException {
+        ServletOutputStream out = action.response.getOutputStream() ;
+        action.response.setContentType(contentTypeJSON);
+        action.response.setCharacterEncoding(charsetUTF8) ;
+        
+        JsonBuilder builder = new JsonBuilder() ; 
+        builder.startObject() ;
+        describeServer(builder, action.request.getLocalPort()) ;
+        describeDatasets(builder, action.getDataAccessPointRegistry()) ;
+        builder.finishObject() ;
+        
+        JsonValue v = builder.build() ;
+        JSON.write(out, v) ;
+        out.println() ; 
+        out.flush() ;
+    }
+
+    private void describeServer(JsonBuilder builder, int requestPort) {
+        String versionStr = Fuseki.VERSION ;
+        String builtDateStr = Fuseki.BUILD_DATE ;
+        if ( versionStr == null || versionStr.startsWith("${") )
+            versionStr = "Development" ;
+        if ( builtDateStr == null || builtDateStr.startsWith("${") )
+            builtDateStr = "Unknown" ;
+
+//        builder
+//            .key(JsonConst.server)
+//            .startObject()
+//            .key(JsonConst.port).value(port)
+//            .finishObject() ;
+//        builder
+//            .key(JsonConst.admin)
+//            .startObject()
+//            .key(JsonConst.port).value(requestPort)
+//            .finishObject() ;
+
+        builder
+            .key(ServerMgtConst.version).value(versionStr)
+            .key(ServerMgtConst.built).value(builtDateStr)
+            .key(ServerMgtConst.startDT).value(Fuseki.serverStartedAt())
+            .key(ServerMgtConst.uptime).value(Fuseki.serverUptimeSeconds())
+            ;
+            
+    }
+
+    private void describeDatasets(JsonBuilder builder, DataAccessPointRegistry registry) {
+        builder.key(ServerConst.datasets) ;
+        JsonDescription.arrayDatasets(builder, registry);
+    }
+
+}
+