http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/Backup.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/Backup.java 
b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/Backup.java
new file mode 100644
index 0000000..1b1d823
--- /dev/null
+++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/Backup.java
@@ -0,0 +1,102 @@
+/**
+ * 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 java.io.* ;
+import java.util.HashSet ;
+import java.util.Set ;
+import java.util.zip.GZIPOutputStream ;
+
+import org.apache.jena.atlas.io.IO ;
+import org.apache.jena.atlas.lib.FileOps ;
+import org.apache.jena.atlas.logging.Log ;
+import org.apache.jena.fuseki.Fuseki ;
+import org.apache.jena.fuseki.FusekiException ;
+import org.apache.jena.riot.Lang ;
+import org.apache.jena.riot.RDFDataMgr ;
+
+import com.hp.hpl.jena.sparql.core.DatasetGraph ;
+import com.hp.hpl.jena.sparql.util.Utils ;
+
+/** Perform a backup */ 
+public class Backup
+{
+    public static final String BackupArea = "backups" ;
+
+    public static String chooseFileName(String dsName) {
+        FileOps.ensureDir(BackupArea) ;
+        final String ds = dsName.startsWith("/") ? dsName : "/" + dsName ;
+    
+        String timestamp = Utils.nowAsString("yyyy-MM-dd_HH-mm-ss") ;
+        final String filename = BackupArea + ds + "_" + timestamp ;
+        return filename ;
+    }
+    
+    // Rcord of all backups so we don't attempt to backup the
+    // same dataset multiple times at the same time. 
+    private static Set<DatasetGraph> activeBackups = new HashSet<>() ;
+    
+    public static void backup(DatasetGraph dsg, String backupfile) {
+        if ( !backupfile.endsWith(".nq") )
+            backupfile = backupfile + ".nq" ;
+
+        // Per backup source lock. 
+        synchronized(activeBackups) {
+            // Atomically check-and-set
+            if ( activeBackups.contains(backupfile) )
+                Log.warn(Fuseki.serverLog, "Backup already in progress") ;
+            activeBackups.add(dsg) ;
+        }
+
+        OutputStream out = null ;
+        try {
+            
+            if ( true ) {
+                // This seems to achive about the same as "gzip -6"
+                // It's not too expensive in elapsed time but it's not
+                // zero cost. GZip, large buffer.
+                out = new FileOutputStream(backupfile + ".gz") ;
+                out = new GZIPOutputStream(out, 8 * 1024) ;
+                out = new BufferedOutputStream(out) ;
+            } else {
+                out = new FileOutputStream(backupfile) ;
+                out = new BufferedOutputStream(out) ;
+            }
+
+            RDFDataMgr.write(out, dsg, Lang.NQUADS) ;
+            out.close() ;
+            out = null ;
+        } catch (FileNotFoundException e) {
+            Log.warn(Fuseki.serverLog, "File not found: " + backupfile) ;
+            throw new FusekiException("File not found: " + backupfile) ;
+        } catch (IOException e) {
+            IO.exception(e) ;
+        } finally {
+            try {
+                if ( out != null )
+                    out.close() ;
+            } catch (IOException e) { /* ignore */}
+            // Remove lock.
+            synchronized(activeBackups) {
+                activeBackups.remove(dsg) ;
+            }
+        }
+    }
+}
+

http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/DumpServlet.java
----------------------------------------------------------------------
diff --git 
a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/DumpServlet.java 
b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/DumpServlet.java
new file mode 100644
index 0000000..0b2a070
--- /dev/null
+++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/DumpServlet.java
@@ -0,0 +1,312 @@
+/*
+ * 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.
+ */
+
+/** A servlet that dumps its request
+ */
+
+// Could be neater - much, much neater!
+package org.apache.jena.fuseki.mgt;
+
+import java.io.BufferedReader ;
+import java.io.IOException ;
+import java.io.PrintWriter ;
+import java.io.StringWriter ;
+import java.util.Date ;
+import java.util.Enumeration ;
+import java.util.Locale ;
+import java.util.Properties ;
+
+import javax.servlet.ServletContext ;
+import javax.servlet.http.Cookie ;
+import javax.servlet.http.HttpServlet ;
+import javax.servlet.http.HttpServletRequest ;
+import javax.servlet.http.HttpServletResponse ;
+
+import org.apache.jena.atlas.io.IO ;
+
+public class DumpServlet extends HttpServlet
+{
+    private static final long serialVersionUID = 99L;  // Serilizable.
+
+
+    public DumpServlet()
+    {
+
+    }
+
+    @Override
+    public void init()
+    {
+        return ;
+    }
+
+    @Override
+    public void doGet(HttpServletRequest req, HttpServletResponse resp)
+    {
+        try {
+            PrintWriter out = resp.getWriter() ;
+            resp.setContentType("text/html");
+
+            String now = new Date().toString() ;
+
+            // HEAD
+            out.println("<html>") ;
+            out.println("<head>") ;
+            out.println("<Title>Dump @ "+now+"</Title>") ;
+            // Reduce the desire to cache it.
+            out.println("<meta CONTENT=now HTTP-EQUIV=expires>") ;
+            out.println("</head>") ;
+
+            // BODY
+            out.println("<body>") ;
+            out.println("<pre>") ;
+
+            out.println("Dump : "+now);
+            out.println() ;
+            out.println("==== Request");
+            out.println() ;
+            out.print(dumpRequest(req)) ;
+            out.println() ;
+                        
+            out.println(">>>> Body");
+            out.println() ;
+            printBody(out, req) ;
+            out.println("<<<< Body");
+            
+            out.println("==== ServletContext");
+            out.println() ;
+            out.print(dumpServletContext());
+            out.println() ;
+
+            out.println("==== Environment");
+            out.println() ;
+            out.print(dumpEnvironment());
+            out.println() ;
+
+            out.println("</pre>") ;
+
+            out.println("</body>") ;
+            out.println("</html>") ;
+            out.flush() ;
+        } catch (IOException e)
+        { }
+    }
+
+    // This resets the input stream
+
+    static public String dumpRequest(HttpServletRequest req)
+    {
+        try ( StringWriter sw = new StringWriter() ;
+              PrintWriter pw = new PrintWriter(sw) ) {
+            // Standard environment
+            pw.println("Method:                 "+req.getMethod());
+            pw.println("getContentLength:       
"+Integer.toString(req.getContentLength()));
+            pw.println("getContentType:         "+req.getContentType());
+            pw.println("getRequestURI:          "+req.getRequestURI());
+            pw.println("getRequestURL:          "+req.getRequestURL());
+            pw.println("getContextPath:         "+req.getContextPath());
+            pw.println("getServletPath:         "+req.getServletPath());
+            pw.println("getPathInfo:            "+req.getPathInfo());
+            pw.println("getPathTranslated:      "+req.getPathTranslated());
+            pw.println("getQueryString:         "+req.getQueryString());
+            pw.println("getProtocol:            "+req.getProtocol());
+            pw.println("getScheme:              "+req.getScheme());
+            pw.println("getServerName:          "+req.getServerName());
+            pw.println("getServerPort:          "+req.getServerPort());
+            pw.println("getRemoteUser:          "+req.getRemoteUser());
+            pw.println("getRemoteAddr:          "+req.getRemoteAddr());
+            pw.println("getRemoteHost:          "+req.getRemoteHost());
+            pw.println("getRequestedSessionId:  "+req.getRequestedSessionId());
+            {
+                Cookie c[] = req.getCookies() ;
+                if ( c == null )
+                    pw.println("getCookies:            <none>");
+                else
+                {
+                    for ( int i = 0 ; i < c.length ; i++ )            
+                    {
+                        pw.println("Cookie:        "+c[i].getName());
+                        pw.println("    value:     "+c[i].getValue());
+                        pw.println("    version:   "+c[i].getVersion());
+                        pw.println("    comment:   "+c[i].getComment());
+                        pw.println("    domain:    "+c[i].getDomain());
+                        pw.println("    maxAge:    "+c[i].getMaxAge());
+                        pw.println("    path:      "+c[i].getPath());
+                        pw.println("    secure:    "+c[i].getSecure());
+                        pw.println();
+                    }
+                }
+            }
+            
+            {
+                // To do: create a string for the output so can send to 
console and return it.
+                Enumeration<String> en = req.getHeaderNames() ;
+
+                for ( ; en.hasMoreElements() ; )
+                {
+                    String name = en.nextElement() ;
+                    String value = req.getHeader(name) ;
+                    pw.println("Head: "+name + " = " + value) ;
+                }
+            }
+            
+            Enumeration<String> en2 = req.getAttributeNames() ;
+            if ( en2.hasMoreElements() )
+                pw.println();
+            for ( ; en2.hasMoreElements() ; )
+            {
+                String name = en2.nextElement() ;
+                String value = req.getAttribute(name).toString() ;
+                pw.println("Attr: "+name + " = " + value) ;
+            }
+
+            // Note that doing this on a form causes the forms content (body) 
to be read
+            // and parsed as form variables.
+//            en = req.getParameterNames() ;
+//            if ( en.hasMoreElements() )
+//                pw.println();
+//            for ( ; en.hasMoreElements() ; )
+//            {
+//                String name = (String)en.nextElement() ;
+//                String value = req.getParameter(name) ;
+//                pw.println("Param: "+name + " = " + value) ;
+//            }
+
+
+            
+//            MultiMap<String, String> map = WebLib.parseQueryString(req) ;
+//            for ( String name : map.keys() )
+//                for ( String value : map.get(name) )
+//                    pw.println("Param: "+name + " = " + value) ;
+            
+            Enumeration<Locale> en = req.getLocales() ;
+            if ( en.hasMoreElements() )
+                pw.println();
+            for ( ; en.hasMoreElements() ; )
+            {
+                String name = en.nextElement().toString() ;
+                pw.println("Locale: "+name) ;
+            }
+
+            pw.println() ;
+            //printBody(pw, req) ;
+
+            return sw.toString() ;
+        } catch (IOException e) { return null ; }
+    }
+
+    static void printBody(PrintWriter pw, HttpServletRequest req) throws 
IOException
+    {
+        BufferedReader in = req.getReader() ;
+        if ( req.getContentLength() > 0 )
+            // Need +2 because last line may not have a CR/LF on it.
+            in.mark(req.getContentLength()+2) ;
+        else
+            // This is a dump - try to do something that works, even if 
inefficient.
+            in.mark(100*1024) ;
+
+        while(true)
+        {
+            String x = in.readLine() ;
+            if ( x == null )
+                break ;
+            x = x.replaceAll("&", "&amp;") ;
+            x = x.replaceAll("<", "&lt;") ;
+            x = x.replaceAll(">", "&gt;") ;
+            pw.println(x) ;
+        }
+        try { in.reset() ; } catch (IOException e) { 
System.out.println("DumpServlet: Reset of content failed: "+e) ; }
+    }
+    
+    /**
+     * <code>dumpEnvironment</code>
+     * @return String that is the HTML of the System properties as name/value 
pairs.
+     * The values are with single quotes independent of whether or not the 
value has
+     * single quotes in it.
+     */
+    static public String dumpEnvironment()
+    {
+        Properties properties = System.getProperties();
+        try ( StringWriter sw = new StringWriter() ;
+            PrintWriter pw = new PrintWriter(sw) ; ) {
+            Enumeration<Object> en = properties.keys();
+            while(en.hasMoreElements())
+            {
+                String key = en.nextElement().toString();
+                pw.println(key+": '"+properties.getProperty(key)+"'");
+            }
+
+            pw.println() ;
+            return sw.toString() ;
+        } catch (IOException e) { IO.exception(e); return null ; }
+    }
+
+    public String dumpServletContext()
+    {
+        try ( StringWriter sw = new StringWriter() ;
+              PrintWriter pw = new PrintWriter(sw) ; ) {
+            ServletContext sc =  getServletContext();
+            pw.println("majorVersion: '"+sc.getMajorVersion()+"'");
+            pw.println("minorVersion: '"+sc.getMinorVersion()+"'");
+            pw.println("contextName:  '"+sc.getServletContextName()+"'");
+            pw.println("servletInfo:  '"+getServletInfo()+"'");
+            pw.println("serverInfo:  '"+sc.getServerInfo()+"'");
+    
+            {
+                Enumeration<String> en = sc.getInitParameterNames();
+                if (en != null) {
+                    pw.println("initParameters: ");
+                    while(en.hasMoreElements())
+                    {
+                        String key = en.nextElement();
+                        pw.println(key+": '"+sc.getInitParameter(key)+"'");
+                    }
+                }
+            }
+            
+            {
+                Enumeration<String> en = sc.getAttributeNames();
+                if (en != null) {
+                    pw.println("attributes: ");
+                    while(en.hasMoreElements())
+                    {
+                        String key = en.nextElement();
+                        pw.println(key+": '"+sc.getAttribute(key)+"'");
+                    }
+                }
+            }
+            pw.println() ;
+         
+             return sw.toString() ;
+        } catch (IOException e) { IO.exception(e); return null ; }
+    }
+
+    
+    @Override
+    public void doPost(HttpServletRequest req, HttpServletResponse resp)
+    {
+        doGet(req, resp) ;
+    }
+
+
+    @Override
+    public String getServletInfo()
+    {
+        return "Dump";
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/JsonConst.java
----------------------------------------------------------------------
diff --git 
a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/JsonConst.java 
b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/JsonConst.java
new file mode 100644
index 0000000..e943ac1
--- /dev/null
+++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/JsonConst.java
@@ -0,0 +1,52 @@
+/**
+ * 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 ;
+
+public class JsonConst
+{
+    public static final String taskId       = "taskId" ;
+    public static final String task         = "task" ;
+    public static final String datasets     = "datasets" ;
+
+    public static final String finished     = "finished" ;
+    public static final String started      = "started" ;
+
+    public static final String uptime       = "uptime" ;
+    public static final String startDT      = "startDateTime" ;
+    public static final String server       = "server" ;
+    public static final String port         = "port" ;
+    public static final String hostname     = "hostname" ;
+    public static final String admin        = "admin" ;
+    public static final String version      = "version" ;
+    public static final String built        = "built" ;
+
+    public static final String services     = "services" ;
+    public static final String operation    = "operation" ;
+    public static final String description  = "description" ;
+    public static final String endpoints    = "endpoints" ;
+
+    public static final String dsName       = "ds.name" ;
+    public static final String dsState      = "ds.state" ;
+    public static final String dsService    = "ds.services" ;
+
+    public static final String srvType          = "srv.type" ;
+    public static final String srvDescription   = "srv.description" ;
+    public static final String srvEndpoints     = "srv.endpoints" ;
+
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/JsonDescription.java
----------------------------------------------------------------------
diff --git 
a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/JsonDescription.java 
b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/JsonDescription.java
new file mode 100644
index 0000000..f54ead2
--- /dev/null
+++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/JsonDescription.java
@@ -0,0 +1,73 @@
+/**
+ * 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 java.util.List ;
+
+import org.apache.jena.atlas.json.JsonBuilder ;
+import org.apache.jena.fuseki.server.DataAccessPoint ;
+import org.apache.jena.fuseki.server.DataAccessPointRegistry ;
+import org.apache.jena.fuseki.server.Endpoint ;
+import org.apache.jena.fuseki.server.OperationName ;
+
+/** Create a description of a service */
+public class JsonDescription {
+    
+    public static void arrayDatasets(JsonBuilder builder, 
DataAccessPointRegistry registry) {
+        builder.startArray() ;
+        for ( String ds : registry.keys() ) {
+            DataAccessPoint access = DataAccessPointRegistry.get().get(ds) ;
+            JsonDescription.describe(builder, access) ;
+        }
+        builder.finishArray() ;
+    }
+    
+    public static void describe(JsonBuilder builder, DataAccessPoint access) {
+        builder.startObject() ;
+        builder.key(JsonConst.dsName).value(access.getName()) ;
+        
+        
builder.key(JsonConst.dsState).value(access.getDataService().isAcceptingRequests())
 ;
+        
+        builder.key(JsonConst.dsService) ;
+        builder.startArray() ;
+        
+        for ( OperationName opName : access.getDataService().getOperations() ) 
{
+            List<Endpoint> endpoints = 
access.getDataService().getOperation(opName) ;
+            describe(builder, opName, endpoints) ;
+        }
+        builder.finishArray() ;
+        builder.finishObject() ;
+    }
+    
+    private static void describe(JsonBuilder builder, OperationName opName, 
List<Endpoint> endpoints) {
+        builder.startObject() ;
+        
+        builder.key(JsonConst.srvType).value(opName.name()) ;
+        builder.key(JsonConst.srvDescription).value(opName.getDescription()) ;
+
+        builder.key(JsonConst.srvEndpoints) ;
+        builder.startArray() ;
+        for ( Endpoint endpoint : endpoints )
+            builder.value(endpoint.getEndpoint()) ;
+        builder.finishArray() ;
+
+        builder.finishObject() ;
+    }
+}
+

http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/MgtConst.java
----------------------------------------------------------------------
diff --git 
a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/MgtConst.java 
b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/MgtConst.java
new file mode 100644
index 0000000..e398894
--- /dev/null
+++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/MgtConst.java
@@ -0,0 +1,30 @@
+/**
+ * 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;
+
+/** Various contants used in the admin functions */ 
+public class MgtConst {
+    public static final String  opDump      = "dump" ;  
+    public static final String  opPing      = "ping" ;
+    
+    public static final String  opStats     = "stats" ;  
+    public static final String  opDatasets  = "datasets" ;
+    public static final String  opServer    = "server" ;
+}
+

http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/MgtJMX.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/MgtJMX.java 
b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/MgtJMX.java
new file mode 100644
index 0000000..f9023fe
--- /dev/null
+++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/MgtJMX.java
@@ -0,0 +1,61 @@
+/**
+ * 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 ;
+
+
+public class MgtJMX
+{
+  public static void removeJMX() {  }
+    
+//    public static void addJMX() {
+//        DatasetRegistry registry = DatasetRegistry.get() ;
+//        for (String ds : registry.keys()) {
+//            DataAccessPoint dsRef = registry.get(ds) ;
+//            addJMX(dsRef) ;
+//        }
+//    }
+//
+//    private static void addJMX(DataAccessPoint dapt) {
+//        String x = datasetNames ;
+//        // if ( x.startsWith("/") )
+//        // x = x.substring(1) ;
+//        ARQMgt.register(Fuseki.PATH + ".dataset:name=" + x, dapt) ;
+//        // For all endpoints
+//        for (ServiceRef sRef : dapt.getServiceRefs()) {
+//            ARQMgt.register(Fuseki.PATH + ".dataset:name=" + x + "/" + 
sRef.name, sRef) ;
+//        }
+//    }
+//
+//    public static void removeJMX() {
+//        DatasetRegistry registry = DatasetRegistry.get() ;
+//        for (String ds : registry.keys()) {
+//            DataAccessPoint ref = registry.get(ds) ;
+//            removeJMX(ref) ;
+//        }
+//    }
+//
+//    private static void removeJMX(DatasetRef dsRef) {
+//        String x = dsRef.getName() ;
+//        ARQMgt.unregister(Fuseki.PATH + ".dataset:name=" + x) ;
+//        for (ServiceRef sRef : dsRef.getServiceRefs()) {
+//            ARQMgt.unregister(Fuseki.PATH + ".dataset:name=" + x + "/" + 
sRef.name) ;
+//        }
+//    }
+
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/migrate/DatasetGraphSwitchable.java
----------------------------------------------------------------------
diff --git 
a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/migrate/DatasetGraphSwitchable.java
 
b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/migrate/DatasetGraphSwitchable.java
new file mode 100644
index 0000000..0e075f6
--- /dev/null
+++ 
b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/migrate/DatasetGraphSwitchable.java
@@ -0,0 +1,88 @@
+/**
+ * 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.migrate;
+
+import java.util.concurrent.atomic.AtomicReference ;
+
+import org.apache.jena.fuseki.FusekiException ;
+
+import com.hp.hpl.jena.sparql.core.DatasetGraph ;
+import com.hp.hpl.jena.sparql.core.DatasetGraphWrapper ;
+
+public class DatasetGraphSwitchable extends DatasetGraphWrapper {
+    // **** Associated query engine factory - QueryEngineFactoryWrapper
+    // which executes on the unwrapped DSG.
+    
+    // *** Modify DatasetGraphWrapper to use a get().
+    
+    // Time to have DatasetGraph.getQueryDataset
+    private final DatasetGraph dsg1 ;
+    private final DatasetGraph dsg2 ;
+    private final AtomicReference<DatasetGraph> current = new 
AtomicReference<DatasetGraph>() ;
+    
+    // Change DatasetGraphWrapper to use protected get() 
+
+    public DatasetGraphSwitchable(DatasetGraph dsg1, DatasetGraph dsg2) {
+        super(null) ;
+        if ( dsg1 == null )
+            // Personally I think IllegalArgumentException is more
+            // appropriate, with NPE for unexpected use of null 
+            // but convention says .... 
+            throw new NullPointerException("First argument is null") ;
+        if ( dsg2 == null )
+            throw new NullPointerException("Second argument is null") ;
+        this.dsg1 = dsg1 ;
+        this.dsg2 = dsg2 ;
+        set(dsg1) ;
+    }
+
+    private void set(DatasetGraph dsg) { current.set(dsg) ; }
+    
+    /** Change to using the other dataset */ 
+    public void flip() {
+        // Don't worry about concurrent calls to flip()
+        // The outcome will be that one call wins (the actual second caller)
+        // and not corrupted data. Noet that get() is only called once per
+        // redirection. 
+        
+        // if dsg1 -- (expected, update)
+        if ( current.compareAndSet(dsg1, dsg2) )
+            return ;
+        // if dsg2 
+        if ( current.compareAndSet(dsg2, dsg1) )
+            return ;
+        throw new FusekiException() ;
+    }
+    
+    /** Current dataset of the switchable pair */
+    public final DatasetGraph getCurrent()  { return get() ; }
+    
+    /** Return dataset1 of the switchable pair */
+    public final DatasetGraph getDataset1() { return dsg1 ; }
+    
+    /** Return dataset2 of the switchable pair */
+    public final DatasetGraph getDataset2() { return dsg2 ; }
+    
+    /** Use dataset1 */
+    public final void useDataset1()         { set(dsg1) ; }
+
+    /** Use dataset2 */
+    public final void useDataset2()         { set(dsg2) ; }
+}
+

http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/migrate/GraphLoadUtils.java
----------------------------------------------------------------------
diff --git 
a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/migrate/GraphLoadUtils.java 
b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/migrate/GraphLoadUtils.java
new file mode 100644
index 0000000..c5f92ae
--- /dev/null
+++ 
b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/migrate/GraphLoadUtils.java
@@ -0,0 +1,76 @@
+/*
+ * 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.migrate;
+
+import org.apache.jena.atlas.web.TypedInputStream ;
+import org.apache.jena.fuseki.Fuseki ;
+import org.apache.jena.riot.RDFDataMgr ;
+import org.apache.jena.riot.system.StreamRDF ;
+import org.apache.jena.riot.system.StreamRDFLib ;
+
+import com.hp.hpl.jena.graph.Factory ;
+import com.hp.hpl.jena.graph.Graph ;
+import com.hp.hpl.jena.rdf.model.Model ;
+import com.hp.hpl.jena.rdf.model.ModelFactory ;
+
+/** A packaging of code to do a controlled read of a graph or model */
+
+public class GraphLoadUtils
+{
+    // ---- Model level
+    
+    public static Model readModel(String uri, int limit)
+    {
+        Graph g = Factory.createGraphMem() ;
+        readUtil(g, uri, limit) ;
+        return ModelFactory.createModelForGraph(g) ;
+    }
+    
+    public static void loadModel(Model model, String uri, int limit) 
+    {
+        Graph g = model.getGraph() ;
+        readUtil(g, uri, limit) ;
+    }
+
+    // ---- Graph level
+    
+    public static Graph readGraph(String uri, int limit)
+    {
+        Graph g = Factory.createGraphMem() ;
+        readUtil(g, uri, limit) ;
+        return g ;
+    }
+    
+    public static void loadGraph(Graph g, String uri, int limit) 
+    {
+        readUtil(g, uri, limit) ;
+    }
+    
+    // ** Worker.
+    private static void readUtil(Graph graph, String uri, int limit)
+    {
+        // We need to do this ourselves, not via riot, to use the 
webStreamManager
+        StreamRDF sink = StreamRDFLib.graph(graph) ;
+        sink = new StreamRDFLimited(sink, limit) ;
+
+        TypedInputStream input = Fuseki.webStreamManager.open(uri) ;
+        RDFDataMgr.parse(sink, input, uri) ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/migrate/Registry.java
----------------------------------------------------------------------
diff --git 
a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/migrate/Registry.java 
b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/migrate/Registry.java
new file mode 100644
index 0000000..bb130c7
--- /dev/null
+++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/migrate/Registry.java
@@ -0,0 +1,42 @@
+/*
+ * 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.migrate;
+
+import java.util.Collection ;
+import java.util.HashMap ;
+import java.util.Map ;
+
+public class Registry<K,T>
+{
+    protected Map<K, T> registry = new HashMap<>() ;
+    
+    public Registry() {}
+    
+    public void put(K key, T value) { registry.put(key, value) ; }
+    
+    public T get(K key) { return registry.get(key) ; }
+    
+    public boolean isRegistered(K key)  { return registry.containsKey(key) ; }
+    public void remove(K key)           { registry.remove(key) ; } 
+    public Collection<K> keys()         { return registry.keySet() ; }
+    //public Iterator<String> keys() { return registry.keySet().iterator() ; }
+    
+    public int size()                   { return registry.size() ; }
+    public boolean isEmpty()            { return registry.isEmpty() ; }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/migrate/StreamRDFLimited.java
----------------------------------------------------------------------
diff --git 
a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/migrate/StreamRDFLimited.java
 
b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/migrate/StreamRDFLimited.java
new file mode 100644
index 0000000..bf9ebf3
--- /dev/null
+++ 
b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/migrate/StreamRDFLimited.java
@@ -0,0 +1,63 @@
+/*
+ * 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.migrate ;
+
+import org.apache.jena.riot.RiotException ;
+import org.apache.jena.riot.system.StreamRDF ;
+import org.apache.jena.riot.system.StreamRDFWrapper ;
+
+import com.hp.hpl.jena.graph.Triple ;
+import com.hp.hpl.jena.sparql.core.Quad ;
+
+/**
+ * Limit triples/quads and stop passing through after a limit is reached.
+ */
+public class StreamRDFLimited extends StreamRDFWrapper {
+    private long       count = 0 ;
+    private final long limit ;
+
+    public StreamRDFLimited(StreamRDF output, long limit) {
+        super(output) ;
+        this.limit = limit ;
+    }
+
+    @Override
+    public void triple(Triple triple) {
+        count++ ;
+        if ( count > limit )
+            throw new RiotException("Limit ("+limit+") reached") ; 
+        super.triple(triple) ;
+    }
+
+    @Override
+    public void quad(Quad quad) {
+        count++ ;
+        if ( count > limit )
+            throw new RiotException("Limit ("+limit+") reached") ; 
+        super.quad(quad) ;
+    }
+
+    public long getCount() {
+        return count ;
+    }
+
+    public long getLimit() {
+        return limit ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/Counter.java
----------------------------------------------------------------------
diff --git 
a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/Counter.java 
b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/Counter.java
new file mode 100644
index 0000000..88d4d37
--- /dev/null
+++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/Counter.java
@@ -0,0 +1,34 @@
+/**
+ * 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.server;
+
+import java.util.concurrent.atomic.AtomicLong ;
+
+/** A statistics counter */
+public class Counter
+{
+    private AtomicLong counter = new AtomicLong(0) ;
+    
+    public Counter()    {}
+    
+    public void inc()   { counter.incrementAndGet() ; } 
+    public void dec()   { counter.decrementAndGet() ; } 
+    public long value() { return counter.get() ; } 
+}
+

http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/CounterMXBean.java
----------------------------------------------------------------------
diff --git 
a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/CounterMXBean.java 
b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/CounterMXBean.java
new file mode 100644
index 0000000..2de7658
--- /dev/null
+++ 
b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/CounterMXBean.java
@@ -0,0 +1,25 @@
+/**
+ * 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.server;
+
+public interface CounterMXBean
+{
+    long getValue() ; 
+}
+

http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/CounterName.java
----------------------------------------------------------------------
diff --git 
a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/CounterName.java 
b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/CounterName.java
new file mode 100644
index 0000000..7a8d306
--- /dev/null
+++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/CounterName.java
@@ -0,0 +1,84 @@
+/**
+ * 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.server;
+
+/** Names for all counters */ 
+public enum CounterName {
+    // There are generic names - apply to all services and datasets - and
+    // also specific ones that relate only to a particular kind of service.
+    
+    // Total request received
+    Requests("requests"),
+    // .. of which some and "good" and some are "bad".
+    // #"good" + #"bad" roughly equals #"requests"
+    // except that the total is incremented at the start, and the outcome at 
the end.
+    // There may also be short term consistency issues.
+    RequestsGood("requests.good"),
+    RequestsBad("requests.bad") ,
+    
+    // SPARQL Protocol - query and update - together with upload.  
+    
+    // Query - standard and ... 
+    QueryTimeouts("query.timeouts") ,
+    QueryExecErrors("query.execerrors") ,
+    QueryIOErrors("query.ioerrors") ,
+    
+    // Update - standard and ...
+    UpdateExecErrors("update.execerrors"),
+    
+    // Upload ... standard counters
+    
+    // Graph Store Protocol. uses HTTP codes.
+
+    // For each HTTP method
+
+    HTTPget("http.get.requests") ,
+    HTTPgetGood("http.get.requests.good") ,
+    HTTPGetBad("http.get.requests.bad") ,
+
+    HTTPpost("http.post.requests") ,
+    HTTPpostGood("http.post.requests.good") ,
+    HTTPpostBad("http.post.requests.bad") ,
+
+    HTTPdelete("http.delete.requests") ,
+    HTTPdeleteGood("http.delete.requests.good") ,
+    HTTPdeleteBad("http.delete.requests.bad") ,
+
+    HTTPput("http.put.requests") ,
+    HTTPputGood("http.put.requests.good") ,
+    HTTPputBad("http.put.requests.bad") ,
+
+    HTTPhead("http.head.requests") ,
+    HTTPheadGood("http.head.requests.good") ,
+    HTTPheadBad("http.head.requests.bad") ,
+
+    HTTPpatch("http.patch.requests") ,
+    HTTPpatchGood("http.patch.requests.good") ,
+    HTTPpatchBad("http.patch.requests.bad") ,
+
+    HTTPoptions("http.options.requests") ,
+    HTTPoptionsGood("http.options.requests.good") ,
+    HTTPoptionsBad("http.options.requests.bad") ,
+    
+    ;
+    
+    public final String name ;
+    private CounterName(String name) { this.name = name ; }
+    
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/CounterSet.java
----------------------------------------------------------------------
diff --git 
a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/CounterSet.java 
b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/CounterSet.java
new file mode 100644
index 0000000..9b8231e
--- /dev/null
+++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/CounterSet.java
@@ -0,0 +1,70 @@
+/**
+ * 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.server ;
+
+import java.util.Collection ;
+import java.util.HashMap ;
+import java.util.Map ;
+
+import org.slf4j.Logger ;
+import org.slf4j.LoggerFactory ;
+
+/** A collection of counters */
+public class CounterSet {
+    private static Logger             log      = 
LoggerFactory.getLogger(CounterSet.class) ;
+
+    private Map<CounterName, Counter> counters = new HashMap<>() ;
+
+    public CounterSet() {}
+
+    public Collection<CounterName> counters() {
+        return counters.keySet() ;
+    }
+
+    public void inc(CounterName c) {
+        get(c).inc() ;
+    }
+
+    public void dec(CounterName c) {
+        get(c).dec() ;
+    }
+
+    public long value(CounterName c) {
+        return get(c).value() ;
+    }
+
+    public void add(CounterName counterName) {
+        if ( counters.containsKey(counterName) ) {
+            log.warn("Duplicate counter in counter set: " + counterName) ;
+            return ;
+        }
+        counters.put(counterName, new Counter()) ;
+    }
+
+    public boolean contains(CounterName cn) {
+        return counters.containsKey(cn) ;
+    }
+
+    public Counter get(CounterName cn) {
+        Counter c = counters.get(cn) ;
+        if ( c == null )
+            log.warn("No counter in counter set: " + cn) ;
+        return c ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/Counters.java
----------------------------------------------------------------------
diff --git 
a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/Counters.java 
b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/Counters.java
new file mode 100644
index 0000000..4e5ca4b
--- /dev/null
+++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/Counters.java
@@ -0,0 +1,25 @@
+/**
+ * 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.server;
+
+/** Objects that have a counter set */ 
+public interface Counters {
+    public  CounterSet getCounters() ;
+}
+

http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/DataAccessPoint.java
----------------------------------------------------------------------
diff --git 
a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/DataAccessPoint.java 
b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/DataAccessPoint.java
new file mode 100644
index 0000000..24275a8
--- /dev/null
+++ 
b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/DataAccessPoint.java
@@ -0,0 +1,75 @@
+/**
+ * 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.server;
+
+import java.util.concurrent.atomic.AtomicLong ;
+
+import org.apache.jena.fuseki.servlets.HttpAction ;
+
+
+/** A name in the URL space of the server */
+public class DataAccessPoint {
+    private final String name ;
+    private DataService dataService ;
+    private DataAccessPoint link ;             // Symbolic link.
+    private AtomicLong requests = new AtomicLong(0) ;
+    
+    public DataAccessPoint(String name) {
+        this.name = canonical(name) ;
+        this.dataService = null ;
+    }
+    
+    public String getName()     { return name ; }
+    
+    public static String canonical(String datasetPath) {
+        if ( datasetPath == null )
+            return datasetPath ;
+        if ( datasetPath.equals("/") )
+            datasetPath = "" ;
+        else
+            if ( !datasetPath.startsWith("/") )
+                datasetPath = "/" + datasetPath ;
+        if ( datasetPath.endsWith("/") )
+            datasetPath = datasetPath.substring(0, datasetPath.length() - 1) ;
+        return datasetPath ;
+    }
+
+    public DataService getDataService() {
+        return dataService;
+    }
+
+    public void setDataService(DataService dataService) {
+        this.dataService = dataService;
+    }
+
+    public DataAccessPoint getLink() {
+        return link;
+    }
+
+    public void setLink(DataAccessPoint link) {
+        this.link = link;
+    }
+
+    public long requestCount()                          { return 
requests.get() ; }
+    
+    public void startRequest(HttpAction httpAction)     { 
requests.incrementAndGet() ; }
+
+    public void finishRequest(HttpAction httpAction)    { 
requests.getAndDecrement() ; }
+}
+

http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/DataAccessPointRegistry.java
----------------------------------------------------------------------
diff --git 
a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/DataAccessPointRegistry.java
 
b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/DataAccessPointRegistry.java
new file mode 100644
index 0000000..19119a9
--- /dev/null
+++ 
b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/DataAccessPointRegistry.java
@@ -0,0 +1,37 @@
+/*
+ * 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.server;
+
+import org.apache.jena.fuseki.FusekiException ;
+import org.apache.jena.fuseki.migrate.Registry ;
+
+public class DataAccessPointRegistry extends Registry<String, DataAccessPoint>
+{
+    public static void register(String name, DataAccessPoint accessPt) {
+        if ( get().isRegistered(name) )
+            throw new FusekiException("Already registered: "+name) ;
+        get().put(name, accessPt);
+    }
+    
+    private static DataAccessPointRegistry singleton = new 
DataAccessPointRegistry() ;
+
+    public static DataAccessPointRegistry get() { return singleton ; }
+    
+    private DataAccessPointRegistry() {}
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/DataService.java
----------------------------------------------------------------------
diff --git 
a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/DataService.java 
b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/DataService.java
new file mode 100644
index 0000000..df9d2b3
--- /dev/null
+++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/DataService.java
@@ -0,0 +1,199 @@
+/**
+ * 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.server;
+
+import static org.apache.jena.fuseki.server.DatasetStatus.CLOSING ;
+import static org.apache.jena.fuseki.server.DatasetStatus.UNINITIALIZED ;
+
+import java.util.* ;
+import java.util.concurrent.atomic.AtomicBoolean ;
+import java.util.concurrent.atomic.AtomicLong ;
+
+import org.apache.jena.atlas.lib.MultiMap ;
+import org.apache.jena.atlas.lib.MultiMapToList ;
+import org.apache.jena.fuseki.DEF ;
+import org.apache.jena.fuseki.Fuseki ;
+import org.apache.jena.fuseki.build.DataServiceDesc ;
+
+import com.hp.hpl.jena.query.ReadWrite ;
+import com.hp.hpl.jena.sparql.core.DatasetGraph ;
+import com.hp.hpl.jena.sparql.core.DatasetGraphFactory ;
+import com.hp.hpl.jena.sparql.core.DatasetGraphReadOnly ;
+import com.hp.hpl.jena.tdb.StoreConnection ;
+import com.hp.hpl.jena.tdb.transaction.DatasetGraphTransaction ;
+
+public class DataService { //implements DatasetMXBean {
+    // XXX Add a "null model assembler".
+    
+    public static DataService serviceOnlyDataService() {
+        return dummy ; 
+    }
+    
+    public static DataService dummy = new DataService(null, null) ;
+    static {
+        dummy.dataset = new 
DatasetGraphReadOnly(DatasetGraphFactory.createMemFixed()) ;
+        dummy.addEndpoint(OperationName.Query, DEF.ServiceQuery) ;
+        dummy.addEndpoint(OperationName.Query, DEF.ServiceQueryAlt) ;
+    }
+    
+    private final DataServiceDesc svcDesc ;
+    private DatasetGraph dataset = null ;              // Only valid if active.
+
+    private MultiMapToList<OperationName, Endpoint> operations     = 
MultiMap.createMapList() ;
+    private Map<String, Endpoint> endpoints                        = new 
HashMap<>() ;
+    
+    private volatile DatasetStatus state = UNINITIALIZED ;
+
+    // DataService-level counters.
+    private final CounterSet counters                   = new CounterSet() ;
+    private final AtomicLong    requestCounter          = new AtomicLong(0) ;  
 
+    private final AtomicBoolean offlineInProgress       = new 
AtomicBoolean(false) ;
+    private final AtomicBoolean acceptingRequests       = new 
AtomicBoolean(true) ;
+
+    public DataService(DataServiceDesc desc, DatasetGraph dataset) {
+        this.svcDesc = desc ;
+        this.dataset = dataset ;
+        counters.add(CounterName.Requests) ;
+        counters.add(CounterName.RequestsGood) ;
+        counters.add(CounterName.RequestsBad) ;
+    }
+    
+    public DatasetGraph getDataset() {
+        return dataset ; 
+    }
+    
+    public void addEndpoint(OperationName operationName, String endpointName) {
+        Endpoint endpoint = new Endpoint(operationName, endpointName) ;
+        endpoints.put(endpointName, endpoint) ;
+        operations.put(operationName, endpoint);
+    }
+    
+    public Endpoint getOperation(String endpointName) {
+        return endpoints.get(endpointName) ;
+    }
+
+    public List<Endpoint> getOperation(OperationName opName) {
+        List<Endpoint> x = operations.get(opName) ;
+        if ( x == null )
+            x = Collections.emptyList() ;
+        return x ;  
+    }
+
+    /** Return the OperationNames available here.
+     *  @see #getOperation(OperationName) to ge the endpoint list
+     */
+    public Collection<OperationName> getOperations() {
+        return operations.keys() ;
+    }
+
+    //@Override
+    public boolean allowUpdate()    { return true ; }
+
+    public void goOffline()         { 
+        offlineInProgress.set(true) ;
+        acceptingRequests.set(false) ;
+        state = DatasetStatus.OFFLINE ; 
+    }
+    
+    public void goActive()         { 
+        offlineInProgress.set(false) ;
+        acceptingRequests.set(true) ;
+        state = DatasetStatus.ACTIVE ; 
+    }
+
+    public boolean isAcceptingRequests() {
+        return acceptingRequests.get() ;
+    }
+    
+    //@Override
+    public  CounterSet getCounters() { return counters ; }
+    
+    //@Override 
+    public long getRequests() { 
+        return counters.value(CounterName.Requests) ;
+    }
+
+    //@Override
+    public long getRequestsGood() {
+        return counters.value(CounterName.RequestsGood) ;
+    }
+    //@Override
+    public long getRequestsBad() {
+        return counters.value(CounterName.RequestsBad) ;
+    }
+
+    /** Counter of active read transactions */
+    public AtomicLong   activeReadTxn           = new AtomicLong(0) ;
+
+    /** Counter of active write transactions */
+    public AtomicLong   activeWriteTxn          = new AtomicLong(0) ;
+
+    /** Cumulative counter of read transactions */
+    public AtomicLong   totalReadTxn            = new AtomicLong(0) ;
+
+    /** Cumulative counter of writer transactions */
+    public AtomicLong   totalWriteTxn           = new AtomicLong(0) ;
+
+    public void startTxn(ReadWrite mode)
+    {
+        switch(mode)
+        {
+            case READ:  
+                activeReadTxn.getAndIncrement() ;
+                totalReadTxn.getAndIncrement() ;
+                break ;
+            case WRITE:
+                activeWriteTxn.getAndIncrement() ;
+                totalWriteTxn.getAndIncrement() ;
+                break ;
+        }
+    }
+
+    public void finishTxn(ReadWrite mode)
+    {
+        switch(mode)
+        {
+            case READ:  
+                activeReadTxn.decrementAndGet() ;
+                break ;
+            case WRITE:
+                activeWriteTxn.decrementAndGet() ;
+                break ;
+        }
+        checkShutdown() ;
+    }
+
+    private void checkShutdown() {
+        if ( state == CLOSING ) {
+            if ( activeReadTxn.get() == 0 && activeWriteTxn.get() == 0 )
+                shutdown() ;
+        }
+    }
+
+    private void shutdown() {
+        Fuseki.serverLog.info("Shutting down dataset") ;
+        dataset.close() ;
+        if ( dataset instanceof DatasetGraphTransaction ) {
+            DatasetGraphTransaction dsgtxn = (DatasetGraphTransaction)dataset ;
+            StoreConnection.release(dsgtxn.getLocation()) ;
+        }
+        dataset = null ; 
+    }
+}
+

http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/DatasetMXBean.java
----------------------------------------------------------------------
diff --git 
a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/DatasetMXBean.java 
b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/DatasetMXBean.java
new file mode 100644
index 0000000..bf38229
--- /dev/null
+++ 
b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/DatasetMXBean.java
@@ -0,0 +1,35 @@
+/**
+ * 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.server;
+
+public interface DatasetMXBean
+{
+    String getName() ;
+    
+    long getRequests() ;
+    long getRequestsGood() ;
+    long getRequestsBad() ;
+    
+//    void enable() ;
+//    void disable() ;
+//    void setReadOnly() ;
+//    boolean getReadOnly() ;
+    
+}
+

http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/DatasetStatus.java
----------------------------------------------------------------------
diff --git 
a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/DatasetStatus.java 
b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/DatasetStatus.java
new file mode 100644
index 0000000..524b050
--- /dev/null
+++ 
b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/DatasetStatus.java
@@ -0,0 +1,40 @@
+/**
+ * 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.server;
+
+import com.hp.hpl.jena.rdf.model.Resource ;
+
+public enum DatasetStatus {
+    UNINITIALIZED("Uninitialized"), ACTIVE("Active"), OFFLINE("Offline"), 
CLOSING("Closing"), CLOSED("Closed") ;
+    public final String name ; 
+    DatasetStatus(String string) { name = string ; }
+    
+    public static DatasetStatus status(Resource r) {
+        if ( FusekiVocab.stateActive.equals(r) )
+            return ACTIVE ;
+        if ( FusekiVocab.stateOffline.equals(r) )
+            return OFFLINE ;
+        if ( FusekiVocab.stateClosing.equals(r) )
+            return CLOSING ;
+        if ( FusekiVocab.stateClosed.equals(r) )
+            return CLOSED ;
+        return null ;
+    }
+}
+

http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/Endpoint.java
----------------------------------------------------------------------
diff --git 
a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/Endpoint.java 
b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/Endpoint.java
new file mode 100644
index 0000000..6de7062
--- /dev/null
+++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/Endpoint.java
@@ -0,0 +1,68 @@
+/**
+ * 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.server;
+
+import org.apache.jena.atlas.lib.InternalErrorException ;
+
+public class Endpoint implements Counters {
+    
+    public final OperationName opName ;
+    public final String endpointName ;
+    // Endpoint-level counters.
+    private final CounterSet counters           = new CounterSet() ;
+
+    public Endpoint(OperationName opName, String endpointName) {
+        this.opName = opName ;
+        if ( opName == null )
+            throw new InternalErrorException("opName is null") ;
+        this.endpointName = endpointName ;
+        // Standard counters - there may be others
+        counters.add(CounterName.Requests) ;
+        counters.add(CounterName.RequestsGood) ;
+        counters.add(CounterName.RequestsBad) ;
+    }
+
+    @Override
+    public  CounterSet getCounters()    { return counters ; }
+
+    //@Override
+    public OperationName getOperationName()      { return opName ; }
+    
+    //@Override
+    public boolean isType(OperationName operationName) { 
+        return opName.equals(operationName) ;
+    }
+
+    public String getEndpoint()         { return endpointName ; }
+    
+    //@Override 
+    public long getRequests() { 
+        return counters.value(CounterName.Requests) ;
+    }
+    //@Override
+    public long getRequestsGood() {
+        return counters.value(CounterName.RequestsGood) ;
+    }
+    //@Override
+    public long getRequestsBad() {
+        return counters.value(CounterName.RequestsBad) ;
+    }
+
+}
+

http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/FusekiServer.java
----------------------------------------------------------------------
diff --git 
a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/FusekiServer.java 
b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/FusekiServer.java
new file mode 100644
index 0000000..bb651b4
--- /dev/null
+++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/FusekiServer.java
@@ -0,0 +1,416 @@
+/**
+ * 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.server;
+
+import java.io.* ;
+import java.nio.file.Files ;
+import java.nio.file.Path ;
+import java.nio.file.Paths ;
+import java.nio.file.StandardCopyOption ;
+import java.util.ArrayList ;
+import java.util.HashMap ;
+import java.util.List ;
+import java.util.Map ;
+
+import org.apache.jena.atlas.io.IO ;
+import org.apache.jena.atlas.lib.DS ;
+import org.apache.jena.atlas.lib.FileOps ;
+import org.apache.jena.atlas.lib.InternalErrorException ;
+import org.apache.jena.atlas.lib.Lib ;
+import org.apache.jena.fuseki.Fuseki ;
+import org.apache.jena.fuseki.FusekiConfigException ;
+import org.apache.jena.fuseki.build.Builder ;
+import org.apache.jena.fuseki.build.FusekiConfig ;
+import org.apache.jena.fuseki.build.Template ;
+import org.apache.jena.fuseki.build.TemplateFunctions ;
+import org.apache.jena.fuseki.servlets.ServletOps ;
+import org.apache.jena.riot.Lang ;
+import org.apache.jena.riot.RDFDataMgr ;
+import org.apache.jena.riot.RDFLanguages ;
+import arq.cmd.CmdException ;
+
+import com.hp.hpl.jena.rdf.model.* ;
+import com.hp.hpl.jena.sparql.core.DatasetGraph ;
+import com.hp.hpl.jena.tdb.sys.Names ;
+import com.hp.hpl.jena.tdb.sys.SystemTDB ;
+
+public class FusekiServer
+{
+    /** Root of the Fuseki installation for fixed files. 
+     * This may be null (e.g. running inside a web application container) */ 
+    public static Path FUSEKI_HOME = null ;
+    
+    /** Root of the varying files in this deployment. Often $FUSEKI_HOME/run.
+     * This is not null - it may be /etc/fuseki, which must be writable.
+     */ 
+    public static Path FUSEKI_BASE = null ;
+    
+    public static final boolean isWindows = SystemTDB.isWindows ;
+        
+    
+    /** Unused */
+    //public static final String DFT_FUSEKI_HOME  = 
+    //    isWindows ? /*What's correct here?*/ "/usr/share/fuseki" : 
"/usr/share/fuseki" ;
+    public static final String DFT_FUSEKI_BASE  = 
+        isWindows ? /*What's correct here?*/ "/etc/fuseki"       : 
"/etc/fuseki" ;
+    
+    // In FUSEKI_BASE
+    public static final String DFT_CONFIG       = "config.ttl" ;
+
+    // Relative names of directories
+    private static final String        runArea                  = "run" ;
+    private static final String        databasesLocationBase    = "databases" ;
+    //private static final String        databaseIndexesDir       = "indexes" 
;       // Place to put Lucene text and spatial indexes.  
+    private static final String        backupDirNameBase        = "backups" ;
+    private static final String        configDirNameBase        = 
"configuration" ;
+    private static final String        logsNameBase             = "logs" ;
+    private static final String        systemDatabaseNameBase   = "system" ;
+    private static final String        systemFileAreaBase       = 
"system_files" ;
+    private static final String        templatesNameBase        = "templates" ;
+    private static final String        DFT_SHIRO_INI            = "shiro.ini" 
; // This name is in web.xml as well. 
+    
+    // --- Set during server initialization
+
+    /** Directory for TDB databases - this is known to the assembler templates 
*/
+    public static Path        dirDatabases       = null ;
+    
+    /** Directory for writing backups */
+    public static Path        dirBackups         = null ;
+
+    /** Directory for assembler files */
+    public static Path        dirConfiguration   = null ;
+    
+    /** Directory for assembler files */
+    public static Path        dirLogs            = null ;
+
+    /** Directory for system database */
+    public static Path        dirSystemDatabase  = null ;
+
+    /** Directory for files uploaded (e.g upload assmbler descriptions); not 
data uploads. */
+    public static Path        dirFileArea        = null ;
+    
+    /** Directory for assembler files */
+    public static Path        dirTemplates       = null ;
+
+    private static boolean    initialized        = false ;
+    public static boolean     serverInitialized  = false ;
+
+    /** For testing - reset the places which initialize once */
+    public synchronized static void reset() {
+        initialized = false ;
+        FusekiServer.initialized = false ;
+    }
+    
+    public synchronized static void init() {
+        if ( initialized )
+            return ;
+        initialized = true ;
+        Fuseki.init() ;
+        
+        // ----  Set and check FUSEKI_HOME and FUSEKI_BASE
+        
+        if ( FUSEKI_HOME == null ) {
+            // Make absolute
+            String x1 = System.getenv("FUSEKI_HOME") ;
+            if ( x1 != null )
+                FUSEKI_HOME = Paths.get(x1) ;
+        }
+            
+        if ( FUSEKI_BASE == null ) {
+            String x2 = System.getenv("FUSEKI_BASE") ;
+            if ( x2 != null )
+                FUSEKI_BASE = Paths.get(x2) ;
+            else {
+                if ( FUSEKI_HOME != null )
+                    FUSEKI_BASE = FUSEKI_HOME.resolve(runArea) ;
+                else
+                    // Neither FUSEKI_HOME nor FUSEKI_BASE set.
+                    FUSEKI_BASE = Paths.get(DFT_FUSEKI_BASE) ;
+            }
+        }
+        
+        if ( FUSEKI_HOME != null )
+            FUSEKI_HOME = FUSEKI_HOME.toAbsolutePath() ;
+        
+        FUSEKI_BASE = FUSEKI_BASE.toAbsolutePath() ;
+        
+        Fuseki.configLog.info("FUSEKI_HOME="+ ((FUSEKI_HOME==null) ? "unset" : 
FUSEKI_HOME.toString())) ;
+        Fuseki.configLog.info("FUSEKI_BASE="+FUSEKI_BASE.toString());
+
+        // If FUSEKI_HOME exists, it may be FUSEKI_BASE.
+        
+        if ( FUSEKI_HOME != null ) {
+            if ( ! Files.isDirectory(FUSEKI_HOME) )
+                throw new FusekiConfigException("FUSEKI_HOME is not a 
directory: "+FUSEKI_HOME) ;
+            if ( ! Files.isReadable(FUSEKI_HOME) )
+                throw new FusekiConfigException("FUSEKI_HOME is not readable: 
"+FUSEKI_HOME) ;
+        }
+            
+        if ( Files.exists(FUSEKI_BASE) ) {
+            if ( ! Files.isDirectory(FUSEKI_BASE) )
+                throw new FusekiConfigException("FUSEKI_BASE is not a 
directory: "+FUSEKI_BASE) ;
+            if ( ! Files.isWritable(FUSEKI_BASE) )
+                throw new FusekiConfigException("FUSEKI_BASE is not writable: 
"+FUSEKI_BASE) ;
+        } else {
+            ensureDir(FUSEKI_BASE);
+        }
+
+        // Ensure FUSEKI_BASE has the assumed directories.
+        dirTemplates        = writeableDirectory(FUSEKI_BASE, 
templatesNameBase) ;
+        dirDatabases        = writeableDirectory(FUSEKI_BASE, 
databasesLocationBase) ;
+        dirBackups          = writeableDirectory(FUSEKI_BASE, 
backupDirNameBase) ;
+        dirConfiguration    = writeableDirectory(FUSEKI_BASE, 
configDirNameBase) ;
+        dirLogs             = writeableDirectory(FUSEKI_BASE, logsNameBase) ;
+        dirSystemDatabase   = writeableDirectory(FUSEKI_BASE, 
systemDatabaseNameBase) ;
+        dirFileArea         = writeableDirectory(FUSEKI_BASE, 
systemFileAreaBase) ;
+        
+        // ---- Initialize with files.
+        
+        if ( Files.isRegularFile(FUSEKI_BASE) ) 
+            throw new FusekiConfigException("FUSEKI_BASE exists but is a 
file") ;
+        
+        // Copy missing files into FUSEKI_BASE
+        copyFileIfMissing(null, DFT_SHIRO_INI, FUSEKI_BASE) ;
+        copyFileIfMissing(null, DFT_CONFIG, FUSEKI_BASE) ;
+        for ( String n : Template.templateNames ) {
+            copyFileIfMissing(null, n, FUSEKI_BASE) ;
+        }
+        
+        serverInitialized = true ;
+    }
+
+    private static boolean emptyDir(Path dir) {
+        return dir.toFile().list().length <= 2 ;
+    }
+    
+    /** Copy a file from src to dst under name fn.
+     * If src is null, try as a classpath resource
+     * @param src   Source directory, or null meaning use java resource. 
+     * @param fn    File name, a relative path.
+     * @param dst   Destination directory.
+     * 
+     */
+    private static void copyFileIfMissing(Path src, String fn, Path dst) {
+        
+        Path dstFile = dst.resolve(fn) ;
+        if ( Files.exists(dstFile) )
+            return ;
+        
+        // fn may be a path.
+        if ( src != null ) {
+            try {
+                Files.copy(src.resolve(fn), dstFile, 
StandardCopyOption.COPY_ATTRIBUTES) ;
+            } catch (IOException e) {
+                IO.exception("Failed to copy file "+src, e);
+                e.printStackTrace();
+            }
+        } else {
+            try {
+                // Get from the file from area "org/apache/jena/fuseki/server" 
 (our package)
+                InputStream in = 
FusekiServer.class.getResource(fn).openStream() ;
+                Files.copy(in, dstFile) ;
+            }
+            catch (IOException e) {
+                IO.exception("Failed to copy file from resource: "+src, e);
+                e.printStackTrace();
+            }
+        }
+    }
+
+    public static void initializeDataAccessPoints(ServerInitialConfig 
initialSetup, String configDir) {
+        List<DataAccessPoint> configFileDBs = 
initServerConfiguration(initialSetup) ;
+        List<DataAccessPoint> directoryDBs =  
FusekiConfig.readConfigurationDirectory(configDir) ;
+        List<DataAccessPoint> systemDBs =     
FusekiConfig.readSystemDatabase(SystemState.getDataset()) ;
+        
+        List<DataAccessPoint> datapoints = new ArrayList<DataAccessPoint>() ;
+        datapoints.addAll(configFileDBs) ;
+        datapoints.addAll(directoryDBs) ;
+        datapoints.addAll(systemDBs) ;
+        
+        // Having found them, set them all running.
+        enable(datapoints);
+    }
+
+    private static void enable(List<DataAccessPoint> datapoints) {
+        for ( DataAccessPoint dap : datapoints ) {
+            Fuseki.configLog.info("Register: "+dap.getName()) ;
+            DataAccessPointRegistry.register(dap.getName(), dap); 
+        }
+    }
+
+    private static List<DataAccessPoint> 
initServerConfiguration(ServerInitialConfig params) { 
+        // Has a side effect of global context setting
+        // when processing a config file.
+        // Compatibility.
+        
+        List<DataAccessPoint> datasets = DS.list() ;
+        if ( params == null )
+            return datasets ;
+
+        if ( params.fusekiConfigFile != null ) {
+            if ( FileOps.exists(params.fusekiConfigFile ) ) {
+                Fuseki.configLog.info("Configuration file: " + 
params.fusekiConfigFile) ;
+                List<DataAccessPoint> cmdLineDatasets = 
FusekiConfig.readConfigFile(params.fusekiConfigFile) ;
+                datasets.addAll(cmdLineDatasets) ;
+            } else {
+                Fuseki.configLog.info("Configuration file '" + 
params.fusekiConfigFile+"' does not exist") ;
+            }
+        } else if ( params.dsg != null ) {
+            DataAccessPoint dap = defaultConfiguration(params.datasetPath, 
params.dsg, params.allowUpdate) ;
+            datasets.add(dap) ;
+        } else if ( params.templateFile != null ) {
+            Fuseki.configLog.info("Template file: " + params.templateFile) ;
+            String dir = params.params.get(Template.DIR) ;
+            if ( dir != null ) {
+                if ( Lib.equal(dir, Names.memName) ) {
+                    Fuseki.configLog.info("TDB dataset: in-memory") ;
+                } else {
+                    if ( !FileOps.exists(dir) )
+                        throw new CmdException("Directory not found: " + dir) ;
+                    Fuseki.configLog.info("TDB dataset: directory=" + dir) ;
+                }
+            }
+            DataAccessPoint dap = configFromTemplate(params.templateFile, 
params.datasetPath, params.params) ;
+            datasets.add(dap) ;
+        }
+        // No datasets is valid.
+        return datasets ;
+    }
+    
+    private static DataAccessPoint configFromTemplate(String templateFile, 
+                                                      String datasetPath, 
+                                                      Map<String, String> 
params) {
+        datasetPath = DataAccessPoint.canonical(datasetPath) ;
+        
+        // DRY -- ActionDatasets (and others?)
+        if ( params == null ) {
+            params = new HashMap<>() ;
+            params.put(Template.NAME, datasetPath) ;
+        } else {
+            if ( ! params.containsKey(Template.NAME) ) {
+                Fuseki.configLog.warn("No NAME found in template parameters 
(added)") ;
+                params.put(Template.NAME, datasetPath) ;   
+            }
+        }
+        
+        addGlobals(params); 
+
+        String str = TemplateFunctions.templateFile(templateFile, params) ;
+        Lang lang = RDFLanguages.filenameToLang(str, Lang.TTL) ;
+        StringReader sr =  new StringReader(str) ;
+        Model model = ModelFactory.createDefaultModel() ;
+        RDFDataMgr.read(model, sr, datasetPath, lang);
+        
+        // Find DataAccessPoint
+        Statement stmt = getOne(model, null, FusekiVocab.pServiceName, null) ;
+        if ( stmt == null ) {
+            StmtIterator sIter = model.listStatements(null, 
FusekiVocab.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") ;
+        }
+        Resource subject = stmt.getSubject() ;
+        DataAccessPoint dap = Builder.buildDataAccessPoint(subject) ;
+        return dap ;
+    }
+    
+    public static void addGlobals(Map<String, String> params) {
+        if ( params == null ) {
+            Fuseki.configLog.warn("FusekiServer.addGlobals : params is null", 
new Throwable()) ;
+            return ;
+        }
+        
+        if ( ! params.containsKey("FUSEKI_BASE") )
+            params.put("FUSEKI_BASE", pathStringOrElse(FUSEKI_BASE, "unset")) ;
+        if ( ! params.containsKey("FUSEKI_HOME") )
+            params.put("FUSEKI_HOME", pathStringOrElse(FUSEKI_HOME, "unset")) ;
+    }
+
+    private static String pathStringOrElse(Path path, String dft) {
+        if ( path == null )
+            return dft ;
+        return path.toString() ;
+    }
+    
+    // DRY -- ActionDatasets (and others?)
+    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 ;
+    }
+    
+    private static DataAccessPoint defaultConfiguration( String name, 
DatasetGraph dsg, boolean updatable) {
+        name = DataAccessPoint.canonical(name) ;
+        DataAccessPoint dap = new DataAccessPoint(name) ;
+        DataService ds = Builder.buildDataService(dsg, updatable) ;
+        dap.setDataService(ds) ;
+        return dap ;
+    }
+    
+    // ---- Helpers
+
+    /** Ensure a directory exists, creating it if necessary.
+     */
+    private static void ensureDir(Path directory) {
+        File dir = directory.toFile() ;
+        if ( ! dir.exists() ) {
+            boolean b = dir.mkdirs() ;
+            if ( ! b )
+                throw new FusekiConfigException("Failed to create directory: 
"+directory) ;
+        }
+        else if ( ! dir.isDirectory())
+            throw new FusekiConfigException("Not a directory: "+directory) ;
+    }
+
+    private static void mustExist(Path directory) {
+        File dir = directory.toFile() ;
+        if ( ! dir.exists() )
+            throw new FusekiConfigException("Does not exist: "+directory) ; 
+        if ( ! dir.isDirectory())
+            throw new FusekiConfigException("Not a directory: "+directory) ;
+    }
+    
+    private static boolean exists(Path directory) {
+        File dir = directory.toFile() ;
+        return dir.exists() ;
+    }
+
+    private static Path writeableDirectory(Path root , String relName ) {
+        Path p = makePath(root, relName) ;
+        ensureDir(p);
+        if ( ! Files.isWritable(p) )
+            throw new FusekiConfigException("Not writable: "+p) ;
+        return p ;
+    }
+    
+    private static Path makePath(Path root , String relName ) {
+        Path path = root.resolve(relName) ;
+        // Must exist
+//        try { path = path.toRealPath() ; }
+//        catch (IOException e) { IO.exception(e) ; }
+        return path ;
+    }
+}

Reply via email to