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/01/14 11:31:49 UTC

[47/93] [abbrv] [partial] jena git commit: Maven modules for Fuseki2

http://git-wip-us.apache.org/repos/asf/jena/blob/470ee4d7/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/async/AsyncTask.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/async/AsyncTask.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/async/AsyncTask.java
new file mode 100644
index 0000000..0b73f7e
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/async/AsyncTask.java
@@ -0,0 +1,114 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.fuseki.async;
+
+import static java.lang.String.format ;
+
+import java.util.concurrent.Callable ;
+
+import com.hp.hpl.jena.sparql.util.Utils ;
+
+import org.apache.jena.atlas.lib.InternalErrorException ;
+import org.apache.jena.atlas.logging.Log ;
+import org.apache.jena.fuseki.Fuseki ;
+import org.apache.jena.fuseki.server.DataService ;
+import org.slf4j.Logger ;
+
+/** An asynchronous task */ 
+public class AsyncTask implements Callable<Object>  
+{
+    private static Logger log = Fuseki.serverLog ; 
+    
+    private final Callable<Object> callable ;
+    private final AsyncPool pool ;
+
+    private final String displayName ;
+    private final DataService dataService ;
+
+    private String startPoint = null ;
+    private String finishPoint = null ;
+
+    private final String taskId ;
+
+    /*package*/ AsyncTask(Callable<Object> callable, 
+                          AsyncPool pool,
+                          String taskId,
+                          String displayName,
+                          DataService dataService ) {
+        this.callable = callable ;
+        this.pool = pool ;
+        this.taskId = taskId ; 
+        this.displayName = displayName ;
+        this.dataService = dataService ;
+    }
+
+    /** Unique task id */
+    public String getTaskId() { return taskId ; }
+    
+    /** Display name - no newlines */
+    public String displayName() { return displayName ; }
+    
+    public DataService getDataService() { return dataService ; }
+
+    private void start() {
+        if ( startPoint != null ) {
+            String msg = format("[Task %s] Async task has already been started", taskId) ;
+            Log.warn(Fuseki.serverLog, msg) ;
+            throw new InternalErrorException("Finish has already been called ["+getTaskId()+"]") ; 
+        }
+            
+        Fuseki.serverLog.info(format("[Task %s] starts : %s",taskId, displayName)) ;
+        startPoint = Utils.nowAsXSDDateTimeString() ;
+    }
+    
+    public void finish() {
+        if ( finishPoint != null ) {
+            String msg = format("[Task %s] Async task has already been finished", taskId) ;
+            Log.warn(Fuseki.serverLog, msg) ;
+            throw new InternalErrorException("Finish has already been called ["+getTaskId()+"]") ; 
+        }
+        finishPoint = Utils.nowAsXSDDateTimeString() ;
+        Fuseki.serverLog.info(format("[Task %s] finishes : %s",taskId, displayName)) ;
+    }
+    
+    @Override
+    public Object call() {
+        try {
+            start() ;
+            return callable.call() ;
+        }
+        catch (Exception ex) {
+            log.error("Async task threw an expection", ex) ;
+            return null ;
+        }
+        finally {
+            finish() ;
+            pool.finished(this) ;
+        }
+    }
+
+    public String getStartPoint() {
+        return startPoint ;
+    }
+
+    public String getFinishPoint() {
+        return finishPoint ;
+    }
+}
+

http://git-wip-us.apache.org/repos/asf/jena/blob/470ee4d7/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/authz/AuthorizationFilter403.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/authz/AuthorizationFilter403.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/authz/AuthorizationFilter403.java
new file mode 100644
index 0000000..ca4cd2e
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/authz/AuthorizationFilter403.java
@@ -0,0 +1,59 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.fuseki.authz;
+
+import java.io.IOException ;
+
+import javax.servlet.ServletRequest ;
+import javax.servlet.ServletResponse ;
+import javax.servlet.http.HttpServletResponse ;
+
+import org.apache.jena.web.HttpSC ;
+import org.apache.shiro.web.filter.authz.AuthorizationFilter ;
+import org.apache.shiro.web.util.WebUtils ;
+
+/** Specialise AuthorizationFilter to yield HTTP 403 on access denied */ 
+public abstract class AuthorizationFilter403 extends AuthorizationFilter
+{    
+    private String message ;
+
+    protected AuthorizationFilter403(String text)   { setMessage(text) ; }
+    protected AuthorizationFilter403()              { this(null) ; }
+    
+    /** Set the message used in HTTP 403 responses */
+    public void setMessage(String msg) { message = msg ; }
+    
+    public String getMessage() { return message ; }
+
+    @Override
+    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException {
+        HttpServletResponse httpResponse ;
+        try { httpResponse = WebUtils.toHttp(response); }
+        catch (ClassCastException ex) { 
+            // Not a HTTP Servlet operation
+            return super.onAccessDenied(request, response) ;
+        }
+        if ( message == null )
+            httpResponse.sendError(HttpSC.FORBIDDEN_403) ;
+        else
+            httpResponse.sendError(HttpSC.FORBIDDEN_403, message) ;
+        return false ;  // No further processing.
+    }
+}
+

http://git-wip-us.apache.org/repos/asf/jena/blob/470ee4d7/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/authz/DenyFilter.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/authz/DenyFilter.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/authz/DenyFilter.java
new file mode 100644
index 0000000..aac7ecd
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/authz/DenyFilter.java
@@ -0,0 +1,33 @@
+/**
+ * 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.authz;
+
+import javax.servlet.ServletRequest ;
+import javax.servlet.ServletResponse ;
+
+/** An authorization filter that always denies access and sends back HTTP 403 */
+public class DenyFilter extends AuthorizationFilter403 {
+
+    public DenyFilter() { super("Access denied") ; }
+
+    @Override
+    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
+        return false ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/470ee4d7/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/authz/LocalhostFilter.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/authz/LocalhostFilter.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/authz/LocalhostFilter.java
new file mode 100644
index 0000000..71de761
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/authz/LocalhostFilter.java
@@ -0,0 +1,62 @@
+/**
+ * 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.authz;
+
+import javax.servlet.ServletRequest ;
+import javax.servlet.ServletResponse ;
+
+import org.apache.shiro.web.filter.authz.PortFilter ;
+
+/**
+ * A Filter that can allow or deny access based on whether the
+ * the host that sent the request is the loopback address (AKA localhost).
+ * Use of the external IP address of the local machine does not permit access,
+ * only the loopback interface is authorized.
+ * Responds with HTTP 403 on any denied request.
+ * 
+ * Example:
+ * <pre>
+ * [main]
+ * localhost=org.apache.jena.fuseki.authz.LocalhostFilter
+ * ...
+ * [urls]
+ * /LocalFilesForLocalPeople/** = localhost
+ * </pre>
+ * @see PortFilter
+ */
+
+public class LocalhostFilter extends AuthorizationFilter403 {
+    
+    private static final String message = "Access denied : only localhost access allowed" ;   
+    
+    public LocalhostFilter() { super(message); } 
+
+    private static String LOCALHOST_IpV6 =  "0:0:0:0:0:0:0:1" ;
+    private static String LOCALHOST_IpV4 =  "127.0.0.1" ;   // Strictly, 127.*.*.*
+    
+    @Override
+    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
+        String remoteAddr = request.getRemoteAddr() ;
+        if ( LOCALHOST_IpV6.equals(remoteAddr) || LOCALHOST_IpV4.equals(remoteAddr) )
+            return true ;
+        return false ;
+    }
+}
+
+

http://git-wip-us.apache.org/repos/asf/jena/blob/470ee4d7/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/Builder.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/Builder.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/Builder.java
new file mode 100644
index 0000000..f8ec3cc
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/Builder.java
@@ -0,0 +1,149 @@
+/**
+ * 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.build;
+
+import static java.lang.String.format ;
+import static org.apache.jena.fuseki.FusekiLib.nodeLabel ;
+import static org.apache.jena.fuseki.FusekiLib.query ;
+import org.apache.jena.fuseki.Fuseki ;
+import org.apache.jena.fuseki.FusekiConfigException ;
+import org.apache.jena.fuseki.FusekiLib ;
+import org.apache.jena.fuseki.server.DataAccessPoint ;
+import org.apache.jena.fuseki.server.DataService ;
+import org.apache.jena.fuseki.server.Endpoint ;
+import org.apache.jena.fuseki.server.OperationName ;
+import org.slf4j.Logger ;
+
+import com.hp.hpl.jena.assembler.Assembler ;
+import com.hp.hpl.jena.datatypes.xsd.XSDDatatype ;
+import com.hp.hpl.jena.query.Dataset ;
+import com.hp.hpl.jena.query.QuerySolution ;
+import com.hp.hpl.jena.query.ResultSet ;
+import com.hp.hpl.jena.rdf.model.Literal ;
+import com.hp.hpl.jena.rdf.model.RDFNode ;
+import com.hp.hpl.jena.rdf.model.Resource ;
+import com.hp.hpl.jena.sparql.core.DatasetGraph ;
+import com.hp.hpl.jena.sparql.util.FmtUtils ;
+import com.hp.hpl.jena.tdb.TDB ;
+import com.hp.hpl.jena.vocabulary.RDF ;
+public class Builder
+{
+    private static Logger log = Fuseki.builderLog ;
+    
+    /** Build a DataAccessPoint, including DataServiceat Resource svc */
+    public static DataAccessPoint buildDataAccessPoint(Resource svc) {
+        RDFNode n = FusekiLib.getOne(svc, "fu:name") ;
+        if ( ! n.isLiteral() )
+            throw new FusekiConfigException("Not a literal for access point name: "+FmtUtils.stringForRDFNode(n));
+        Literal object = n.asLiteral() ;
+
+        if ( object.getDatatype() != null && ! object.getDatatype().equals(XSDDatatype.XSDstring) )
+            Fuseki.configLog.error(format("Service name '%s' is not a string", FmtUtils.stringForRDFNode(object)));
+        String name = object.getLexicalForm() ;
+        name = DataAccessPoint.canonical(name) ;
+
+        DataService dataService = Builder.buildDataService(svc) ;
+        DataAccessPoint dataAccess = new DataAccessPoint(name) ;
+        dataAccess.setDataService(dataService) ;
+        return dataAccess ;
+    }
+
+    /** Build a DatasetRef starting at Resource svc */
+    public static DataService buildDataService(Resource svc) {
+        log.info("Service: " + nodeLabel(svc)) ;
+        // DO REAL WORK
+        Resource datasetDesc = ((Resource)getOne(svc, "fu:dataset")) ;
+        
+        // Check if it is in the model.
+        if ( !datasetDesc.hasProperty(RDF.type) )
+            throw new FusekiConfigException("No rdf:type for dataset " + nodeLabel(datasetDesc)) ;
+        Dataset ds = (Dataset)Assembler.general.open(datasetDesc) ;
+        // In case the assembler included ja:contents
+        TDB.sync(ds) ;
+        DataService dataService = new DataService(null, ds.asDatasetGraph()) ;
+        addServiceEP(dataService, OperationName.Query,  svc,    "fu:serviceQuery") ;
+        addServiceEP(dataService, OperationName.Update, svc,    "fu:serviceUpdate") ;
+        addServiceEP(dataService, OperationName.Upload, svc,    "fu:serviceUpload") ;
+        addServiceEP(dataService, OperationName.GSP_R,  svc,    "fu:serviceReadGraphStore") ;
+        addServiceEP(dataService, OperationName.GSP,    svc,    "fu:serviceReadWriteGraphStore") ;
+        
+        if ( ! dataService.getOperation(OperationName.GSP).isEmpty() )
+            dataService.addEndpoint(OperationName.Quads, "") ;
+        else if ( ! dataService.getOperation(OperationName.GSP_R).isEmpty() )
+            dataService.addEndpoint(OperationName.Quads, "") ;
+        
+        // XXX 
+//        // Extract timeout overriding configuration if present.
+//        if ( svc.hasProperty(FusekiVocab.pAllowTimeoutOverride) ) {
+//            sDesc.allowTimeoutOverride = svc.getProperty(FusekiVocab.pAllowTimeoutOverride).getObject().asLiteral().getBoolean() ;
+//            if ( svc.hasProperty(FusekiVocab.pMaximumTimeoutOverride) ) {
+//                sDesc.maximumTimeoutOverride = (int)(svc.getProperty(FusekiVocab.pMaximumTimeoutOverride).getObject().asLiteral().getFloat() * 1000) ;
+//            }
+//        }
+
+        return dataService ;
+    }
+    
+    /** Build a DataService starting at Resource svc */
+    public static DataService buildDataService(DatasetGraph dsg, boolean allowUpdate) {
+        DataService dataService = new DataService(null, dsg) ;
+        addServiceEP(dataService, OperationName.Query, "query") ;
+        addServiceEP(dataService, OperationName.Query, "sparql") ;
+        if ( ! allowUpdate ) {
+            addServiceEP(dataService, OperationName.Quads, "quads") ;
+            addServiceEP(dataService, OperationName.GSP_R, "data") ;
+            return dataService ;
+        }
+        addServiceEP(dataService, OperationName.GSP,    "data") ;
+        addServiceEP(dataService, OperationName.Update, "update") ;
+        addServiceEP(dataService, OperationName.Upload, "upload") ;
+        addServiceEP(dataService, OperationName.Quads,  "") ;
+        return dataService ;
+    }
+
+    private static void addServiceEP(DataService dataService, OperationName opName, String epName) {
+        dataService.addEndpoint(opName, epName) ; 
+    }
+
+    public static RDFNode getOne(Resource svc, String property) {
+        String ln = property.substring(property.indexOf(':') + 1) ;
+        ResultSet rs = FusekiLib.query("SELECT * { ?svc " + property + " ?x}", svc.getModel(), "svc", svc) ;
+        if ( !rs.hasNext() )
+            throw new FusekiConfigException("No " + ln + " for service " + FusekiLib.nodeLabel(svc)) ;
+        RDFNode x = rs.next().get("x") ;
+        if ( rs.hasNext() )
+            throw new FusekiConfigException("Multiple " + ln + " for service " + FusekiLib.nodeLabel(svc)) ;
+        return x ;
+    }
+    
+
+    private static void addServiceEP(DataService dataService, OperationName opName, Resource svc, String property) {
+        ResultSet rs = query("SELECT * { ?svc " + property + " ?ep}", svc.getModel(), "svc", svc) ;
+        for ( ; rs.hasNext() ; ) {
+            QuerySolution soln = rs.next() ;
+            String epName = soln.getLiteral("ep").getLexicalForm() ;
+            Endpoint operation = new Endpoint(opName, epName) ;
+            addServiceEP(dataService, opName, epName); 
+            //log.info("  " + opName.name + " = " + dataAccessPoint.getName() + "/" + epName) ;
+        }
+    }
+
+
+}
+

http://git-wip-us.apache.org/repos/asf/jena/blob/470ee4d7/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/DataServiceDesc.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/DataServiceDesc.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/DataServiceDesc.java
new file mode 100644
index 0000000..2b85c99
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/DataServiceDesc.java
@@ -0,0 +1,107 @@
+/**
+ * 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.build;
+
+import java.io.StringReader ;
+import java.util.HashMap ;
+import java.util.Map ;
+
+import org.apache.jena.fuseki.FusekiConfigException ;
+import org.apache.jena.fuseki.server.DataService ;
+import org.apache.jena.fuseki.server.FusekiServer ;
+import org.apache.jena.fuseki.server.FusekiVocab ;
+import org.apache.jena.riot.Lang ;
+import org.apache.jena.riot.RDFDataMgr ;
+import org.apache.jena.riot.RDFLanguages ;
+
+import com.hp.hpl.jena.rdf.model.Model ;
+import com.hp.hpl.jena.rdf.model.ModelFactory ;
+import com.hp.hpl.jena.rdf.model.Resource ;
+import com.hp.hpl.jena.sparql.util.FmtUtils ;
+import com.hp.hpl.jena.sparql.util.TypeNotUniqueException ;
+import com.hp.hpl.jena.sparql.util.graph.GraphUtils ;
+
+// Check whether this is used or needed
+public abstract class DataServiceDesc
+{
+    public static DataServiceDesc createFromTemplate(String templateFile, String dbName) {
+        Map<String, String> params = new HashMap<>() ;
+        params.put(Template.NAME, dbName) ;
+        FusekiServer.addGlobals(params); 
+        String template = TemplateFunctions.templateFile(templateFile, params) ;
+        Lang lang = RDFLanguages.filenameToLang(templateFile, Lang.TTL) ;
+        StringReader sr = new StringReader(template) ;
+        return create(sr, lang) ;
+    }
+    
+    public static DataServiceDesc create(StringReader strReader, Lang lang ) {
+        Model model = ModelFactory.createDefaultModel() ;
+        RDFDataMgr.read(model, strReader, "http://base/", lang) ;
+        Resource root ;
+        try {
+            root = GraphUtils.findRootByType(model, FusekiVocab.fusekiService) ;
+            if ( root == null )
+                throw new FusekiConfigException("No root of type "
+                    + FmtUtils.stringForResource(FusekiVocab.fusekiService) + "found") ;
+        } catch (TypeNotUniqueException ex) {
+            throw new FusekiConfigException("Multiple items of type: " + FusekiVocab.fusekiService) ;
+        }
+        return new DataServiceDescResource(root) ;
+    }
+
+    public static DataServiceDesc create(DataService dataService) {
+        return new DataServiceDescPrebuilt(dataService) ;
+    }
+    
+    //public abstract Resource getResource() ;
+
+    public abstract DataService build() ;
+//    public abstract void unbuild() ;
+
+
+    private static class DataServiceDescResource extends DataServiceDesc {
+        protected Resource resource ; 
+
+        protected DataServiceDescResource(Resource resource) {
+            this.resource = resource ;
+        }
+
+        public Resource getResource() { return resource ; }
+
+        @Override
+        public DataService build() {
+            return Builder.buildDataService(resource) ;
+        }
+    }
+    
+    private static class DataServiceDescPrebuilt extends DataServiceDesc {
+
+        private DataService dataService ;
+
+        protected DataServiceDescPrebuilt(DataService dataService) {
+            this.dataService = dataService ;
+        }
+
+        @Override
+        public DataService build() {
+            return dataService ;
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/470ee4d7/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/FusekiConfig.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/FusekiConfig.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/FusekiConfig.java
new file mode 100644
index 0000000..10319a4
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/FusekiConfig.java
@@ -0,0 +1,261 @@
+/*
+ * 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.build ;
+
+import java.io.File ;
+import java.io.FilenameFilter ;
+import java.lang.reflect.Method ;
+import java.util.ArrayList ;
+import java.util.Collections ;
+import java.util.List ;
+
+import org.apache.jena.atlas.iterator.Iter ;
+import org.apache.jena.atlas.lib.StrUtils ;
+import org.apache.jena.fuseki.Fuseki ;
+import org.apache.jena.fuseki.FusekiConfigException ;
+import org.apache.jena.fuseki.FusekiLib ;
+import org.apache.jena.fuseki.server.DataAccessPoint ;
+import org.apache.jena.fuseki.server.DatasetStatus ;
+import org.apache.jena.fuseki.server.FusekiVocab ;
+import org.apache.jena.fuseki.server.SystemState ;
+import org.apache.jena.riot.RDFDataMgr ;
+import org.slf4j.Logger ;
+
+import com.hp.hpl.jena.assembler.JA ;
+import com.hp.hpl.jena.query.Dataset ;
+import com.hp.hpl.jena.query.QuerySolution ;
+import com.hp.hpl.jena.query.ResultSet ;
+import com.hp.hpl.jena.rdf.model.* ;
+import com.hp.hpl.jena.sparql.core.assembler.AssemblerUtils ;
+import com.hp.hpl.jena.update.UpdateAction ;
+import com.hp.hpl.jena.update.UpdateFactory ;
+import com.hp.hpl.jena.update.UpdateRequest ;
+import com.hp.hpl.jena.vocabulary.RDF ;
+
+public class FusekiConfig {
+    static { Fuseki.init() ; }
+    
+    private static Logger log = Fuseki.configLog ;
+    
+    private static FilenameFilter visibleFiles = 
+        new FilenameFilter() {
+        @Override
+        public boolean accept(File dir, String name) {
+            if ( name.startsWith(".") )
+                return false ;
+            File f = new File(dir, name) ;
+            return f.isFile() ;
+        }
+    } ;
+    
+    /** Has side effects in server setup */
+    public static List<DataAccessPoint> readConfigFile(String filename) {
+        // Old-style config file.
+        Model model = RDFDataMgr.loadModel(filename) ;
+        if ( model.size() == 0 )
+            return Collections.emptyList() ;
+        additionalRDF(model) ;
+        server(model) ;
+        return servicesAndDatasets(model) ;
+    }
+
+    private static void server(Model model) {
+        // Find one server.
+        List<Resource> servers = getByType(FusekiVocab.tServer, model) ;
+        if ( servers.size() == 0 )
+            return ; 
+        if ( servers.size() > 1 )
+            throw new FusekiConfigException(servers.size()
+                                            + " servers found (must be exactly one in a configuration file)") ;
+        // ---- Server
+        Resource server = servers.get(0) ;
+        processServer(server) ;
+    }
+
+    private static void processServer(Resource server) {
+        // Global, currently.
+        AssemblerUtils.setContext(server, Fuseki.getContext()) ;
+
+        StmtIterator sIter = server.listProperties(JA.loadClass) ;
+        for ( ; sIter.hasNext() ; ) {
+            Statement s = sIter.nextStatement() ;
+            RDFNode rn = s.getObject() ;
+            String className = null ;
+            if ( rn instanceof Resource ) {
+                String uri = ((Resource)rn).getURI() ;
+                if ( uri == null ) {
+                    log.warn("Blank node for class to load") ;
+                    continue ;
+                }
+                String javaScheme = "java:" ;
+                if ( !uri.startsWith(javaScheme) ) {
+                    log.warn("Class to load is not 'java:': " + uri) ;
+                    continue ;
+                }
+                className = uri.substring(javaScheme.length()) ;
+            }
+            if ( rn instanceof Literal )
+                className = ((Literal)rn).getLexicalForm() ;
+            /* Loader. */loadAndInit(className) ;
+        }
+    }
+    
+    private static List<DataAccessPoint> servicesAndDatasets(Model model) {
+        // Old style configuration file : server to services.
+        // ---- Services
+        ResultSet rs = FusekiLib.query("SELECT * { ?s fu:services [ list:member ?member ] }", model) ;
+        // If the old config.ttl file becomes just the server configuration file,
+        // then don't warn here.
+//        if ( !rs.hasNext() )
+//            log.warn("No services found") ;
+
+        List<DataAccessPoint> accessPoints = new ArrayList<>() ;
+
+        for ( ; rs.hasNext() ; ) {
+            QuerySolution soln = rs.next() ;
+            Resource svc = soln.getResource("member") ;
+            DataAccessPoint acc = Builder.buildDataAccessPoint(svc) ;
+            accessPoints.add(acc) ;
+        }
+        return accessPoints ;
+    }
+    
+    private static void loadAndInit(String className) {
+        try {
+            Class<? > classObj = Class.forName(className) ;
+            log.info("Loaded " + className) ;
+            Method initMethod = classObj.getMethod("init") ;
+            initMethod.invoke(null) ;
+        }
+        catch (ClassNotFoundException ex) {
+            log.warn("Class not found: " + className) ;
+        }
+        catch (Exception e) {
+            throw new FusekiConfigException(e) ;
+        }
+    }
+    
+    // XXX Move to utils
+    private static Model additionalRDF(Model m) {
+        SystemState.init$();        // Why? mvn jetty:run-war
+        String x1 = StrUtils.strjoinNL
+            ( SystemState.PREFIXES, 
+              "INSERT                    { [] ja:loadClass 'com.hp.hpl.jena.tdb.TDB' }",
+              "WHERE { FILTER NOT EXISTS { [] ja:loadClass 'com.hp.hpl.jena.tdb.TDB' } }"
+             ) ;
+        String x2 = StrUtils.strjoinNL
+            (SystemState.PREFIXES,
+             "INSERT DATA {",
+             "   tdb:DatasetTDB  rdfs:subClassOf  ja:RDFDataset .",
+             "   tdb:GraphTDB    rdfs:subClassOf  ja:Model .",
+             "}" 
+             ) ;
+        execute(m, x1) ;
+        execute(m, x2) ;
+        return m ;
+    }
+
+    private static void execute(Model m, String x) {
+        UpdateRequest req = UpdateFactory.create(x) ;
+        UpdateAction.execute(req, m);
+    }
+
+    // XXX Move to a library
+    private static List<Resource> getByType(Resource type, Model m) {
+        ResIterator rIter = m.listSubjectsWithProperty(RDF.type, type) ;
+        return Iter.toList(rIter) ;
+    }
+    
+    // ---- Directory of assemblers
+    
+    /** Read service descriptions in the given directory */ 
+    public static List<DataAccessPoint> readConfigurationDirectory(String dir) {
+        List<DataAccessPoint> dataServiceRef = new ArrayList<>() ;
+        File d = new File(dir) ;
+        String[] aFiles = d.list(visibleFiles) ;
+        if ( aFiles == null ) {
+            log.warn("Not found: directory for assembler files for services: '"+dir+"'") ;
+            return Collections.emptyList() ;
+        }
+        for ( String assemFile : aFiles ) {
+            Model m = RDFDataMgr.loadModel(assemFile) ;
+            DataAccessPoint acc = readConfiguration(m) ; 
+            dataServiceRef.add(acc) ;
+        }
+
+        return dataServiceRef ;
+    }
+
+    private static DataAccessPoint readConfiguration(Model m) {
+        additionalRDF(m) ;
+        List<Resource> services = getByType(FusekiVocab.fusekiService, m) ; 
+
+        if ( services.size() == 0 ) {
+            log.error("No services found") ;
+            throw new FusekiConfigException() ;
+        }
+
+        // Remove?
+        if ( services.size() > 1 ) {
+            log.error("Multiple services found") ;
+            throw new FusekiConfigException() ;
+        }
+
+        Resource service = services.get(0) ;
+        DataAccessPoint acc = Builder.buildDataAccessPoint(service) ; 
+        return acc ;
+    }
+
+    // ---- System database
+    /** Read the system database */
+    public static List<DataAccessPoint> readSystemDatabase(Dataset ds) {
+        String qs = StrUtils.strjoinNL
+            (SystemState.PREFIXES ,
+             "SELECT * {" ,
+             "  GRAPH ?g {",
+             "     ?s fu:name ?name ;" ,
+             "        fu:status ?status ." ,
+             "  }",
+             "}"
+             ) ;
+        
+        List<DataAccessPoint> refs = new ArrayList<>() ;
+        
+        ResultSet rs = FusekiLib.query(qs, ds) ;
+        
+//        ResultSetFormatter.out(rs); 
+//        ((ResultSetRewindable)rs).reset();
+        
+        for ( ; rs.hasNext() ; ) {
+            QuerySolution row = rs.next() ;
+            Resource s = row.getResource("s") ;
+            Resource g = row.getResource("g") ;
+            Resource rStatus = row.getResource("status") ;
+            //String name = row.getLiteral("name").getLexicalForm() ;
+            DatasetStatus status = DatasetStatus.status(rStatus) ;
+            
+            Model m = ds.getNamedModel(g.getURI()) ;
+            // Rebase the resoure of the service description to the containing graph.
+            Resource svc = m.wrapAsResource(s.asNode()) ;
+            DataAccessPoint ref = Builder.buildDataAccessPoint(svc) ;
+            refs.add(ref) ;
+        }
+        return refs ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/470ee4d7/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/Template.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/Template.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/Template.java
new file mode 100644
index 0000000..55a449e
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/Template.java
@@ -0,0 +1,52 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.fuseki.build;
+
+import java.nio.file.Path ;
+
+import org.apache.jena.fuseki.server.FusekiEnv ;
+
+public class Template
+{
+    public static Path getPath(String templateName) {
+        return FusekiEnv.FUSEKI_BASE.resolve(templateName) ;
+    }
+    
+    public static final String templateDir          = "templates" ;
+    public static final String templateMemFN        = templateDir+"/config-mem" ;
+    public static final String templateTDBFN        = templateDir+"/config-tdb" ;
+    public static final String templateTDBMemFN     = templateDir+"/config-tdb-mem" ; 
+    public static final String templateTDBDirFN     = templateDir+"/config-tdb-dir" ;
+    public static final String templateServiceFN    = templateDir+"/config-service" ;       // Dummy used by dataset-less service.
+    
+    // Template may be in a resources area of a jar file so you can't do a directory listing.
+    public static final String[] templateNames = {
+        templateMemFN ,
+        templateTDBFN ,
+        templateTDBMemFN ,
+        templateTDBDirFN ,
+        templateServiceFN
+    } ;
+    
+    public static final String NAME = "NAME" ;
+    public static final String DATA = "DATA" ;
+    public static final String DIR =  "DIR" ;
+    
+}
+

http://git-wip-us.apache.org/repos/asf/jena/blob/470ee4d7/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/TemplateFunctions.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/TemplateFunctions.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/TemplateFunctions.java
new file mode 100644
index 0000000..41c21c5
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/build/TemplateFunctions.java
@@ -0,0 +1,68 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.fuseki.build;
+
+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.fuseki.Fuseki ;
+
+import com.hp.hpl.jena.util.FileUtils ;
+
+public class TemplateFunctions
+{
+    /** Read in a template from a file, substitute for {NAME} and return the string. */
+    public static String templateFile(String templateName, Map<String, String> params) {
+        String templateFilename = Template.getPath(templateName).toString() ;
+        String template ;
+        try { template = FileUtils.readWholeFileAsUTF8(templateFilename) ; }
+        catch (IOException ex) { 
+            Fuseki.serverLog.error("File not found: "+templateFilename);
+            IO.exception(ex); return null ;
+        }
+        return templateString(template, params) ;
+    }
+    
+    /** Read a template file, substitute for {NAME} and return the model. */
+    public static String templateResource(String resourceName, Map<String, String> params) {
+        String template ;
+        try {
+            InputStream in = TemplateFunctions.class.getClassLoader().getResourceAsStream(resourceName) ;
+            if ( in == null )
+                Fuseki.serverLog.error("Resource not found: "+resourceName);
+            template = FileUtils.readWholeFileAsUTF8(in) ;
+        }
+        catch (IOException ex) { 
+            Fuseki.serverLog.error("Error reading resource: "+resourceName);
+            IO.exception(ex); return null ;
+        }
+        return templateString(template, params) ;
+    }
+
+    /** Create a template from a String */ 
+    public static String templateString(String template, Map<String, String> params) {
+        for ( Entry<String, String> e : params.entrySet() ) {
+            template = template.replaceAll("\\{"+e.getKey()+"\\}", e.getValue()) ;
+        }
+        return template ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/470ee4d7/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/cmd/FusekiCmd.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/cmd/FusekiCmd.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/cmd/FusekiCmd.java
new file mode 100644
index 0000000..a7cb0d4
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/cmd/FusekiCmd.java
@@ -0,0 +1,339 @@
+/*
+ * 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.cmd ;
+
+import java.util.List ;
+
+import org.apache.jena.atlas.lib.FileOps ;
+import org.apache.jena.fuseki.Fuseki ;
+import org.apache.jena.fuseki.FusekiLogging ;
+import org.apache.jena.fuseki.build.Template ;
+import org.apache.jena.fuseki.jetty.JettyServerConfig ;
+import org.apache.jena.fuseki.jetty.JettyFuseki ;
+import org.apache.jena.fuseki.server.FusekiServerListener ;
+import org.apache.jena.fuseki.server.ServerInitialConfig ;
+import org.apache.jena.riot.Lang ;
+import org.apache.jena.riot.RDFDataMgr ;
+import org.apache.jena.riot.RDFLanguages ;
+import org.slf4j.Logger ;
+import arq.cmd.CmdException ;
+import arq.cmdline.ArgDecl ;
+import arq.cmdline.CmdARQ ;
+import arq.cmdline.ModDatasetAssembler ;
+
+import com.hp.hpl.jena.query.ARQ ;
+import com.hp.hpl.jena.query.Dataset ;
+import com.hp.hpl.jena.sparql.core.DatasetGraphFactory ;
+import com.hp.hpl.jena.tdb.TDB ;
+import com.hp.hpl.jena.tdb.sys.Names ;
+import com.hp.hpl.jena.tdb.transaction.TransactionManager ;
+
+public class FusekiCmd {
+    // This allows us to set logging before calling FusekiCmdInner
+    // FusekiCmdInner inherits from CmdMain which statically sets logging.
+    // By java classloading, super class statics run before the 
+    // statics of a class are run.
+
+    static {
+        FusekiLogging.setLogging() ;
+    }
+
+    static public void main(String... argv) {
+        FusekiCmdInner.innerMain(argv);
+    }
+    
+    static class FusekiCmdInner extends CmdARQ {
+        // --mgt. --mgtPort  :: Legacy.
+        private static ArgDecl  argMgt          = new ArgDecl(ArgDecl.NoValue, "mgt") ;
+        private static ArgDecl  argMgtPort      = new ArgDecl(ArgDecl.HasValue, "mgtPort", "mgtport") ;
+        
+        // --home :: Legacy - do not use.
+        private static ArgDecl  argHome         = new ArgDecl(ArgDecl.HasValue, "home") ;
+        private static ArgDecl  argPages        = new ArgDecl(ArgDecl.HasValue, "pages") ;
+
+        private static ArgDecl  argMem          = new ArgDecl(ArgDecl.NoValue, "mem") ;
+        private static ArgDecl  argAllowUpdate  = new ArgDecl(ArgDecl.NoValue, "update", "allowUpdate") ;
+        private static ArgDecl  argFile         = new ArgDecl(ArgDecl.HasValue, "file") ;
+        private static ArgDecl  argMemTDB       = new ArgDecl(ArgDecl.NoValue, "memtdb", "memTDB") ;
+        private static ArgDecl  argTDB          = new ArgDecl(ArgDecl.HasValue, "loc", "location") ;
+        private static ArgDecl  argPort         = new ArgDecl(ArgDecl.HasValue, "port") ;
+        private static ArgDecl  argLocalhost    = new ArgDecl(ArgDecl.NoValue, "localhost", "local") ;
+        private static ArgDecl  argTimeout      = new ArgDecl(ArgDecl.HasValue, "timeout") ;
+        private static ArgDecl  argFusekiConfig = new ArgDecl(ArgDecl.HasValue, "config", "conf") ;
+        private static ArgDecl  argJettyConfig  = new ArgDecl(ArgDecl.HasValue, "jetty-config") ;
+        private static ArgDecl  argGZip         = new ArgDecl(ArgDecl.HasValue, "gzip") ;
+
+        // Deprecated.  Use shiro.
+        private static ArgDecl  argBasicAuth    = new ArgDecl(ArgDecl.HasValue, "basic-auth") ;
+
+        // private static ModLocation modLocation = new ModLocation() ;
+        private static ModDatasetAssembler modDataset      = new ModDatasetAssembler() ;
+
+        // fuseki [--mem|--desc assembler.ttl] [--port PORT] **** /datasetURI
+
+        static public void innerMain(String... argv) {
+            // Just to make sure ...
+            ARQ.init() ;
+            TDB.init() ;
+            Fuseki.init() ;
+            new FusekiCmdInner(argv).mainRun() ;
+        }
+
+        private JettyServerConfig   jettyServerConfig = new JettyServerConfig() ;
+        {
+            jettyServerConfig.port = 3030 ;
+            jettyServerConfig.contextPath = "/" ;
+            jettyServerConfig.jettyConfigFile = null ;
+            jettyServerConfig.pages = Fuseki.PagesStatic ;
+            jettyServerConfig.enableCompression = true ;
+            jettyServerConfig.verboseLogging = false ;
+        }
+
+        private ServerInitialConfig cmdLineDataset  = new ServerInitialConfig() ;
+
+        public FusekiCmdInner(String... argv) {
+            super(argv) ;
+
+            if ( false )
+                // Consider ...
+                TransactionManager.QueueBatchSize = TransactionManager.QueueBatchSize / 2 ;
+
+            getUsage().startCategory("Fuseki") ;
+            addModule(modDataset) ;
+            add(argMem, "--mem", "Create an in-memory, non-persistent dataset for the server") ;
+            add(argFile, "--file=FILE",
+                "Create an in-memory, non-persistent dataset for the server, initialised with the contents of the file") ;
+            add(argTDB, "--loc=DIR", "Use an existing TDB database (or create if does not exist)") ;
+            add(argMemTDB, "--memTDB", "Create an in-memory, non-persistent dataset using TDB (testing only)") ;
+            add(argPort, "--port", "Listen on this port number") ;
+            add(argPages, "--pages=DIR", "Set of pages to serve as static content") ;
+            // Set via jetty config file.
+            add(argLocalhost, "--localhost", "Listen only on the localhost interface") ;
+            add(argTimeout, "--timeout=", "Global timeout applied to queries (value in ms) -- format is X[,Y] ") ;
+            add(argAllowUpdate, "--update", "Allow updates (via SPARQL Update and SPARQL HTTP Update)") ;
+            add(argFusekiConfig, "--config=", "Use a configuration file to determine the services") ;
+            add(argJettyConfig, "--jetty-config=FILE", "Set up the server (not services) with a Jetty XML file") ;
+            add(argBasicAuth) ;
+            //add(argMgt,     "--mgt",          "Enable the management commands") ;
+            add(argMgt) ; // Legacy
+            add(argMgtPort) ; // Legacy
+            //add(argMgtPort, "--mgtPort=port", "Port for management optations") ;
+            //add(argHome, "--home=DIR", "Root of Fuseki installation (overrides environment variable FUSEKI_HOME)") ;
+            add(argGZip, "--gzip=on|off", "Enable GZip compression (HTTP Accept-Encoding) if request header set") ;
+
+            //add(argUber) ;
+            // add(argGSP) ;
+
+            super.modVersion.addClass(TDB.class) ;
+            super.modVersion.addClass(Fuseki.class) ;
+        }
+
+        static String argUsage = "[--config=FILE] [--mem|--desc=AssemblerFile|--file=FILE] [--port PORT] /DatasetPathName" ;
+
+        @Override
+        protected String getSummary() {
+            return getCommandName() + " " + argUsage ;
+        }
+
+        @Override
+        protected void processModulesAndArgs() {
+            int x = 0 ;
+
+            Logger log = Fuseki.serverLog ;
+
+            if ( contains(argFusekiConfig) )
+                cmdLineDataset.fusekiConfigFile = getValue(argFusekiConfig) ;
+
+            ArgDecl assemblerDescDecl = new ArgDecl(ArgDecl.HasValue, "desc", "dataset") ;
+
+            // ---- Datasets
+
+            if ( contains(argMem) )             
+                x++ ;
+            if ( contains(argFile) )
+                x++ ;
+            if ( contains(assemblerDescDecl) )
+                x++ ;
+            if ( contains(argTDB) )
+                x++ ;
+            if ( contains(argMemTDB) )
+                x++ ;
+
+            if ( cmdLineDataset.fusekiConfigFile != null ) {
+                if ( x >= 1 )
+                    throw new CmdException("Dataset specified on the command line but a configuration file also given.") ;
+            } else {
+                // No configuration file.  0 or 1 legal.
+                if ( x > 1 )
+                    throw new CmdException("Multiple ways providing a dataset. Only one of --mem, --file, --loc or --desc") ;
+            }
+
+            if ( contains(argMem) ) {
+                log.info("Dataset: in-memory") ;
+                cmdLineDataset = new ServerInitialConfig() ;
+                cmdLineDataset.templateFile = Template.templateMemFN ; 
+            }
+
+            if ( contains(argFile) ) {
+                String filename = getValue(argFile) ;
+                log.info("Dataset: in-memory: load file: " + filename) ;
+                if ( !FileOps.exists(filename) )
+                    throw new CmdException("File not found: " + filename) ;
+
+                // Directly populate the dataset.
+                cmdLineDataset = new ServerInitialConfig() ;
+                cmdLineDataset.dsg = DatasetGraphFactory.createMem() ;
+
+                // INITIAL DATA.
+                Lang language = RDFLanguages.filenameToLang(filename) ;
+                if ( language == null )
+                    throw new CmdException("Can't guess language for file: " + filename) ;
+                RDFDataMgr.read(cmdLineDataset.dsg, filename) ;
+            }
+
+            if ( contains(argMemTDB) ) {
+                //log.info("TDB dataset: in-memory") ;
+                cmdLineDataset = new ServerInitialConfig() ;
+                cmdLineDataset.templateFile = Template.templateTDBMemFN ;
+                cmdLineDataset.params.put(Template.DIR, Names.memName) ;
+            }
+
+            if ( contains(argTDB) ) {
+                cmdLineDataset = new ServerInitialConfig() ;
+                cmdLineDataset.templateFile = Template.templateTDBDirFN ;
+
+                String dir = getValue(argTDB) ;
+                cmdLineDataset.params.put(Template.DIR, dir) ;
+            }
+
+            // Otherwise
+            if ( contains(assemblerDescDecl) ) {
+                log.info("Dataset from assembler") ;
+                // Need to add service details.
+                Dataset ds = modDataset.createDataset() ;
+                //cmdLineDataset.dsg = ds.asDatasetGraph() ;
+            }
+
+            if ( cmdLineDataset != null ) {
+                if ( getPositional().size() > 1 )
+                    throw new CmdException("Multiple dataset path names given") ;
+                if ( getPositional().size() != 0 ) {
+                    cmdLineDataset.datasetPath = getPositionalArg(0) ;
+                    if ( cmdLineDataset.datasetPath.length() > 0 && !cmdLineDataset.datasetPath.startsWith("/") )
+                        throw new CmdException("Dataset path name must begin with a /: " + cmdLineDataset.datasetPath) ;
+                    cmdLineDataset.allowUpdate = contains(argAllowUpdate) ;
+                    if ( ! cmdLineDataset.allowUpdate )
+                        Fuseki.serverLog.info("Running in read-only mode for "+cmdLineDataset.datasetPath) ;
+                    // Include the dataset name as NAME for any templates.
+                    cmdLineDataset.params.put(Template.NAME,  cmdLineDataset.datasetPath) ;
+                }
+            }
+
+            // ---- Jetty server
+            if ( contains(argBasicAuth) )
+                Fuseki.configLog.warn("--basic-auth ignored: Use Apache Shiro security - see shiro.ini") ;
+
+            if ( contains(argPort) ) {
+                String portStr = getValue(argPort) ;
+                try {
+                    jettyServerConfig.port = Integer.parseInt(portStr) ;
+                } catch (NumberFormatException ex) {
+                    throw new CmdException(argPort.getKeyName() + " : bad port number: " + portStr) ;
+                }
+            }
+
+            if ( contains(argMgt) )
+                Fuseki.configLog.warn("Fuseki v2: Management functions are always enabled.  --mgt not needed.") ; 
+            
+            if ( contains(argMgtPort) )
+                Fuseki.configLog.warn("Fuseki v2: Management functions are always on the same port as the server.  --mgtPort ignored.") ; 
+
+//            if ( contains(argMgt) ) {
+//                jettyServerConfig.mgtPort = 0 ;
+//                if (  contains(argMgtPort) ) {
+//                    String mgtPortStr = getValue(argMgtPort) ;
+//                    try {
+//                        jettyServerConfig.mgtPort = Integer.parseInt(mgtPortStr) ;
+//                    } catch (NumberFormatException ex) {
+//                        throw new CmdException("--"+argMgtPort.getKeyName() + " : bad port number: " + mgtPortStr) ;
+//                    }
+//                }
+//            }
+
+            if ( contains(argLocalhost) )
+                jettyServerConfig.loopback = true ;
+
+            if ( contains(argTimeout) ) {
+                String str = getValue(argTimeout) ;
+                ARQ.getContext().set(ARQ.queryTimeout, str) ;
+            }
+
+            if ( contains(argJettyConfig) ) {
+                jettyServerConfig.jettyConfigFile = getValue(argJettyConfig) ;
+                if ( !FileOps.exists(jettyServerConfig.jettyConfigFile) )
+                    throw new CmdException("No such file: " + jettyServerConfig.jettyConfigFile) ;
+            }
+
+            if ( contains(argBasicAuth) ) {
+                jettyServerConfig.authConfigFile = getValue(argBasicAuth) ;
+                if ( !FileOps.exists(jettyServerConfig.authConfigFile) )
+                    throw new CmdException("No such file: " + jettyServerConfig.authConfigFile) ;
+            }
+
+            if ( contains(argHome) ) {
+                Fuseki.configLog.warn("--home ignored (use enviroment variables $FUSEKI_HOME and $FUSEKI_BASE)") ;
+//                List<String> args = super.getValues(argHome) ;
+//                homeDir = args.get(args.size() - 1) ;
+            }
+
+            if ( contains(argPages) ) {
+                List<String> args = super.getValues(argPages) ;
+                jettyServerConfig.pages = args.get(args.size() - 1) ;
+            }
+
+            if ( contains(argGZip) ) {
+                if ( !hasValueOfTrue(argGZip) && !hasValueOfFalse(argGZip) )
+                    throw new CmdException(argGZip.getNames().get(0) + ": Not understood: " + getValue(argGZip)) ;
+                jettyServerConfig.enableCompression = super.hasValueOfTrue(argGZip) ;
+            }
+        }
+
+        private static String sort_out_dir(String path) {
+            path.replace('\\', '/') ;
+            if ( !path.endsWith("/") )
+                path = path + "/" ;
+            return path ;
+        }
+
+        @Override
+        protected void exec() {
+            FusekiServerListener.initialSetup = cmdLineDataset ;
+            // For standalone, command line use ...
+            JettyFuseki.initializeServer(jettyServerConfig) ;
+            JettyFuseki.instance.start() ;
+            JettyFuseki.instance.join() ;
+            System.exit(0) ;
+        }
+
+        @Override
+        protected String getCommandName() {
+            return "fuseki" ;
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/jena/blob/470ee4d7/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/conneg/ConNeg.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/conneg/ConNeg.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/conneg/ConNeg.java
new file mode 100644
index 0000000..672d2b9
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/conneg/ConNeg.java
@@ -0,0 +1,206 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.fuseki.conneg;
+
+import static org.apache.jena.riot.web.HttpNames.hAcceptCharset ;
+
+import javax.servlet.http.HttpServletRequest ;
+
+import org.apache.jena.atlas.web.AcceptList ;
+import org.apache.jena.atlas.web.MediaRange ;
+import org.apache.jena.atlas.web.MediaType ;
+import org.slf4j.Logger ;
+import org.slf4j.LoggerFactory ;
+
+/**
+ * <p>Content negotiation is a mechanism defined in the HTTP specification
+ * that makes it possible to serve different versions of a document
+ * (or more generally, a resource representation) at the same URI, so that
+ * user agents can specify which version fit their capabilities the best.</p>
+ *
+ * <p>ConNeg is used in Fuseki to help matching the content media type requested
+ * by the user, against the list of offered media types.</p>
+ *
+ * @see <a href="http://en.wikipedia.org/wiki/Content_negotiation">http://en.wikipedia.org/wiki/Content_negotiation</a>
+ * @see <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1">http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1</a>
+ */
+public class ConNeg
+{
+    // Logger
+    private static Logger log = LoggerFactory.getLogger(ConNeg.class) ;
+    // See riot.ContentNeg (client side).
+    // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1
+
+    /**
+     * Parses the content type. It splits the string by semi-colon and finds the
+     * other features such as the "q" quality factor.  For a complete documentation
+     * on how the parsing happens, see
+     * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1">http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1</a>.
+     *
+     * @param contentType content type string
+     * @return parsed media type
+     */
+    static public MediaType parse(String contentType)
+    {
+        try {
+            return MediaType.create(contentType) ;
+        } catch (RuntimeException ex) { return null ; }
+    }
+
+    /**
+     * <p>Creates a {@link AcceptList} with the given HTTP header string and
+     * uses the {@link AcceptList#match(MediaType)} method to decide which
+     * media type matches the HTTP header string.</p>
+     *
+     * <p>The <em>q</em> quality factor is used to decide which choice is the best
+     * match.</p>
+     *
+     * @param headerString HTTP header string
+     * @param offerList accept list
+     * @return matched media type
+     */
+    static public MediaType match(String headerString, AcceptList offerList)
+    {
+        AcceptList l = new AcceptList(headerString) ;
+        return AcceptList.match(l, offerList) ;
+    }
+
+    /**
+     * Match a single media type against a header string.
+     *
+     * @param headerString HTTP header string
+     * @param mediaRangeStr Semi-colon separated list of media types
+     * @return the matched media type or <code>null</code> if there was no match
+     */
+    public static String match(String headerString, String mediaRangeStr)
+    {
+        AcceptList l = new AcceptList(headerString) ;
+        MediaRange aItem = new MediaRange(mediaRangeStr) ;  // MediaType
+        MediaType m = l.match(aItem) ;
+        if ( m == null )
+            return null ;
+        return m.toHeaderString() ;
+    }
+
+    /**
+     * Split and trims a string using a given regex.
+     *
+     * @param s string
+     * @param splitStr given regex
+     * @return an array with the trimmed strings found
+     */
+    /*package*/ static String[] split(String s, String splitStr)
+    {
+        String[] x = s.split(splitStr,2) ;
+        for ( int i = 0 ; i < x.length ; i++ )
+        {
+            x[i] = x[i].trim() ;
+        }
+        return x ;
+    }
+
+    /**
+     * <p>Chooses the charset by using the Accept-Charset HTTP header.</p>
+     *
+     * <p>See {@link ConNeg#choose(String, AcceptList, MediaType)}.</p>
+     *
+     * @param httpRequest HTTP request
+     * @param myPrefs accept list
+     * @param defaultMediaType default media type
+     * @return media type chosen
+     */
+    public static MediaType chooseCharset(HttpServletRequest httpRequest,
+                                          AcceptList myPrefs,
+                                          MediaType defaultMediaType)
+    {
+        String a = httpRequest.getHeader(hAcceptCharset) ;
+        if ( log.isDebugEnabled() )
+            log.debug("Accept-Charset request: "+a) ;
+        
+        MediaType item = choose(a, myPrefs, defaultMediaType) ;
+        
+        if ( log.isDebugEnabled() )
+            log.debug("Charset chosen: "+item) ;
+    
+        return item ;
+    }
+
+    /**
+     * <p>Choose the content media type by extracting the Accept HTTP header from
+     * the HTTP request and choosing
+     * (see {@link ConNeg#choose(String, AcceptList, MediaType)}) a content media
+     * type that matches the header.</p>
+     *
+     * @param httpRequest HTTP request
+     * @param myPrefs accept list
+     * @param defaultMediaType default media type
+     * @return media type chosen
+     */
+    public static MediaType chooseContentType(HttpServletRequest httpRequest,
+                                              AcceptList myPrefs,
+                                              MediaType defaultMediaType)
+    {
+        String a = WebLib.getAccept(httpRequest) ;
+        if ( log.isDebugEnabled() )
+            log.debug("Accept request: "+a) ;
+        
+        MediaType item = choose(a, myPrefs, defaultMediaType) ;
+    
+        if ( log.isDebugEnabled() )
+            log.debug("Content type chosen: "+item) ;
+    
+        return item ;
+    }
+
+    /**
+     * <p>This method receives a HTTP header string, an {@link AcceptList} and a
+     * default {@link MediaType}.</p>
+     *
+     * <p>If the header string is null, it returns the given default MediaType.</p>
+     *
+     * <p>Otherwise it builds an {@link AcceptList} object with the header string
+     * and uses it to match against the given MediaType.</p>
+     *
+     * @param headerString HTTP header string
+     * @param myPrefs accept list
+     * @param defaultMediaType default media type
+     * @return a media type or <code>null</code> if none matched or if the
+     *          HTTP header string and the default media type are <code>null</code>.
+     */
+    private static MediaType choose(String headerString, AcceptList myPrefs,
+                                    MediaType defaultMediaType)
+    {
+        if ( headerString == null )
+            return defaultMediaType ;
+        
+        AcceptList headerList = new AcceptList(headerString) ;
+        
+        if ( myPrefs == null )
+        {
+            MediaType i = headerList.first() ;
+            if ( i == null ) return defaultMediaType ;
+            return i ;
+        }
+    
+        MediaType i = AcceptList.match(headerList, myPrefs) ;
+        if ( i == null )
+            return defaultMediaType ;
+        return i ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/470ee4d7/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/conneg/WebLib.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/conneg/WebLib.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/conneg/WebLib.java
new file mode 100644
index 0000000..38dc265
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/conneg/WebLib.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.fuseki.conneg;
+
+import java.util.Enumeration ;
+
+import javax.servlet.http.HttpServletRequest ;
+
+import org.apache.jena.riot.web.HttpNames ;
+
+public class WebLib
+{
+    /** Split a string, removing whitespace around the split string.
+     * e.g. Use in splitting HTTP accept/content-type headers.  
+     */
+    public static String[] split(String s, String splitStr)
+    {
+        String[] x = s.split(splitStr,2) ;
+        for ( int i = 0 ; i < x.length ; i++ )
+        {
+            x[i] = x[i].trim() ;
+        }
+        return x ;
+    }
+
+    /** Migrate to WebLib */
+    public static String getAccept(HttpServletRequest httpRequest)
+    {
+        // There can be multiple accept headers -- note many tools don't allow these to be this way (e.g. wget, curl)
+        Enumeration<String> en = httpRequest.getHeaders(HttpNames.hAccept) ;
+        if ( ! en.hasMoreElements() )
+            return null ;
+        StringBuilder sb = new StringBuilder() ;
+        String sep = "" ;
+        for ( ; en.hasMoreElements() ; )
+        {
+            String x = en.nextElement() ;
+            sb.append(sep) ;
+            sep = ", " ;
+            sb.append(x) ;
+        }
+        return sb.toString() ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/470ee4d7/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/jetty/FusekiErrorHandler.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/jetty/FusekiErrorHandler.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/jetty/FusekiErrorHandler.java
new file mode 100644
index 0000000..40361de
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/jetty/FusekiErrorHandler.java
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.fuseki.jetty;
+
+import static java.lang.String.format ;
+
+import java.io.* ;
+
+import javax.servlet.http.HttpServletRequest ;
+import javax.servlet.http.HttpServletResponse ;
+
+import org.apache.jena.atlas.io.IO ;
+import org.apache.jena.fuseki.Fuseki ;
+import org.apache.jena.fuseki.servlets.ServletOps ;
+import org.apache.jena.web.HttpSC ;
+import org.eclipse.jetty.http.HttpMethod ;
+import org.eclipse.jetty.http.MimeTypes ;
+import org.eclipse.jetty.server.Request ;
+import org.eclipse.jetty.server.Response ;
+import org.eclipse.jetty.server.handler.ErrorHandler ;
+
+public class FusekiErrorHandler extends ErrorHandler
+{
+    public FusekiErrorHandler() {}
+    
+    @Override
+    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
+    {
+        String method = request.getMethod();
+     
+        if ( !method.equals(HttpMethod.GET.asString())
+             && !method.equals(HttpMethod.POST.asString())
+             && !method.equals(HttpMethod.HEAD.asString()) )
+            return ;
+        
+        response.setContentType(MimeTypes.Type.TEXT_PLAIN_UTF_8.asString()) ;
+        ServletOps.setNoCache(response) ;
+        
+        ByteArrayOutputStream bytes = new ByteArrayOutputStream(1024) ;
+        try ( Writer writer = IO.asUTF8(bytes) ) {
+            String reason=(response instanceof Response)?((Response)response).getReason():null;
+            handleErrorPage(request, writer, response.getStatus(), reason) ;
+
+            if ( ! Fuseki.VERSION.equalsIgnoreCase("development") &&
+                ! Fuseki.VERSION.equals("${project.version}") )
+            {
+                writer.write("\n") ;
+                writer.write("\n") ;
+                writer.write(format("Fuseki - version %s (Build date: %s)\n", Fuseki.VERSION, Fuseki.BUILD_DATE)) ;
+            }
+            writer.flush();
+            response.setContentLength(bytes.size()) ;
+            // Copy :-(
+            response.getOutputStream().write(bytes.toByteArray()) ;
+        }
+    }
+    
+    @Override
+    protected void handleErrorPage(HttpServletRequest request, Writer writer, int code, String message)
+        throws IOException
+    {
+        if ( message == null )
+            message = HttpSC.getMessage(code) ;
+        writer.write(format("Error %d: %s\n", code, message)) ;
+        
+        Throwable th = (Throwable)request.getAttribute("javax.servlet.error.exception");
+        while(th!=null)
+        {
+            writer.write("\n");
+            StringWriter sw = new StringWriter();
+            PrintWriter pw = new PrintWriter(sw);
+            th.printStackTrace(pw);
+            pw.flush();
+            writer.write(sw.getBuffer().toString());
+            writer.write("\n");
+            th = th.getCause();
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/470ee4d7/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/jetty/JettyFuseki.java
----------------------------------------------------------------------
diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/jetty/JettyFuseki.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/jetty/JettyFuseki.java
new file mode 100644
index 0000000..8d9046c
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/jetty/JettyFuseki.java
@@ -0,0 +1,305 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.fuseki.jetty ;
+
+import static java.lang.String.format ;
+import static org.apache.jena.fuseki.Fuseki.serverLog ;
+
+import java.io.FileInputStream ;
+
+import org.apache.jena.atlas.lib.FileOps ;
+import org.apache.jena.fuseki.Fuseki ;
+import org.apache.jena.fuseki.FusekiException ;
+import org.apache.jena.fuseki.mgt.MgtJMX ;
+import org.apache.jena.fuseki.server.FusekiEnv ;
+import org.eclipse.jetty.security.* ;
+import org.eclipse.jetty.security.authentication.BasicAuthenticator ;
+import org.eclipse.jetty.server.* ;
+import org.eclipse.jetty.servlet.ServletContextHandler ;
+import org.eclipse.jetty.util.security.Constraint ;
+import org.eclipse.jetty.webapp.WebAppContext ;
+import org.eclipse.jetty.xml.XmlConfiguration ;
+
+import com.hp.hpl.jena.sparql.util.Utils ;
+
+/** Standalone server, not run as a WAR file.
+ * Used in testing and development.
+ * 
+ * SPARQLServer is the Jena server instance which wraps/utilizes 
+ * {@link org.eclipse.jetty.server.Server}. This class provides
+ * immediate access to the {@link org.eclipse.jetty.server.Server#start()} and 
+ * {@link org.eclipse.jetty.server.Server#stop()} commands as well as obtaining
+ * instances of the server and server configuration. Finally we can obtain 
+ * instances of {@link org.apache.jena.fuseki.jetty.JettyServerConfig}.
+ */
+public class JettyFuseki {
+    // Jetty specific.
+    // This class is becoming less important - it now sets up a Jetty server for in-process use
+    // either for the command line in development  
+    // and in testing but not direct webapp deployments. 
+    static { Fuseki.init() ; }
+
+    public static JettyFuseki  instance    = null ;
+
+    private ServerConnector serverConnector = null ;
+    // If a separate ...
+    private ServerConnector mgtConnector    = null ;
+    
+    private JettyServerConfig serverConfig ;
+
+    // The jetty server.
+    private Server              server         = null ;
+    
+    // webapp setup - standard maven layout
+    public static       String contextpath     = "/" ;
+    // Standalone jar
+    public static final String resourceBase1   = "webapp" ;
+    // Development
+    public static final String resourceBase2   = "src/main/webapp" ;
+    
+
+    /**
+     * Default setup which requires a {@link org.apache.jena.fuseki.jetty.JettyServerConfig}
+     * object as input.  We use this config to pass in the command line arguments for dataset, 
+     * name etc. 
+     * @param config
+     */
+    
+    public static void initializeServer(JettyServerConfig config) {
+        // Currently server-wide.
+        Fuseki.verboseLogging = config.verboseLogging ;
+        instance = new JettyFuseki(config) ;
+    }
+    
+    /** Build a Jetty server using the development files for the webapp
+     *  No command line configuration. 
+     */
+    public static Server create(int port) {
+        return create("/", port) ;
+    }
+
+    public static Server create(String contextPath, int port) {
+        Server server = new Server(port) ;
+        WebAppContext webapp = createWebApp(contextPath) ;
+        server.setHandler(webapp) ;
+        return server ;
+    }
+
+    private JettyFuseki(JettyServerConfig config) {
+        this.serverConfig = config ;
+        
+        buildServerWebapp(serverConfig.contextPath, serverConfig.jettyConfigFile, config.enableCompression) ;
+        
+        if ( mgtConnector == null )
+            mgtConnector = serverConnector ;
+    }
+
+    /**
+     * Initialize the {@link JettyFuseki} instance.
+     */
+    public void start() {
+        
+        String version = Fuseki.VERSION ;
+        String buildDate = Fuseki.BUILD_DATE ;
+        
+        if ( version != null && version.equals("${project.version}") )
+            version = null ;
+        if ( buildDate != null && buildDate.equals("${build.time.xsd}") )
+            buildDate = Utils.nowAsXSDDateTimeString() ;
+        
+        if ( version != null && buildDate != null )
+            serverLog.info(format("%s %s %s", Fuseki.NAME, version, buildDate)) ;
+        // This does not get set usefully for Jetty as we use it.
+        // String jettyVersion = org.eclipse.jetty.server.Server.getVersion() ;
+        // serverLog.info(format("Jetty %s",jettyVersion)) ;
+        
+        String host = serverConnector.getHost() ;
+        if ( host != null )
+            serverLog.info("Incoming connections limited to " + host) ;
+
+        try {
+            server.start() ;
+        } catch (java.net.BindException ex) {
+            serverLog.error("SPARQLServer (port="+serverConnector.getPort()+"): Failed to start server: " + ex.getMessage()) ;
+            System.exit(1) ;
+        } catch (Exception ex) {
+            serverLog.error("SPARQLServer: Failed to start server: " + ex.getMessage(), ex) ;
+            System.exit(1) ;
+        }
+        String now = Utils.nowAsString() ;
+        serverLog.info(format("Started %s on port %d", now, serverConnector.getPort())) ;
+    }
+
+    /**
+     * Sync with the {@link JettyFuseki} instance.
+     * Returns only if the server exits cleanly 
+     */
+    public void join() {
+        try {
+            server.join() ;
+        } catch (InterruptedException ex) { }
+    }
+
+        /**
+     * Stop the {@link JettyFuseki} instance.
+     */
+    public void stop() {
+        String now = Utils.nowAsString() ;
+        serverLog.info(format("Stopped %s on port %d", now, serverConnector.getPort())) ;
+        try {
+            server.stop() ;
+        } catch (Exception ex) {
+            Fuseki.serverLog.warn("SPARQLServer: Exception while stopping server: " + ex.getMessage(), ex) ;
+        }
+        MgtJMX.removeJMX() ;
+    }
+
+    public static WebAppContext createWebApp(String contextPath) {
+        FusekiEnv.setEnvironment();
+        WebAppContext webapp = new WebAppContext();
+        webapp.getServletContext().getContextHandler().setMaxFormContentSize(10 * 1000 * 1000) ;
+
+        // Hunt for the webapp for the standalone jar (or development system). 
+        // Note that Path FUSEKI_HOME is not initialized until the webapp starts
+        // so it is not available here.
+
+        String resourceBase3 = null ;
+        String resourceBase4 = null ;
+        if ( FusekiEnv.FUSEKI_HOME != null ) {
+            String HOME = FusekiEnv.FUSEKI_HOME.toString() ;
+            resourceBase3 = HOME+"/"+resourceBase1 ;
+            resourceBase4 = HOME+"/"+resourceBase2 ;
+        }
+
+        String resourceBase = tryResourceBase(resourceBase1, null) ;
+        resourceBase = tryResourceBase(resourceBase2, resourceBase) ;
+        resourceBase = tryResourceBase(resourceBase3, resourceBase) ;
+        resourceBase = tryResourceBase(resourceBase4, resourceBase) ;
+
+        if ( resourceBase == null ) {
+            if ( resourceBase3 == null )
+                Fuseki.serverLog.error("Can't find resourceBase (tried "+resourceBase1+" and "+resourceBase2+")") ;
+            else
+                Fuseki.serverLog.error("Can't find resourceBase (tried "+resourceBase1+", "+resourceBase2+", "+resourceBase3+" and "+resourceBase4+")") ;
+            Fuseki.serverLog.error("Failed to start") ;
+            throw new FusekiException("Failed to start") ;
+        }
+
+        webapp.setDescriptor(resourceBase+"/WEB-INF/web.xml");
+        webapp.setResourceBase(resourceBase);
+        webapp.setContextPath(contextPath);
+
+        //-- Jetty setup for the ServletContext logger.
+        // The name of the Jetty-allocated slf4j/log4j logger is
+        // the display name or, if null, the context path name.   
+        // It is set, without checking for a previous call of setLogger in "doStart"
+        // which happens during server startup. 
+        // This the name of the ServletContext logger as well
+        webapp.setDisplayName(Fuseki.servletRequestLogName);  
+        webapp.setParentLoaderPriority(true);  // Normal Java classloader behaviour.
+        webapp.setErrorHandler(new FusekiErrorHandler()) ;
+        return webapp ;
+    }
+    
+
+    public static String getenv(String name) {
+        String x = System.getenv(name) ;
+        if ( x == null )
+            x = System.getProperty(name) ;
+        return x ;
+    }
+
+    private static String tryResourceBase(String maybeResourceBase, String currentResourceBase) {
+        if ( currentResourceBase != null )
+            return currentResourceBase ;
+        if ( maybeResourceBase != null && FileOps.exists(maybeResourceBase) )
+            return maybeResourceBase ;
+        return currentResourceBase ;
+    }
+    
+    private void buildServerWebapp(String contextPath, String jettyConfig, boolean enableCompression) {
+        if ( jettyConfig != null )
+            // --jetty-config=jetty-fuseki.xml
+            // for detailed configuration of the server using Jetty features.
+            configServer(jettyConfig) ;
+        else
+            defaultServerConfig(serverConfig.port, serverConfig.loopback) ;
+
+        WebAppContext webapp = createWebApp(contextPath) ;
+        server.setHandler(webapp) ;
+        // Replaced by Shiro.
+        if ( jettyConfig == null && serverConfig.authConfigFile != null )
+            security(webapp, serverConfig.authConfigFile) ;
+    }
+    
+    private static void security(ServletContextHandler context, String authfile) {
+        Constraint constraint = new Constraint() ;
+        constraint.setName(Constraint.__BASIC_AUTH) ;
+        constraint.setRoles(new String[]{"fuseki"}) ;
+        constraint.setAuthenticate(true) ;
+
+        ConstraintMapping mapping = new ConstraintMapping() ;
+        mapping.setConstraint(constraint) ;
+        mapping.setPathSpec("/*") ;
+
+        IdentityService identService = new DefaultIdentityService() ;
+
+        ConstraintSecurityHandler securityHandler = new ConstraintSecurityHandler() ;
+        securityHandler.addConstraintMapping(mapping) ;
+        securityHandler.setIdentityService(identService) ;
+
+        HashLoginService loginService = new HashLoginService("Fuseki Authentication", authfile) ;
+        loginService.setIdentityService(identService) ;
+
+        securityHandler.setLoginService(loginService) ;
+        securityHandler.setAuthenticator(new BasicAuthenticator()) ;
+
+        context.setSecurityHandler(securityHandler) ;
+
+        serverLog.debug("Basic Auth Configuration = " + authfile) ;
+    }
+
+    private void configServer(String jettyConfig) {
+        try {
+            serverLog.info("Jetty server config file = " + jettyConfig) ;
+            server = new Server() ;
+            XmlConfiguration configuration = new XmlConfiguration(new FileInputStream(jettyConfig)) ;
+            configuration.configure(server) ;
+            serverConnector = (ServerConnector)server.getConnectors()[0] ;
+        } catch (Exception ex) {
+            serverLog.error("SPARQLServer: Failed to configure server: " + ex.getMessage(), ex) ;
+            throw new FusekiException("Failed to configure a server using configuration file '" + jettyConfig + "'") ;
+        }
+    }
+
+    private void defaultServerConfig(int port, boolean loopback) {
+        server = new Server() ;
+        ConnectionFactory f1 = new HttpConnectionFactory() ;
+        ConnectionFactory f2 = new SslConnectionFactory() ;
+        
+        ServerConnector connector = new ServerConnector(server, f1) ; //, f2) ;
+        connector.setPort(port);
+        server.addConnector(connector);
+        
+        if ( loopback )
+            connector.setHost("localhost");
+        connector.setPort(port) ;
+        serverConnector = connector ;
+    }
+}

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