jena-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From rve...@apache.org
Subject [35/68] [abbrv] [partial] reverting accidentally deleted files
Date Wed, 22 Oct 2014 11:39:50 GMT
http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/conneg/WebLib.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/conneg/WebLib.java b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/conneg/WebLib.java
new file mode 100644
index 0000000..38dc265
--- /dev/null
+++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/conneg/WebLib.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.conneg;
+
+import java.util.Enumeration ;
+
+import javax.servlet.http.HttpServletRequest ;
+
+import org.apache.jena.riot.web.HttpNames ;
+
+public class WebLib
+{
+    /** Split a string, removing whitespace around the split string.
+     * e.g. Use in splitting HTTP accept/content-type headers.  
+     */
+    public static String[] split(String s, String splitStr)
+    {
+        String[] x = s.split(splitStr,2) ;
+        for ( int i = 0 ; i < x.length ; i++ )
+        {
+            x[i] = x[i].trim() ;
+        }
+        return x ;
+    }
+
+    /** Migrate to WebLib */
+    public static String getAccept(HttpServletRequest httpRequest)
+    {
+        // There can be multiple accept headers -- note many tools don't allow these to be this way (e.g. wget, curl)
+        Enumeration<String> en = httpRequest.getHeaders(HttpNames.hAccept) ;
+        if ( ! en.hasMoreElements() )
+            return null ;
+        StringBuilder sb = new StringBuilder() ;
+        String sep = "" ;
+        for ( ; en.hasMoreElements() ; )
+        {
+            String x = en.nextElement() ;
+            sb.append(sep) ;
+            sep = ", " ;
+            sb.append(x) ;
+        }
+        return sb.toString() ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/jetty/FusekiErrorHandler.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/jetty/FusekiErrorHandler.java b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/jetty/FusekiErrorHandler.java
new file mode 100644
index 0000000..40361de
--- /dev/null
+++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/jetty/FusekiErrorHandler.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.jetty;
+
+import static java.lang.String.format ;
+
+import java.io.* ;
+
+import javax.servlet.http.HttpServletRequest ;
+import javax.servlet.http.HttpServletResponse ;
+
+import org.apache.jena.atlas.io.IO ;
+import org.apache.jena.fuseki.Fuseki ;
+import org.apache.jena.fuseki.servlets.ServletOps ;
+import org.apache.jena.web.HttpSC ;
+import org.eclipse.jetty.http.HttpMethod ;
+import org.eclipse.jetty.http.MimeTypes ;
+import org.eclipse.jetty.server.Request ;
+import org.eclipse.jetty.server.Response ;
+import org.eclipse.jetty.server.handler.ErrorHandler ;
+
+public class FusekiErrorHandler extends ErrorHandler
+{
+    public FusekiErrorHandler() {}
+    
+    @Override
+    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
+    {
+        String method = request.getMethod();
+     
+        if ( !method.equals(HttpMethod.GET.asString())
+             && !method.equals(HttpMethod.POST.asString())
+             && !method.equals(HttpMethod.HEAD.asString()) )
+            return ;
+        
+        response.setContentType(MimeTypes.Type.TEXT_PLAIN_UTF_8.asString()) ;
+        ServletOps.setNoCache(response) ;
+        
+        ByteArrayOutputStream bytes = new ByteArrayOutputStream(1024) ;
+        try ( Writer writer = IO.asUTF8(bytes) ) {
+            String reason=(response instanceof Response)?((Response)response).getReason():null;
+            handleErrorPage(request, writer, response.getStatus(), reason) ;
+
+            if ( ! Fuseki.VERSION.equalsIgnoreCase("development") &&
+                ! Fuseki.VERSION.equals("${project.version}") )
+            {
+                writer.write("\n") ;
+                writer.write("\n") ;
+                writer.write(format("Fuseki - version %s (Build date: %s)\n", Fuseki.VERSION, Fuseki.BUILD_DATE)) ;
+            }
+            writer.flush();
+            response.setContentLength(bytes.size()) ;
+            // Copy :-(
+            response.getOutputStream().write(bytes.toByteArray()) ;
+        }
+    }
+    
+    @Override
+    protected void handleErrorPage(HttpServletRequest request, Writer writer, int code, String message)
+        throws IOException
+    {
+        if ( message == null )
+            message = HttpSC.getMessage(code) ;
+        writer.write(format("Error %d: %s\n", code, message)) ;
+        
+        Throwable th = (Throwable)request.getAttribute("javax.servlet.error.exception");
+        while(th!=null)
+        {
+            writer.write("\n");
+            StringWriter sw = new StringWriter();
+            PrintWriter pw = new PrintWriter(sw);
+            th.printStackTrace(pw);
+            pw.flush();
+            writer.write(sw.getBuffer().toString());
+            writer.write("\n");
+            th = th.getCause();
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/jetty/JettyFuseki.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/jetty/JettyFuseki.java b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/jetty/JettyFuseki.java
new file mode 100644
index 0000000..94c9f33
--- /dev/null
+++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/jetty/JettyFuseki.java
@@ -0,0 +1,267 @@
+/*
+ * 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.jetty ;
+
+import static java.lang.String.format ;
+import static org.apache.jena.fuseki.Fuseki.serverLog ;
+
+import java.io.FileInputStream ;
+
+import org.apache.jena.atlas.lib.FileOps ;
+import org.apache.jena.atlas.logging.LogCtl ;
+import org.apache.jena.fuseki.Fuseki ;
+import org.apache.jena.fuseki.FusekiException ;
+import org.apache.jena.fuseki.mgt.MgtJMX ;
+import org.eclipse.jetty.security.* ;
+import org.eclipse.jetty.security.authentication.BasicAuthenticator ;
+import org.eclipse.jetty.server.* ;
+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 ;
+
+import com.hp.hpl.jena.sparql.util.Utils ;
+
+/** Standalone 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 ;
+    
+    // webapp setup - standard maven layout
+    public static       String contextpath    = "/" ;
+    public static final String resourceBase1   = "webapp" ;             // Standalone jar
+    public static final String resourceBase2   = "src/main/webapp" ;    // Development
+
+    /**
+     * 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) {
+        // Currently server-wide.
+        Fuseki.verboseLogging = config.verboseLogging ;
+        instance = new JettyFuseki(config) ;
+    }
+    
+    /** Build a Jetty server using the development files for the webapp
+     *  No command line configuration. 
+     */
+    public static Server create(int port) {
+        return create("/", port) ;
+    }
+
+    public static Server create(String contextPath, int port) {
+        Server server = new Server(port) ;
+        WebAppContext webapp = createWebApp(contextPath) ;
+        server.setHandler(webapp) ;
+        return server ;
+    }
+
+    private JettyFuseki(JettyServerConfig config) {
+        this.serverConfig = config ;
+        
+        buildServerWebapp(serverConfig.contextPath, serverConfig.jettyConfigFile, config.enableCompression) ;
+        
+        if ( mgtConnector == null )
+            mgtConnector = serverConnector ;
+    }
+
+    /**
+     * 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 = Utils.nowAsXSDDateTimeString() ;
+        
+        if ( version != null && buildDate != null )
+            serverLog.info(format("%s %s %s", Fuseki.NAME, version, buildDate)) ;
+        // 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()) ;
+            System.exit(1) ;
+        } catch (Exception ex) {
+            serverLog.error("SPARQLServer: Failed to start server: " + ex.getMessage(), ex) ;
+            System.exit(1) ;
+        }
+        String now = Utils.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 = Utils.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) ;
+        }
+        MgtJMX.removeJMX() ;
+    }
+
+    public static WebAppContext createWebApp(String contextPath) {
+      WebAppContext webapp = new WebAppContext();
+      webapp.getServletContext().getContextHandler().setMaxFormContentSize(10 * 1000 * 1000) ;
+      String resourceBase = null ;
+      if ( /*resourceBase == null &&*/ FileOps.exists(resourceBase1) )
+          resourceBase = resourceBase1 ;
+      if ( resourceBase == null && FileOps.exists(resourceBase2) )
+          resourceBase = resourceBase2 ;
+      if ( resourceBase == null )
+          Fuseki.serverLog.warn("Can't find resourceBase (tried "+resourceBase1+" and "+resourceBase2+")") ;
+      
+      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);  
+      LogCtl.set(Fuseki.servletRequestLogName, "WARN"); 
+      
+      webapp.setParentLoaderPriority(true);  // Normal Java classloader behaviour.
+      webapp.setErrorHandler(new FusekiErrorHandler()) ;
+      return webapp ;
+    }
+    
+    private void buildServerWebapp(String contextPath, String jettyConfig, boolean enableCompression) {
+        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) ;
+        server.setHandler(webapp) ;
+        // Replaced by Shiro.
+        if ( jettyConfig == null && serverConfig.authConfigFile != null )
+            security(webapp, serverConfig.authConfigFile) ;
+    }
+    
+    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() ;
+        ConnectionFactory f1 = new HttpConnectionFactory() ;
+        ConnectionFactory f2 = new SslConnectionFactory() ;
+        
+        ServerConnector connector = new ServerConnector(server, f1) ; //, f2) ;
+        connector.setPort(port);
+        server.addConnector(connector);
+        
+        if ( loopback )
+            connector.setHost("localhost");
+        connector.setPort(port) ;
+        serverConnector = connector ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/jetty/JettyServerConfig.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/jetty/JettyServerConfig.java b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/jetty/JettyServerConfig.java
new file mode 100644
index 0000000..71012ef
--- /dev/null
+++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/jetty/JettyServerConfig.java
@@ -0,0 +1,51 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.fuseki.jetty;
+
+
+/** Configuration of the Jetty server when run from the command line directly,
+ *  not as a webapp/war file.
+ */
+
+public class JettyServerConfig
+{
+    public JettyServerConfig() {}
+    
+    /** Port to run the server service on */
+    public int port ;
+    /** Port to run the server service on */
+    public String contextPath ;
+    /** Jetty config file - if null, use the built-in configuration of Jetty */
+    public String jettyConfigFile = null ;
+    /** Listen only on the loopback (localhost) interface */
+    public boolean loopback = false ;
+    /** The local directory for serving the static pages */ 
+    public String pages ;
+    /** Enable Accept-Encoding compression. Set to false by default.*/
+    public boolean enableCompression = false ;
+    
+    /** Enable additional logging */
+    public boolean verboseLogging = false ;
+    /**
+     * Authentication config file used to setup Jetty Basic auth, if a Jetty config file was set this is ignored since Jetty config allows much more complex auth methods to be implemented
+     */
+    public String authConfigFile ;
+
+}
+

http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionAsyncTask.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionAsyncTask.java b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionAsyncTask.java
new file mode 100644
index 0000000..8d8a080
--- /dev/null
+++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionAsyncTask.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.mgt;
+
+import javax.servlet.http.HttpServletRequest ;
+import javax.servlet.http.HttpServletResponse ;
+
+import org.apache.jena.atlas.json.JsonValue ;
+import org.apache.jena.atlas.lib.InternalErrorException ;
+import org.apache.jena.fuseki.async.AsyncPool ;
+import org.apache.jena.fuseki.async.AsyncTask ;
+import org.apache.jena.fuseki.servlets.HttpAction ;
+import org.apache.jena.fuseki.servlets.ServletOps ;
+
+/** Base helper class for creating async tasks on "items", based on POST  */ 
+public abstract class ActionAsyncTask extends ActionItem
+{
+    // ?? Better as a library (mixin) so can be used outside ActionItem
+    private static AsyncPool asyncPool = AsyncPool.get() ;
+    
+    public ActionAsyncTask() { super() ; }
+    
+    @Override
+    final
+    protected void doGet(HttpServletRequest request, HttpServletResponse response) {
+        ServletOps.errorMethodNotAllowed(METHOD_GET);
+    }
+
+    @Override
+    final
+    protected JsonValue execGetItem(HttpAction action) { 
+        throw new InternalErrorException("GET for AsyncTask -- Should not be here!") ;
+    }
+
+    @Override
+    final
+    protected JsonValue execPostItem(HttpAction action) {
+        Runnable task = createRunnable(action) ;
+        AsyncTask aTask = Async.execASyncTask(action, AsyncPool.get(), "backup", task) ;
+        Async.setLocationHeader(action, aTask);
+        return Async.asJson(aTask) ;
+    }
+    
+    public static AsyncTask execASyncTask(HttpAction action, AsyncPool asyncPool, String displayName, Runnable task) {
+        AsyncTask atask = Async.asyncTask(asyncPool, displayName, action.getDataService(), task) ;
+        Async.setLocationHeader(action, atask);
+        JsonValue v = Async.asJson(atask) ;
+        ServletOps.sendJsonReponse(action, v);
+        return atask ;
+    }
+    
+    protected abstract Runnable createRunnable(HttpAction action) ;
+}
+

http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionBackup.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionBackup.java b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionBackup.java
new file mode 100644
index 0000000..34f134e
--- /dev/null
+++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionBackup.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.mgt;
+
+import static java.lang.String.format ;
+
+import javax.servlet.http.HttpServletRequest ;
+import javax.servlet.http.HttpServletResponse ;
+
+import org.apache.jena.fuseki.servlets.HttpAction ;
+import org.apache.jena.fuseki.servlets.ServletOps ;
+import org.slf4j.Logger ;
+import org.slf4j.LoggerFactory ;
+
+import com.hp.hpl.jena.sparql.core.DatasetGraph ;
+
+public class ActionBackup extends ActionAsyncTask
+{
+    public ActionBackup() { super() ; }
+    
+    // Only POST
+    @Override
+    protected void doPost(HttpServletRequest request, HttpServletResponse response) {
+        doCommon(request, response);
+    }
+
+    @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 implements Runnable {
+        static private Logger log = LoggerFactory.getLogger("Backup") ;
+        
+        private final long actionId ;
+        private final DatasetGraph dataset ;
+        private final String datasetName ;
+        
+        public BackupTask(HttpAction action) {
+            this.actionId = action.id ;
+            action.getDataAccessPoint() ;
+            action.getDataAccessPoint().getDataService() ;
+            action.getDataAccessPoint().getDataService().getDataset() ;
+            this.dataset = action.getDataAccessPoint().getDataService().getDataset() ;
+            this.datasetName = action.getDatasetName() ;
+        }
+
+        @Override
+        public void run() {
+            try {
+                String backupFilename = Backup.chooseFileName(datasetName) ;
+                log.info(format("[%d] >>>> Start backup %s -> %s", actionId, datasetName, backupFilename)) ;
+                Backup.backup(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/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionContainerItem.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionContainerItem.java b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionContainerItem.java
new file mode 100644
index 0000000..134afc4
--- /dev/null
+++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionContainerItem.java
@@ -0,0 +1,94 @@
+/**
+ * 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 org.apache.jena.atlas.json.JsonValue ;
+import org.apache.jena.fuseki.servlets.HttpAction ;
+import org.apache.jena.fuseki.servlets.ServletOps ;
+import org.apache.jena.web.HttpSC ;
+
+/** Base for actions that are container and also have action on items */ 
+public abstract class ActionContainerItem extends ActionCtl {
+    
+    public ActionContainerItem() { super() ; }
+    
+    @Override
+    final
+    protected void perform(HttpAction action) {
+        String method = action.request.getMethod() ;
+        if ( method.equals(METHOD_GET) )
+            execGet(action) ;
+        else if ( method.equals(METHOD_POST) )
+            execPost(action) ;
+        else if ( method.equals(METHOD_DELETE) )
+            execDelete(action) ;
+        else
+            ServletOps.error(HttpSC.METHOD_NOT_ALLOWED_405) ;
+    }
+
+    protected void execGet(HttpAction action) {
+        JsonValue v ;
+        if ( isContainerAction(action)  )
+            v = execGetContainer(action) ;
+        else
+            v = execGetItem(action) ;
+        
+        ServletOps.sendJsonReponse(action, v);
+    }
+    
+    /** GET request on the container - respond with JSON, or null for plain 200 */  
+    protected abstract JsonValue execGetContainer(HttpAction action) ;
+    /** GET request on an item in the container - repond with JSON, or null for plain 200 */  
+    protected abstract JsonValue execGetItem(HttpAction action) ;
+
+    protected void execPost(HttpAction action) {
+        JsonValue v ;
+        if ( isContainerAction(action) )
+            v = execPostContainer(action) ;
+        else
+            v = execPostItem(action) ;
+        
+        ServletOps.sendJsonReponse(action, v);
+    }
+    
+    /** POST request on an item in the container - respond with JSON, or null for plain 200 */  
+    protected abstract JsonValue execPostContainer(HttpAction action) ;
+    /** POST request on an item in the container - respond with JSON, or null for plain 200 */  
+    protected abstract JsonValue execPostItem(HttpAction action) ;
+
+    
+    /** DELETE request */
+    protected void execDelete(HttpAction action) {
+        if ( isContainerAction(action)  )
+            execDeleteContainer(action) ;
+        else 
+            execDeleteItem(action) ;
+        ServletOps.success(action) ;
+    }
+    
+    /** DELETE request on an item in the container */
+    protected void execDeleteContainer(HttpAction action) {
+        ServletOps.errorMethodNotAllowed(METHOD_DELETE, "DELETE applied to a container") ;
+    }
+
+    /** DELETE request on an item in the container */
+    protected void execDeleteItem(HttpAction action) {
+        ServletOps.errorMethodNotAllowed(METHOD_DELETE) ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionCtl.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionCtl.java b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionCtl.java
new file mode 100644
index 0000000..20073d9
--- /dev/null
+++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionCtl.java
@@ -0,0 +1,97 @@
+/*
+ * 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 org.apache.jena.fuseki.Fuseki ;
+import org.apache.jena.fuseki.server.DataAccessPoint ;
+import org.apache.jena.fuseki.server.DataAccessPointRegistry ;
+import org.apache.jena.fuseki.server.DataService ;
+import org.apache.jena.fuseki.servlets.ActionBase ;
+import org.apache.jena.fuseki.servlets.HttpAction ;
+import org.apache.jena.fuseki.servlets.ServletOps ;
+
+/** Control/admin request lifecycle */
+public abstract class ActionCtl extends ActionBase
+{
+    protected ActionCtl() { super(Fuseki.adminLog) ; }
+    
+    @Override
+    final
+    protected void execCommonWorker(HttpAction action) {
+        DataAccessPoint dataAccessPoint ;
+        DataService dSrv ;
+        
+        String datasetUri = mapRequestToDatasetName(action) ;
+        if ( datasetUri != null ) {
+            dataAccessPoint = DataAccessPointRegistry.get().get(datasetUri) ;
+            if ( dataAccessPoint == null ) {
+                ServletOps.errorNotFound("Not found: "+datasetUri) ;
+                return ;
+            }
+        }
+        else {
+            // This is a placeholder when creating new DatasetRefs
+            // and also if addressing a container, not a dataset
+            dataAccessPoint = null ;
+            dSrv = DataService.serviceOnlyDataService() ;
+        }
+        
+        action.setControlRequest(dataAccessPoint, datasetUri) ;
+        action.setEndpoint(null, null) ;   // No operation or service name.
+        executeAction(action) ;
+    }
+
+    protected String mapRequestToDatasetName(HttpAction action) {
+        return extractItemName(action) ;
+    }
+
+    // Execute - no stats.
+    // Intercept point for the UberServlet 
+    protected void executeAction(HttpAction action) {
+        executeLifecycle(action) ;
+    }
+    
+    // This is the service request lifecycle.
+    final
+    protected void executeLifecycle(HttpAction action)
+    {
+        startRequest(action) ;
+        try {
+            perform(action) ;
+        }
+        finally { 
+            finishRequest(action) ;
+        }
+    }
+    
+    final
+    protected boolean isContainerAction(HttpAction action) {
+        return (action.getDataAccessPoint() == null ) ;
+    }
+    
+    protected abstract void perform(HttpAction action) ;
+
+//    /** Map request to uri in the registry.
+//     *  null means no mapping done (passthrough). 
+//     */
+//    protected String mapRequestToDataset(HttpAction action) 
+//    {
+//        return ActionLib.mapRequestToDataset(action.request.getRequestURI()) ;
+//    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionDatasets.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionDatasets.java b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionDatasets.java
new file mode 100644
index 0000000..a623c28
--- /dev/null
+++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionDatasets.java
@@ -0,0 +1,400 @@
+/**
+ * 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.* ;
+import java.util.HashMap ;
+import java.util.Iterator ;
+import java.util.Map ;
+
+import javax.servlet.ServletException ;
+import javax.servlet.ServletOutputStream ;
+import javax.servlet.http.HttpServletRequest ;
+import javax.servlet.http.HttpServletResponse ;
+
+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.InternalErrorException ;
+import org.apache.jena.atlas.lib.StrUtils ;
+import org.apache.jena.atlas.web.ContentType ;
+import org.apache.jena.fuseki.FusekiLib ;
+import org.apache.jena.fuseki.build.Builder ;
+import org.apache.jena.fuseki.build.Template ;
+import org.apache.jena.fuseki.build.TemplateFunctions ;
+import org.apache.jena.fuseki.server.* ;
+import org.apache.jena.fuseki.servlets.* ;
+import org.apache.jena.riot.Lang ;
+import org.apache.jena.riot.RDFDataMgr ;
+import org.apache.jena.riot.RDFLanguages ;
+import org.apache.jena.riot.WebContent ;
+import org.apache.jena.riot.system.StreamRDF ;
+import org.apache.jena.riot.system.StreamRDFLib ;
+import org.apache.jena.web.HttpSC ;
+
+import com.hp.hpl.jena.datatypes.xsd.XSDDatatype ;
+import com.hp.hpl.jena.graph.Node ;
+import com.hp.hpl.jena.graph.NodeFactory ;
+import com.hp.hpl.jena.query.Dataset ;
+import com.hp.hpl.jena.query.ReadWrite ;
+import com.hp.hpl.jena.rdf.model.* ;
+import com.hp.hpl.jena.shared.uuid.JenaUUID ;
+import com.hp.hpl.jena.sparql.core.DatasetGraph ;
+import com.hp.hpl.jena.sparql.core.Quad ;
+import com.hp.hpl.jena.sparql.util.FmtUtils ;
+import com.hp.hpl.jena.tdb.transaction.DatasetGraphTransaction ;
+import com.hp.hpl.jena.update.UpdateAction ;
+import com.hp.hpl.jena.update.UpdateFactory ;
+import com.hp.hpl.jena.update.UpdateRequest ;
+
+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 tDatabasetTDB       = "tdb" ;
+    private static final String tDatabasetMem       = "mem" ;
+
+    public ActionDatasets() { super() ; }
+    
+    @Override
+    protected void doGet(HttpServletRequest request, HttpServletResponse response) {
+        doCommon(request, response);
+    }
+
+    @Override
+    protected void doPost(HttpServletRequest request, HttpServletResponse response) {
+        doCommon(request, response);
+    }
+    
+    @Override
+    protected void doDelete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+        doCommon(request, response);
+    }
+    
+    // ---- 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(JsonConst.datasets) ;
+        JsonDescription.arrayDatasets(builder, DataAccessPointRegistry.get());
+        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 = DataAccessPointRegistry.get().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() ;
+        String newURI = uuid.asURI() ;
+        Node gn = NodeFactory.createURI(newURI) ;
+        
+        ContentType ct = FusekiLib.getContentType(action) ;
+        
+        boolean committed = false ;
+        system.begin(ReadWrite.WRITE) ;
+        try {
+            Model model = system.getNamedModel(gn.getURI()) ;
+            StreamRDF dest = StreamRDFLib.graph(model.getGraph()) ;
+    
+            if ( WebContent.isHtmlForm(ct) )
+                assemblerFromForm(action, dest) ;
+            else if ( WebContent.isMultiPartForm(ct) )
+                assemblerFromUpload(action, dest) ;
+            else
+                assemblerFromBody(action, dest) ;
+            
+            // Keep a persistent copy.
+            String filename = FusekiServer.dirFileArea.resolve(uuid.asString()).toString() ;
+            try ( OutputStream outCopy = new FileOutputStream(filename) ) {
+                RDFDataMgr.write(outCopy, model, Lang.TURTLE) ;
+            }
+            
+            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 datasetName = object.getLexicalForm() ;
+            String datasetPath = DataAccessPoint.canonical(datasetName) ;
+            action.log.info(format("[%d] Create database : name = %s", action.id, datasetPath)) ;
+            
+            if ( DataAccessPointRegistry.get().isRegistered(datasetPath) )
+                // And abort.
+                ServletOps.error(HttpSC.CONFLICT_409, "Name already registered "+datasetPath) ;
+                
+            model.removeAll(null, pStatus, null) ;
+            model.add(subject, pStatus, FusekiVocab.stateActive) ;
+            
+            // Need to be in Resource space at this point.
+            DataAccessPoint ref = Builder.buildDataAccessPoint(subject) ;
+            DataAccessPointRegistry.register(datasetPath, ref) ;
+            action.getResponse().setContentType(WebContent.contentTypeTextPlain); 
+            ServletOutputStream out = action.getResponse().getOutputStream() ;
+            out.println("That went well") ;
+            ServletOps.success(action) ;
+            system.commit();
+            committed = true ;
+            
+        } catch (IOException ex) { IO.exception(ex); }
+        finally { 
+            if ( ! committed ) 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 ;
+    }
+
+    private void assemblerFromBody(HttpAction action, StreamRDF dest) {
+        bodyAsGraph(action, dest) ;
+    }
+
+    private void assemblerFromForm(HttpAction action, StreamRDF dest) {
+        String dbType = action.getRequest().getParameter(paramDatasetType) ;
+        String dbName = action.getRequest().getParameter(paramDatasetName) ;
+        Map<String, String> params = new HashMap<>() ;
+        params.put(Template.NAME, dbName) ;
+        FusekiServer.addGlobals(params); 
+        
+        action.log.info(format("[%d] Create database : name = %s, type = %s", action.id, dbName, dbType )) ;
+        if ( dbType == null || dbName == null )
+            ServletOps.errorBadRequest("Required parameters: dbName and dbType");
+        if ( ! dbType.equals(tDatabasetTDB) && ! dbType.equals(tDatabasetMem) )
+            ServletOps.errorBadRequest(format("dbType can be only '%s' or '%s'", tDatabasetTDB, tDatabasetMem)) ;
+        
+        String template = null ;
+        if ( dbType.equalsIgnoreCase(tDatabasetTDB))
+            template = TemplateFunctions.templateFile(Template.templateTDBFN, params) ;
+        if ( dbType.equalsIgnoreCase(tDatabasetMem))
+            template = TemplateFunctions.templateFile(Template.templateMemFN, params) ;
+        RDFDataMgr.parse(dest, new StringReader(template), "http://base/", Lang.TTL) ;
+    }
+
+    private void assemblerFromUpload(HttpAction action, StreamRDF dest) {
+        Upload.fileUploadWorker(action, dest);
+    }
+
+    // ---- DELETE
+
+    @Override
+    protected void execDeleteItem(HttpAction action) {
+//      if ( isContainerAction(action) ) {
+//      ServletOps.errorBadRequest("DELETE only applies to a specific dataset.") ;
+//      return ;
+//  }
+  
+        // Does not exist?
+        String name = action.getDatasetName() ;
+        if ( name == null )
+            name = "" ;
+        action.log.info(format("[%d] DELETE ds=%s", action.id, name)) ;
+
+        if ( ! DataAccessPointRegistry.get().isRegistered(name) )
+            ServletOps.errorNotFound("No such dataset registered: "+name);
+
+        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 = DataAccessPointRegistry.get().get(name) ;
+            // Redo check inside transaction.
+            if ( ref == null )
+                ServletOps.errorNotFound("No such dataset registered: "+name);
+                
+            // Name to graph
+            Quad q = getOne(SystemState.getDatasetGraph(), null, null, pServiceName.asNode(), null) ;
+            if ( q == null )
+                ServletOps.errorBadRequest("Failed to find dataset for '"+name+"'");
+            Node gn = q.getGraph() ;
+
+            action.log.info("SHUTDOWN NEEDED");
+            DataAccessPointRegistry.get().remove(name) ;
+            systemDSG.deleteAny(gn, null, null, null) ;
+            systemDSG.commit() ;
+            committed = true ;
+            ServletOps.success(action) ;
+        } finally { 
+            if ( ! committed ) systemDSG.abort() ; 
+            systemDSG.end() ; 
+        }
+    }
+
+    // Persistent state change.
+    private 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
+                (SystemState.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() ; 
+        }
+    }
+    
+    // ---- Auxilary 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) ; }
+
+        int len = request.getContentLength() ;
+//        if ( 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+"#");
+        ActionSPARQL.parse(action, dest, input, lang, base) ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionItem.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionItem.java b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionItem.java
new file mode 100644
index 0000000..72d3c65
--- /dev/null
+++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionItem.java
@@ -0,0 +1,45 @@
+/**
+ * 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 org.apache.jena.atlas.json.JsonValue ;
+import org.apache.jena.fuseki.servlets.HttpAction ;
+import org.apache.jena.fuseki.servlets.ServletOps ;
+import org.apache.jena.web.HttpSC ;
+
+/** Action on items in a container, but not the container itself */ 
+public abstract class ActionItem extends ActionContainerItem
+{
+    public ActionItem() { super() ; }
+    
+    @Override
+    final
+    protected JsonValue execGetContainer(HttpAction action) {
+        ServletOps.error(HttpSC.METHOD_NOT_ALLOWED_405) ;
+        return null ;
+    }
+
+    @Override
+    final
+    protected JsonValue execPostContainer(HttpAction action) {
+        ServletOps.error(HttpSC.METHOD_NOT_ALLOWED_405) ;
+        return null ;
+    }
+}
+

http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionLogs.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionLogs.java b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionLogs.java
new file mode 100644
index 0000000..c4d6579
--- /dev/null
+++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionLogs.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.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.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/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionPing.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionPing.java b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionPing.java
new file mode 100644
index 0000000..2426af6
--- /dev/null
+++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionPing.java
@@ -0,0 +1,71 @@
+/**
+ * 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.HttpServlet ;
+import javax.servlet.http.HttpServletRequest ;
+import javax.servlet.http.HttpServletResponse ;
+
+import org.apache.jena.fuseki.Fuseki ;
+import org.apache.jena.fuseki.servlets.ServletOps ;
+import org.apache.jena.web.HttpSC ;
+
+public class ActionPing extends HttpServlet
+{
+    // Ping is special.
+    // To avoid excessive logging and id allocation for a "noise" operation,
+    // this is a raw servlet.
+    public ActionPing() { 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 doHead(HttpServletRequest req, HttpServletResponse resp) {
+        doCommon(req, resp); 
+    }
+
+    protected void doCommon(HttpServletRequest request, HttpServletResponse response) {
+        try {
+            ServletOps.setNoCache(response) ; 
+            ServletOutputStream out = response.getOutputStream() ;
+            response.setContentType(contentTypeTextPlain);
+            response.setCharacterEncoding(charsetUTF8) ;
+            response.setStatus(HttpSC.OK_200);
+        } catch (IOException ex) {
+            Fuseki.serverLog.warn("ping :: IOException :: "+ex.getMessage());
+        }
+    }
+}
+
+

http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionServerStatus.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionServerStatus.java b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionServerStatus.java
new file mode 100644
index 0000000..d8f5b1e
--- /dev/null
+++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionServerStatus.java
@@ -0,0 +1,114 @@
+/**
+ * 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.server.DataAccessPointRegistry ;
+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) ;
+        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(JsonConst.version).value(versionStr)
+            .key(JsonConst.built).value(builtDateStr)
+            .key(JsonConst.startDT).value(Fuseki.serverStartedAt())
+            .key(JsonConst.uptime).value(Fuseki.serverUptimeSeconds())
+            ;
+            
+    }
+
+    private void describeDatasets(JsonBuilder builder) {
+        builder.key(JsonConst.datasets) ;
+        JsonDescription.arrayDatasets(builder, DataAccessPointRegistry.get());
+    }
+
+}
+

http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionSleep.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionSleep.java b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionSleep.java
new file mode 100644
index 0000000..e53eb9a
--- /dev/null
+++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionSleep.java
@@ -0,0 +1,98 @@
+/**
+ * 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 javax.servlet.http.HttpServletRequest ;
+import javax.servlet.http.HttpServletResponse ;
+
+import org.apache.jena.atlas.json.JsonValue ;
+import org.apache.jena.atlas.lib.Lib ;
+import org.apache.jena.fuseki.async.AsyncPool ;
+import org.apache.jena.fuseki.async.AsyncTask ;
+import org.apache.jena.fuseki.servlets.HttpAction ;
+import org.apache.jena.fuseki.servlets.ServletOps ;
+import org.slf4j.Logger ;
+
+/** A task that kicks off a asynchornous operation that simply waits and exits.  For testing. */
+public class ActionSleep extends ActionCtl /* Not ActionAsyncTask - that is a container */
+{
+    public ActionSleep() { super() ; }
+    
+    // And only POST
+    @Override
+    protected void doPost(HttpServletRequest request, HttpServletResponse response) {
+        doCommon(request, response);
+    }
+
+    @Override
+    protected void perform(HttpAction action) {
+        Runnable task = createRunnable(action) ;
+        AsyncTask aTask = Async.execASyncTask(action, AsyncPool.get(), "sleep", task) ;
+        JsonValue v = Async.asJson(aTask) ;
+        Async.setLocationHeader(action, aTask);
+        ServletOps.sendJsonReponse(action, v);
+    }
+
+    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 ;
+            name = "''" ;
+        }
+        
+        String interval = action.request.getParameter("interval") ;
+        int sleepMilli = 5000 ;
+        if ( interval != null )
+            try {
+                sleepMilli = Integer.parseInt(interval) ;
+            } catch (NumberFormatException ex) {
+                action.log.error(format("[%d] NumberFormatException: %s", action.id, interval)) ; 
+            }
+        action.log.info(format("[%d] Sleep %s %d ms", action.id, name, sleepMilli)) ;
+        return new SleepTask(action, sleepMilli) ;
+    }
+
+    static class SleepTask implements Runnable {
+        private final Logger log ;
+        private final long actionId ;
+        private final int sleepMilli ;
+        
+        public SleepTask(HttpAction action, int sleepMilli ) {
+            this.log = action.log ;
+            this.actionId = action.id ;
+            this.sleepMilli = sleepMilli ;
+        }
+
+        @Override
+        public void run() {
+            try {
+                log.info(format("[%d] >> Sleep start", actionId)) ;
+                Lib.sleep(sleepMilli) ;
+                log.info(format("[%d] << Sleep finish", actionId)) ;
+            } catch (Exception ex) {
+                log.info(format("[%d] **** Exception", actionId), ex) ;
+            }
+        }
+    }
+}
+

http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionStats.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionStats.java b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionStats.java
new file mode 100644
index 0000000..490bce2
--- /dev/null
+++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionStats.java
@@ -0,0 +1,214 @@
+/**
+ * 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 static org.apache.jena.riot.WebContent.charsetUTF8 ;
+import static org.apache.jena.riot.WebContent.contentTypeTextPlain ;
+
+import java.io.IOException ;
+import java.util.Iterator ;
+import java.util.List ;
+
+import javax.servlet.ServletOutputStream ;
+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.server.* ;
+import org.apache.jena.fuseki.servlets.HttpAction ;
+
+public class ActionStats extends ActionContainerItem
+{
+    // XXX Use ActionContainerItem
+    public ActionStats() { super() ; } 
+    
+    @Override
+    protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
+        doCommon(req, resp); 
+    }
+    
+    // This does not consult the system database for dormant etc.
+    @Override
+    protected JsonValue execGetContainer(HttpAction action) { 
+        action.log.info(format("[%d] GET stats all", action.id)) ;
+        JsonBuilder builder = new JsonBuilder() ;
+        builder.startObject("top") ;
+        
+        builder.key(JsonConst.datasets) ;
+        builder.startObject("datasets") ;
+        for ( String ds : DataAccessPointRegistry.get().keys() )
+            statsDataset(builder, ds) ; 
+        builder.finishObject("datasets") ;
+        
+        builder.finishObject("top") ;
+        return builder.build() ;
+    }
+
+    @Override
+    protected JsonValue execGetItem(HttpAction action) {
+        action.log.info(format("[%d] GET stats dataset %s", action.id, action.getDatasetName())) ;
+        
+        JsonBuilder builder = new JsonBuilder() ;
+        String datasetPath = DataAccessPoint.canonical(action.getDatasetName()) ;
+        builder.startObject("TOP") ;
+        
+        builder.key(JsonConst.datasets) ;
+        builder.startObject("datasets") ;
+        statsDataset(builder, datasetPath) ;
+        builder.finishObject("datasets") ;
+        
+        builder.finishObject("TOP") ;
+        return builder.build() ;
+    }
+
+    private void statsDataset(JsonBuilder builder, String ds) {
+        // Object started
+        builder.key(ds) ;
+        
+        DataAccessPoint access = DataAccessPointRegistry.get().get(ds) ;
+        DataService dSrv = access.getDataService() ;
+        builder.startObject("counters") ;
+        
+        builder.key(CounterName.Requests.name()).value(dSrv.getCounters().value(CounterName.Requests)) ;
+        builder.key(CounterName.RequestsGood.name()).value(dSrv.getCounters().value(CounterName.RequestsGood)) ;
+        builder.key(CounterName.RequestsBad.name()).value(dSrv.getCounters().value(CounterName.RequestsBad)) ;
+        
+        
+        // Build the operation -> endpoint list map.
+        
+//      MultiMap<OperationName, Endpoint> map = MultiMap.createMapList() ;
+//      for ( OperationName operName : dSrv.getOperations() ) {
+//          List<Endpoint> endpoints = access.getDataService().getOperation(operName) ;
+//          for ( Endpoint endpoint : endpoints )
+//              map.put(operName, endpoint) ; 
+//      }
+        
+        
+        builder.key(JsonConst.endpoints).startObject("endpoints") ;
+        
+        for ( OperationName operName : dSrv.getOperations() ) {
+            List<Endpoint> endpoints = access.getDataService().getOperation(operName) ;
+//            System.err.println(operName+" : "+endpoints.size()) ;
+//            for ( Endpoint endpoint : endpoints )
+//                System.err.println("  "+endpoint.getEndpoint()) ;
+            
+            for ( Endpoint endpoint : endpoints ) {
+                
+                // Endpoint names are unique but not services.
+                
+                builder.key(endpoint.getEndpoint()) ;
+                builder.startObject() ;
+                
+                operationCounters(builder, endpoint);
+                builder.key(JsonConst.operation).value(operName.name()) ;
+                builder.key(JsonConst.description).value(operName.getDescription()) ;
+                
+                builder.finishObject() ;
+            }
+        }
+        builder.finishObject("endpoints") ;
+        builder.finishObject("counters") ;
+
+    }
+
+    private void operationCounters(JsonBuilder builder, Endpoint operation) {
+        for (CounterName cn : operation.getCounters().counters()) {
+            Counter c = operation.getCounters().get(cn) ;
+            builder.key(cn.name()).value(c.value()) ;
+        }
+    }
+
+    private void statsTxt(HttpServletResponse resp) throws IOException
+    {
+        ServletOutputStream out = resp.getOutputStream() ;
+        resp.setContentType(contentTypeTextPlain);
+        resp.setCharacterEncoding(charsetUTF8) ;
+
+        Iterator<String> iter = DataAccessPointRegistry.get().keys().iterator() ;
+        while(iter.hasNext())
+        {
+            String ds = iter.next() ;
+            DataAccessPoint desc = DataAccessPointRegistry.get().get(ds) ;
+            statsTxt(out, desc) ;
+            if ( iter.hasNext() )
+                out.println() ;
+        }
+        out.flush() ;
+    }
+    
+    private void statsTxt(ServletOutputStream out, DataAccessPoint desc) throws IOException
+    {
+        DataService dSrv = desc.getDataService() ;
+        out.println("Dataset: "+desc.getName()) ;
+        out.println("    Requests      = "+dSrv.getCounters().value(CounterName.Requests)) ;
+        out.println("    Good          = "+dSrv.getCounters().value(CounterName.RequestsGood)) ;
+        out.println("    Bad           = "+dSrv.getCounters().value(CounterName.RequestsBad)) ;
+
+        out.println("  SPARQL Query:") ;
+        out.println("    Request       = "+counter(dSrv, OperationName.Query, CounterName.Requests)) ;
+        out.println("    Good          = "+counter(dSrv, OperationName.Query, CounterName.RequestsGood)) ;
+        out.println("    Bad requests  = "+counter(dSrv, OperationName.Query, CounterName.RequestsBad)) ;
+        out.println("    Timeouts      = "+counter(dSrv, OperationName.Query, CounterName.QueryTimeouts)) ;
+        out.println("    Bad exec      = "+counter(dSrv, OperationName.Query, CounterName.QueryExecErrors)) ;
+        out.println("    IO Errors     = "+counter(dSrv, OperationName.Query, CounterName.QueryIOErrors)) ;
+
+        out.println("  SPARQL Update:") ;
+        out.println("    Request       = "+counter(dSrv, OperationName.Update, CounterName.Requests)) ;
+        out.println("    Good          = "+counter(dSrv, OperationName.Update, CounterName.RequestsGood)) ;
+        out.println("    Bad requests  = "+counter(dSrv, OperationName.Update, CounterName.RequestsBad)) ;
+        out.println("    Bad exec      = "+counter(dSrv, OperationName.Update, CounterName.UpdateExecErrors)) ;
+        
+        out.println("  Upload:") ;
+        out.println("    Requests      = "+counter(dSrv, OperationName.Upload, CounterName.Requests)) ;
+        out.println("    Good          = "+counter(dSrv, OperationName.Upload, CounterName.RequestsGood)) ;
+        out.println("    Bad           = "+counter(dSrv, OperationName.Upload, CounterName.RequestsBad)) ;
+        
+        out.println("  SPARQL Graph Store Protocol:") ;
+        out.println("    GETs          = "+gspValue(dSrv, CounterName.HTTPget)+ " (good="+gspValue(dSrv, CounterName.HTTPgetGood)+"/bad="+gspValue(dSrv, CounterName.HTTPGetBad)+")") ;
+        out.println("    PUTs          = "+gspValue(dSrv, CounterName.HTTPput)+ " (good="+gspValue(dSrv, CounterName.HTTPputGood)+"/bad="+gspValue(dSrv, CounterName.HTTPputBad)+")") ;
+        out.println("    POSTs         = "+gspValue(dSrv, CounterName.HTTPpost)+ " (good="+gspValue(dSrv, CounterName.HTTPpostGood)+"/bad="+gspValue(dSrv, CounterName.HTTPpostBad)+")") ;
+        out.println("    DELETEs       = "+gspValue(dSrv, CounterName.HTTPdelete)+ " (good="+gspValue(dSrv, CounterName.HTTPdeleteGood)+"/bad="+gspValue(dSrv, CounterName.HTTPdeleteBad)+")") ;
+        out.println("    HEADs         = "+gspValue(dSrv, CounterName.HTTPhead)+ " (good="+gspValue(dSrv, CounterName.HTTPheadGood)+"/bad="+gspValue(dSrv, CounterName.HTTPheadBad)+")") ;
+    }
+    
+    private long counter(DataService dSrv, OperationName opName, CounterName cName) {
+        return 0 ;
+    }
+    
+    private long gspValue(DataService dSrv, CounterName cn) {
+        return  counter(dSrv, OperationName.GSP, cn) +
+                counter(dSrv, OperationName.GSP_R, cn) ;
+    }
+
+    // We shouldn't get here - no doPost above.
+    
+    @Override
+    protected JsonValue execPostContainer(HttpAction action) {
+        throw new InternalError(METHOD_POST+" container") ;
+    }
+
+    @Override
+    protected JsonValue execPostItem(HttpAction action) {
+        throw new InternalError(METHOD_POST+" item") ;
+    }
+}
+
+

http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionTasks.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionTasks.java b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionTasks.java
new file mode 100644
index 0000000..97f8027
--- /dev/null
+++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionTasks.java
@@ -0,0 +1,125 @@
+/**
+ * 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 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.Fuseki ;
+import org.apache.jena.fuseki.async.AsyncPool ;
+import org.apache.jena.fuseki.async.AsyncTask ;
+import org.apache.jena.fuseki.servlets.ActionBase ;
+import org.apache.jena.fuseki.servlets.HttpAction ;
+import org.apache.jena.fuseki.servlets.ServletOps ;
+import org.apache.jena.web.HttpSC ;
+
+public class ActionTasks extends ActionBase //ActionContainerItem
+{
+    private static AsyncPool[] pools = { AsyncPool.get() } ; 
+    
+    public ActionTasks() { super(Fuseki.serverLog) ; }
+    
+    @Override
+    protected void doGet(HttpServletRequest request, HttpServletResponse response) {
+        doCommon(request, response);
+    }
+
+    @Override
+    protected void doPost(HttpServletRequest request, HttpServletResponse response) {
+        doCommon(request, response);
+    }
+
+    private static String prefix = "/" ;
+    
+    @Override
+    protected void execCommonWorker(HttpAction action) {
+        String name = extractItemName(action) ;
+        if ( name != null ) {
+            if ( name.startsWith(prefix))
+                name = name.substring(prefix.length()) ; 
+            else
+                log.warn("Unexpected task name : "+name) ;
+        }
+        
+        String method = action.request.getMethod() ;
+        if ( method.equals(METHOD_GET) )
+            execGet(action, name) ;
+        else if ( method.equals(METHOD_POST) )
+            execPost(action, name) ;
+        else
+            ServletOps.error(HttpSC.METHOD_NOT_ALLOWED_405) ;
+    }
+
+    private void execGet(HttpAction action, String name) {
+        if ( name == null )
+            log.info(format("[%d] Tasks", action.id));
+        else
+            log.info(format("[%d] Task %s", action.id, name));
+
+        JsonValue responseBody = null ;
+        
+        if ( name == null ) {
+            JsonBuilder builder = new JsonBuilder() ;
+            builder.startArray() ;
+            
+            for ( AsyncPool pool : pools ) {
+                for ( AsyncTask aTask : pool.tasks() ) {
+                    //builder.value(aTask.getTaskId()) ;
+                    descOneTask(builder, aTask) ;
+                }
+            }
+            builder.finishArray() ;
+            responseBody = builder.build(); 
+        } else {
+            for ( AsyncPool pool : pools ) {
+                // Assumes first is only.
+                AsyncTask aTask = pool.getTask(name) ;
+                if ( aTask != null ) {
+                    JsonBuilder builder = new JsonBuilder() ;
+                    descOneTask(builder, aTask);
+                    responseBody = builder.build() ;
+                }
+            }
+        }
+        
+        if ( responseBody == null )
+            ServletOps.errorNotFound("Task '"+name+"' not found") ;
+        ServletOps.setNoCache(action) ; 
+        ServletOps.sendJsonReponse(action, responseBody); 
+    }
+
+    private void execPost(HttpAction action, String name) {
+        
+    }
+    
+    private static void descOneTask(JsonBuilder builder, AsyncTask aTask) {
+        builder.startObject("SingleTask") ;
+        builder.key(JsonConst.task).value(aTask.displayName()) ;
+        builder.key(JsonConst.taskId).value(aTask.getTaskId()) ;
+        if ( aTask.getStartPoint() != null )
+            builder.key(JsonConst.started).value(aTask.getStartPoint()) ;
+        if ( aTask.getFinishPoint() != null )
+            builder.key(JsonConst.finished).value(aTask.getFinishPoint()) ;
+        builder.finishObject("SingleTask") ;
+    }
+}
+

http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/Async.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/Async.java b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/Async.java
new file mode 100644
index 0000000..1cbda48
--- /dev/null
+++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/Async.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.mgt;
+
+import org.apache.http.HttpHeaders ;
+import org.apache.jena.atlas.json.JsonBuilder ;
+import org.apache.jena.atlas.json.JsonValue ;
+import org.apache.jena.fuseki.async.AsyncPool ;
+import org.apache.jena.fuseki.async.AsyncTask ;
+import org.apache.jena.fuseki.server.DataService ;
+import org.apache.jena.fuseki.servlets.HttpAction ;
+
+public class Async
+{
+    public static AsyncTask asyncTask(AsyncPool asyncPool, String displayName, DataService dataService, Runnable task) {
+        AsyncTask asyncTask = asyncPool.submit(task, displayName, dataService) ;
+        return asyncTask ;
+    }
+    
+    public static JsonValue asJson(AsyncTask asyncTask) {
+        JsonBuilder builder = new JsonBuilder() ;
+        builder.startObject("outer") ;
+        builder.key(JsonConst.taskId).value(asyncTask.getTaskId()) ;
+        builder.finishObject("outer") ;
+        return builder.build() ;
+    }
+    
+    public static void setLocationHeader(HttpAction action, AsyncTask asyncTask) {
+        String x = action.getRequest().getRequestURI() ;
+        if ( ! x.endsWith("/") )
+            x += "/" ;
+        x += asyncTask.getTaskId() ;
+        //String x = "/$/tasks/"+asyncTask.getTaskId() ;
+        action.getResponse().setHeader(HttpHeaders.LOCATION, x) ;
+    }
+
+    public static AsyncTask execASyncTask(HttpAction action, AsyncPool asyncPool, String displayName, Runnable runnable) {
+        AsyncTask atask = Async.asyncTask(asyncPool, "backup", action.getDataService(), runnable) ;
+        Async.setLocationHeader(action, atask); 
+        return atask ;
+    }
+    
+    // Combined does not work very well - e.g sleep does not set Location.
+//        public static AsyncTask execASyncTask(HttpAction action, AsyncPool asyncPool, String displayName, Runnable task) {
+//        AsyncTask atask = Async.asyncTask(asyncPool, displayName, action.getDataService(), task) ;
+//        Async.setLocationHeader(action, atask);
+//        JsonValue v = Async.asJson(atask) ;
+//        ServletOps.sendJsonReponse(action, v);
+//        return atask ;
+//    }
+}
+


Mime
View raw message