You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jena.apache.org by an...@apache.org on 2015/01/05 18:34:23 UTC

[44/51] [abbrv] [partial] jena git commit: Maven modules for Fuseki2

http://git-wip-us.apache.org/repos/asf/jena/blob/470ee4d7/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/RequestLog.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/RequestLog.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/RequestLog.java
new file mode 100644
index 0000000..db79d6a
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/RequestLog.java
@@ -0,0 +1,148 @@
+/**
+ * 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.text.DateFormat ;
+import java.text.SimpleDateFormat ;
+import java.util.Collection ;
+import java.util.Date ;
+import java.util.Enumeration ;
+import java.util.TimeZone ;
+
+import javax.servlet.http.HttpServletRequest ;
+import javax.servlet.http.HttpServletResponse ;
+
+import org.apache.jena.atlas.logging.Log ;
+import org.apache.jena.fuseki.servlets.HttpAction ;
+
+/** Create standard request logs (NCSA etc) */ 
+public class RequestLog {
+    /*
+      http://httpd.apache.org/docs/current/mod/mod_log_config.html
+Common Log Format (CLF)
+    "%h %l %u %t \"%r\" %>s %b"
+Common Log Format with Virtual Host
+    "%v %h %l %u %t \"%r\" %>s %b"
+NCSA extended/combined log format
+    "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\""
+     */
+    /*
+        %l -- Identity or -
+        %u -- Remote user or -
+        %t -- Timestamp
+        "%r" -- Request line
+        %>s -- Final request status
+        %b -- Size in bytes
+        Headers.
+        %{}i for request header.
+        %{}o for response header.
+      */  
+    
+    private static DateFormat dateFormatter ; 
+    static {
+        dateFormatter = new SimpleDateFormat("dd/MMM/yyyy:HH:mm:ss Z") ;
+        dateFormatter.setTimeZone(TimeZone.getTimeZone("UTC"));
+    }
+
+    /** NCSA combined log format *
+     * LogFormat "%{X-Forwarded-For}i %l %u %t \"%r\" %>s %b  \"%{Referer}i\" \"%{User-Agent}i\"" combinedfwd
+     * XXX.XXX.XXX.XXX - - [01/Feb/2014:03:19:09 +0000] "GET / HTTP/1.1" 200 6190  "-" "check_http/v1.4.16 (nagios-plugins 1.4.16)"
+     */
+    public static String combinedNCSA(HttpAction action) {
+        StringBuilder builder = new StringBuilder() ;
+        HttpServletRequest request = action.request ;
+        HttpServletResponse response = action.response ;
+        
+        // Remote
+        String remote = get(request, "X-Forwarded-For", request.getRemoteAddr()) ;
+        builder.append(remote) ;
+        builder.append(" ") ;
+        
+        // %l %u : User identity (unrelaible)
+        builder.append("- - ") ;
+        
+        // %t
+        // Expensive? 
+        builder.append("[");
+        // Better?
+        builder.append(dateFormatter.format(new Date())) ;
+        builder.append("] ");
+        
+        // "%r"
+        builder.append("\"")  ;
+        builder.append(request.getMethod()) ;
+        builder.append(" ") ;
+        // No query string - they are long and logged readably elsewhere
+        builder.append(request.getRequestURI()) ; 
+        builder.append("\"")  ;
+        //%>s -- Final request status
+        builder.append(" ")  ;
+        builder.append(response.getStatus())  ;
+        
+        //%b -- Size in bytes
+        builder.append(" ")  ;
+        //String size = getField()
+        String size = get(response, "Content-Length", "-") ;
+        builder.append(size)  ;
+        
+        // "%{Referer}i" 
+        builder.append(" \"")  ;
+        builder.append(get(request, "Referer", "")) ;
+        builder.append("\"")  ;
+        // "%{User-Agent}i"
+        builder.append(" \"")  ;
+        builder.append(get(request, "User-Agent", "")) ;
+        builder.append("\"")  ;
+        
+        return builder.toString() ;
+    }
+    
+    private static String get(HttpServletRequest request, String name, String dft) {
+        String x = get(request, name) ;
+        if ( x == null )
+            x = dft ;
+        return x ;
+    }
+    
+    private static String get(HttpServletRequest request, String name) {
+        Enumeration<String> en = request.getHeaders(name) ;
+        if ( ! en.hasMoreElements() ) return null ;
+        String x = en.nextElement() ;
+        if ( en.hasMoreElements() ) {
+            Log.warn(RequestLog.class, "Multiple request header values") ;
+        }
+        return x ;
+    }
+
+    private static String get(HttpServletResponse response, String name, String dft) {
+        String x = get(response, name) ;
+        if ( x == null )
+            x = dft ;
+        return x ;
+    }
+
+    
+    private static String get(HttpServletResponse response, String name) {
+        Collection<String> en = response.getHeaders(name) ;
+        if ( en.isEmpty() )return null ;
+        if ( en.size() != 1 ) Log.warn(RequestLog.class, "Multiple response header values") ;
+        return response.getHeader(name) ;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/470ee4d7/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/ServerInitialConfig.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/ServerInitialConfig.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/ServerInitialConfig.java
new file mode 100644
index 0000000..67f5d26
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/ServerInitialConfig.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 java.util.HashMap ;
+import java.util.Map ;
+
+import com.hp.hpl.jena.sparql.core.DatasetGraph ;
+
+/** Dataset setup (command line, config file) for a dataset (or several if config file) */
+public class ServerInitialConfig {
+    // Either this ...
+    public String    templateFile     = null ;
+    public Map<String,String> params  = new HashMap<>() ;
+    public String    datasetPath      = null ;
+    public boolean   allowUpdate      = false ;
+    // Or this ...
+    public String    fusekiConfigFile = null ;
+    
+    // Special case - directly pass in the dataset graphs - datasetPath must be given.
+    // This is not persistet across server restarts. 
+    public DatasetGraph dsg           = null ;
+    
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/470ee4d7/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/ServiceMXBean.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/ServiceMXBean.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/ServiceMXBean.java
new file mode 100644
index 0000000..11c7330
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/ServiceMXBean.java
@@ -0,0 +1,32 @@
+/**
+ * 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 ServiceMXBean
+{
+    String getName() ;
+    
+    long getRequests() ;
+    long getRequestsGood() ;
+    long getRequestsBad() ;
+    
+//    void enable() ;
+//    void disable() ;
+}
+

http://git-wip-us.apache.org/repos/asf/jena/blob/470ee4d7/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/ShiroEnvironmentLoader.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/ShiroEnvironmentLoader.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/ShiroEnvironmentLoader.java
new file mode 100644
index 0000000..f33fe92
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/ShiroEnvironmentLoader.java
@@ -0,0 +1,164 @@
+/**
+ * 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.IOException ;
+import java.io.InputStream ;
+import java.nio.file.Path ;
+import java.nio.file.Paths ;
+
+import javax.servlet.ServletContext ;
+import javax.servlet.ServletContextEvent ;
+import javax.servlet.ServletContextListener ;
+
+import org.apache.jena.fuseki.Fuseki ;
+import org.apache.shiro.config.ConfigurationException ;
+import org.apache.shiro.io.ResourceUtils ;
+import org.apache.shiro.web.env.EnvironmentLoader ;
+import org.apache.shiro.web.env.ResourceBasedWebEnvironment ;
+import org.apache.shiro.web.env.WebEnvironment ;
+
+import com.hp.hpl.jena.util.FileUtils ;
+
+/** A place to perform Fuseki-specific initialization of Apache Shiro.
+ *  Runs after listener FusekiServerEnvironmentInit and before FusekiServerListener
+ *  This means finding shiro.ini in multiple possible places, based on
+ *  different deployment setups.
+ */
+public class ShiroEnvironmentLoader extends EnvironmentLoader implements ServletContextListener {
+    private ServletContext servletContext ; 
+    
+    public ShiroEnvironmentLoader() {}
+    
+    @Override
+    public void contextInitialized(ServletContextEvent sce) {
+        FusekiServer.init() ; 
+        this.servletContext = sce.getServletContext() ;
+        try { 
+            // Shiro.
+            initEnvironment(servletContext);
+        } catch (ConfigurationException  ex) {
+            Fuseki.configLog.error("Shiro initialization failed: "+ex.getMessage());
+            // Exit?
+            throw ex ;
+        }
+    }
+
+    @Override
+    public void contextDestroyed(ServletContextEvent sce) {
+        destroyEnvironment(sce.getServletContext());
+    }
+
+    /** 
+     * Normal Shiro initialization only supports one location for an INI file.
+     *  
+     * When given multiple multiple locations for the shiro.ini file, and 
+     * if a {@link ResourceBasedWebEnvironment}, check the list of configuration
+     * locations, testing whether the name identified an existing resource.  
+     * For the first resource name found to exist, reset the {@link ResourceBasedWebEnvironment}
+     * to name that resource alone so the normal Shiro initialization  
+     */
+    @Override
+    protected void customizeEnvironment(WebEnvironment environment) {
+        if ( environment instanceof ResourceBasedWebEnvironment ) {
+            ResourceBasedWebEnvironment env = (ResourceBasedWebEnvironment)environment ;
+            String[] locations = env.getConfigLocations() ;
+            String loc = huntForShiroIni(locations) ;
+            Fuseki.configLog.info("Shiro file: "+loc);
+            if (loc != null )
+                locations = new String[] {loc} ;
+            env.setConfigLocations(locations);
+        }
+    }
+    
+    private static final String FILE = "file" ;
+    
+    /** Look for a Shiro ini file, or return null */
+    private static String huntForShiroIni(String[] locations) {
+        FusekiEnv.setEnvironment() ;
+        Fuseki.init();
+        for ( String loc : locations ) {
+            // If file:, look for that file.
+            // If a relative name without scheme, look in FUSEKI_BASE, FUSEKI_HOME, webapp. 
+            String scheme = FileUtils.getScheme(loc) ;
+            
+            // Covers C:\\ as a "scheme name"
+            if ( scheme != null ) {
+                if ( scheme.equalsIgnoreCase(FILE)) {
+                    // Test file: for exists
+                    Path p = Paths.get(loc.substring(FILE.length()+1)) ;
+                    if ( ! p.toFile().exists() )
+                        continue ;
+                    // Fall through.
+                }
+                // Can't test - try 
+                return loc ;
+            }
+            // No scheme .
+            Path p = Paths.get(loc) ;
+            
+            String fn = resolve(FusekiEnv.FUSEKI_BASE, p) ;
+            if ( fn != null )
+                return "file://"+fn ;
+            fn = resolve(FusekiEnv.FUSEKI_HOME, p) ;
+            if ( fn != null )
+                return "file://"+fn ;
+            
+            // Try in webapp.
+            
+            try ( InputStream is = ResourceUtils.getInputStreamForPath(loc); ) {
+                boolean exists = (is != null ) ;
+                return loc ;
+            } catch (IOException e) { }
+        }
+        return null ;
+    }
+    
+    /** Directory + name -> filename if it exists */ 
+    private static String resolve(Path dir, Path file) {
+        Path p = dir.resolve(file) ;
+        if ( p.toFile().exists() )
+            return p.normalize().toString() ;
+        return null ;
+    }
+
+//    /** 
+//     * Test whether a name identified an existing resource
+//     * @param resource    A String in Shiro-resource name format (e.g. URL scheme names) 
+//     * @return True/false as to whether the resource can be found or not. 
+//     */
+//    
+//    private boolean resourceExists(String resource) {
+//        try {
+//            // See IniWebEnvironment.convertPathToIni
+//            if (!ResourceUtils.hasResourcePrefix(resource)) {
+//                //Sort out "path" and open as a webapp resource.
+//                resource = WebUtils.normalize(resource);
+//                URL url = servletContext.getResource(resource) ;
+//                return ( url == null ) ;
+//            } else {
+//                // Treat as a plain name. 
+//                InputStream is = ResourceUtils.getInputStreamForPath(resource);
+//                boolean exists = (is != null ) ;
+//                is.close() ;
+//                return exists ;
+//            }
+//        } catch (IOException e) { return false ; }
+//    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/470ee4d7/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/SystemState.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/SystemState.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/SystemState.java
new file mode 100644
index 0000000..74117c9
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/SystemState.java
@@ -0,0 +1,108 @@
+/**
+ * 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.FileOps ;
+import org.apache.jena.atlas.lib.StrUtils ;
+import org.apache.jena.fuseki.Fuseki ;
+
+import com.hp.hpl.jena.query.Dataset ;
+import com.hp.hpl.jena.tdb.StoreConnection ;
+import com.hp.hpl.jena.tdb.TDB ;
+import com.hp.hpl.jena.tdb.TDBFactory ;
+import com.hp.hpl.jena.tdb.base.block.FileMode ;
+import com.hp.hpl.jena.tdb.base.file.Location ;
+import com.hp.hpl.jena.tdb.setup.StoreParams ;
+import com.hp.hpl.jena.tdb.transaction.DatasetGraphTransaction ;
+
+public class SystemState {
+    private static String SystemDatabaseLocation ;
+    // Testing may reset this.
+    public static Location location ; 
+    
+    private  static Dataset                 dataset   = null ;
+    private  static DatasetGraphTransaction dsg       = null ;
+    
+    public static Dataset getDataset() {
+        init() ;
+        return dataset ;
+    }
+    
+    public static DatasetGraphTransaction getDatasetGraph() {
+        init() ;
+        return dsg ;
+    }
+    
+    private static boolean initialized = false ; 
+    private static void init() {
+        init$() ;
+    }
+    
+    /** Small footprint database.  The system database records the server state.
+     * It should not be performance critical, mainly being used for system admin
+     * functions.
+     * <p>Direct mode so that it is not competing for OS file cache space.
+     * <p>Small caches - 
+     */
+    private static final StoreParams systemDatabaseParams = StoreParams.builder()
+        .fileMode(FileMode.direct)
+        .blockReadCacheSize(20)
+        .blockWriteCacheSize(20)
+        .node2NodeIdCacheSize(5000)
+        .nodeId2NodeCacheSize(5000)
+        .nodeMissCacheSize(100)
+        .build() ;
+    
+    public /* for testing */ static void init$() {
+        if ( initialized )
+            return ;
+        initialized = true ;
+        
+        if ( location == null )
+            location = Location.create(FusekiServer.dirSystemDatabase.toString()) ;
+        
+        if ( ! location.isMem() )
+            FileOps.ensureDir(location.getDirectoryPath()) ;
+        
+        // Force it into the store connection as a low footprint
+        if ( StoreConnection.getExisting(location) != null )
+            Fuseki.serverLog.warn("System database already in the StoreConnection cache") ;
+        StoreConnection.make(location, systemDatabaseParams) ;
+        
+        dataset = TDBFactory.createDataset(location) ;
+        dsg     = (DatasetGraphTransaction)(dataset.asDatasetGraph()) ;
+        dsg.getContext().set(TDB.symUnionDefaultGraph, false) ;
+    }
+    
+    public static String PREFIXES = StrUtils.strjoinNL
+        ("BASE <http://example/base#>",
+         "PREFIX ja:      <http://jena.hpl.hp.com/2005/11/Assembler#>",
+         "PREFIX fu:      <http://jena.apache.org/fuseki#>",
+         "PREFIX fuseki:  <http://jena.apache.org/fuseki#>",
+         "PREFIX rdf:     <http://www.w3.org/1999/02/22-rdf-syntax-ns#>",
+         "PREFIX rdfs:    <http://www.w3.org/2000/01/rdf-schema#>",
+         "PREFIX tdb:     <http://jena.hpl.hp.com/2008/tdb#>",
+         "PREFIX sdb:     <http://jena.hpl.hp.com/20087/sdb#>",
+         "PREFIX list:    <http://jena.hpl.hp.com/ARQ/list#>",
+         "PREFIX xsd:     <http://www.w3.org/2001/XMLSchema#>",
+         "PREFIX apf:     <http://jena.hpl.hp.com/ARQ/property#>",
+         "PREFIX afn:     <http://jena.hpl.hp.com/ARQ/function#>",
+         "") ;
+}
+

http://git-wip-us.apache.org/repos/asf/jena/blob/470ee4d7/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionBase.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionBase.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionBase.java
new file mode 100644
index 0000000..3e45c78
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionBase.java
@@ -0,0 +1,265 @@
+/*
+ * 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.servlets;
+
+import static java.lang.String.format ;
+
+import java.io.IOException ;
+import java.util.Enumeration ;
+import java.util.Map ;
+
+import javax.servlet.ServletException ;
+import javax.servlet.http.HttpServletRequest ;
+import javax.servlet.http.HttpServletResponse ;
+
+import org.apache.jena.atlas.RuntimeIOException ;
+import org.apache.jena.fuseki.Fuseki ;
+import org.apache.jena.riot.web.HttpNames ;
+import org.apache.jena.web.HttpSC ;
+import org.slf4j.Logger ;
+
+import com.hp.hpl.jena.query.ARQ ;
+import com.hp.hpl.jena.query.QueryCancelledException ;
+import com.hp.hpl.jena.sparql.util.Context ;
+
+/** General request lifecycle */
+public abstract class ActionBase extends ServletBase
+{
+    protected final Logger log ;
+
+    protected ActionBase(Logger log) {
+        super() ;
+        this.log = log ;
+    }
+    
+    @Override 
+    public void init() {
+//        log.info("["+Utils.className(this)+"] ServletContextName = "+getServletContext().getServletContextName()) ;
+//        log.info("["+Utils.className(this)+"] ContextPath        = "+getServletContext().getContextPath()) ;
+
+        //super.init() ;
+    }
+    
+    /**
+     * Common framework for handling HTTP requests.
+     * @param request
+     * @param response
+     */
+    protected void doCommon(HttpServletRequest request, HttpServletResponse response)
+    {
+        try {
+            long id = allocRequestId(request, response);
+            
+            // Lifecycle
+            HttpAction action = allocHttpAction(id, request, response) ;
+
+            printRequest(action) ;
+            action.setStartTime() ;
+            
+            // The response may be changed to a HttpServletResponseTracker
+            response = action.response ;
+            initResponse(request, response) ;
+            Context cxt = ARQ.getContext() ;
+            
+            try {
+                execCommonWorker(action) ;
+            } catch (QueryCancelledException ex) {
+                // Also need the per query info ...
+                String message = String.format("The query timed out (restricted to %s ms)", cxt.get(ARQ.queryTimeout));
+                // Possibility :: response.setHeader("Retry-after", "600") ;    // 5 minutes
+                ServletOps.responseSendError(response, HttpSC.SERVICE_UNAVAILABLE_503, message);
+            } catch (ActionErrorException ex) {
+                if ( ex.exception != null )
+                    ex.exception.printStackTrace(System.err) ;
+                // Log message done by printResponse in a moment.
+                if ( ex.message != null )
+                    ServletOps.responseSendError(response, ex.rc, ex.message) ;
+                else
+                    ServletOps.responseSendError(response, ex.rc) ;
+            } catch (RuntimeIOException ex) {
+                log.warn(format("[%d] Runtime IO Exception (client left?) RC = %d : %s", id, HttpSC.INTERNAL_SERVER_ERROR_500, ex.getMessage()), ex) ;
+                ServletOps.responseSendError(response, HttpSC.INTERNAL_SERVER_ERROR_500, ex.getMessage()) ;
+            } catch (Throwable ex) {
+                // This should not happen.
+                //ex.printStackTrace(System.err) ;
+                log.warn(format("[%d] RC = %d : %s", id, HttpSC.INTERNAL_SERVER_ERROR_500, ex.getMessage()), ex) ;
+                ServletOps.responseSendError(response, HttpSC.INTERNAL_SERVER_ERROR_500, ex.getMessage()) ;
+            }
+    
+            action.setFinishTime() ;
+            printResponse(action) ;
+            archiveHttpAction(action) ;
+        } catch (Throwable th) {
+            log.error("Internal error", th) ;
+        }
+    }
+
+    // ---- Operation lifecycle
+
+    /**
+     * Returns a fresh HTTP Action for this request.
+     * @param id the Request ID
+     * @param request HTTP request
+     * @param response HTTP response
+     * @return a new HTTP Action
+     */
+    protected HttpAction allocHttpAction(long id, HttpServletRequest request, HttpServletResponse response) {
+        // Need a way to set verbose logging on a per servlet and per request basis. 
+        return new HttpAction(id, log, request, response, Fuseki.verboseLogging) ;
+    }
+
+    /**
+     * Begin handling an {@link HttpAction}  
+     * @param action
+     */
+    protected final void startRequest(HttpAction action) {
+        action.startRequest() ;
+    }
+    
+    /**
+     * Stop handling an {@link HttpAction}  
+     */
+    protected final void finishRequest(HttpAction action) {
+        action.finishRequest() ;
+    }
+    
+    /**
+     * Archives the HTTP Action.
+     * @param action HTTP Action
+     * @see HttpAction#minimize()
+     */
+    private void archiveHttpAction(HttpAction action) {
+        action.minimize() ;
+    }
+
+    /**
+     * Execute this request, which maybe a admin operation or a client request. 
+     * @param action HTTP Action
+     */
+    protected abstract void execCommonWorker(HttpAction action) ;
+    
+    /** Extract the name after the container name (serverlet name).
+     * Returns "/name" or null 
+     */  
+    protected static String extractItemName(HttpAction action) {
+//      action.log.info("context path  = "+action.request.getContextPath()) ;
+//      action.log.info("pathinfo      = "+action.request.getPathInfo()) ;
+//      action.log.info("servlet path  = "+action.request.getServletPath()) ;
+      // if /name
+      //    request.getServletPath() otherwise it's null
+      // if /*
+      //    request.getPathInfo() ; otherwise it's null.
+      
+      // PathInfo is after the servlet name. 
+      String x1 = action.request.getServletPath() ;
+      String x2 = action.request.getPathInfo() ;
+      
+      String pathInfo = action.request.getPathInfo() ;
+      if ( pathInfo == null || pathInfo.isEmpty() || pathInfo.equals("/") )
+          // Includes calling as a container. 
+          return null ;
+      String name = pathInfo ;
+      // pathInfo starts with a "/"
+      int idx = pathInfo.lastIndexOf('/') ;
+      if ( idx > 0 )
+          name = name.substring(idx) ;
+      // Returns "/name"
+      return name ; 
+  }
+
+    @SuppressWarnings("unused") // ServletException
+    protected void doPatch(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+        response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, "HTTP PATCH not supported");
+    }
+    
+    private void printRequest(HttpAction action) {
+        String url = ActionLib.wholeRequestURL(action.request) ;
+        String method = action.request.getMethod() ;
+
+        log.info(format("[%d] %s %s", action.id, method, url)) ;
+        if ( action.verbose ) {
+            Enumeration<String> en = action.request.getHeaderNames() ;
+            for (; en.hasMoreElements();) {
+                String h = en.nextElement() ;
+                Enumeration<String> vals = action.request.getHeaders(h) ;
+                if ( !vals.hasMoreElements() )
+                    log.info(format("[%d]   ", action.id, h)) ;
+                else {
+                    for (; vals.hasMoreElements();)
+                        log.info(format("[%d]   %-20s %s", action.id, h, vals.nextElement())) ;
+                }
+            }
+        }
+    }
+
+    private void initResponse(HttpServletRequest request, HttpServletResponse response) {
+        setCommonHeaders(response) ;
+        String method = request.getMethod() ;
+        // All GET and HEAD operations are sensitive to conneg so ...
+        if ( HttpNames.METHOD_GET.equalsIgnoreCase(method) || HttpNames.METHOD_HEAD.equalsIgnoreCase(method) )
+            setVaryHeader(response) ;
+    }
+
+    private void printResponse(HttpAction action) {
+        long time = action.getTime() ;
+
+        HttpServletResponseTracker response = action.response ;
+        if ( action.verbose ) {
+            if ( action.contentType != null )
+                log.info(format("[%d]   %-20s %s", action.id, HttpNames.hContentType, action.contentType)) ;
+            if ( action.contentLength != -1 )
+                log.info(format("[%d]   %-20s %d", action.id, HttpNames.hContentLengh, action.contentLength)) ;
+            for (Map.Entry<String, String> e : action.headers.entrySet())
+                log.info(format("[%d]   %-20s %s", action.id, e.getKey(), e.getValue())) ;
+        }
+
+        String timeStr = fmtMillis(time) ;
+
+        if ( action.message == null )
+            log.info(String.format("[%d] %d %s (%s) ", action.id, action.statusCode,
+                                   HttpSC.getMessage(action.statusCode), timeStr)) ;
+        else
+            log.info(String.format("[%d] %d %s (%s) ", action.id, action.statusCode, action.message, timeStr)) ;
+        
+        // See also HttpAction.finishRequest - request logging happens there.
+    }
+
+    /**
+     * <p>Given a time point, return the time as a milli second string if it is less than 1000,
+     * otherwise return a seconds string.</p>
+     * <p>It appends a 'ms' suffix when using milli seconds,
+     *  and <i>s</i> for seconds.</p>
+     * <p>For instance: </p>
+     * <ul>
+     * <li>10 emits 10 ms</li>
+     * <li>999 emits 999 ms</li>
+     * <li>1000 emits 1.000 s</li>
+     * <li>10000 emits 10.000 s</li>
+     * </ul>
+     * @param time the time in milliseconds
+     * @return the time as a display string
+     */
+
+    private static String fmtMillis(long time) {
+        // Millis only? seconds only?
+        if ( time < 1000 )
+            return String.format("%,d ms", time) ;
+        return String.format("%,.3f s", time / 1000.0) ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/470ee4d7/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionErrorException.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionErrorException.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionErrorException.java
new file mode 100644
index 0000000..c87cfe8
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionErrorException.java
@@ -0,0 +1,32 @@
+/*
+ * 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.servlets;
+
+public class ActionErrorException extends RuntimeException
+{
+    public final Throwable exception ;
+    public final String message ;
+    public final int rc ;
+    public ActionErrorException(Throwable ex, String message, int rc)
+    {
+        this.exception = ex ;
+        this.message = message ;
+        this.rc = rc ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/470ee4d7/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionLib.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionLib.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionLib.java
new file mode 100644
index 0000000..182b46e
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionLib.java
@@ -0,0 +1,180 @@
+/**
+ * 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.servlets;
+
+import javax.servlet.http.HttpServletRequest ;
+
+import org.apache.jena.atlas.web.AcceptList ;
+import org.apache.jena.atlas.web.MediaType ;
+import org.apache.jena.fuseki.DEF ;
+import org.apache.jena.fuseki.conneg.ConNeg ;
+import org.apache.jena.fuseki.server.DataAccessPoint ;
+import org.apache.jena.fuseki.server.DataAccessPointRegistry ;
+
+/** Operations related to servlets */
+
+public class ActionLib {
+    /**
+     * A possible implementation for {@link ActionSPARQL#mapRequestToDataset}
+     * that assumes the form /dataset/service.
+     * @param action the request
+     * @return the dataset
+     */    public static String mapRequestToDataset(HttpAction action) {
+         String uri = action.getActionURI() ;
+         return mapActionRequestToDataset(uri) ;
+     }
+    
+    /** Map request to uri in the registry.
+     *  A possible implementation for mapRequestToDataset(String)
+     *  that assumes the form /dataset/service 
+     *  Returning null means no mapping found.
+     *  The URI must be the action URI (no contact path) 
+     */
+    
+    public static String mapActionRequestToDataset(String uri) {
+        // Chop off trailing part - the service selector
+        // e.g. /dataset/sparql => /dataset 
+        int i = uri.lastIndexOf('/') ;
+        if ( i == -1 )
+            return null ;
+        if ( i == 0 )
+        {
+            // started with '/' - leave.
+            return uri ;
+        }
+        
+        return uri.substring(0, i) ;
+    }
+
+    /** Calculate the operation , given action and data access point */ 
+    public static String mapRequestToOperation(HttpAction action, DataAccessPoint dsRef) {
+        if ( dsRef == null )
+            return "" ;
+        String uri = action.getActionURI() ;
+        String name = dsRef.getName();
+        if ( name.length() >= uri.length() )
+            return "" ;
+        return uri.substring(name.length()+1) ;   // Skip the separating "/"
+        
+    }
+    
+    /** Implementation of mapRequestToDataset(String) that looks for
+     * the longest match in the registry.
+     * This includes use in direct naming GSP. 
+     */
+    public static String mapRequestToDatasetLongest$(String uri) 
+    {
+        if ( uri == null )
+            return null ;
+        
+        // This covers local, using the URI as a direct name for
+        // a graph, not just using the indirect ?graph= or ?default 
+        // forms.
+
+        String ds = null ;
+        for ( String ds2 : DataAccessPointRegistry.get().keys() ) {
+            if ( ! uri.startsWith(ds2) )
+                continue ;
+
+            if ( ds == null )
+            {
+                ds = ds2 ;
+                continue ; 
+            }
+            if ( ds.length() < ds2.length() )
+            {
+                ds = ds2 ;
+                continue ;
+            }
+        }
+        return ds ;
+    }
+
+    /** Calculate the fill URL including query string
+     * for the HTTP request. This may be quite long.
+     * @param request HttpServletRequest
+     * @return String The full URL, including query string.
+     */
+    public static String wholeRequestURL(HttpServletRequest request) {
+        StringBuffer sb = request.getRequestURL() ;
+        String queryString = request.getQueryString() ;
+        if ( queryString != null ) {
+            sb.append("?") ;
+            sb.append(queryString) ;
+        }
+        return sb.toString() ;
+    }
+
+    /* 
+     * The context path can be:
+     * "" for the root context
+     * "/APP" for named contexts
+     * so:
+     * "/dataset/server" becomes "/dataset/server"
+     * "/APP/dataset/server" becomes "/dataset/server"
+     */
+    public static String removeContextPath(HttpAction action) {
+
+        return actionURI(action.request) ;
+    }
+    
+    public static String actionURI(HttpServletRequest request) {
+//      Log.info(this, "URI                     = '"+request.getRequestURI()) ;
+//      Log.info(this, "Context path            = '"+request.getContextPath()+"'") ;
+//      Log.info(this, "Servlet path            = '"+request.getServletPath()+"'") ;
+//      ServletContext cxt = this.getServletContext() ;
+//      Log.info(this, "ServletContext path     = '"+cxt.getContextPath()+"'") ;
+        
+        String contextPath = request.getServletContext().getContextPath() ;
+        String uri = request.getRequestURI() ;
+        if ( contextPath == null )
+            return uri ;
+        if ( contextPath.isEmpty())
+            return uri ;
+        String x = uri ;
+        if ( uri.startsWith(contextPath) )
+            x = uri.substring(contextPath.length()) ;
+        //log.info("uriWithoutContextPath: uri = "+uri+" contextPath="+contextPath+ "--> x="+x) ;
+        return x ;
+    }
+
+    /** Negotiate the content-type and set the response headers */ 
+    public static MediaType contentNegotation(HttpAction action, AcceptList myPrefs,
+                                              MediaType defaultMediaType) {
+        MediaType mt = ConNeg.chooseContentType(action.request, myPrefs, defaultMediaType) ;
+        if ( mt == null )
+            return null ;
+        if ( mt.getContentType() != null )
+            action.response.setContentType(mt.getContentType()) ;
+        if ( mt.getCharset() != null )
+            action.response.setCharacterEncoding(mt.getCharset()) ;
+        return mt ;
+    }
+    
+    /** Negotiate the content-type for an RDF triples syntax and set the response headers */ 
+    public static MediaType contentNegotationRDF(HttpAction action) {
+        return contentNegotation(action, DEF.rdfOffer, DEF.acceptRDFXML) ;
+    }
+
+    /** Negotiate the content-type for an RDF quads syntax and set the response headers */ 
+    public static MediaType contentNegotationQuads(HttpAction action) {
+        return contentNegotation(action, DEF.quadsOffer, DEF.acceptNQuads) ;
+    }
+}
+

http://git-wip-us.apache.org/repos/asf/jena/blob/470ee4d7/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionREST.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionREST.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionREST.java
new file mode 100644
index 0000000..e2c7e4a
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionREST.java
@@ -0,0 +1,161 @@
+/*
+ * 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.servlets;
+
+import java.io.IOException ;
+import java.util.Locale ;
+
+import javax.servlet.ServletException ;
+import javax.servlet.http.HttpServletRequest ;
+import javax.servlet.http.HttpServletResponse ;
+
+import org.apache.jena.fuseki.server.CounterName ;
+
+/** Common point for operations that are "REST"ish (use GET/PUT etc as operations). */ 
+public abstract class ActionREST extends ActionSPARQL
+{
+    public ActionREST()
+    { super() ; }
+
+    @Override
+    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+        // Direct all verbs to our common framework.
+        doCommon(request, response) ;
+    }
+    
+    @Override
+    protected void perform(HttpAction action) {
+        dispatch(action) ;
+    }
+
+    private void dispatch(HttpAction action) {
+        HttpServletRequest req = action.request ;
+        HttpServletResponse resp = action.response ;
+        String method = req.getMethod().toUpperCase(Locale.ROOT) ;
+
+        if (method.equals(METHOD_GET))
+            doGet$(action);
+        else if (method.equals(METHOD_HEAD))
+            doHead$(action);
+        else if (method.equals(METHOD_POST))
+            doPost$(action);
+        else if (method.equals(METHOD_PATCH))
+            doPatch$(action) ;
+        else if (method.equals(METHOD_OPTIONS))
+            doOptions$(action) ;
+        else if (method.equals(METHOD_TRACE))
+            //doTrace(action) ;
+            ServletOps.errorMethodNotAllowed("TRACE") ;
+        else if (method.equals(METHOD_PUT))
+            doPut$(action) ;   
+        else if (method.equals(METHOD_DELETE))
+            doDelete$(action) ;
+        else
+            ServletOps.errorNotImplemented("Unknown method: "+method) ;
+    }
+
+    // Counter wrappers
+    
+    // XXX Out of date - we now add HTTP counters to all endpoints. 
+    
+    private final void doGet$(HttpAction action) {
+        incCounter(action.getEndpoint(), CounterName.HTTPget) ;
+        try {
+            doGet(action) ;
+            incCounter(action.getEndpoint(), CounterName.HTTPgetGood) ;
+        } catch ( ActionErrorException ex) {
+            incCounter(action.getEndpoint(), CounterName.HTTPGetBad) ;
+            throw ex ;
+        }
+    }
+
+    private final void doHead$(HttpAction action) {
+        incCounter(action.getEndpoint(), CounterName.HTTPhead) ;
+        try {
+            doHead(action) ;
+            incCounter(action.getEndpoint(), CounterName.HTTPheadGood) ;
+        } catch ( ActionErrorException ex) {
+            incCounter(action.getEndpoint(), CounterName.HTTPheadBad) ;
+            throw ex ;
+        }
+    }
+
+    private final void doPost$(HttpAction action) {
+        incCounter(action.getEndpoint(), CounterName.HTTPpost) ;
+        try {
+            doPost(action) ;
+            incCounter(action.getEndpoint(), CounterName.HTTPpostGood) ;
+        } catch ( ActionErrorException ex) {
+            incCounter(action.getEndpoint(), CounterName.HTTPpostBad) ;
+            throw ex ;
+        }
+    }
+
+    private final void doPatch$(HttpAction action) {
+        incCounter(action.getEndpoint(), CounterName.HTTPpatch) ;
+        try {
+            doPatch(action) ;
+            incCounter(action.getEndpoint(), CounterName.HTTPpatchGood) ;
+        } catch ( ActionErrorException ex) {
+            incCounter(action.getEndpoint(), CounterName.HTTPpatchBad) ;
+            throw ex ;
+        }
+    }
+
+    private final void doDelete$(HttpAction action) {
+        incCounter(action.getEndpoint(), CounterName.HTTPdelete) ;
+        try {
+            doDelete(action) ;
+            incCounter(action.getEndpoint(), CounterName.HTTPdeleteGood) ;
+        } catch ( ActionErrorException ex) {
+            incCounter(action.getEndpoint(), CounterName.HTTPdeleteBad) ;
+            throw ex ;
+        }
+    }
+
+    private final void doPut$(HttpAction action) {
+        incCounter(action.getEndpoint(), CounterName.HTTPput) ;
+        try {
+            doPut(action) ;
+            incCounter(action.getEndpoint(), CounterName.HTTPputGood) ;
+        } catch ( ActionErrorException ex) {
+            incCounter(action.getEndpoint(), CounterName.HTTPputBad) ;
+            throw ex ;
+        }
+    }
+
+    private final void doOptions$(HttpAction action) {
+        incCounter(action.getEndpoint(), CounterName.HTTPoptions) ;
+        try {
+            doOptions(action) ;
+            incCounter(action.getEndpoint(), CounterName.HTTPoptionsGood) ;
+        } catch ( ActionErrorException ex) {
+            incCounter(action.getEndpoint(), CounterName.HTTPoptionsBad) ;
+            throw ex ;
+        }
+    }
+    
+    protected abstract void doGet(HttpAction action) ;
+    protected abstract void doHead(HttpAction action) ;
+    protected abstract void doPost(HttpAction action) ;
+    protected abstract void doPatch(HttpAction action) ;
+    protected abstract void doDelete(HttpAction action) ;
+    protected abstract void doPut(HttpAction action) ;
+    protected abstract void doOptions(HttpAction action) ;
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/470ee4d7/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionSPARQL.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionSPARQL.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionSPARQL.java
new file mode 100644
index 0000000..d26bc55
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ActionSPARQL.java
@@ -0,0 +1,207 @@
+/*
+ * 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.servlets;
+
+import static org.apache.jena.fuseki.server.CounterName.Requests ;
+import static org.apache.jena.fuseki.server.CounterName.RequestsBad ;
+import static org.apache.jena.fuseki.server.CounterName.RequestsGood ;
+
+import java.io.InputStream ;
+
+import org.apache.jena.atlas.RuntimeIOException ;
+import org.apache.jena.fuseki.Fuseki ;
+import org.apache.jena.fuseki.server.* ;
+import org.apache.jena.riot.Lang ;
+import org.apache.jena.riot.RDFDataMgr ;
+import org.apache.jena.riot.ReaderRIOT ;
+import org.apache.jena.riot.RiotException ;
+import org.apache.jena.riot.system.ErrorHandler ;
+import org.apache.jena.riot.system.ErrorHandlerFactory ;
+import org.apache.jena.riot.system.StreamRDF ;
+
+import com.hp.hpl.jena.query.QueryCancelledException ;
+
+/** SPARQL request lifecycle */
+public abstract class ActionSPARQL extends ActionBase
+{
+    protected ActionSPARQL() { super(Fuseki.actionLog) ; }
+    
+    protected abstract void validate(HttpAction action) ;
+    protected abstract void perform(HttpAction action) ;
+
+    /**
+     * Executes common tasks, including mapping the request to the right dataset, setting the dataset into the HTTP
+     * action, and retrieving the service for the dataset requested. Finally, it calls the
+     * {@link #executeAction(HttpAction)} method, which executes the HTTP Action life cycle.
+     * @param action HTTP Action
+     */
+    @Override
+    protected void execCommonWorker(HttpAction action) {
+        DataAccessPoint dataAccessPoint ;
+        DataService dSrv ;
+        
+        String datasetUri = mapRequestToDataset(action) ;
+        if ( datasetUri != null ) {
+            dataAccessPoint = DataAccessPointRegistry.get().get(datasetUri) ;
+            if ( dataAccessPoint == null ) {
+                ServletOps.errorNotFound("No dataset for URI: "+datasetUri) ;
+                return ;
+            }
+            //dataAccessPoint.
+            dSrv = dataAccessPoint.getDataService() ;
+            if ( ! dSrv.isAcceptingRequests() ) {
+                ServletOps.errorNotFound("Dataset not active: "+datasetUri) ;
+                return ;
+            }
+        } else {
+            dataAccessPoint = null ;
+            dSrv = DataService.serviceOnlyDataService() ;
+        }
+
+        String operationName = mapRequestToOperation(action, dataAccessPoint) ;
+        action.setRequest(dataAccessPoint, dSrv) ;
+        
+        //operationName = ""
+        
+        Endpoint op = dSrv.getOperation(operationName) ;
+        action.setEndpoint(op, operationName);
+        executeAction(action) ;
+    }
+
+    /** Execute a SPARQL request. Statistics have not been adjusted at this point.
+     * 
+     * @param action
+     */
+    protected void executeAction(HttpAction action) {
+        executeLifecycle(action) ;
+    }
+    
+    /** 
+     * Standard execution lifecycle for a SPARQL Request.
+     * <ul>
+     * <li>{@link #startRequest(HttpAction)}</li>
+     * <li>initial statistics,</li>
+     * <li>{@link #validate(HttpAction)} request,</li>
+     * <li>{@link #perform(HttpAction)} request,</li>
+     * <li>completion/error statistics,</li>
+     * <li>{@link #finishRequest(HttpAction)}
+     * </ul>
+     * 
+     * @param action
+     */
+    // This is the service request lifecycle.
+    final
+    protected void executeLifecycle(HttpAction action) {
+        startRequest(action) ;
+        // And also HTTP counter
+        CounterSet csService = action.getDataService().getCounters() ;
+        CounterSet csOperation = action.getEndpoint().getCounters() ;
+        
+        incCounter(csService, Requests) ;
+        incCounter(csOperation, Requests) ;
+        try {
+            // Either exit this via "bad request" on validation
+            // or in execution in perform. 
+            try {
+                validate(action) ;
+            } catch (ActionErrorException ex) {
+                incCounter(csOperation, RequestsBad) ;
+                incCounter(csService, RequestsBad) ;
+                throw ex ;
+            }
+
+            try {
+                perform(action) ;
+                // Success
+                incCounter(csOperation, RequestsGood) ;
+                incCounter(csService, RequestsGood) ;
+            } catch (ActionErrorException | QueryCancelledException | RuntimeIOException ex) {
+                incCounter(csOperation, RequestsBad) ;
+                incCounter(csService, RequestsBad) ;
+                throw ex ;
+            }
+        } finally {
+            finishRequest(action) ;
+        }
+    }
+    
+    /**
+     * Map request {@link HttpAction} to uri in the registry.
+     * A return of ull means no mapping done (passthrough).
+     * @param uri the URI
+     * @return the dataset
+     */
+    protected String mapRequestToDataset(HttpAction action) {
+        return ActionLib.mapRequestToDataset(action) ;
+    }
+
+    /**
+     * Map request to uri in the registry. null means no mapping done
+     * (passthrough).
+     */
+    protected String mapRequestToOperation(HttpAction action, DataAccessPoint dataAccessPoint) {
+        return ActionLib.mapRequestToOperation(action, dataAccessPoint) ;
+    }
+
+    /** Increment counter */
+    protected static void incCounter(Counters counters, CounterName name) {
+        if ( counters == null ) return ;
+        incCounter(counters.getCounters(), name) ; 
+    }
+    
+    /** Decrement counter */
+    protected static void decCounter(Counters counters, CounterName name) {
+        if ( counters == null ) return ;
+        decCounter(counters.getCounters(), name) ; 
+    }
+
+    protected static void incCounter(CounterSet counters, CounterName name) {
+        if ( counters == null )
+            return ;
+        try {
+            if ( counters.contains(name) )
+                counters.inc(name) ;
+        } catch (Exception ex) {
+            Fuseki.serverLog.warn("Exception on counter inc", ex) ;
+        }
+    }
+    
+    protected static void decCounter(CounterSet counters, CounterName name) {
+        if ( counters == null )
+            return ;
+        try {
+            if ( counters.contains(name) )
+                counters.dec(name) ;
+        } catch (Exception ex) {
+            Fuseki.serverLog.warn("Exception on counter dec", ex) ;
+        }
+    }
+
+    public static void parse(HttpAction action, StreamRDF dest, InputStream input, Lang lang, String base) {
+        try {
+            ReaderRIOT r = RDFDataMgr.createReader(lang) ;
+            if ( r == null )
+                ServletOps.errorBadRequest("No parser for language '"+lang.getName()+"'") ;
+            ErrorHandler errorHandler = ErrorHandlerFactory.errorHandlerStd(action.log);
+            r.setErrorHandler(errorHandler); 
+            r.read(input, base, null, dest, null) ; 
+        } 
+        catch (RiotException ex) { ServletOps.errorBadRequest("Parse error: "+ex.getMessage()) ; }
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/470ee4d7/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ConcurrencyPolicyMRSW.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ConcurrencyPolicyMRSW.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ConcurrencyPolicyMRSW.java
new file mode 100644
index 0000000..1f86539
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/ConcurrencyPolicyMRSW.java
@@ -0,0 +1,113 @@
+/*
+ * 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.servlets;
+
+import java.util.ConcurrentModificationException ;
+import java.util.concurrent.atomic.AtomicLong ;
+
+import org.apache.jena.fuseki.Fuseki ;
+import org.slf4j.Logger ;
+
+public final class ConcurrencyPolicyMRSW
+{
+    static private Logger log = Fuseki.actionLog ; //org.slf4j.LoggerFactory.getLogger(ConcurrencyPolicyMRSW.class) ;
+    static private final boolean logging = false ; //log.isDebugEnabled() ;
+    
+    // This is a simplified version of ConcurrencyPolicyMRSW from TDB. 
+    private final AtomicLong readCounter = new AtomicLong(0) ;
+    private final AtomicLong writeCounter = new AtomicLong(0) ;
+    static private final AtomicLong policyCounter = new AtomicLong(0) ;
+
+    public ConcurrencyPolicyMRSW()
+    { policyCounter.incrementAndGet() ; }
+
+    // Loggin -inside the operation.
+    
+    //@Override
+    public void startRead()
+    {
+        readCounter.getAndIncrement() ;
+        log() ;
+        checkConcurrency() ;
+    }
+
+    //@Override
+    public void finishRead()
+    {
+        log() ;
+        readCounter.decrementAndGet() ;
+        checkConcurrency() ;
+    }
+
+    //@Override
+    public void startUpdate()
+    {
+        writeCounter.getAndIncrement() ;
+        log() ;
+        checkConcurrency() ;
+    }
+
+    //@Override
+    public void finishUpdate()
+    {
+        log() ;
+        writeCounter.decrementAndGet() ;
+        checkConcurrency() ;
+    }
+
+    private synchronized void checkConcurrency()
+    {
+        long R = readCounter.get() ;
+        long W = writeCounter.get() ;
+        long id = policyCounter.get();
+        if ( R > 0 && W > 0 )
+            policyError(id, R, W) ;
+        if ( W > 1 )
+            policyError(id, R, W) ;
+    }
+
+    private void log()
+    {
+        if ( ! logging ) 
+            return ;
+        long R , W , id ;
+        synchronized(this)
+        {
+            R = readCounter.get() ;
+            W = writeCounter.get() ;
+            id = policyCounter.get();
+        }
+        log.info(format(id, R, W)) ;
+    }
+    
+    private static void policyError(long id, long R, long W)
+    {
+        policyError(format(id, R, W)) ;
+    }
+
+    private static void policyError(String message)
+    {
+        throw new ConcurrentModificationException(message) ;
+    }
+    
+    private static String format(long id, long R, long W)
+    {
+        return String.format("(lock=%d) Reader = %d, Writer = %d", id, R, W) ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/470ee4d7/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/FusekiFilter.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/FusekiFilter.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/FusekiFilter.java
new file mode 100644
index 0000000..7a79cd3
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/FusekiFilter.java
@@ -0,0 +1,87 @@
+/**
+ * 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.servlets;
+
+import java.io.IOException ;
+
+import javax.servlet.* ;
+import javax.servlet.http.HttpServletRequest ;
+import javax.servlet.http.HttpServletResponse ;
+
+import org.apache.jena.fuseki.Fuseki ;
+import org.apache.jena.fuseki.server.DataAccessPointRegistry ;
+import org.slf4j.Logger ;
+
+/** Look at all requests and see if they match a registered dataset name; 
+ * if they do, pass down to the uber servlet, which can dispatch any request
+ * for any service. 
+ */
+public class FusekiFilter implements Filter {
+    private static Logger log = Fuseki.serverLog ;
+    private static SPARQL_UberServlet überServlet = new SPARQL_UberServlet.AccessByConfig() ;
+    
+    @Override
+    public void init(FilterConfig filterConfig) throws ServletException {
+//        log.info("Filter: ["+Utils.className(this)+"] ServletContextName = "+filterConfig.getServletContext().getServletContextName()) ;
+//        log.info("Filter: ["+Utils.className(this)+"] ContextPath        = "+filterConfig.getServletContext().getContextPath()) ;
+    }
+
+    private static final boolean LogFilter = false ;     // Development debugging (can be excessive!)
+    
+    @Override
+    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+        throws IOException, ServletException {
+        try {
+            HttpServletRequest req = (HttpServletRequest)request ;
+            HttpServletResponse resp = (HttpServletResponse)response ;
+
+            // Handle context path
+            String uri = ActionLib.actionURI(req) ;
+            String datasetUri = ActionLib.mapActionRequestToDataset(uri) ;
+
+            // is it a long running operation?
+            // (this could be a separate filter)
+            
+            if ( LogFilter ) {
+                log.info("Filter: Request URI = "+req.getRequestURI()) ;
+                log.info("Filter: Action URI  = "+uri) ;
+                log.info("Filter: Dataset URI = "+datasetUri) ;
+            }
+            
+            if ( datasetUri != null ) {        
+                if ( DataAccessPointRegistry.get().isRegistered(datasetUri) ) {
+                    if ( LogFilter )
+                        log.info("Filter: dispatch") ;
+                    überServlet.doCommon(req, resp) ;
+                    return ;
+                }
+            }
+        } catch (Exception ex) {}
+        
+        if ( LogFilter )
+            log.info("Filter: pass to chain") ;
+        // Not found - continue. 
+        chain.doFilter(request, response);
+    }
+
+    @Override
+    public void destroy() {}
+
+}
+

http://git-wip-us.apache.org/repos/asf/jena/blob/470ee4d7/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/HttpAction.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/HttpAction.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/HttpAction.java
new file mode 100644
index 0000000..245ed0f
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/HttpAction.java
@@ -0,0 +1,387 @@
+/*
+ * 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.servlets;
+
+import static com.hp.hpl.jena.query.ReadWrite.READ ;
+import static com.hp.hpl.jena.query.ReadWrite.WRITE ;
+
+import java.util.HashMap ;
+import java.util.Map ;
+
+import javax.servlet.http.HttpServletRequest ;
+import javax.servlet.http.HttpServletResponse ;
+
+import org.apache.jena.atlas.logging.Log ;
+import org.apache.jena.fuseki.Fuseki ;
+import org.apache.jena.fuseki.FusekiException ;
+import org.apache.jena.fuseki.server.* ;
+import org.slf4j.Logger ;
+
+import com.hp.hpl.jena.query.ReadWrite ;
+import com.hp.hpl.jena.sparql.SystemARQ ;
+import com.hp.hpl.jena.sparql.core.DatasetGraph ;
+import com.hp.hpl.jena.sparql.core.DatasetGraphWithLock ;
+import com.hp.hpl.jena.sparql.core.DatasetGraphWrapper ;
+import com.hp.hpl.jena.sparql.core.Transactional ;
+
+/**
+ * HTTP action that represents the user request lifecycle. Its state is handled in the
+ * {@link ActionSPARQL#executeAction(HttpAction)} method.
+ */
+public class HttpAction
+{
+    public final long id ;
+    public final boolean verbose ;
+    public final Logger log ;
+    
+    // ----
+    // Worth subclassing? Given this is allocated in the general lifecycle
+    // it would mean there are downcasts to the specific type.
+    
+    // -- Valid only for operational actions (e.g. SPARQL).
+    
+    public  String          endpointName    = null ;        // Endpoint name srv was found under 
+    public  Endpoint        endpoint        = null ;
+    private Transactional   transactional   = null ;
+    private boolean         isTransactional = false ;
+    private DatasetGraph    activeDSG       = null ;        // Set when inside begin/end.
+    private ReadWrite       activeMode      = null ;        // Set when inside begin/end.
+    
+    // -- Valid only for administration actions.
+    
+    // -- Shared items (but exact meaning may differ)
+    /** Handle to dataset+services being acted on (maybe null) */
+    private DataAccessPoint dataAccessPoint = null ;
+    private DataService dataService         = null ;
+    private String datasetName              = null ;        // Dataset URI used (e.g. registry)
+    private DatasetGraph dsg                = null ;
+
+    // ----
+    
+    private boolean startTimeIsSet = false ;
+    private boolean finishTimeIsSet = false ;
+
+    private long startTime = -2 ;
+    private long finishTime = -2 ;
+    
+    // Outcome.
+    public int statusCode = -1 ;
+    public String message = null ;
+    public int contentLength = -1 ;
+    public String contentType = null ;
+    
+    // Cleared to archive:
+    public Map <String, String> headers = new HashMap<>() ;
+    public HttpServletRequest request;
+    public HttpServletResponseTracker response ;
+    private final String actionURI ;
+    private final String contextPath ;
+    
+    /**
+     * Creates a new HTTP Action, using the HTTP request and response, and a given ID.
+     *
+     * @param id given ID
+     * @param log Logger for this action 
+     * @param request HTTP request
+     * @param response HTTP response
+     * @param verbose verbose flag
+     */
+    public HttpAction(long id, Logger log, HttpServletRequest request, HttpServletResponse response, boolean verbose) {
+        this.id = id ;
+        this.log = log ;
+        this.request = request ;
+        this.response = new HttpServletResponseTracker(this, response) ;
+        // Should this be set when setDataset is called from the dataset context?
+        // Currently server-wide, e.g. from the command line.
+        this.verbose = verbose ;
+        this.contextPath = request.getServletContext().getContextPath() ;
+        this.actionURI = ActionLib.actionURI(request) ;
+    }
+
+    /** Initialization after action creation during lifecycle setup.
+     * <p>Sets the action dataset. Setting will replace any existing {@link DataAccessPoint} and {@link DataService},
+     * as the {@link DatasetGraph} of the current HTTP Action.</p>
+     *
+     * <p>Once it has updated its members, the HTTP Action will change its transactional state and
+     * {@link Transactional} instance according to its base dataset graph.</p>
+     *
+     * @param dataAccessPoint {@link DataAccessPoint}
+     * @param dService {@link DataService}
+     * @see Transactional
+     */
+    
+    public void setRequest(DataAccessPoint dataAccessPoint, DataService dService) {
+        this.dataAccessPoint = dataAccessPoint ;
+        if ( dataAccessPoint != null )
+            this.datasetName = dataAccessPoint.getName() ; 
+
+        if ( this.dataService != null )
+            throw new FusekiException("Redefinition of DatasetRef in the request action") ;
+        
+        this.dataService = dService ;
+        if ( dService == null || dService.getDataset() == null )
+            // Null does not happens for service requests, (it does for admin requests - call setControlRequest) 
+            throw new FusekiException("Null DataService in the request action") ;
+        
+        this.dsg = dService.getDataset() ;
+        DatasetGraph basedsg = unwrap(dsg) ;
+
+        if ( isTransactional(basedsg) && isTransactional(dsg) ) {
+            // Use transactional if it looks safe - abort is necessary.
+            transactional = (Transactional)dsg ;
+            isTransactional = true ;
+        } else {
+            // Unsure if safesetControlRef
+            transactional = new DatasetGraphWithLock(dsg) ;
+            // No real abort.
+            isTransactional = false ;
+        }
+    }
+    
+    public void setControlRequest(DataAccessPoint dataAccessPoint, String datasetUri) {
+        this.dataAccessPoint = dataAccessPoint ;
+        this.dataService = null ;
+        this.datasetName = datasetUri ;
+    }
+    
+    /**
+     * Returns <code>true</code> iff the given {@link DatasetGraph} is an instance of {@link Transactional},
+     * <code>false otherwise</code>.
+     *
+     * @param dsg a {@link DatasetGraph}
+     * @return <code>true</code> iff the given {@link DatasetGraph} is an instance of {@link Transactional},
+     * <code>false otherwise</code>
+     */
+    private static boolean isTransactional(DatasetGraph dsg) {
+        return (dsg instanceof Transactional) ;
+    }
+
+    /**
+     * A {@link DatasetGraph} may contain other <strong>wrapped DatasetGraph's</strong>. This method will return
+     * the first instance (including the argument to this method) that <strong>is not</strong> an instance of
+     * {@link DatasetGraphWrapper}.
+     *
+     * @param dsg a {@link DatasetGraph}
+     * @return the first found {@link DatasetGraph} that is not an instance of {@link DatasetGraphWrapper}
+     */
+   private static DatasetGraph unwrap(DatasetGraph dsg) {
+        while (dsg instanceof DatasetGraphWrapper) {
+            dsg = ((DatasetGraphWrapper)dsg).getWrapped() ;
+        }
+        return dsg ;
+    }
+        
+    /** This is the requestURI with the context path removed.
+     *  It should be used internally for dispatch.
+     */
+    public String getActionURI() {
+        return actionURI ;
+    }
+    
+    /** Get the context path.
+     */
+    public String getContextPath() {
+        return contextPath ;
+    }
+    
+    
+    /** Set the endpoint and endpoint name that this is an action for. 
+     * @param srvRef {@link Endpoint}
+     * @param endpointName
+     */
+    public void setEndpoint(Endpoint srvRef, String endpointName) {
+        this.endpoint = srvRef ; 
+        this.endpointName = endpointName ;
+    }
+    
+    /** Get the endpoint for the action (may be null) . */
+    public Endpoint getEndpoint() {
+        return endpoint ; 
+    }
+
+    /**
+     * Returns whether or not the underlying DatasetGraph is fully transactional (supports rollback)
+     */
+    public boolean isTransactional() {
+        return isTransactional ;
+    }
+
+    public void beginRead() {
+        activeMode = READ ;
+        transactional.begin(READ) ;
+        activeDSG = dsg ;
+        dataService.startTxn(READ) ;
+    }
+
+    public void endRead() {
+        dataService.finishTxn(READ) ;
+        activeMode = null ;
+        transactional.end() ;
+        activeDSG = null ;
+    }
+
+    public void beginWrite() {
+        transactional.begin(WRITE) ;
+        activeMode = WRITE ;
+        activeDSG = dsg ;
+        dataService.startTxn(WRITE) ;
+    }
+
+    public void commit() {
+        transactional.commit() ;
+        activeDSG = null ;
+    }
+
+    public void abort() {
+        try { transactional.abort() ; } 
+        catch (Exception ex) {
+            // Some datasets claim to be transactional but
+            // don't provide a real abort. We tried to avoid
+            // them earlier but even if they sneek through,
+            // we try to continue server operation.
+            Log.warn(this, "Exception during abort (operation attempts to continue): "+ex.getMessage()) ; 
+        }
+        activeDSG = null ;
+    }
+
+    public void endWrite() {
+        dataService.finishTxn(WRITE) ;
+        activeMode = null ;
+
+        if ( transactional.isInTransaction() ) {
+            Log.warn(this, "Transaction still active in endWriter - no commit or abort seen (forced abort)") ;
+            try {
+                transactional.abort() ;
+            } catch (RuntimeException ex) {
+                Log.warn(this, "Exception in forced abort (trying to continue)", ex) ;
+            }
+        }
+        transactional.end() ;
+        activeDSG = null ;
+    }
+
+    public final void startRequest()
+    { 
+        if ( dataAccessPoint != null ) 
+            dataAccessPoint.startRequest(this) ;
+    }
+
+    public final void finishRequest() { 
+        if ( dataAccessPoint != null ) 
+            dataAccessPoint.finishRequest(this) ;
+        // Standard logging goes here.
+        if ( Fuseki.requestLog != null && Fuseki.requestLog.isInfoEnabled() ) { 
+            String s = RequestLog.combinedNCSA(this) ;
+            Fuseki.requestLog.info(s);
+        }
+    }
+    
+    /** If inside the transaction for the action, return the active {@link DatasetGraph},
+     *  otherwise return null.
+     * @return Current active {@link DatasetGraph}
+     */
+    public final DatasetGraph getActiveDSG() {
+        return activeDSG ;
+    }
+
+    public final DataAccessPoint getDataAccessPoint() {
+        return dataAccessPoint;
+    }
+
+//    public void setDataAccessPoint(DataAccessPoint dataAccessPoint) {
+//        this.dataAccessPoint = dataAccessPoint;
+//    }
+
+    public final DataService getDataService() {
+        return dataService;
+    }
+
+//    public final void setDataService(DataService dataService) {
+//        this.dataService = dataService;
+//    }
+
+    public final String getDatasetName() {
+        return datasetName;
+    }
+
+//    public void setDatasetName(String datasetName) {
+//        this.datasetName = datasetName;
+//    }
+
+    /** Reduce to a size that can be kept around for sometime. 
+     * Release resources like datasets that may be closed, reset etc.
+     */
+    public void minimize() {
+        this.request = null ;
+        this.response = null ;
+        this.dsg = null ;
+        this.dataService = null ;
+        this.activeDSG = null ;
+        this.endpoint = null ;
+    }
+
+    public void setStartTime() {
+        if ( startTimeIsSet ) 
+            Log.warn(this,  "Start time reset") ;
+        startTimeIsSet = true ;
+        this.startTime = System.nanoTime() ;
+    }
+
+    /** Start time, in system nanos */
+    public long getStartTime() { 
+        if ( ! startTimeIsSet ) 
+            Log.warn(this,  "Start time is not set") ;
+        return startTime ;
+    }
+
+    /** Start time, in system nanos */
+    public long getFinishTime() { 
+        if ( ! finishTimeIsSet ) 
+            Log.warn(this,  "Finish time is not set") ;
+        return finishTime ;
+    }
+    
+    public void setFinishTime() {
+        if ( finishTimeIsSet ) 
+            Log.warn(this,  "Finish time reset") ;
+        finishTimeIsSet = true ;
+        this.finishTime = System.nanoTime() ;
+    }
+
+    public HttpServletRequest getRequest()              { return request ; }
+
+    public HttpServletResponseTracker getResponse()     { return response ; }
+    
+    /** Return the recorded time taken in milliseconds. 
+     *  {@link #setStartTime} and {@link #setFinishTime}
+     *  must have been called.
+     */
+    public long getTime()
+    {
+        if ( ! startTimeIsSet ) 
+            Log.warn(this,  "Start time not set") ;
+        if ( ! finishTimeIsSet ) 
+            Log.warn(this,  "Finish time not set") ;
+        return (finishTime-startTime)/(1000*1000) ;
+    }
+
+    public void sync() {
+        SystemARQ.sync(dsg) ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/470ee4d7/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/HttpServletResponseTracker.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/HttpServletResponseTracker.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/HttpServletResponseTracker.java
new file mode 100644
index 0000000..c39e728
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/HttpServletResponseTracker.java
@@ -0,0 +1,140 @@
+/*
+ * 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.servlets;
+
+import java.io.IOException ;
+
+import javax.servlet.http.HttpServletResponse ;
+import javax.servlet.http.HttpServletResponseWrapper ;
+
+import org.apache.jena.atlas.logging.Log ;
+
+/** Intercepting wrapper so we can track the response settings for logging purposes */
+
+public class HttpServletResponseTracker extends HttpServletResponseWrapper
+{
+    private final HttpAction action ;
+
+    public HttpServletResponseTracker(HttpAction action, HttpServletResponse response)
+    {
+        super(response) ;
+        this.action = action ;
+    }
+
+    @Override
+    public void sendError(int sc, String msg) throws IOException
+    {
+        action.statusCode = sc ;
+        action.message = msg ;
+        super.sendError(sc, msg) ;
+    }
+
+    @Override
+    public void sendError(int sc) throws IOException
+    {
+        action.statusCode = sc ;
+        action.message = null ;
+        super.sendError(sc) ;
+    }
+
+    @Override
+    public void setHeader(String name, String value)
+    {
+        super.setHeader(name, value) ;
+        action.headers.put(name, value) ;
+    }
+
+    @Override
+    public void addHeader(String name, String value)
+    {
+        Log.warn(this, "Unexpected addHeader - not recorded in log") ;
+        super.addHeader(name, value) ;
+    }
+    @Override
+    public void setStatus(int sc) 
+    {
+        action.statusCode = sc ;
+        action.message = null ;
+        super.setStatus(sc) ;
+    }
+
+    @Override
+    @Deprecated
+    public void setStatus(int sc, String sm)
+    {
+        action.statusCode = sc ;
+        action.message = sm ;
+        super.setStatus(sc, sm) ;
+    }
+
+    @Override
+    public void setContentLength(int len)
+    {
+        action.contentLength = len ;
+        super.setContentLength(len) ;
+    }
+
+    @Override
+    public void setContentType(String type)
+    {
+        action.contentType = type ;
+        super.setContentType(type) ;
+    }
+      
+      // From HttpServletResponse
+//      public void addCookie(Cookie cookie) {}
+//      public boolean containsHeader(String name) {}
+//      public String encodeURL(String url) { }
+//      public String encodeRedirectURL(String url) {}
+//      public String encodeUrl(String url) {}
+//      public String encodeRedirectUrl(String url) {}
+//      public void sendError(int sc, String msg) throws IOException
+//      public void sendError(int sc) throws IOException
+//      public void sendRedirect(String location) throws IOException {}
+//      public void setDateHeader(String name, long date) {}
+//      public void addDateHeader(String name, long date) {}
+//      public void setHeader(String name, String value)
+//      public void addHeader(String name, String value)
+//      public void setIntHeader(String name, int value) {}
+//      public void addIntHeader(String name, int value) {}
+//      public void setStatus(int sc) 
+//      public void setStatus(int sc, String sm)
+//      public void sendRedirect(String location) throws IOException {}
+//      public void setDateHeader(String name, long date) {}
+//      public void addDateHeader(String name, long date) {}
+        
+        // From ServletResponse.
+//         public ServletResponse getResponse() {}
+//         public void setResponse(ServletResponse response) {}
+//         public void setCharacterEncoding(String charset) {}
+//         public String getCharacterEncoding() {}
+//         public ServletOutputStream getOutputStream() throws IOException {}
+//         public PrintWriter getWriter() throws IOException {}
+//         public void setContentLength(int len) {}
+//         public void setContentType(String type) {}
+//         public String getContentType() {
+//         public void setBufferSize(int size) {}
+//         public int getBufferSize() {}
+//         public void flushBuffer() throws IOException {}
+//         public boolean isCommitted() {}
+//         public void reset() {}
+//         public void resetBuffer() {}
+//         public void setLocale(Locale loc) {}
+//         public Locale getLocale() {}
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/470ee4d7/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/NullOutputStream.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/NullOutputStream.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/NullOutputStream.java
new file mode 100644
index 0000000..63e6562
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/servlets/NullOutputStream.java
@@ -0,0 +1,53 @@
+/*
+ * 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.servlets;
+
+import java.io.* ;
+
+/** 
+* Code needed to implement an OutputStream that does nothing.
+*/
+
+
+public class NullOutputStream extends /*Filter*/OutputStream
+{
+	public NullOutputStream()
+	{
+	}
+
+	// The OutputStream operations
+	@Override
+    public void close() { /* .close() ;*/ }
+	@Override
+    public void flush() { /* .flush() ;*/ }
+
+	// Need to implement this one.
+	@Override
+    public void write(int b) { /* .write(b) ;*/ }
+	@Override
+    public void write(byte b[]) { /* this.write(b, 0, b.length) ; */}
+
+	// Good to implement this one.
+	@Override
+    public void write(byte[] b, int off, int len)
+	{
+		// Work function
+	}
+	
+}