http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_REST_RW.java
----------------------------------------------------------------------
diff --git 
a/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_REST_RW.java 
b/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_REST_RW.java
new file mode 100644
index 0000000..32a65e6
--- /dev/null
+++ 
b/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_REST_RW.java
@@ -0,0 +1,231 @@
+/*
+ * 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)
+    {
+        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/36855e1b/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_ServletBase.java
----------------------------------------------------------------------
diff --git 
a/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_ServletBase.java
 
b/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_ServletBase.java
new file mode 100644
index 0000000..a3d5271
--- /dev/null
+++ 
b/jena-fuseki/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/36855e1b/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_UberServlet.java
----------------------------------------------------------------------
diff --git 
a/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_UberServlet.java
 
b/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_UberServlet.java
new file mode 100644
index 0000000..0c10cee
--- /dev/null
+++ 
b/jena-fuseki/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) ; }
+}
+

http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_Update.java
----------------------------------------------------------------------
diff --git 
a/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_Update.java 
b/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_Update.java
new file mode 100644
index 0000000..a54715c
--- /dev/null
+++ 
b/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_Update.java
@@ -0,0 +1,307 @@
+/*
+ * 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.Fuseki.requestLog ;
+import static org.apache.jena.fuseki.HttpNames.paramRequest ;
+import static org.apache.jena.fuseki.HttpNames.paramUpdate ;
+import static org.apache.jena.fuseki.HttpNames.paramUsingGraphURI ;
+import static org.apache.jena.fuseki.HttpNames.paramUsingNamedGraphURI ;
+import static org.apache.jena.fuseki.server.CounterName.UpdateExecErrors ;
+
+import java.io.ByteArrayInputStream ;
+import java.io.IOException ;
+import java.io.InputStream ;
+import java.util.Arrays ;
+import java.util.Collection ;
+import java.util.Enumeration ;
+import java.util.List ;
+
+import javax.servlet.ServletException ;
+import javax.servlet.http.HttpServletRequest ;
+import javax.servlet.http.HttpServletResponse ;
+
+import org.apache.jena.atlas.io.IO ;
+import org.apache.jena.atlas.lib.StrUtils ;
+import org.apache.jena.atlas.web.ContentType ;
+import org.apache.jena.fuseki.FusekiLib ;
+import org.apache.jena.fuseki.HttpNames ;
+import org.apache.jena.iri.IRI ;
+import org.apache.jena.riot.WebContent ;
+import org.apache.jena.riot.system.IRIResolver ;
+import org.apache.jena.web.HttpSC ;
+
+import com.hp.hpl.jena.graph.Node ;
+import com.hp.hpl.jena.graph.NodeFactory ;
+import com.hp.hpl.jena.query.QueryParseException ;
+import com.hp.hpl.jena.query.Syntax ;
+import com.hp.hpl.jena.sparql.modify.UsingList ;
+import com.hp.hpl.jena.update.UpdateAction ;
+import com.hp.hpl.jena.update.UpdateException ;
+import com.hp.hpl.jena.update.UpdateFactory ;
+import com.hp.hpl.jena.update.UpdateRequest ;
+
+public class SPARQL_Update extends SPARQL_Protocol
+{
+    // Base URI used to isolate parsing from the current directory of the 
server. 
+    private static final String UpdateParseBase = 
"http://example/update-base/"; ;
+    private static final IRIResolver resolver = 
IRIResolver.create(UpdateParseBase) ;
+    
+    public SPARQL_Update()
+    { super() ; }
+
+    @Override
+    protected void doGet(HttpServletRequest request, HttpServletResponse 
response)
+    throws ServletException, IOException
+    {
+        response.sendError(HttpSC.BAD_REQUEST_400, "Attempt to perform SPARQL 
update by GET.  Use POST") ;
+    }
+    
+    @Override
+    protected void doPost(HttpServletRequest request, HttpServletResponse 
response)
+    throws ServletException, IOException
+    {
+        doCommon(request, response) ;
+    }
+    
+    @Override
+    protected void doOptions(HttpServletRequest request, HttpServletResponse 
response)
+    {
+        response.setHeader(HttpNames.hAllow, "OPTIONS,POST");
+        response.setHeader(HttpNames.hContentLengh, "0") ;
+    }
+
+    @Override
+    protected void perform(HttpAction action)
+    {
+        // WebContent needs to migrate to using ContentType.
+        String ctStr ;
+        {
+            ContentType ct = FusekiLib.getContentType(action) ;
+            if ( ct == null )
+                ctStr = WebContent.contentTypeSPARQLUpdate ;
+            else
+                ctStr = ct.getContentType() ;
+        }
+
+        if (WebContent.contentTypeSPARQLUpdate.equals(ctStr))
+        {
+            executeBody(action) ;
+            return ;
+        }
+        if (WebContent.contentTypeHTMLForm.equals(ctStr))
+        {
+            executeForm(action) ;
+            return ;
+        }
+        error(HttpSC.UNSUPPORTED_MEDIA_TYPE_415, "Bad content type: " + 
action.request.getContentType()) ;
+    }
+
+    protected static List<String> paramsForm = Arrays.asList(paramRequest, 
paramUpdate, 
+                                                             
paramUsingGraphURI, paramUsingNamedGraphURI) ;
+    protected static List<String> paramsPOST = 
Arrays.asList(paramUsingGraphURI, paramUsingNamedGraphURI) ;
+    
+    @Override
+    protected void validate(HttpAction action)
+    {
+        HttpServletRequest request = action.request ;
+        
+        if ( ! HttpNames.METHOD_POST.equalsIgnoreCase(request.getMethod()) )
+            errorMethodNotAllowed("SPARQL Update : use POST") ;
+        
+        ContentType incoming = FusekiLib.getContentType(action) ;
+        String ctStr = ( incoming == null ) ? 
WebContent.contentTypeSPARQLUpdate : incoming.getContentType() ;
+        // ----
+        
+        if ( WebContent.contentTypeSPARQLUpdate.equals(ctStr) )
+        {
+            String charset = request.getCharacterEncoding() ;
+            if ( charset != null && ! 
charset.equalsIgnoreCase(WebContent.charsetUTF8) )
+                errorBadRequest("Bad charset: "+charset) ;
+            validate(request, paramsPOST) ;
+            return ;
+        }
+        
+        if ( WebContent.contentTypeHTMLForm.equals(ctStr) )
+        {
+            int x = countParamOccurences(request, paramUpdate) + 
countParamOccurences(request, paramRequest) ;
+            if ( x == 0 )
+                errorBadRequest("SPARQL Update: No 'update=' parameter") ;
+            if ( x != 1 )
+                errorBadRequest("SPARQL Update: Multiple 'update=' 
parameters") ;
+            
+            String requestStr = request.getParameter(paramUpdate) ;
+            if ( requestStr == null )
+                requestStr = request.getParameter(paramRequest) ;
+            if ( requestStr == null )
+                errorBadRequest("SPARQL Update: No update= in HTML form") ;
+            validate(request, paramsForm) ;
+            return ;
+        }
+        
+        error(HttpSC.UNSUPPORTED_MEDIA_TYPE_415, "Must be 
"+WebContent.contentTypeSPARQLUpdate+" or "+WebContent.contentTypeHTMLForm+" 
(got "+ctStr+")") ;
+    }
+    
+    protected void validate(HttpServletRequest request, Collection<String> 
params)
+    {
+        if ( params != null )
+        {
+            Enumeration<String> en = request.getParameterNames() ;
+            for ( ; en.hasMoreElements() ; )
+            {
+                String name = en.nextElement() ;
+                if ( ! params.contains(name) )
+                    warning("SPARQL Update: Unrecognize request parameter 
(ignored): "+name) ;
+            }
+        }
+    }
+
+    private void executeBody(HttpAction action)
+    {
+        InputStream input = null ;
+        try { input = action.request.getInputStream() ; }
+        catch (IOException ex) { errorOccurred(ex) ; }
+
+        if ( action.verbose )
+        {
+            // Verbose mode only .... capture request for logging (does not 
scale). 
+            String requestStr = null ;
+            try { requestStr = IO.readWholeFileAsUTF8(input) ; }
+            catch (IOException ex) { IO.exception(ex) ; }
+            requestLog.info(format("[%d] Update = %s", action.id, 
formatForLog(requestStr))) ;
+            
+            input = new ByteArrayInputStream(requestStr.getBytes());
+            requestStr = null;
+        }
+        
+        execute(action, input) ;
+        successNoContent(action) ;
+    }
+
+    private void executeForm(HttpAction action)
+    {
+        String requestStr = action.request.getParameter(paramUpdate) ;
+        if ( requestStr == null )
+            requestStr = action.request.getParameter(paramRequest) ;
+        
+        if ( action.verbose )
+            //requestLog.info(format("[%d] Form update = %s", action.id, 
formatForLog(requestStr))) ;
+            requestLog.info(format("[%d] Form update = \n%s", action.id, 
requestStr)) ;
+        // A little ugly because we are taking a copy of the string, but 
hopefully shouldn't be too big if we are in this code-path
+        // If we didn't want this additional copy, we could make the parser 
take a Reader in addition to an InputStream
+        byte[] b = StrUtils.asUTF8bytes(requestStr) ;
+        ByteArrayInputStream input = new ByteArrayInputStream(b);
+        requestStr = null;  // free it early at least
+        execute(action, input);
+        successPage(action,"Update succeeded") ;
+    }
+    
+    private void execute(HttpAction action, InputStream input)
+    {
+        UsingList usingList = processProtocol(action.request) ;
+        
+        // If the dsg is transactional, then we can parse and execute the 
update in a streaming fashion.
+        // If it isn't, we need to read the entire update request before 
performing any updates, because
+        // we have to attempt to make the request atomic in the face of 
malformed queries
+        UpdateRequest req = null ;
+        if (!action.isTransactional()) 
+        {
+            try {
+                // TODO implement a spill-to-disk version of this
+                req = UpdateFactory.read(usingList, input, UpdateParseBase, 
Syntax.syntaxARQ);
+            }
+            catch (UpdateException ex) { errorBadRequest(ex.getMessage()) ; 
return ; }
+            catch (QueryParseException ex) { 
errorBadRequest(messageForQPE(ex)) ; return ; }
+        }
+        
+        action.beginWrite() ;
+        try {
+            if (req == null )
+                UpdateAction.parseExecute(usingList, action.getActiveDSG(), 
input, UpdateParseBase, Syntax.syntaxARQ);
+            else
+                UpdateAction.execute(req, action.getActiveDSG()) ;
+            action.commit() ;
+        } catch (UpdateException ex) {
+            action.abort() ;
+            incCounter(action.srvRef, UpdateExecErrors) ;
+            errorOccurred(ex.getMessage()) ;
+        } catch (QueryParseException ex) {
+            action.abort() ;
+            // Counter inc'ed further out.
+            errorBadRequest(messageForQPE(ex)) ;
+        } catch (Throwable ex) {
+            if ( ! ( ex instanceof ActionErrorException ) )
+            {
+                try { action.abort() ; } catch (Exception ex2) {}
+                errorOccurred(ex.getMessage(), ex) ;
+            }
+        } finally { action.endWrite(); }
+    }
+
+    /* [It is an error to supply the using-graph-uri or using-named-graph-uri 
parameters 
+     * when using this protocol to convey a SPARQL 1.1 Update request that 
contains an 
+     * operation that uses the USING, USING NAMED, or WITH clause.]
+     * 
+     * We will simply capture any using parameters here and pass them to the 
parser, which will be
+     * responsible for throwing an UpdateException if the query violates the 
above requirement,
+     * and will also be responsible for adding the using parameters to update 
queries that can
+     * accept them.
+     */
+    private UsingList processProtocol(HttpServletRequest request)
+    {
+        UsingList toReturn = new UsingList();
+        
+        String[] usingArgs = request.getParameterValues(paramUsingGraphURI) ;
+        String[] usingNamedArgs = 
request.getParameterValues(paramUsingNamedGraphURI) ;
+        if ( usingArgs == null && usingNamedArgs == null )
+            return toReturn;
+        if ( usingArgs == null )
+            usingArgs = new String[0] ;
+        if ( usingNamedArgs == null )
+            usingNamedArgs = new String[0] ;
+        // Impossible.
+//        if ( usingArgs.length == 0 && usingNamedArgs.length == 0 )
+//            return ;
+        
+        for (String nodeUri : usingArgs)
+        {
+            toReturn.addUsing(createNode(nodeUri));
+        }
+        for (String nodeUri : usingNamedArgs)
+        {
+            toReturn.addUsingNamed(createNode(nodeUri));
+        }
+        
+        return toReturn;
+    }
+    
+    private static Node createNode(String x)
+    {
+        try {
+            IRI iri = resolver.resolve(x) ;
+            return NodeFactory.createURI(iri.toString()) ;
+        } catch (Exception ex)
+        {
+            errorBadRequest("SPARQL Update: bad IRI: "+x) ;
+            return null ;
+        }
+        
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_Upload.java
----------------------------------------------------------------------
diff --git 
a/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_Upload.java 
b/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_Upload.java
new file mode 100644
index 0000000..d5956e5
--- /dev/null
+++ 
b/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/SPARQL_Upload.java
@@ -0,0 +1,259 @@
+/*
+ * 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.io.PrintWriter ;
+import java.util.zip.GZIPInputStream ;
+
+import javax.servlet.ServletException ;
+import javax.servlet.http.HttpServletRequest ;
+import javax.servlet.http.HttpServletResponse ;
+
+import org.apache.commons.fileupload.FileItemIterator ;
+import org.apache.commons.fileupload.FileItemStream ;
+import org.apache.commons.fileupload.servlet.ServletFileUpload ;
+import org.apache.commons.fileupload.util.Streams ;
+import org.apache.jena.atlas.lib.Pair ;
+import org.apache.jena.atlas.web.ContentType ;
+import org.apache.jena.fuseki.FusekiLib ;
+import org.apache.jena.fuseki.HttpNames ;
+import org.apache.jena.iri.IRI ;
+import org.apache.jena.riot.Lang ;
+import org.apache.jena.riot.RDFLanguages ;
+import org.apache.jena.riot.lang.StreamRDFCounting ;
+import org.apache.jena.riot.system.* ;
+import org.apache.jena.web.HttpSC ;
+
+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.Quad ;
+import com.hp.hpl.jena.sparql.graph.GraphFactory ;
+
+public class SPARQL_Upload extends SPARQL_ServletBase 
+{
+    private static ErrorHandler errorHandler = 
ErrorHandlerFactory.errorHandlerStd(log) ;
+    
+    public SPARQL_Upload() {
+        super() ;
+    }
+
+    // Methods to respond to.
+    @Override
+    protected void doPost(HttpServletRequest request, HttpServletResponse 
response)
+    throws ServletException, IOException
+    {
+        doCommon(request, response) ;
+    }
+    
+    @Override
+    protected void doOptions(HttpServletRequest request, HttpServletResponse 
response)
+    {
+        response.setHeader(HttpNames.hAllow, "OPTIONS,POST");
+        response.setHeader(HttpNames.hContentLengh, "0") ;
+    }
+    
+    @Override
+    protected void perform(HttpAction action)
+    {
+        // Only allows one file in the upload.
+        boolean isMultipart = 
ServletFileUpload.isMultipartContent(action.request);
+        if ( ! isMultipart )
+            error(HttpSC.BAD_REQUEST_400 , "Not a file upload") ;
+        long count = upload(action, "http://example/upload-base/";) ;
+        try {
+            action.response.setContentType("text/html") ;
+            action.response.setStatus(HttpSC.OK_200);
+            PrintWriter out = action.response.getWriter() ;
+            out.println("<html>") ;
+            out.println("<head>") ;
+            out.println("</head>") ;
+            out.println("<body>") ;
+            out.println("<h1>Success</h1>");
+            out.println("<p>") ;
+            out.println("Triples = "+count + "\n");
+            out.println("<p>") ;
+            out.println("</p>") ;
+            out.println("<button onclick=\"timeFunction()\">Back to 
Fuseki</button>");
+            out.println("</p>") ;
+            out.println("<script type=\"text/javascript\">");
+            out.println("function timeFunction(){");
+            out.println("window.location.href = \"/fuseki.html\";}");
+            out.println("</script>");
+            out.println("</body>") ;
+            out.println("</html>") ;
+            out.flush() ;
+            success(action) ;
+        }
+        catch (Exception ex) { errorOccurred(ex) ; }
+    }
+    
+    // Also used by SPARQL_REST
+    static public long upload(HttpAction action, String base)
+    {
+        if ( action.isTransactional() )
+            return uploadTxn(action, base) ;
+        else
+            return uploadNonTxn(action, base) ;
+    }
+
+    /** Non-transaction - buffer to a temporary graph so that parse errors
+     * are caught before inserting any data. 
+     */
+     private static long uploadNonTxn(HttpAction action, String base) {
+         Pair<String, Graph> p = uploadWorker(action, base) ;
+         String graphName = p.getLeft() ;
+         Graph graphTmp = p.getRight() ;
+         long tripleCount = graphTmp.size() ;
+
+         log.info(format("[%d] Upload: Graph: %s (%d triple(s))", 
+                         action.id, graphName,  tripleCount)) ;
+
+         Node gn = graphName.equals(HttpNames.valueDefault)
+             ? Quad.defaultGraphNodeGenerated 
+             : NodeFactory.createURI(graphName) ;
+
+         action.beginWrite() ;
+         try {
+            FusekiLib.addDataInto(graphTmp, action.getActiveDSG(), gn) ;
+            action.commit() ;
+            return tripleCount ;
+        } catch (RuntimeException ex)
+        {
+            // If anything went wrong, try to backout.
+            try { action.abort() ; } catch (Exception ex2) {}
+            errorOccurred(ex.getMessage()) ;
+            return -1 ;
+        } 
+        finally { action.endWrite() ; }
+    }
+
+     /** Transactional - we'd like data to go straight to the destination, 
with an abort on parse error.
+      * But file upload with a name means that the name can be after the data
+      * (it is in the Fuseki default pages).
+      * Use Graph Store protocol for bulk uploads.
+      * (It would be possible to process the incoming stream and see the graph 
name first.)
+      */
+      private static long uploadTxn(HttpAction action, String base) {
+          // We can't do better than the non-transaction approach.
+          return uploadNonTxn(action, base) ;
+      }
+     
+    /**  process an HTTP upload of RDF.
+     *   We can't stream straight into a dataset because the graph name can be 
after the data. 
+     *  @return graph name and count
+     */
+    
+    static private Pair<String, Graph> uploadWorker(HttpAction action, String 
base)
+    {
+        Graph graphTmp = GraphFactory.createDefaultGraph() ;
+        ServletFileUpload upload = new ServletFileUpload();
+        String graphName = null ;
+        long count = -1 ;
+        
+        String name = null ;  
+        ContentType ct = null ;
+        Lang lang = null ;
+
+        try {
+            FileItemIterator iter = upload.getItemIterator(action.request);
+            while (iter.hasNext()) {
+                FileItemStream item = iter.next();
+                String fieldName = item.getFieldName();
+                InputStream stream = item.openStream();
+                if (item.isFormField())
+                {
+                    // Graph name.
+                    String value = Streams.asString(stream, "UTF-8") ;
+                    if ( fieldName.equals(HttpNames.paramGraph) )
+                    {
+                        graphName = value ;
+                        if ( graphName != null && ! graphName.equals("") && ! 
graphName.equals(HttpNames.valueDefault) )
+                        {
+                            IRI iri = IRIResolver.parseIRI(value) ;
+                            if ( iri.hasViolation(false) )
+                                errorBadRequest("Bad IRI: "+graphName) ;
+                            if ( iri.getScheme() == null )
+                                errorBadRequest("Bad IRI: no IRI scheme name: 
"+graphName) ;
+                            if ( iri.getScheme().equalsIgnoreCase("http") || 
iri.getScheme().equalsIgnoreCase("https")) 
+                            {
+                                // Redundant??
+                                if ( iri.getRawHost() == null ) 
+                                    errorBadRequest("Bad IRI: no host name: 
"+graphName) ;
+                                if ( iri.getRawPath() == null || 
iri.getRawPath().length() == 0 )
+                                    errorBadRequest("Bad IRI: no path: 
"+graphName) ;
+                                if ( iri.getRawPath().charAt(0) != '/' )
+                                    errorBadRequest("Bad IRI: Path does not 
start '/': "+graphName) ;
+                            } 
+                        }
+                    }
+                    else if ( fieldName.equals(HttpNames.paramDefaultGraphURI) 
)
+                        graphName = null ;
+                    else
+                        // Add file type?
+                        log.info(format("[%d] Upload: Field=%s ignored", 
action.id, fieldName)) ;
+                } else {
+                    // Process the input stream
+                    name = item.getName() ; 
+                    if ( name == null || name.equals("") || name.equals("UNSET 
FILE NAME") ) 
+                        errorBadRequest("No name for content - can't determine 
RDF syntax") ;
+
+                    String contentTypeHeader = item.getContentType() ;
+                    ct = ContentType.create(contentTypeHeader) ;
+
+                    lang = RDFLanguages.contentTypeToLang(ct.getContentType()) 
;
+
+                    if ( lang == null ) {
+                        lang = RDFLanguages.filenameToLang(name) ;
+                        
+                        //JENA-600 filenameToLang() strips off certain 
extensions such as .gz and 
+                        //we need to ensure that if there was a .gz extension 
present we wrap the stream accordingly
+                        if (name.endsWith(".gz"))
+                            stream = new GZIPInputStream(stream);
+                    }
+                    if ( lang == null )
+                        // Desperate.
+                        lang = RDFLanguages.RDFXML ;
+
+                    log.info(format("[%d] Upload: Filename: %s, 
Content-Type=%s, Charset=%s => %s", 
+                                    action.id, name,  ct.getContentType(), 
ct.getCharset(), lang.getName())) ;
+                    
+                    StreamRDF x = StreamRDFLib.graph(graphTmp) ;
+                    StreamRDFCounting dest =  StreamRDFLib.count(x) ;
+                    SPARQL_REST.parse(action, dest, stream, lang, base);
+                    count = dest.count() ;
+                }
+            }    
+
+            if ( graphName == null || graphName.equals("") ) 
+                graphName = HttpNames.valueDefault ;
+            return Pair.create(graphName, graphTmp) ;
+        }
+        catch (ActionErrorException ex) { throw ex ; }
+        catch (Exception ex)            { errorOccurred(ex) ; return null ; }
+    }            
+
+    @Override
+    protected void validate(HttpAction action)
+    {}
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/ServletBase.java
----------------------------------------------------------------------
diff --git 
a/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/ServletBase.java 
b/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/ServletBase.java
new file mode 100644
index 0000000..473a7db
--- /dev/null
+++ b/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/ServletBase.java
@@ -0,0 +1,237 @@
+/*
+ * 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.io.PrintWriter ;
+import java.util.concurrent.atomic.AtomicLong ;
+
+import javax.servlet.http.HttpServlet ;
+import javax.servlet.http.HttpServletRequest ;
+import javax.servlet.http.HttpServletResponse ;
+
+import org.apache.jena.atlas.lib.StrUtils ;
+import org.apache.jena.fuseki.Fuseki ;
+import org.apache.jena.fuseki.HttpNames ;
+import org.apache.jena.web.HttpSC ;
+import org.slf4j.Logger ;
+
+/**
+ * An abstract HTTP Servlet. Contains implementation methods for setting the 
request status in a HTTP Action,
+ * and a mechanism to allocate unique ID's to new requests.
+ */
+public abstract class ServletBase extends HttpServlet
+{
+    protected static final Logger log = Fuseki.requestLog ;
+    public final boolean verboseLogging = Fuseki.verboseLogging ;
+    private static AtomicLong requestIdAlloc = new AtomicLong(0) ;
+
+    protected ServletBase()     { }
+    
+    /**
+     * Helper method which gets a unique request ID and appends it as a header 
to the response
+     * @param request  HTTP Request
+     * @param response HTTP Response
+     * @return Request ID
+     */
+    protected long allocRequestId(HttpServletRequest request, 
HttpServletResponse response) {
+        long id = requestIdAlloc.incrementAndGet();
+        addRequestId(response, id);
+        return id;
+    }
+    
+    /**
+     * Helper method for attaching a request ID to a response as a header
+     * @param response Response
+     * @param id Request ID
+     */
+    protected void addRequestId(HttpServletResponse response, long id) {
+        response.addHeader("Fuseki-Request-ID", Long.toString(id));
+    }
+    
+    protected void responseSendError(HttpServletResponse response, int 
statusCode, String message)
+    {
+        try { response.sendError(statusCode, message) ; }
+        catch (IOException ex) { errorOccurred(ex) ; }
+        catch (IllegalStateException ex) { }
+    }
+    
+    protected void responseSendError(HttpServletResponse response, int 
statusCode)
+    {
+        try { response.sendError(statusCode) ; }
+        catch (IOException ex) { errorOccurred(ex) ; }
+    }
+
+    /**
+     * Returns the HTTP request URL, appended with any additional URL 
parameters used.
+     *
+     * @param request HTTP request
+     * @return complete request URL
+     */
+    protected static String wholeRequestURL(HttpServletRequest request)
+    {
+        StringBuffer sb = request.getRequestURL() ;
+        String queryString = request.getQueryString() ;
+        if ( queryString != null )
+        {
+            sb.append("?") ;
+            sb.append(queryString) ;
+        }
+        return sb.toString() ;
+    }
+    
+    protected static void successNoContent(HttpAction action)
+    {
+        success(action, HttpSC.NO_CONTENT_204);
+    }
+    
+    protected static void success(HttpAction action)
+    {
+        success(action, HttpSC.OK_200);
+    }
+
+    protected static void successCreated(HttpAction action)
+    {
+        success(action, HttpSC.CREATED_201);
+    }
+    
+    // When 404 is no big deal e.g. HEAD
+    protected static void successNotFound(HttpAction action) 
+    {
+        success(action, HttpSC.NOT_FOUND_404) ;
+    }
+
+    //
+    protected static void success(HttpAction action, int httpStatusCode)
+    {
+        action.response.setStatus(httpStatusCode);
+    }
+    
+    protected static void successPage(HttpAction action, String message)
+    {
+        try {
+            action.response.setContentType("text/html");
+            action.response.setStatus(HttpSC.OK_200);
+            PrintWriter out = action.response.getWriter() ;
+            out.println("<html>") ;
+            out.println("<head>") ;
+            out.println("</head>") ;
+            out.println("<body>") ;
+            out.println("<h1>Success</h1>");
+            if ( message != null )
+            {
+                out.println("<p>") ;
+                out.println(message) ;
+                out.println("</p>") ;
+            }
+            out.println("</body>") ;
+            out.println("</html>") ;
+            out.flush() ;
+        } catch (IOException ex) { errorOccurred(ex) ; }
+    }
+    
+    protected static void warning(String string)
+    {
+        log.warn(string) ;
+    }
+    
+    protected static void warning(String string, Throwable thorwable)
+    {
+        log.warn(string, thorwable) ;
+    }
+    
+    protected static void errorBadRequest(String string)
+    {
+        error(HttpSC.BAD_REQUEST_400, string) ;
+    }
+
+    protected static void errorNotFound(String string)
+    {
+        error(HttpSC.NOT_FOUND_404, string) ;
+    }
+
+    protected static void errorNotImplemented(String msg)
+    {
+        error(HttpSC.NOT_IMPLEMENTED_501, msg) ;
+    }
+    
+    protected static void errorMethodNotAllowed(String method)
+    {
+        error(HttpSC.METHOD_NOT_ALLOWED_405, "HTTP method not allowed: 
"+method) ;
+    }
+
+    protected static void errorForbidden(String msg)
+    {
+        if ( msg != null )
+            error(HttpSC.FORBIDDEN_403, msg) ;
+        else
+            error(HttpSC.FORBIDDEN_403, "Forbidden") ;
+    }
+    
+    protected static void error(int statusCode)
+    {
+        throw new ActionErrorException(null, null, statusCode) ;
+    }
+    
+
+    protected static void error(int statusCode, String string)
+    {
+        throw new ActionErrorException(null, string, statusCode) ;
+    }
+    
+    protected static void errorOccurred(String message)
+    {
+        errorOccurred(message, null) ;
+    }
+
+    protected static void errorOccurred(Throwable ex)
+    {
+        errorOccurred(null, ex) ;
+    }
+
+    protected static void errorOccurred(String message, Throwable ex)
+    {
+        throw new ActionErrorException(ex, message, 
HttpSC.INTERNAL_SERVER_ERROR_500) ;
+    }
+    
+    protected static String formatForLog(String string)
+    {
+        string = string.replace('\n', ' ') ;
+        string = string.replace('\r', ' ') ;
+        return string ; 
+    }
+
+    static String varyHeaderSetting = 
+        StrUtils.strjoin(",", 
+                         HttpNames.hAccept, 
+                         HttpNames.hAcceptEncoding, 
+                         HttpNames.hAcceptCharset ) ;
+    
+    public static void setVaryHeader(HttpServletResponse httpResponse)
+    {
+        httpResponse.setHeader(HttpNames.hVary, varyHeaderSetting) ;
+    }
+
+    
+    public static void setCommonHeaders(HttpServletResponse httpResponse)
+    {
+        httpResponse.setHeader(HttpNames.hAccessControlAllowOrigin, "*") ;
+        httpResponse.setHeader(HttpNames.hServer, Fuseki.serverHttpName) ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/SimpleVelocity.java
----------------------------------------------------------------------
diff --git 
a/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/SimpleVelocity.java 
b/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/SimpleVelocity.java
new file mode 100644
index 0000000..7f5cad9
--- /dev/null
+++ 
b/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/SimpleVelocity.java
@@ -0,0 +1,78 @@
+/**
+ * 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.io.Writer ;
+import java.util.Map ;
+
+import org.apache.velocity.Template ;
+import org.apache.velocity.VelocityContext ;
+import org.apache.velocity.app.VelocityEngine ;
+import org.apache.velocity.exception.MethodInvocationException ;
+import org.apache.velocity.exception.ParseErrorException ;
+import org.apache.velocity.exception.ResourceNotFoundException ;
+import org.apache.velocity.runtime.RuntimeConstants ;
+import org.apache.velocity.runtime.log.LogChute ;
+import org.apache.velocity.runtime.log.NullLogChute ;
+import org.slf4j.Logger ;
+import org.slf4j.LoggerFactory ;
+
+public class SimpleVelocity
+{
+    private static LogChute velocityLogChute = new NullLogChute() ;
+    private static Logger velocityLog = LoggerFactory.getLogger("Velocity");
+
+    /** Process a template */
+    public static void process(String base, String path, Writer out, 
Map<String, Object> params)
+    {
+        process(base, path, out, createContext(params)) ;
+    }
+    
+    /** Process a template */
+    public static void process(String base, String path, Writer out, 
VelocityContext context)
+    {
+        VelocityEngine velocity = new VelocityEngine() ;
+        // Turn off logging - catch exceptions and log ourselves
+        velocity.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM, 
velocityLogChute) ;
+        velocity.setProperty(RuntimeConstants.INPUT_ENCODING, "UTF-8") ;
+        velocity.setProperty(RuntimeConstants.FILE_RESOURCE_LOADER_PATH, base) 
;
+        velocity.init() ;
+        try {
+            Template temp = velocity.getTemplate(path) ;
+            temp.merge(context, out) ;
+            out.flush();
+        } 
+        catch (ResourceNotFoundException ex) { velocityLog.error("Resource not 
found: "+ex.getMessage()) ; }
+        catch (ParseErrorException ex)       { velocityLog.error("Parse error 
("+path+") : "+ex.getMessage()) ; }
+        catch (MethodInvocationException ex) { velocityLog.error("Method 
invocation exception ("+path+") : "+ex.getMessage()) ; }
+        catch (IOException ex)               { velocityLog.warn("IOException", 
ex) ; }
+    }
+    
+    public static VelocityContext createContext(Map<String, Object> params)
+    {
+        // Velocity requires a mutable map.
+        // Scala leads to immutable maps ... be safe and copy.
+        VelocityContext context = new VelocityContext() ;
+        for ( Map.Entry<String, Object> e : params.entrySet() )
+            context.put(e.getKey(), e.getValue()) ;
+        return context ;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/SimpleVelocityServlet.java
----------------------------------------------------------------------
diff --git 
a/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/SimpleVelocityServlet.java
 
b/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/SimpleVelocityServlet.java
new file mode 100644
index 0000000..8773eba
--- /dev/null
+++ 
b/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/SimpleVelocityServlet.java
@@ -0,0 +1,178 @@
+/**
+ * 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.io.Writer ;
+import java.util.Map ;
+
+import javax.servlet.http.HttpServlet ;
+import javax.servlet.http.HttpServletRequest ;
+import javax.servlet.http.HttpServletResponse ;
+
+import org.apache.velocity.VelocityContext ;
+import org.apache.velocity.app.VelocityEngine ;
+import org.apache.velocity.runtime.RuntimeConstants ;
+import org.apache.velocity.runtime.RuntimeServices ;
+import org.apache.velocity.runtime.log.LogChute ;
+import org.apache.velocity.runtime.log.NullLogChute ;
+import org.slf4j.Logger ;
+import org.slf4j.LoggerFactory ;
+
+
+/** Simple servlet that uses <a href="http://velocity.apache.org/";>Velocity</a>
+ *  to format pages.  It isolates the use of velocity by taking a 
configuration map. 
+ *  Use with a servlet mapping of "*.vm" or some such extension.
+ */
+public class SimpleVelocityServlet extends HttpServlet
+{
+    //private static Logger log = 
LoggerFactory.getLogger(SimpleVelocityServlet.class) ;
+    /* Velocity logging
+     * Instead of internal velocity logging, we catch the exceptions, 
+     * log the message ourselves. This gives a celaner log file without
+     * loosing information that the application could use.  
+     */
+    
+    private static Logger vlog = LoggerFactory.getLogger("Velocity") ;
+    private static LogChute velocityLog = new NullLogChute() ;
+    //private static LogChute velocityLog = new SimpleSLF4JLogChute(vlog) ;
+    
+    private String docbase ;
+    private VelocityEngine velocity ;
+    private String functionsName = null ;
+    private final Map<String, Object> datamodel ;
+    
+    public SimpleVelocityServlet(String base, Map<String, Object> datamodel)
+    {
+        this.docbase = base ;
+        this.datamodel = datamodel ;
+        velocity = new VelocityEngine();
+        // Turn off logging - catch exceptions and log ourselves
+        velocity.setProperty( RuntimeConstants.RUNTIME_LOG_LOGSYSTEM, 
velocityLog) ;
+        velocity.setProperty( RuntimeConstants.INPUT_ENCODING, "UTF-8" ) ;
+        velocity.setProperty( RuntimeConstants.FILE_RESOURCE_LOADER_PATH, 
base) ;
+        velocity.init();
+    }
+    
+    // See also 
+    @Override
+    public void doGet(HttpServletRequest req, HttpServletResponse resp)
+    {
+        process(req, resp) ;
+    }
+    
+    @Override
+    public void doPost(HttpServletRequest req, HttpServletResponse resp)
+    {
+        process(req, resp) ;
+    }
+
+    private void process(HttpServletRequest req, HttpServletResponse resp)
+    {
+        try
+        {
+            resp.setContentType("text/html") ;
+            resp.setCharacterEncoding("UTF-8") ;
+            Writer out = resp.getWriter() ;
+            String path = path(req) ;
+            VelocityContext vc = SimpleVelocity.createContext(datamodel) ;
+            vc.put("request", req) ;
+            SimpleVelocity.process(docbase, path, out, vc) ;
+        } catch (IOException ex)
+        {
+            vlog.warn("IOException", ex) ;
+        }
+    }
+    
+    private String path(HttpServletRequest request)
+    {     
+        String path = request.getPathInfo();
+        if (path != null) return path;
+        path = request.getServletPath();
+        if (path != null) return path;
+        return null ;
+    }
+
+    @Override
+    public String getServletInfo()
+    {
+        return "Lightweight Velocity Servlet";
+    }
+    
+    /** Velocity logger to SLF4J */ 
+    static class SimpleSLF4JLogChute implements LogChute
+    {
+        // Uusally for debugging only.
+        private Logger logger ;
+
+        SimpleSLF4JLogChute( Logger log )
+        {
+            this.logger = log ; 
+        }
+        
+        @Override
+        public void init(RuntimeServices rs) throws Exception
+        { }
+
+        @Override
+        public void log(int level, String message)
+        {
+            if ( logger == null ) return ;
+            switch(level)
+            {
+                case LogChute.TRACE_ID : logger.trace(message) ; return ;
+                case LogChute.DEBUG_ID : logger.debug(message) ; return ;
+                case LogChute.INFO_ID :  logger.info(message) ;  return ;
+                case LogChute.WARN_ID :  logger.warn(message) ;  return ;
+                case LogChute.ERROR_ID : logger.error(message) ; return ;
+            }
+        }
+
+        @Override
+        public void log(int level, String message, Throwable t)
+        {
+            if ( logger == null ) return ;
+            // Forget the stack trace - velcoity internal - long - unhelpful 
to application. 
+            t = null ;
+            switch (level)
+            {
+                case LogChute.TRACE_ID : logger.trace(message, t) ; return ;
+                case LogChute.DEBUG_ID : logger.debug(message, t) ; return ;
+                case LogChute.INFO_ID :  logger.info(message, t) ;  return ;
+                case LogChute.WARN_ID :  logger.warn(message, t) ;  return ;
+                case LogChute.ERROR_ID : logger.error(message, t) ; return ;
+            }
+        }
+
+        @Override
+        public boolean isLevelEnabled(int level)
+        {
+            switch(level)
+            {
+                case LogChute.TRACE_ID:   return logger.isTraceEnabled() ;
+                case LogChute.DEBUG_ID:   return logger.isDebugEnabled() ;
+                case LogChute.INFO_ID:    return logger.isInfoEnabled() ;
+                case LogChute.WARN_ID:    return logger.isWarnEnabled() ;
+                case LogChute.ERROR_ID:   return logger.isErrorEnabled() ;
+            }
+            return true ;
+        }
+    }
+}
+

Reply via email to