You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jena.apache.org by rv...@apache.org on 2015/03/17 12:21:15 UTC

[03/52] [abbrv] jena git commit: Rename folder jena-fuseki to jena-fuseki1

http://git-wip-us.apache.org/repos/asf/jena/blob/662cf71d/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_Query.java
----------------------------------------------------------------------
diff --git a/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_Query.java b/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_Query.java
new file mode 100644
index 0000000..06f8340
--- /dev/null
+++ b/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_Query.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 java.lang.String.format ;
+import static org.apache.jena.fuseki.HttpNames.* ;
+import static org.apache.jena.fuseki.server.CounterName.QueryExecErrors ;
+import static org.apache.jena.fuseki.server.CounterName.QueryTimeouts ;
+import static org.apache.jena.fuseki.server.CounterName.RequestsBad ;
+
+import java.io.IOException ;
+import java.io.InputStream ;
+import java.util.* ;
+
+import javax.servlet.http.HttpServletRequest ;
+import javax.servlet.http.HttpServletResponse ;
+
+import org.apache.jena.atlas.RuntimeIOException ;
+import org.apache.jena.atlas.io.IO ;
+import org.apache.jena.atlas.io.IndentedLineBuffer ;
+import org.apache.jena.atlas.web.ContentType ;
+import org.apache.jena.fuseki.FusekiException ;
+import org.apache.jena.fuseki.FusekiLib ;
+import org.apache.jena.fuseki.HttpNames ;
+import org.apache.jena.riot.WebContent ;
+import org.apache.jena.riot.web.HttpOp ;
+import org.apache.jena.web.HttpSC ;
+
+import com.hp.hpl.jena.query.* ;
+import com.hp.hpl.jena.rdf.model.Model ;
+import com.hp.hpl.jena.sparql.core.Prologue ;
+import com.hp.hpl.jena.sparql.resultset.SPARQLResult ;
+
+/**
+ * Handles SPARQL Query requests.
+ */
+public abstract class SPARQL_Query extends SPARQL_Protocol
+{
+    public SPARQL_Query()   { super() ; }
+
+    // Choose REST verbs to support.
+    
+    @Override
+    protected void doPost(HttpServletRequest request, HttpServletResponse response)
+    { doCommon(request, response) ; }
+    
+    @Override
+    protected void doGet(HttpServletRequest request, HttpServletResponse response)
+    { doCommon(request, response) ; }
+
+    // HEAD
+    
+    @Override
+    protected void doOptions(HttpServletRequest request, HttpServletResponse response)
+    {
+        setCommonHeadersForOptions(response) ;
+        response.setHeader(HttpNames.hAllow, "GET,OPTIONS,POST");
+        response.setHeader(HttpNames.hContentLengh, "0") ;
+    }
+    
+    @Override
+    protected final void perform(HttpAction action)
+    {
+        // GET
+        if ( action.request.getMethod().equals(HttpNames.METHOD_GET) ) {
+            executeWithParameter(action) ;
+            return ;
+        }
+
+        ContentType ct = FusekiLib.getContentType(action) ;
+        if ( ct == null ) {
+            // Validation check it's POST with ?query=
+            executeWithParameter(action) ;
+            return ;
+        }
+        
+        String incoming = ct.getContentType() ;
+        // POST application/sparql-query
+        if (WebContent.contentTypeSPARQLQuery.equals(incoming)) {
+            executeBody(action) ;
+            return ;
+        }
+        // POST application/x-www-form-url
+        if (WebContent.contentTypeHTMLForm.equals(incoming)) {
+            executeWithParameter(action) ;
+            return ;
+        }
+        
+        error(HttpSC.UNSUPPORTED_MEDIA_TYPE_415, "Bad content type: "+incoming) ;
+    }
+
+    // All the params we support
+
+    protected static List<String> allParams  = Arrays.asList(paramQuery, 
+                                                             paramDefaultGraphURI, paramNamedGraphURI, 
+                                                             paramQueryRef,
+                                                             paramStyleSheet,
+                                                             paramAccept,
+                                                             paramOutput1, paramOutput2, 
+                                                             paramCallback, 
+                                                             paramForceAccept,
+                                                             paramTimeout) ;
+
+    @Override
+    protected void validate(HttpAction action)
+    {
+        String method = action.request.getMethod().toUpperCase(Locale.ROOT) ;
+        
+        if ( ! HttpNames.METHOD_POST.equals(method) && ! HttpNames.METHOD_GET.equals(method) )
+            errorMethodNotAllowed("Not a GET or POST request") ;
+            
+        if ( HttpNames.METHOD_GET.equals(method) && action.request.getQueryString() == null )
+        {
+            warning("Service Description / SPARQL Query / "+action.request.getRequestURI()) ;
+            errorNotFound("Service Description: "+action.request.getRequestURI()) ;
+        }
+        
+        // Use of the dataset describing parameters is check later.
+        try {
+            validateParams(action.request, allParams) ;
+            validateRequest(action) ; 
+        } catch (ActionErrorException ex) { 
+            throw ex ; 
+        } 
+        // Query not yet parsed.
+    }
+    
+    /**
+     * Validate the request after checking HTTP method and HTTP Parameters.
+     * @param action HTTP Action
+     */
+    protected abstract void validateRequest(HttpAction action) ;
+    
+    /**
+     * Helper method for validating request.
+     * @param request HTTP request
+     * @param params parameters in a collection of Strings
+     */
+    protected void validateParams(HttpServletRequest request, Collection<String> params)
+    {
+        ContentType ct = FusekiLib.getContentType(request) ;
+        boolean mustHaveQueryParam = true ;
+        if ( ct != null )
+        {
+            String incoming = ct.getContentType() ;
+            
+            if ( WebContent.contentTypeSPARQLQuery.equals(incoming) )
+            {
+                mustHaveQueryParam = false ;
+                //error(HttpSC.UNSUPPORTED_MEDIA_TYPE_415, "Unofficial "+WebContent.contentTypeSPARQLQuery+" not supported") ;
+            }
+            else if ( WebContent.contentTypeHTMLForm.equals(incoming) ) {}
+            else
+                error(HttpSC.UNSUPPORTED_MEDIA_TYPE_415, "Unsupported: "+incoming) ;
+        }
+        
+        // GET/POST of a form at this point.
+        
+        if ( mustHaveQueryParam )
+        {
+            int N = countParamOccurences(request, paramQuery) ; 
+            
+            if ( N == 0 ) errorBadRequest("SPARQL Query: No 'query=' parameter") ;
+            if ( N > 1 ) errorBadRequest("SPARQL Query: Multiple 'query=' parameters") ;
+            
+            // application/sparql-query does not use a query param.
+            String queryStr = request.getParameter(HttpNames.paramQuery) ;
+            
+            if ( queryStr == null )
+                errorBadRequest("SPARQL Query: No query specified (no 'query=' found)") ;
+            if ( queryStr.isEmpty() )
+                errorBadRequest("SPARQL Query: Empty query string") ;
+        }
+
+        if ( params != null )
+        {
+            Enumeration<String> en = request.getParameterNames() ;
+            for ( ; en.hasMoreElements() ; )
+            {
+                String name = en.nextElement() ;
+                if ( ! params.contains(name) )
+                    warning("SPARQL Query: Unrecognize request parameter (ignored): "+name) ;
+            }
+        }
+    }
+
+    private void executeWithParameter(HttpAction action)
+    {
+        String queryString = action.request.getParameter(paramQuery) ;
+        execute(queryString, action) ;
+    }
+
+    private void executeBody(HttpAction action)
+    {
+        String queryString = null ;
+        try { 
+            InputStream input = action.request.getInputStream() ; 
+            queryString = IO.readWholeFileAsUTF8(input) ;
+        }
+        catch (IOException ex) { errorOccurred(ex) ; }
+        execute(queryString, action) ;
+    }
+
+    private void execute(String queryString, HttpAction action)
+    {
+        String queryStringLog = formatForLog(queryString) ;
+        if ( action.verbose )
+            log.info(format("[%d] Query = \n%s", action.id, queryString));
+        else
+            log.info(format("[%d] Query = %s", action.id, queryStringLog));
+
+        Query query = null ;
+        try {
+            // NB syntax is ARQ (a superset of SPARQL)
+            query = QueryFactory.create(queryString, "http://example/query-base", Syntax.syntaxARQ) ;
+            queryStringLog = formatForLog(query) ;
+            validateQuery(action, query) ;
+        } catch (ActionErrorException ex) {
+            incCounter(action.srvRef, RequestsBad) ;
+            throw ex ;
+        } catch (QueryParseException ex) {
+            incCounter(action.srvRef, RequestsBad) ;
+            errorBadRequest("Parse error: \n" + queryString + "\n\r" + messageForQPE(ex)) ;
+        } catch (RuntimeIOException ex) {
+            errorBadRequest("Runtime IO Exception: \n" + queryString + "\n\r" + ex.getMessage()) ;
+        }
+        // Should not happen.
+        catch (QueryException ex) {
+            errorBadRequest("Error: \n" + queryString + "\n\r" + ex.getMessage()) ;
+        }
+        
+        // Assumes finished whole thing by end of sendResult. 
+        action.beginRead() ;
+        try {
+            Dataset dataset = decideDataset(action, query, queryStringLog) ; 
+            try(QueryExecution qExec = createQueryExecution(query, dataset)) {
+                SPARQLResult result = executeQuery(action, qExec, query, queryStringLog) ;
+                // Deals with exceptions itself.
+                sendResults(action, result, query.getPrologue()) ;
+            }
+        } catch (QueryCancelledException ex) {
+            // Additional counter information.
+            incCounter(action.srvRef, QueryTimeouts) ; 
+            throw ex ; 
+        } catch (RuntimeIOException ex) {
+            incCounter(action.srvRef, QueryExecErrors) ;
+            throw ex ;
+        } catch (QueryExecException ex) { 
+            // Additional counter information.
+            incCounter(action.srvRef, QueryExecErrors) ; 
+            throw ex ;
+        } finally { 
+            action.endRead() ;
+        }
+    }
+
+    /**
+     * Check the query, throwing ActionErrorException when not valid, or calling super#error.
+     * @param action HTTP Action
+     * @param query the Query
+     */
+    protected abstract void validateQuery(HttpAction action, Query query) ;
+
+    protected QueryExecution createQueryExecution(Query query, Dataset dataset)
+    {
+        return QueryExecutionFactory.create(query, dataset) ;
+    }
+
+    protected SPARQLResult executeQuery(HttpAction action, QueryExecution qExec, Query query, String queryStringLog)
+    {
+        setAnyTimeouts(qExec, action);
+
+        if ( query.isSelectType() )
+        {
+            ResultSet rs = qExec.execSelect() ;
+            
+            // Force some query execution now.
+            //
+            // If the timeout-first-row goes off, the output stream has not 
+            // been started so the HTTP error code is sent. 
+            
+            rs.hasNext() ;
+
+            // If we wanted perfect query time cancellation, we could consume the result now
+            // to see if the timeout-end-of-query goes off.  
+            
+            //rs = ResultSetFactory.copyResults(rs) ;
+
+            log.info(format("[%d] exec/select", action.id)) ;
+            return new SPARQLResult(rs) ;
+        }
+
+        if ( query.isConstructType() )
+        {
+            Model model = qExec.execConstruct() ;
+            log.info(format("[%d] exec/construct", action.id)) ;
+            return new SPARQLResult(model) ;
+        }
+
+        if ( query.isDescribeType() )
+        {
+            Model model = qExec.execDescribe() ;
+            log.info(format("[%d] exec/describe",action.id)) ;
+            return new SPARQLResult(model) ;
+        }
+
+        if ( query.isAskType() )
+        {
+            boolean b = qExec.execAsk() ;
+            log.info(format("[%d] exec/ask",action.id)) ;
+            return new SPARQLResult(b) ;
+        }
+
+        errorBadRequest("Unknown query type - "+queryStringLog) ;
+        return null ;
+    }
+
+    private void setAnyTimeouts(QueryExecution qexec, HttpAction action) {
+        if (!(action.getDatasetRef().allowTimeoutOverride))
+            return;
+
+        long desiredTimeout = Long.MAX_VALUE;
+        String timeoutHeader = action.request.getHeader("Timeout");
+        String timeoutParameter = action.request.getParameter("timeout");
+        if (timeoutHeader != null) {
+            try {
+                desiredTimeout = (int) Float.parseFloat(timeoutHeader) * 1000;
+            } catch (NumberFormatException e) {
+                throw new FusekiException("Timeout header must be a number", e);
+            }
+        } else if (timeoutParameter != null) {
+            try {
+                desiredTimeout = (int) Float.parseFloat(timeoutParameter) * 1000;
+            } catch (NumberFormatException e) {
+                throw new FusekiException("timeout parameter must be a number", e);
+            }
+        }
+
+        desiredTimeout = Math.min(action.getDatasetRef().maximumTimeoutOverride, desiredTimeout);
+        if (desiredTimeout != Long.MAX_VALUE)
+            qexec.setTimeout(desiredTimeout);
+    }
+
+    protected abstract Dataset decideDataset(HttpAction action, Query query, String queryStringLog) ;
+
+    protected void sendResults(HttpAction action, SPARQLResult result, Prologue qPrologue)
+    {
+        if ( result.isResultSet() )
+            ResponseResultSet.doResponseResultSet(action, result.getResultSet(), qPrologue) ;
+        else if ( result.isGraph() )
+            ResponseModel.doResponseModel(action, result.getModel()) ;
+        else if ( result.isBoolean() )
+            ResponseResultSet.doResponseResultSet(action, result.getBooleanResult()) ;
+        else
+            errorOccurred("Unknown or invalid result type") ;
+    }
+    
+    private String formatForLog(Query query)
+    {
+        IndentedLineBuffer out = new IndentedLineBuffer() ;
+        out.setFlatMode(true) ;
+        query.serialize(out) ;
+        return out.asString() ;
+    }
+        
+    private String getRemoteString(String queryURI)
+    {
+        return HttpOp.execHttpGetString(queryURI) ;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/662cf71d/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_QueryDataset.java
----------------------------------------------------------------------
diff --git a/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_QueryDataset.java b/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_QueryDataset.java
new file mode 100644
index 0000000..9e9df36
--- /dev/null
+++ b/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_QueryDataset.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.servlets;
+
+import com.hp.hpl.jena.query.Dataset ;
+import com.hp.hpl.jena.query.DatasetFactory ;
+import com.hp.hpl.jena.query.Query ;
+import com.hp.hpl.jena.sparql.core.DatasetDescription ;
+import com.hp.hpl.jena.sparql.core.DatasetGraph ;
+import com.hp.hpl.jena.sparql.core.DynamicDatasets ;
+
+public class SPARQL_QueryDataset extends SPARQL_Query
+{
+    public SPARQL_QueryDataset(boolean verbose)     { super() ; }
+
+    public SPARQL_QueryDataset()
+    { this(false) ; }
+    
+    @Override
+    protected void validateRequest(HttpAction action) 
+    { }
+
+    @Override
+    protected void validateQuery(HttpAction action, Query query) 
+    { }
+   
+    @Override
+    protected Dataset decideDataset(HttpAction action, Query query, String queryStringLog) 
+    { 
+        DatasetGraph dsg = action.getActiveDSG() ;
+        
+        // query.getDatasetDescription() ;
+        
+        // Protocol.
+        DatasetDescription dsDesc = getDatasetDescription(action) ;
+        if (dsDesc != null )
+        {
+            //errorBadRequest("SPARQL Query: Dataset description in the protocol request") ;
+            dsg = DynamicDatasets.dynamicDataset(dsDesc, dsg, false) ;
+        }
+        
+        return DatasetFactory.create(dsg) ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/662cf71d/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_QueryGeneral.java
----------------------------------------------------------------------
diff --git a/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_QueryGeneral.java b/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_QueryGeneral.java
new file mode 100644
index 0000000..a022e96
--- /dev/null
+++ b/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_QueryGeneral.java
@@ -0,0 +1,143 @@
+/*
+ * 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.util.List ;
+
+import org.apache.jena.atlas.lib.InternalErrorException ;
+import org.apache.jena.fuseki.migrate.GraphLoadUtils ;
+import org.apache.jena.riot.RiotException ;
+
+import com.hp.hpl.jena.query.Dataset ;
+import com.hp.hpl.jena.query.DatasetFactory ;
+import com.hp.hpl.jena.query.Query ;
+import com.hp.hpl.jena.rdf.model.Model ;
+import com.hp.hpl.jena.rdf.model.ModelFactory ;
+import com.hp.hpl.jena.sparql.core.DatasetDescription ;
+
+public class SPARQL_QueryGeneral extends SPARQL_Query
+{
+    final static int MaxTriples = 100*1000 ; 
+    
+    public SPARQL_QueryGeneral()    { super() ; }
+
+    @Override
+    protected void validateRequest(HttpAction action) {}
+
+    @Override
+    protected void validateQuery(HttpAction action, Query query) {}
+    
+    @Override
+    protected String mapRequestToDataset(String uri)
+    { return null ; }
+    
+    @Override
+    protected Dataset decideDataset(HttpAction action, Query query, String queryStringLog) 
+    {
+        DatasetDescription datasetDesc = getDatasetDescription(action) ;
+        if ( datasetDesc == null )
+            datasetDesc = getDatasetDescription(query) ;
+        if ( datasetDesc == null )
+            errorBadRequest("No dataset description in protocol request or in the query string") ;
+
+        return datasetFromDescription(action, datasetDesc) ;
+    }
+
+    /**
+     * Construct a Dataset based on a dataset description.
+     */
+    
+    protected static Dataset datasetFromDescription(HttpAction action, DatasetDescription datasetDesc)
+    {
+        try {
+            if ( datasetDesc == null )
+                return null ;
+            if ( datasetDesc.isEmpty() )
+                return null ;
+            
+            List<String> graphURLs = datasetDesc.getDefaultGraphURIs() ;
+            List<String> namedGraphs = datasetDesc.getNamedGraphURIs() ;
+            
+            if ( graphURLs.size() == 0 && namedGraphs.size() == 0 )
+                    return null ;
+            
+            Dataset dataset = DatasetFactory.createMem() ;
+            // Look in cache for loaded graphs!!
+
+            // ---- Default graph
+            {
+                Model model = ModelFactory.createDefaultModel() ;
+                for ( String uri : graphURLs )
+                {
+                    if ( uri == null || uri.equals("") )
+                        throw new InternalErrorException("Default graph URI is null or the empty string")  ;
+
+                    try {
+                        //TODO Clearup - RIOT integration.
+                        GraphLoadUtils.loadModel(model, uri, MaxTriples) ;
+                        log.info(format("[%d] Load (default graph) %s", action.id, uri)) ;
+                    } catch (RiotException ex) {
+                        log.info(format("[%d] Parsing error loading %s: %s", action.id, uri, ex.getMessage())) ;
+                        errorBadRequest("Failed to load URL (parse error) "+uri+" : "+ex.getMessage()) ;
+                    } catch (Exception ex)
+                    {
+                        log.info(format("[%d] Failed to load (default) %s: %s", action.id, uri, ex.getMessage())) ;
+                        errorBadRequest("Failed to load URL "+uri) ;
+                    }
+                }
+                dataset.setDefaultModel(model) ;
+            }
+            // ---- Named graphs
+            if ( namedGraphs != null )
+            {
+                for ( String uri : namedGraphs )
+                {
+                    if ( uri == null || uri.equals("") )
+                        throw new InternalErrorException("Named graph URI is null or the empty string")  ;
+
+                    try {
+                        Model model = ModelFactory.createDefaultModel() ;
+                        GraphLoadUtils.loadModel(model, uri, MaxTriples) ;
+                        log.info(format("[%d] Load (named graph) %s", action.id, uri)) ;
+                        dataset.addNamedModel(uri, model) ;
+                    } catch (RiotException ex) {
+                        log.info(format("[%d] Parsing error loading %s: %s", action.id, uri, ex.getMessage())) ;
+                        errorBadRequest("Failed to load URL (parse error) "+uri+" : "+ex.getMessage()) ;
+                    } catch (Exception ex)
+                    {
+                        log.info(format("[%d] Failed to load (named graph) %s: %s", action.id, uri, ex.getMessage())) ;
+                        errorBadRequest("Failed to load URL "+uri) ;
+                    }
+                }
+            }
+            
+            return dataset ;
+            
+        } 
+        catch (ActionErrorException ex) { throw ex ; }
+        catch (Exception ex)
+        {
+            log.info(format("[%d] SPARQL parameter error: "+ex.getMessage(),action.id, ex)) ;
+            errorBadRequest("Parameter error: "+ex.getMessage());
+            return null ;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/662cf71d/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_REST.java
----------------------------------------------------------------------
diff --git a/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_REST.java b/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_REST.java
new file mode 100644
index 0000000..4ab386b
--- /dev/null
+++ b/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_REST.java
@@ -0,0 +1,354 @@
+/*
+ * 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.HttpNames.* ;
+
+import java.io.IOException ;
+import java.io.InputStream ;
+import java.util.Enumeration ;
+import java.util.Locale ;
+
+import javax.servlet.ServletException ;
+import javax.servlet.http.HttpServletRequest ;
+import javax.servlet.http.HttpServletResponse ;
+
+import org.apache.jena.fuseki.HttpNames ;
+import org.apache.jena.fuseki.server.CounterName ;
+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.IRIResolver ;
+import org.apache.jena.riot.system.StreamRDF ;
+import org.slf4j.Logger ;
+import org.slf4j.LoggerFactory ;
+
+import com.hp.hpl.jena.graph.Graph ;
+import com.hp.hpl.jena.graph.Node ;
+import com.hp.hpl.jena.graph.NodeFactory ;
+import com.hp.hpl.jena.sparql.core.DatasetGraph ;
+
+public abstract class SPARQL_REST extends SPARQL_ServletBase
+{
+    protected static Logger classLog = LoggerFactory.getLogger(SPARQL_REST.class) ;
+    
+    protected static ErrorHandler errorHandler = ErrorHandlerFactory.errorHandlerStd(log) ;
+
+    protected final static Target determineTarget(HttpAction action) {
+        // Delayed until inside a transaction.
+        if ( action.getActiveDSG() == null )
+            errorOccurred("Internal error : No action graph (not in a transaction?)") ;
+        
+        boolean dftGraph = getOneOnly(action.request, HttpNames.paramGraphDefault) != null ;
+        String uri = getOneOnly(action.request, HttpNames.paramGraph) ;
+        
+        if ( !dftGraph && uri == null ) {
+            // Direct naming or error.
+            uri = action.request.getRequestURL().toString() ;
+            if ( action.request.getRequestURI().equals(action.getDatasetRef().name) )
+                // No name 
+                errorBadRequest("Neither default graph nor named graph specified; no direct name") ;
+        }
+        
+        if ( dftGraph )
+            return Target.createDefault(action.getActiveDSG()) ;
+        
+        // Named graph
+        if ( uri.equals(HttpNames.valueDefault ) )
+            // But "named" default
+            return Target.createDefault(action.getActiveDSG()) ;
+        
+        // Strictly, a bit naughty on the URI resolution.  But more sensible. 
+        // Base is dataset.
+        String base = action.request.getRequestURL().toString() ; //wholeRequestURL(request) ;
+        // Make sure it ends in "/", ie. dataset as container.
+        if ( action.request.getQueryString() != null && ! base.endsWith("/") )
+            base = base + "/" ;
+        
+        String absUri = IRIResolver.resolveString(uri, base) ;
+        Node gn = NodeFactory.createURI(absUri) ;
+        return Target.createNamed(action.getActiveDSG(), absUri, gn) ;
+    }
+    
+
+    // struct for target
+    protected static final class Target
+    {
+        final boolean isDefault ;
+        final DatasetGraph dsg ;
+        private Graph _graph ;
+        final String name ;
+        final Node graphName ;
+
+        static Target createNamed(DatasetGraph dsg, String name, Node graphName) {
+            return new Target(false, dsg, name, graphName) ;
+        }
+
+        static Target createDefault(DatasetGraph dsg) {
+            return new Target(true, dsg, null, null) ;
+        }
+
+        private Target(boolean isDefault, DatasetGraph dsg, String name, Node graphName) {
+            this.isDefault = isDefault ;
+            this.dsg = dsg ;
+            this._graph = null ;
+            this.name  = name ;
+            this.graphName = graphName ;
+
+            if ( isDefault )
+            {
+                if ( name != null || graphName != null )
+                    throw new IllegalArgumentException("Inconsistent: default and a graph name/node") ;       
+            }
+            else
+            {
+                if ( name == null || graphName == null )
+                    throw new IllegalArgumentException("Inconsistent: not default and/or no graph name/node") ;
+            }                
+        }
+
+        /** Get a graph for the action - this may create a graph in the dataset - this is not a test for graph existence */
+        public Graph graph() {
+            if ( ! isGraphSet() )
+            {
+                if ( isDefault ) 
+                    _graph = dsg.getDefaultGraph() ;
+                else
+                    _graph = dsg.getGraph(graphName) ;
+            }
+            return _graph ;
+        }
+
+        public boolean exists()
+        {
+            if ( isDefault ) return true ;
+            return dsg.containsGraph(graphName) ;
+        }
+
+        public boolean isGraphSet()
+        {
+            return _graph != null ;
+        }
+
+        @Override
+        public String toString()
+        {
+            if ( isDefault ) return "default" ;
+            return name ;
+        }
+    }
+
+    public SPARQL_REST()
+    { super() ; }
+
+    @Override
+    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+        // Direct all verbs to our common framework.
+        doCommon(request, response) ;
+    }
+    
+    private void maybeSetLastModified(HttpServletResponse resp, long lastModified) {
+        if (resp.containsHeader(HEADER_LASTMOD)) return ;
+        if (lastModified >= 0) resp.setDateHeader(HEADER_LASTMOD, lastModified);
+    }
+    
+    @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) ;
+            errorMethodNotAllowed("TRACE") ;
+        else if (method.equals(METHOD_PUT))
+            doPut$(action) ;   
+        else if (method.equals(METHOD_DELETE))
+            doDelete$(action) ;
+        else
+            errorNotImplemented("Unknown method: "+method) ;
+    }
+
+    // Counter wrappers
+    
+    protected void doGet$(HttpAction action) {
+        incCounter(action.srvRef, CounterName.GSPget) ;
+        try {
+            doGet(action) ;
+            incCounter(action.srvRef, CounterName.GSPgetGood) ;
+        } catch ( ActionErrorException ex) {
+            incCounter(action.srvRef, CounterName.GSPgetBad) ;
+            throw ex ;
+        }
+    }
+
+    protected void doHead$(HttpAction action) {
+        incCounter(action.srvRef, CounterName.GSPhead) ;
+        try {
+            doHead(action) ;
+            incCounter(action.srvRef, CounterName.GSPheadGood) ;
+        } catch ( ActionErrorException ex) {
+            incCounter(action.srvRef, CounterName.GSPheadBad) ;
+            throw ex ;
+        }
+    }
+
+    protected void doPost$(HttpAction action) {
+        incCounter(action.srvRef, CounterName.GSPpost) ;
+        try {
+            doPost(action) ;
+            incCounter(action.srvRef, CounterName.GSPpostGood) ;
+        } catch ( ActionErrorException ex) {
+            incCounter(action.srvRef, CounterName.GSPpostBad) ;
+            throw ex ;
+        }
+    }
+
+    protected void doPatch$(HttpAction action) {
+        incCounter(action.srvRef, CounterName.GSPpatch) ;
+        try {
+            doPatch(action) ;
+            incCounter(action.srvRef, CounterName.GSPpatchGood) ;
+        } catch ( ActionErrorException ex) {
+            incCounter(action.srvRef, CounterName.GSPpatchBad) ;
+            throw ex ;
+        }
+    }
+
+    protected void doDelete$(HttpAction action) {
+        incCounter(action.srvRef, CounterName.GSPdelete) ;
+        try {
+            doDelete(action) ;
+            incCounter(action.srvRef, CounterName.GSPdeleteGood) ;
+        } catch ( ActionErrorException ex) {
+            incCounter(action.srvRef, CounterName.GSPdeleteBad) ;
+            throw ex ;
+        }
+    }
+
+    protected void doPut$(HttpAction action) {
+        incCounter(action.srvRef, CounterName.GSPput) ;
+        try {
+            doPut(action) ;
+            incCounter(action.srvRef, CounterName.GSPputGood) ;
+        } catch ( ActionErrorException ex) {
+            incCounter(action.srvRef, CounterName.GSPputBad) ;
+            throw ex ;
+        }
+    }
+
+    protected void doOptions$(HttpAction action) {
+        incCounter(action.srvRef, CounterName.GSPoptions) ;
+        try {
+            doOptions(action) ;
+            incCounter(action.srvRef, CounterName.GSPoptionsGood) ;
+        } catch ( ActionErrorException ex) {
+            incCounter(action.srvRef, CounterName.GSPoptionsBad) ;
+            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) ;
+    
+    // @@ Move to SPARQL_ServletBase
+    // Check for all RiotReader
+    public static void parse(HttpAction action, StreamRDF dest, InputStream input, Lang lang, String base) {
+        try {
+            ReaderRIOT r = RDFDataMgr.createReader(lang) ;
+            if ( r == null )
+                errorBadRequest("No parser for language '"+lang.getName()+"'") ;
+            r.setErrorHandler(errorHandler); 
+            r.read(input, base, null, dest, null) ; 
+        } 
+        catch (RiotException ex) { errorBadRequest("Parse error: "+ex.getMessage()) ; }
+    }
+
+    @Override
+    protected void validate(HttpAction action)
+    {
+        HttpServletRequest request = action.request ;
+        // Direct naming.
+        if ( request.getQueryString() == null )
+            //errorBadRequest("No query string") ;
+            return ;
+        
+        String g = request.getParameter(HttpNames.paramGraph) ;
+        String d = request.getParameter(HttpNames.paramGraphDefault) ;
+        
+        if ( g != null && d !=null )
+            errorBadRequest("Both ?default and ?graph in the query string of the request") ;
+        
+        if ( g == null && d == null )
+            errorBadRequest("Neither ?default nor ?graph in the query string of the request") ;
+        
+        int x1 = SPARQL_Protocol.countParamOccurences(request, HttpNames.paramGraph) ;
+        int x2 = SPARQL_Protocol.countParamOccurences(request, HttpNames.paramGraphDefault) ;
+        
+        if ( x1 > 1 )
+            errorBadRequest("Multiple ?default in the query string of the request") ;
+        if ( x2 > 1 )
+            errorBadRequest("Multiple ?graph in the query string of the request") ;
+        
+        Enumeration<String> en = request.getParameterNames() ;
+        for ( ; en.hasMoreElements() ; )
+        {
+            String h = en.nextElement() ;
+            if ( ! HttpNames.paramGraph.equals(h) && ! HttpNames.paramGraphDefault.equals(h) )
+                errorBadRequest("Unknown parameter '"+h+"'") ;
+            // one of ?default and &graph
+            if ( request.getParameterValues(h).length != 1 )
+                errorBadRequest("Multiple parameters '"+h+"'") ;
+        }
+    }
+
+    protected static String getOneOnly(HttpServletRequest request, String name)
+    {
+        String[] values = request.getParameterValues(name) ;
+        if ( values == null )
+            return null ;
+        if ( values.length == 0 )
+            return null ;
+        if ( values.length > 1 )
+            errorBadRequest("Multiple occurrences of '"+name+"'") ;
+        return values[0] ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/662cf71d/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_REST_R.java
----------------------------------------------------------------------
diff --git a/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_REST_R.java b/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_REST_R.java
new file mode 100644
index 0000000..0c02b51
--- /dev/null
+++ b/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_REST_R.java
@@ -0,0 +1,128 @@
+/*
+ * 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 javax.servlet.ServletOutputStream ;
+
+import org.apache.jena.atlas.web.MediaType ;
+import org.apache.jena.atlas.web.TypedOutputStream ;
+import org.apache.jena.fuseki.HttpNames ;
+import org.apache.jena.riot.* ;
+
+import com.hp.hpl.jena.graph.Graph ;
+
+/** Only the READ operations */
+public class SPARQL_REST_R extends SPARQL_REST
+{
+    public SPARQL_REST_R()
+    { super() ; }
+    
+    
+    @Override
+    protected String mapRequestToDataset(String uri) { return mapRequestToDatasetLongest$(uri) ; } 
+
+    @Override
+    protected void doGet(HttpAction action)
+    {
+        // Assume success - do the set up before grabbing the lock.
+        // Sets content type.
+        MediaType mediaType = HttpAction.contentNegotationRDF(action) ;
+       
+        ServletOutputStream output ;
+        try { output = action.response.getOutputStream() ; }
+        catch (IOException ex) { errorOccurred(ex) ; output = null ; }
+        
+        TypedOutputStream out = new TypedOutputStream(output, mediaType) ;
+        Lang lang = RDFLanguages.contentTypeToLang(mediaType.getContentType()) ;
+
+        if ( action.verbose )
+            log.info(format("[%d]   Get: Content-Type=%s, Charset=%s => %s", 
+                            action.id, mediaType.getContentType(), mediaType.getCharset(), lang.getName())) ;
+
+        action.beginRead() ;
+        setCommonHeaders(action.response) ;
+
+        try {
+            Target target = determineTarget(action) ;
+            if ( log.isDebugEnabled() )
+                log.debug("GET->"+target) ;
+            boolean exists = target.exists() ;
+            if ( ! exists )
+                errorNotFound("No such graph: <"+target.name+">") ;
+            // If we want to set the Content-Length, we need to buffer.
+            //response.setContentLength(??) ;
+            String ct = lang.getContentType().toHeaderString() ;
+            action.response.setContentType(ct) ;
+            Graph g = target.graph() ;
+            //Special case RDF/XML to be the plain (faster, less readable) form
+            RDFFormat fmt = 
+                ( lang == Lang.RDFXML ) ? RDFFormat.RDFXML_PLAIN : RDFWriterRegistry.defaultSerialization(lang) ;  
+            RDFDataMgr.write(out, g, fmt) ;
+            success(action) ;
+        } finally { action.endRead() ; }
+    }
+    
+    @Override
+    protected void doOptions(HttpAction action)
+    {
+        setCommonHeadersForOptions(action.response) ;
+        action.response.setHeader(HttpNames.hAllow, "GET,HEAD,OPTIONS") ;
+        action.response.setHeader(HttpNames.hContentLengh, "0") ;
+        success(action) ;
+    }
+
+    @Override
+    protected void doHead(HttpAction action)
+    {
+        setCommonHeaders(action.response) ;
+        action.beginRead() ;
+        try { 
+            Target target = determineTarget(action) ;
+            if ( log.isDebugEnabled() )
+                log.debug("HEAD->"+target) ;
+            if ( ! target.exists() )
+            {
+                successNotFound(action) ;
+                return ;
+            }
+            MediaType mediaType = HttpAction.contentNegotationRDF(action) ;
+            success(action) ;
+        } finally { action.endRead() ; }
+    }
+
+    @Override
+    protected void doPost(HttpAction action)
+    { errorMethodNotAllowed("POST") ; }
+
+    @Override
+    protected void doDelete(HttpAction action)
+    { errorMethodNotAllowed("DELETE") ; }
+
+    @Override
+    protected void doPut(HttpAction action)
+    { errorMethodNotAllowed("PUT") ; }
+
+    @Override
+    protected void doPatch(HttpAction action)
+    { errorMethodNotAllowed("PATCH") ; }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/662cf71d/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_REST_RW.java
----------------------------------------------------------------------
diff --git a/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_REST_RW.java b/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_REST_RW.java
new file mode 100644
index 0000000..712d543
--- /dev/null
+++ b/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_REST_RW.java
@@ -0,0 +1,232 @@
+/*
+ * 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.io.InputStream ;
+import java.util.Map ;
+import java.util.Map.Entry ;
+
+import org.apache.jena.atlas.io.IO ;
+import org.apache.jena.atlas.web.ContentType ;
+import org.apache.jena.fuseki.FusekiLib ;
+import org.apache.jena.fuseki.HttpNames ;
+import org.apache.jena.riot.Lang ;
+import org.apache.jena.riot.RDFLanguages ;
+import org.apache.jena.riot.RiotException ;
+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.graph.Graph ;
+import com.hp.hpl.jena.sparql.graph.GraphFactory ;
+
+/** The WRITE operations added to the READ operations */
+public class SPARQL_REST_RW extends SPARQL_REST_R
+{
+    public SPARQL_REST_RW()
+    { super() ; }
+
+    @Override
+    protected void doOptions(HttpAction action)
+    {
+        setCommonHeadersForOptions(action.response) ;
+        action.response.setHeader(HttpNames.hAllow, "GET,HEAD,OPTIONS,PUT,DELETE,POST");
+        action.response.setHeader(HttpNames.hContentLengh, "0") ;
+        success(action) ;
+    }
+    
+    @Override
+    protected void doDelete(HttpAction action)
+    {
+        action.beginWrite() ;
+        try {
+            Target target = determineTarget(action) ;
+            if ( log.isDebugEnabled() )
+                log.debug("DELETE->"+target) ;
+            boolean existedBefore = target.exists() ; 
+            if ( ! existedBefore)
+            {
+                // commit, not abort, because locking "transactions" don't support abort. 
+                action.commit() ;
+                errorNotFound("No such graph: "+target.name) ;
+            } 
+            deleteGraph(action) ;
+            action.commit() ;
+        }
+        finally { action.endWrite() ; }
+        ServletBase.successNoContent(action) ;
+    }
+
+    @Override
+    protected void doPut(HttpAction action)     { doPutPost(action, true) ; }
+
+    @Override
+    protected void doPost(HttpAction action)     { doPutPost(action, false) ; }
+
+    private void doPutPost(HttpAction action, boolean overwrite) {
+        ContentType ct = FusekiLib.getContentType(action) ;
+        if ( ct == null )
+            errorBadRequest("No Content-Type:") ;
+
+        // Helper case - if it's a possible HTTP file upload, pretend that's the action.
+        if ( WebContent.contentTypeMultipartFormData.equalsIgnoreCase(ct.getContentType()) ) {
+            String base = wholeRequestURL(action.request) ;
+            SPARQL_Upload.upload(action, base) ;
+            return ; 
+        }
+
+        if ( WebContent.matchContentType(WebContent.ctMultipartMixed, ct) )
+            error(HttpSC.UNSUPPORTED_MEDIA_TYPE_415, "multipart/mixed not supported") ;
+        
+        boolean existedBefore = false ;
+        if ( action.isTransactional() )
+            existedBefore = addDataIntoTxn(action, overwrite) ;
+        else
+            existedBefore = addDataIntoNonTxn(action, overwrite) ;
+            
+        if ( existedBefore )
+            ServletBase.successNoContent(action) ;
+        else
+            ServletBase.successCreated(action) ;
+    }
+
+    /** Directly add data in a transaction.
+     * Assumes recovery from parse errors by transaction abort.
+     * Return whether the target existed before.
+     * @param action
+     * @param cleanDest Whether to remove data first (true = PUT, false = POST)
+     * @return whether the target existed beforehand
+     */
+    protected static boolean addDataIntoTxn(HttpAction action, boolean overwrite) {   
+        action.beginWrite();
+        Target target = determineTarget(action) ;
+        boolean existedBefore = false ;
+        try {
+            if ( log.isDebugEnabled() )
+                log.debug("  ->"+target) ;
+            existedBefore = target.exists() ;
+            Graph g = target.graph() ;
+            if ( overwrite && existedBefore )
+                clearGraph(target) ;
+            StreamRDF sink = StreamRDFLib.graph(g) ;
+            incomingData(action, sink);
+            action.commit() ;
+            return existedBefore ;
+        } catch (RiotException ex) { 
+            // Parse error
+            action.abort() ;
+            errorBadRequest(ex.getMessage()) ;
+            return existedBefore ;
+        } catch (Exception ex) {
+            // Something else went wrong.  Backout.
+            action.abort() ;
+            errorOccurred(ex.getMessage()) ;
+            return existedBefore ;
+        } finally {
+            action.endWrite() ;
+        }
+    }
+
+    /** Add data where the destination does not support full transactions.
+     *  In particular, with no abort, and actions probably going to the real storage
+     *  parse errors can lead to partial updates.  Instead, parse to a temporary
+     *  graph, then insert that data.  
+     * @param action
+     * @param cleanDest Whether to remove data first (true = PUT, false = POST)
+     * @return whether the target existed beforehand.
+     */
+    
+    protected static boolean addDataIntoNonTxn(HttpAction action, boolean overwrite) {
+        Graph graphTmp = GraphFactory.createGraphMem() ;
+        StreamRDF dest = StreamRDFLib.graph(graphTmp) ;
+
+        try { incomingData(action, dest); }
+        catch (RiotException ex) {
+            errorBadRequest(ex.getMessage()) ;
+            return false ;
+        }
+        // Now insert into dataset
+        action.beginWrite() ;
+        Target target = determineTarget(action) ;
+        boolean existedBefore = false ;
+        try {
+            if ( log.isDebugEnabled() )
+                log.debug("  ->"+target) ;
+            existedBefore = target.exists() ; 
+            if ( overwrite && existedBefore )
+                clearGraph(target) ;
+            FusekiLib.addDataInto(graphTmp, target.dsg, target.graphName) ;
+            action.commit() ;
+            return existedBefore ;
+        } catch (Exception ex) {
+            // We parsed into a temporary graph so an exception at this point
+            // is not because of a parse error.
+            // We're in the non-transactional branch, this probably will not work
+            // but it might and there is no harm safely trying. 
+            try { action.abort() ; } catch (Exception ex2) {} 
+            errorOccurred(ex.getMessage()) ;
+            return existedBefore ;            
+        } finally { action.endWrite() ; }
+    }
+    
+    private static void incomingData(HttpAction action, StreamRDF dest) {
+        String base = wholeRequestURL(action.request) ;
+        ContentType ct = FusekiLib.getContentType(action) ;
+        Lang lang = RDFLanguages.contentTypeToLang(ct.getContentType()) ;
+        if ( lang == null ) {
+            errorBadRequest("Unknown content type for triples: " + ct) ;
+            return ;
+        }
+        InputStream input = null ;
+        try { input = action.request.getInputStream() ; } 
+        catch (IOException ex) { IO.exception(ex) ; }
+    
+        int len = action.request.getContentLength() ;
+        if ( action.verbose ) {
+            if ( len >= 0 )
+                log.info(format("[%d]   Body: Content-Length=%d, Content-Type=%s, Charset=%s => %s", action.id, len,
+                                ct.getContentType(), ct.getCharset(), lang.getName())) ;
+            else
+                log.info(format("[%d]   Body: Content-Type=%s, Charset=%s => %s", action.id, ct.getContentType(),
+                                ct.getCharset(), lang.getName())) ;
+        }
+    
+        parse(action, dest, input, lang, base) ;
+    }
+
+    protected static void deleteGraph(HttpAction action) {
+        Target target = determineTarget(action) ;
+        if ( target.isDefault )
+            target.graph().clear() ;
+        else
+            action.getActiveDSG().removeGraph(target.graphName) ;
+    }
+
+    protected static void clearGraph(Target target) {
+        Graph g = target.graph() ;
+        g.clear() ;
+        Map<String, String> pm = g.getPrefixMapping().getNsPrefixMap() ;
+        for ( Entry<String, String> e : pm.entrySet() ) 
+            g.getPrefixMapping().removeNsPrefix(e.getKey()) ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/662cf71d/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_ServletBase.java
----------------------------------------------------------------------
diff --git a/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_ServletBase.java b/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_ServletBase.java
new file mode 100644
index 0000000..a3d5271
--- /dev/null
+++ b/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_ServletBase.java
@@ -0,0 +1,458 @@
+/*
+ * 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 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.IOException ;
+import java.util.Enumeration ;
+import java.util.Map ;
+import java.util.concurrent.atomic.AtomicLong ;
+
+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.fuseki.HttpNames ;
+import org.apache.jena.fuseki.server.* ;
+import org.apache.jena.web.HttpSC ;
+
+import com.hp.hpl.jena.query.ARQ ;
+import com.hp.hpl.jena.query.QueryCancelledException ;
+import com.hp.hpl.jena.sparql.util.Context ;
+
+/**
+ * Base servlet for SPARQL requests.
+ */
+public abstract class SPARQL_ServletBase extends ServletBase
+{
+    /**
+     * Creates a new SPARQL base Servlet.
+     */
+    protected SPARQL_ServletBase()      {   super() ; }
+    
+    // Common framework for handling HTTP requests
+    /**
+     * Handles GET and POST requests.
+     * @param request HTTP request
+     * @param response HTTP response
+     */
+    protected void doCommon(HttpServletRequest request, HttpServletResponse response)
+    //throws ServletException, IOException
+    {
+        try {
+            long id = allocRequestId(request, response);
+            
+            // Lifecycle
+            HttpAction action = allocHttpAction(id, request, response) ;
+            // then add to doCommonWorker
+            // work with HttpServletResponseTracker
+            
+            printRequest(action) ;
+            action.setStartTime() ;
+            
+            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
+                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 )
+                    responseSendError(response, ex.rc, ex.message) ;
+                else
+                    responseSendError(response, ex.rc) ;
+            } catch (RuntimeIOException ex) {
+                log.warn(format("[%d] Runtime IO Exception (client left?) RC = %d", id, HttpSC.INTERNAL_SERVER_ERROR_500)) ;
+                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) ;
+                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, request, response, verboseLogging) ;
+    }
+
+    /**
+     * Validates a HTTP Action.
+     * @param action HTTP Action
+     */
+    protected abstract void validate(HttpAction action) ;
+
+    /**
+     * Performs the HTTP Action.
+     * @param action HTTP Action
+     */
+    protected abstract void perform(HttpAction action) ;
+
+    /**
+     * Default start step.
+     * @param action HTTP Action
+     */
+    protected void startRequest(HttpAction action) {
+    }
+
+    /**
+     * Default finish step.
+     * @param action HTTP Action
+     */
+    protected void finishRequest(HttpAction action) { }
+
+    /**
+     * Archives the HTTP Action.
+     * @param action HTTP Action
+     * @see HttpAction#minimize()
+     */
+    private void archiveHttpAction(HttpAction action)
+    {
+        action.minimize() ;
+    }
+
+    /**
+     * 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
+     */
+    private void execCommonWorker(HttpAction action)
+    {
+        DatasetRef dsRef = null ;
+        String uri = action.request.getRequestURI() ;
+
+        String datasetUri = mapRequestToDataset(uri) ;
+        
+        if ( datasetUri != null ) {
+            dsRef = DatasetRegistry.get().get(datasetUri) ;
+            if ( dsRef == null ) {
+                errorNotFound("No dataset for URI: "+datasetUri) ;
+                return ;
+            }
+        } else
+            dsRef = FusekiConfig.serviceOnlyDatasetRef() ;
+
+        action.setDataset(dsRef) ;
+        String serviceName = mapRequestToService(dsRef, uri, datasetUri) ;
+        ServiceRef srvRef = dsRef.getServiceRef(serviceName) ;
+        action.setService(srvRef) ;
+        executeAction(action) ;
+    }
+
+    /**
+     * Utility method, that increments and returns the AtomicLong value.
+     * @param x AtomicLong
+     */
+    protected void inc(AtomicLong x)
+    {
+        x.incrementAndGet() ;
+    }
+
+    /**
+     * Executes the HTTP Action. Serves as intercept point for the UberServlet.
+     * @param action HTTP Action
+     */
+    protected void executeAction(HttpAction action)
+    {
+        executeLifecycle(action) ;
+    }
+    
+    /**
+     * Handles the service request lifecycle. Called directly by the UberServlet,
+     * which has not done any stats by this point.
+     * @param action {@link HttpAction}
+     * @see HttpAction
+     */
+    protected void executeLifecycle(HttpAction action)
+    {
+        incCounter(action.dsRef, Requests) ;
+        incCounter(action.srvRef, Requests) ;
+
+        startRequest(action) ;
+        try {
+            validate(action) ;
+        } catch (ActionErrorException ex) {
+            incCounter(action.dsRef,RequestsBad) ;
+            throw ex ;
+        }
+
+        try {
+            perform(action) ;
+            // Success
+            incCounter(action.srvRef, RequestsGood) ;
+            incCounter(action.dsRef, RequestsGood) ;
+        } catch (ActionErrorException ex) {
+            incCounter(action.srvRef, RequestsBad) ;
+            incCounter(action.dsRef, RequestsBad) ;
+            throw ex ;
+        } catch (QueryCancelledException ex) {
+            incCounter(action.srvRef, RequestsBad) ;
+            incCounter(action.dsRef, RequestsBad) ;
+            throw ex ;
+        } finally {
+            finishRequest(action) ;
+        }
+    }
+
+    /**
+     * Increments a counter.
+     * @param counters a {@link Counter}
+     * @param name a {@link CounterName}
+     */
+    protected static void incCounter(Counters counters, CounterName name) {
+        try {
+            if ( counters.getCounters().contains(name) )
+                counters.getCounters().inc(name) ;
+        } catch (Exception ex) {
+            Fuseki.serverLog.warn("Exception on counter inc", ex) ;
+        }
+    }
+
+    /**
+     * Decrements a counter.
+     * @param counters a {@link Counter}
+     * @param name a {@link CounterName}
+     */
+    protected static void decCounter(Counters counters, CounterName name) {
+        try {
+            if ( counters.getCounters().contains(name) )
+                counters.getCounters().dec(name) ;
+        } catch (Exception ex) {
+            Fuseki.serverLog.warn("Exception on counter dec", ex) ;
+        }
+    }
+
+    /**
+     * <p>Sends an <strong>error</strong> when the PATCH method is called.</p>
+     * <p>Throws ServletException or IOException as per overloaded method signature.</p>
+     * @param request HTTP request
+     * @param response HTTP response
+     * @throws ServletException from overloaded method signature
+     * @throws IOException from overloaded method signature
+     */
+    protected void doPatch(HttpServletRequest request, HttpServletResponse response)
+    throws ServletException, IOException
+    {
+        response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, "HTTP PATCH not supported");
+    }
+
+    /**
+     * Prints the HTTP Action request to the program log, using the INFO level.
+     * @param action {@link HttpAction}
+     */
+    private void printRequest(HttpAction action)
+    {
+        String url = 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())) ;
+                }
+            }
+        }
+    }
+
+    /**
+     * Initiates the response, by setting common headers such as Access-Control-Allow-Origin and Server, and
+     * the Vary header if the request method used was a GET.
+     * @param request HTTP request
+     * @param response HTTP response
+     */
+    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) ;
+    }
+
+    /**
+     * Prints the HTTP Action response to the program log, using the INFO level.
+     * @param action {@link HttpAction}
+     */
+    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)) ;
+    }
+
+    /**
+     * <p>Given a time epoch, it will return the time in milli seconds if it is less than 1000,
+     * otherwise it will normalize it to display as second.</p>
+     * <p>It appends a 'ms' suffix when using milli seconds, and ditto <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.000000 s</li>
+     * <li>10000 emits 10.000000 s</li>
+     * </ul>
+     * @param time the time epoch
+     * @return the time in milli seconds or in seconds
+     */
+    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) ;
+    }
+
+    /**
+     * Map request to uri in the registry. null means no mapping done (passthrough).
+     * @param uri the URI
+     * @return the dataset
+     */
+    protected String mapRequestToDataset(String uri) 
+    {
+        return mapRequestToDataset$(uri) ;
+    }
+    
+    /**
+     * A possible implementation for mapRequestToDataset(String) that assumes the form /dataset/service.
+     * @param uri the URI
+     * @return the dataset
+     */
+    protected static String mapRequestToDataset$(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) ;
+    }
+
+    /**
+     * Maps a request to a service (e.g. Query, Update).
+     * @param dsRef a {@link DatasetRef}
+     * @param uri the URI
+     * @param datasetURI the dataset URI
+     * @return an empty String (i.e. "") if the DatasetRef is null, or if its name is longer than the URI's name.
+     * Otherwise will return the service name.
+     */
+    protected String mapRequestToService(DatasetRef dsRef, String uri, String datasetURI)
+    {
+        if ( dsRef == null )
+            return "" ;
+        if ( dsRef.name.length() >= uri.length() )
+            return "" ;
+        return uri.substring(dsRef.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.
+     * @param uri the URI
+     * @return <code>null</code> if the URI is null, otherwise will return the longest match in the registry.
+     */
+    protected 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 : DatasetRegistry.get().keys() ) {
+            if ( ! uri.startsWith(ds2) )
+                continue ;
+
+            if ( ds == null )
+            {
+                ds = ds2 ;
+                continue ; 
+            }
+            if ( ds.length() < ds2.length() )
+            {
+                ds = ds2 ;
+                continue ;
+            }
+        }
+        return ds ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/662cf71d/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_UberServlet.java
----------------------------------------------------------------------
diff --git a/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_UberServlet.java b/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_UberServlet.java
new file mode 100644
index 0000000..0c10cee
--- /dev/null
+++ b/jena-fuseki1/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_UberServlet.java
@@ -0,0 +1,338 @@
+/**
+ * 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.util.List ;
+
+import javax.servlet.http.HttpServletRequest ;
+import javax.servlet.http.HttpServletResponse ;
+
+import org.apache.jena.atlas.web.MediaType ;
+import org.apache.jena.fuseki.DEF ;
+import org.apache.jena.fuseki.FusekiException ;
+import org.apache.jena.fuseki.HttpNames ;
+import org.apache.jena.fuseki.conneg.ConNeg ;
+import org.apache.jena.fuseki.server.DatasetRef ;
+import org.apache.jena.fuseki.server.ServiceRef ;
+import org.apache.jena.riot.WebContent ;
+
+/** This servlet can be attached to a dataset location
+ *  and acts as a router for all SPARQL operations
+ *  (query, update, graph store, both direct and indirect naming). 
+ */
+public abstract class SPARQL_UberServlet extends SPARQL_ServletBase
+{
+    protected abstract boolean allowQuery(HttpAction action) ;
+    protected abstract boolean allowUpdate(HttpAction action) ;
+    protected abstract boolean allowREST_R(HttpAction action) ;
+    protected abstract boolean allowREST_W(HttpAction action) ;
+    protected abstract boolean allowQuadsR(HttpAction action) ;
+    protected abstract boolean allowQuadsW(HttpAction action) ;
+    
+    public static class ReadOnly extends SPARQL_UberServlet
+    {
+        public ReadOnly()    { super() ; }
+        @Override protected boolean allowQuery(HttpAction action)    { return true ; }
+        @Override protected boolean allowUpdate(HttpAction action)   { return false ; }
+        @Override protected boolean allowREST_R(HttpAction action)   { return true ; }
+        @Override protected boolean allowREST_W(HttpAction action)   { return false ; }
+        @Override protected boolean allowQuadsR(HttpAction action)   { return true ; }
+        @Override protected boolean allowQuadsW(HttpAction action)   { return false ; }
+    }
+
+    public static class ReadWrite extends SPARQL_UberServlet
+    {
+        public ReadWrite()    { super() ; }
+        @Override protected boolean allowQuery(HttpAction action)    { return true ; }
+        @Override protected boolean allowUpdate(HttpAction action)   { return true ; }
+        @Override protected boolean allowREST_R(HttpAction action)   { return true ; }
+        @Override protected boolean allowREST_W(HttpAction action)   { return true ; }
+        @Override protected boolean allowQuadsR(HttpAction action)   { return true ; }
+        @Override protected boolean allowQuadsW(HttpAction action)   { return true ; }
+    }
+
+    public static class AccessByConfig extends SPARQL_UberServlet
+    {
+        public AccessByConfig()    { super() ; }
+        @Override protected boolean allowQuery(HttpAction action)    { return isEnabled(action.dsRef.query) ; }
+        @Override protected boolean allowUpdate(HttpAction action)   { return isEnabled(action.dsRef.update) ; }
+        @Override protected boolean allowREST_R(HttpAction action)   { return isEnabled(action.dsRef.readGraphStore) || allowREST_W(action); }
+        @Override protected boolean allowREST_W(HttpAction action)   { return isEnabled(action.dsRef.readWriteGraphStore) ; }
+        // Quad operations tied to presence/absence of GSP.
+        @Override protected boolean allowQuadsR(HttpAction action)   { return isEnabled(action.dsRef.readGraphStore) ; }
+        @Override protected boolean allowQuadsW(HttpAction action)   { return isEnabled(action.dsRef.readWriteGraphStore) ; }
+
+        private boolean isEnabled(ServiceRef service) { return service.isActive() ; } 
+    }
+    
+    /*  This can be used for a single servlet for everything (über-servlet)
+     *  
+     *  It can check for a request that looks like a service request and passes it on.
+     * This takes precedence over direct naming.
+     */
+    
+    // Refactor? Extract the direct naming handling.
+    // To test: enable in SPARQLServer.configureOneDataset
+    
+    private final SPARQL_ServletBase queryServlet    = new SPARQL_QueryDataset() ;
+    private final SPARQL_ServletBase updateServlet   = new SPARQL_Update() ;
+    private final SPARQL_ServletBase uploadServlet   = new SPARQL_Upload() ;
+    private final SPARQL_REST        restServlet_RW  = new SPARQL_REST_RW() ;
+    private final SPARQL_REST        restServlet_R   = new SPARQL_REST_R() ;
+    private final SPARQL_ServletBase restQuads       = new REST_Quads() ;
+    
+    public SPARQL_UberServlet() { super(); }
+
+    private String getEPName(String dsname, List<String> endpoints)
+    {
+        if (endpoints == null || endpoints.size() == 0) return null ;
+        String x = endpoints.get(0) ;
+        if ( ! dsname.endsWith("/") )
+            x = dsname+"/"+x ;
+        else
+            x = dsname+x ;
+        return x ;
+    }
+    
+    // These calls should not happen because we hook in at executeAction
+    @Override protected void validate(HttpAction action) { throw new FusekiException("Call to SPARQL_UberServlet.validate") ; }
+    @Override protected void perform(HttpAction action)  { throw new FusekiException("Call to SPARQL_UberServlet.perform") ; }
+
+    /** Map request to uri in the registry.
+     *  null means no mapping done 
+     */
+    @Override
+    protected String mapRequestToDataset(String uri) 
+    {
+        return mapRequestToDatasetLongest$(uri) ;
+    }
+    
+
+    /** Intercept the processing cycle at the point where the action has been set up,
+     *  the dataset target decided but no validation or execution has been done, 
+     *  nor any stats have been done.
+     */
+    @Override
+    protected void executeAction(HttpAction action)
+    {
+        long id = action.id ;
+        HttpServletRequest request = action.request ;
+        HttpServletResponse response = action.response ;
+        String uri = request.getRequestURI() ;
+        String method = request.getMethod() ;
+        DatasetRef desc = action.dsRef ;
+        
+        String trailing = findTrailing(uri, desc.name) ;
+        String qs = request.getQueryString() ;
+
+        boolean hasParams = request.getParameterMap().size() > 0 ;
+        
+        // Test for parameters - includes HTML forms.
+        boolean hasParamQuery           = request.getParameter(HttpNames.paramQuery) != null ;
+        // Include old name "request="
+        boolean hasParamUpdate          = request.getParameter(HttpNames.paramUpdate) != null || request.getParameter(HttpNames.paramRequest) != null ;
+        boolean hasParamGraph           = request.getParameter(HttpNames.paramGraph) != null ;
+        boolean hasParamGraphDefault    = request.getParameter(HttpNames.paramGraphDefault) != null ;
+        boolean isForm                  = WebContent.contentTypeHTMLForm.equalsIgnoreCase(request.getContentType()) ;
+
+        String ct = request.getContentType() ;
+        String charset = request.getCharacterEncoding() ;
+        
+        MediaType mt = null ;
+        if ( ct != null )
+            mt = MediaType.create(ct, charset) ;
+        
+        log.info(format("[%d] All: %s %s :: '%s' :: %s ? %s", id, method, desc.name, trailing, (mt==null?"<none>":mt), (qs==null?"":qs))) ;
+                       
+        boolean hasTrailing = ( trailing.length() != 0 ) ;
+        
+        if ( ! hasTrailing && ! hasParams )
+        {
+            restQuads.executeLifecycle(action) ;
+            return ;
+        }
+        
+        if ( ! hasTrailing )
+        {
+            // Has params of some kind.
+            if ( hasParamQuery || WebContent.contentTypeSPARQLQuery.equalsIgnoreCase(ct) )
+            {
+                // SPARQL Query
+                if ( ! allowQuery(action))
+                    errorForbidden("Forbidden: SPARQL query") ; 
+                executeRequest(action, queryServlet, desc.query) ;
+                return ;
+            }
+                 
+            if ( hasParamUpdate || WebContent.contentTypeSPARQLUpdate.equalsIgnoreCase(ct) )
+            {
+                // SPARQL Update
+                if ( ! allowQuery(action))
+                    errorForbidden("Forbidden: SPARQL query") ; 
+                executeRequest(action, updateServlet, desc.update) ;
+                return ;
+            }
+            
+            if ( hasParamGraph || hasParamGraphDefault )
+            {
+                doGraphStoreProtocol(action) ;
+                return ;
+            }
+            
+            errorBadRequest("Malformed request") ;
+            errorForbidden("Forbidden: SPARQL Graph Store Protocol : Read operation : "+method) ;
+        }
+        
+        final boolean checkForPossibleService = true ;
+        if ( checkForPossibleService )
+        {
+            // There is a trailing part.
+            // Check it's not the same name as a registered service.
+            // If so, dispatch to that service.
+            if ( serviceDispatch(action, desc.query, trailing, queryServlet) ) return ; 
+            if ( serviceDispatch(action, desc.update, trailing, updateServlet) ) return ; 
+            if ( serviceDispatch(action, desc.upload, trailing, uploadServlet) ) return ; 
+            if ( serviceDispatch(action, desc.readGraphStore, trailing, restServlet_R) ) return ; 
+            if ( serviceDispatch(action, desc.readWriteGraphStore, trailing, restServlet_RW) ) return ; 
+        }       
+        // There is a trailing part - params are illegal by this point.
+        if ( hasParams )
+            // ?? Revisit to include query-on-one-graph 
+            //errorBadRequest("Can't invoke a query-string service on a direct named graph") ;
+            errorNotFound("Not found: dataset='"+printName(desc.name)+"' service='"+printName(trailing)+"'");
+
+        // There is a trailing part - not a service, no params ==> GSP direct naming.
+        doGraphStoreProtocol(action) ;
+    }
+    
+    private String printName(String x) {
+        if ( x.startsWith("/") )
+            return x.substring(1) ;
+        return x ;
+    }
+    
+    private void doGraphStoreProtocol(HttpAction action)
+    {
+        // The GSP servlets handle direct and indirect naming. 
+        DatasetRef desc = action.dsRef ;
+        String method = action.request.getMethod() ;
+        
+        if ( HttpNames.METHOD_GET.equalsIgnoreCase(method) ||
+             HttpNames.METHOD_HEAD.equalsIgnoreCase(method) ) 
+       {
+           if ( ! allowREST_R(action))
+           // Graphs Store Protocol, indirect naming, read
+           // Indirect naming. Prefer the R service if available.
+           if ( desc.readGraphStore.isActive() )
+               executeRequest(action, restServlet_R, desc.readGraphStore) ;
+           else if ( desc.readWriteGraphStore.isActive() )
+               executeRequest(action, restServlet_RW, desc.readWriteGraphStore) ;
+           else
+               errorMethodNotAllowed(method) ;
+           return ;
+       }
+       
+       // Graphs Store Protocol, indirect naming, write
+       if ( ! allowREST_W(action))
+           errorForbidden("Forbidden: SPARQL Graph Store Protocol : Write operation : "+method) ;
+       executeRequest(action, restServlet_RW, desc.readWriteGraphStore) ;
+       return ;
+    }
+
+    private void executeRequest(HttpAction action, SPARQL_ServletBase servlet, ServiceRef service)
+    {
+        if ( service.endpoints.size() == 0 )
+            errorMethodNotAllowed(action.request.getMethod()) ;
+        servlet.executeLifecycle(action) ;
+    }
+
+    private void executeRequest(HttpAction action,SPARQL_ServletBase servlet)
+    {
+        servlet.executeLifecycle(action) ;
+//      // Forwarded dispatch.
+//      try
+//      {
+//          String target = getEPName(desc.name, endpointList) ;
+//          if ( target == null )
+//              errorMethodNotAllowed(request.getMethod()) ;
+//          // ** relative servlet forward
+//          request.getRequestDispatcher(target).forward(request, response) ;    
+        
+
+//          // ** absolute srvlet forward
+//          // getServletContext().getRequestDispatcher(target) ;
+//      } catch (Exception e) { errorOccurred(e) ; }        
+    }
+
+    protected static MediaType contentNegotationQuads(HttpAction action)
+    {
+        MediaType mt = ConNeg.chooseContentType(action.request, DEF.quadsOffer, DEF.acceptNQuads) ;
+        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 ;
+    }
+
+    /** return true if dispatched */
+    private boolean serviceDispatch(HttpAction action, ServiceRef service, String srvName , SPARQL_ServletBase servlet)
+    {
+        if ( ! service.endpoints.contains(srvName) )
+            return false ;
+        servlet.executeLifecycle(action) ;
+        return true ;
+    }
+
+    /** Find the graph (direct naming) or service name */ 
+    protected String findTrailing(String uri, String dsname) 
+    {
+        if ( dsname.length() >= uri.length() )
+            return "" ;
+        return uri.substring(dsname.length()+1) ;   // Skip the separating "/"
+    }
+
+    @Override
+    protected void doHead(HttpServletRequest request, HttpServletResponse response)
+    { doCommon(request, response) ; }
+    
+    @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 doOptions(HttpServletRequest request, HttpServletResponse response)
+    { doCommon(request, response) ; }
+    
+    @Override
+    protected void doPut(HttpServletRequest request, HttpServletResponse response)
+    { doCommon(request, response) ; }
+
+    @Override
+    protected void doDelete(HttpServletRequest request, HttpServletResponse response)
+    { doCommon(request, response) ; }
+}
+