http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/FusekiConfig.java ---------------------------------------------------------------------- diff --git a/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/FusekiConfig.java b/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/FusekiConfig.java new file mode 100644 index 0000000..9c36a7c --- /dev/null +++ b/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/FusekiConfig.java @@ -0,0 +1,374 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.server; + +import java.lang.reflect.Method ; +import java.util.ArrayList ; +import java.util.Arrays ; +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.HttpNames ; +import org.slf4j.Logger ; + +import com.hp.hpl.jena.assembler.Assembler ; +import com.hp.hpl.jena.assembler.JA ; +import com.hp.hpl.jena.query.ARQ ; +import com.hp.hpl.jena.query.Dataset ; +import com.hp.hpl.jena.query.Query ; +import com.hp.hpl.jena.query.QueryExecution ; +import com.hp.hpl.jena.query.QueryExecutionFactory ; +import com.hp.hpl.jena.query.QueryFactory ; +import com.hp.hpl.jena.query.QuerySolution ; +import com.hp.hpl.jena.query.QuerySolutionMap ; +import com.hp.hpl.jena.query.ResultSet ; +import com.hp.hpl.jena.query.ResultSetFactory ; +import com.hp.hpl.jena.rdf.model.Literal ; +import com.hp.hpl.jena.rdf.model.Model ; +import com.hp.hpl.jena.rdf.model.RDFNode ; +import com.hp.hpl.jena.rdf.model.ResIterator ; +import com.hp.hpl.jena.rdf.model.Resource ; +import com.hp.hpl.jena.rdf.model.Statement ; +import com.hp.hpl.jena.rdf.model.StmtIterator ; +import com.hp.hpl.jena.shared.PrefixMapping ; +import com.hp.hpl.jena.sparql.core.DatasetGraph ; +import com.hp.hpl.jena.sparql.core.DatasetGraphFactory ; +import com.hp.hpl.jena.sparql.core.DatasetGraphReadOnly ; +import com.hp.hpl.jena.sparql.core.assembler.AssemblerUtils ; +import com.hp.hpl.jena.tdb.TDB ; +import com.hp.hpl.jena.util.FileManager ; +import com.hp.hpl.jena.vocabulary.RDF ; +import com.hp.hpl.jena.vocabulary.RDFS ; + +public class FusekiConfig +{ + static { Fuseki.init(); } + + // The datastructure that captures a servers configuration. + + // Server port + int port ; + // Management command port - -1 for none. + int mgtPort ; + List<DatasetRef> datasets = null ; + + + private static Logger log = Fuseki.configLog ; + + private static String prefixes = StrUtils.strjoinNL( + "PREFIX fu: <http://jena.apache.org/fuseki#>" , + "PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>", + "PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>", + "PREFIX tdb: <http://jena.hpl.hp.com/2008/tdb#>", + "PREFIX list: <http://jena.hpl.hp.com/ARQ/list#>", + "PREFIX list: <http://jena.hpl.hp.com/ARQ/list#>", + "PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>", + "PREFIX apf: <http://jena.hpl.hp.com/ARQ/property#>", + "PREFIX afn: <http://jena.hpl.hp.com/ARQ/function#>" , + "") ; + + public static ServerConfig defaultConfiguration(String datasetPath, DatasetGraph dsg, boolean allowUpdate, boolean listenLocal) + { + DatasetRef dbDesc = new DatasetRef() ; + dbDesc.name = datasetPath ; + dbDesc.dataset = dsg ; + dbDesc.query.endpoints.add(HttpNames.ServiceQuery) ; + dbDesc.query.endpoints.add(HttpNames.ServiceQueryAlt) ; + + if ( allowUpdate ) + { + dbDesc.update.endpoints.add(HttpNames.ServiceUpdate) ; + dbDesc.upload.endpoints.add(HttpNames.ServiceUpload) ; + dbDesc.readWriteGraphStore.endpoints.add(HttpNames.ServiceData) ; + dbDesc.allowDatasetUpdate = true ; + } + else + dbDesc.readGraphStore.endpoints.add(HttpNames.ServiceData) ; + ServerConfig config = new ServerConfig() ; + config.datasets = Arrays.asList(dbDesc) ; + config.port = 3030 ; + config.mgtPort = 3031 ; + config.pagesPort = config.port ; + config.loopback = listenLocal ; + config.jettyConfigFile = null ; + config.pages = Fuseki.PagesStatic ; + config.enableCompression = true ; + config.verboseLogging = false ; + return config ; + } + + public static ServerConfig configure(String filename) + { + // Be absolutely sure everything has initialized. + // Some initialization registers assemblers and sets abbreviation vocabulary. + ARQ.init(); + TDB.init() ; + Fuseki.init() ; + Model m = FileManager.get().loadModel(filename) ; + + // Find one server. + List<Resource> servers = getByType(FusekiVocab.tServer, m) ; + if ( servers.size() == 0 ) + throw new FusekiConfigException("No server found (no resource with type "+strForResource(FusekiVocab.tServer)) ; + 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) ; + + // ---- Services + ResultSet rs = query("SELECT * { ?s fu:services [ list:member ?member ] }", m) ; + if ( ! rs.hasNext() ) + log.warn("No services found") ; + + List<DatasetRef> services = new ArrayList<DatasetRef>() ; + + for ( ; rs.hasNext() ; ) + { + QuerySolution soln = rs.next() ; + Resource svc = soln.getResource("member") ; + DatasetRef sd = processService(svc) ; + services.add(sd) ; + } + + // TODO Properties for the other fields. + ServerConfig config = new ServerConfig() ; + config.datasets = services ; + config.port = 3030 ; + config.mgtPort = 3031 ; + config.pagesPort = config.port ; + config.jettyConfigFile = null ; + config.pages = Fuseki.PagesStatic ; + config.enableCompression = true ; + config.verboseLogging = false ; + return config ; + } + + + // DatasetRef used where there isn't a real Dataset e.g. the SPARQL processor. + + private static DatasetRef noDataset = new DatasetRef() ; + private static DatasetGraph dummyDSG = new DatasetGraphReadOnly(DatasetGraphFactory.createMemFixed()) ; + static { + noDataset.name = "" ; + noDataset.dataset = dummyDSG ; + noDataset.query.endpoints.add(HttpNames.ServiceQuery) ; + noDataset.query.endpoints.add(HttpNames.ServiceQueryAlt) ; + noDataset.allowDatasetUpdate = false ; + noDataset.init(); + // Don't register it. + // This is used as a placeholder and shoudl not be found by "all datasets" + // DatasetRegistry.get().put("", noDataset) ; + } + + /** Return the DatasetRef (read-only) for when there is no dataset, just a SPARQL Query processor */ + public static DatasetRef serviceOnlyDatasetRef() { return noDataset ; } + + 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 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) ; } + } + + private static DatasetRef processService(Resource svc) + { + log.info("Service: "+nodeLabel(svc)) ; + DatasetRef sDesc = new DatasetRef() ; + sDesc.name = ((Literal)getOne(svc, "fu:name")).getLexicalForm() ; + log.info(" name = "+sDesc.name) ; + + addServiceEP("query", sDesc.name, sDesc.query, svc, "fu:serviceQuery") ; + addServiceEP("update", sDesc.name, sDesc.update, svc, "fu:serviceUpdate") ; + addServiceEP("upload", sDesc.name, sDesc.upload, svc, "fu:serviceUpload") ; + addServiceEP("graphStore(RW)", sDesc.name, sDesc.readWriteGraphStore, svc, "fu:serviceReadWriteGraphStore") ; + addServiceEP("graphStore(R)", sDesc.name, sDesc.readGraphStore, svc, "fu:serviceReadGraphStore") ; + // 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); + } + } + + 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) ; + sDesc.dataset = ds.asDatasetGraph() ; + return sDesc ; + + } + + private static RDFNode getOne(Resource svc, String property) + { + String ln = property.substring(property.indexOf(':')+1) ; + ResultSet rs = query("SELECT * { ?svc "+property+" ?x}", svc.getModel(), "svc", svc) ; + if ( ! rs.hasNext() ) + throw new FusekiConfigException("No "+ln+" for service "+nodeLabel(svc)) ; + RDFNode x = rs.next().get("x") ; + if ( rs.hasNext() ) + throw new FusekiConfigException("Multiple "+ln+" for service "+nodeLabel(svc)) ; + return x ; + } + + private static List<Resource> getByType(Resource type, Model m) + { + ResIterator rIter = m.listSubjectsWithProperty(RDF.type, type) ; + return Iter.toList(rIter) ; + } + + private static void addServiceEP(String label, String name, ServiceRef service, 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() ; + service.endpoints.add(epName) ; + log.info(" "+label+" = /"+name+"/"+epName) ; + } + } + + + private static ResultSet query(String string, Model m) + { + return query(string, m, null, null) ; + } + + private static ResultSet query(String string, Model m, String varName, RDFNode value) + { + Query query = QueryFactory.create(prefixes+string) ; + QuerySolutionMap initValues = null ; + if ( varName != null ) + initValues = querySolution(varName, value) ; + try(QueryExecution qExec = QueryExecutionFactory.create(query, m, initValues)) { + ResultSet rs = ResultSetFactory.copyResults(qExec.execSelect()) ; + return rs ; + } + } + + private static QuerySolutionMap querySolution(String varName, RDFNode value) + { + QuerySolutionMap qsm = new QuerySolutionMap() ; + querySolution(qsm, varName, value) ; + return qsm ; + } + + private static QuerySolutionMap querySolution(QuerySolutionMap qsm, String varName, RDFNode value) + { + qsm.add(varName, value) ; + return qsm ; + } + + // Node presentation + private static String nodeLabel(RDFNode n) + { + if ( n == null ) + return "<null>" ; + if ( n instanceof Resource ) + return strForResource((Resource)n) ; + + Literal lit = (Literal)n ; + return lit.getLexicalForm() ; + } + + private static String strForResource(Resource r) { return strForResource(r, r.getModel()) ; } + + private static String strForResource(Resource r, PrefixMapping pm) + { + if ( r == null ) + return "NULL "; + if ( r.hasProperty(RDFS.label)) + { + RDFNode n = r.getProperty(RDFS.label).getObject() ; + if ( n instanceof Literal ) + return ((Literal)n).getString() ; + } + + if ( r.isAnon() ) + return "<<blank node>>" ; + + if ( pm == null ) + pm = r.getModel() ; + + return strForURI(r.getURI(), pm ) ; + } + + private static String strForURI(String uri, PrefixMapping pm) + { + if ( pm != null ) + { + String x = pm.shortForm(uri) ; + + if ( ! x.equals(uri) ) + return x ; + } + return "<"+uri+">" ; + } +} +
http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/FusekiErrorHandler.java ---------------------------------------------------------------------- diff --git a/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/FusekiErrorHandler.java b/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/FusekiErrorHandler.java new file mode 100644 index 0000000..d1660f5 --- /dev/null +++ b/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/FusekiErrorHandler.java @@ -0,0 +1,92 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.server; + +import static java.lang.String.format ; + +import java.io.* ; + +import javax.servlet.http.HttpServletRequest ; +import javax.servlet.http.HttpServletResponse ; + +import org.apache.jena.fuseki.Fuseki ; +import org.apache.jena.web.HttpSC ; +import org.eclipse.jetty.http.HttpHeaders ; +import org.eclipse.jetty.http.HttpMethods ; +import org.eclipse.jetty.http.MimeTypes ; +import org.eclipse.jetty.server.AbstractHttpConnection ; +import org.eclipse.jetty.server.Request ; +import org.eclipse.jetty.server.handler.ErrorHandler ; + +public class FusekiErrorHandler extends ErrorHandler +{ + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + { + AbstractHttpConnection connection = AbstractHttpConnection.getCurrentConnection(); + connection.getRequest().setHandled(true); + String method = request.getMethod(); + + if(!method.equals(HttpMethods.GET) && !method.equals(HttpMethods.POST) && !method.equals(HttpMethods.HEAD)) + return; + + response.setContentType(MimeTypes.TEXT_PLAIN_UTF_8) ; + response.setHeader(HttpHeaders.CACHE_CONTROL, "must-revalidate,no-cache,no-store") ; + + ByteArrayOutputStream bytes = new ByteArrayOutputStream(1024) ; + //String writer = IO.UTF8(null) ; + try(Writer writer = new OutputStreamWriter(bytes, "UTF-8")) { + + handleErrorPage(request, writer, connection.getResponse().getStatus(), connection.getResponse().getReason()); + + if ( ! Fuseki.VERSION.equalsIgnoreCase("development") ) + { + writer.write("\n") ; + writer.write("\n") ; + writer.write(format("Fuseki - version %s (Build date: %s)\n", Fuseki.VERSION, Fuseki.BUILD_DATE)) ; + } + writer.flush(); + } + response.setContentLength(bytes.size()) ; + // Copy + response.getOutputStream().write(bytes.toByteArray()) ; + } + + @Override + protected void handleErrorPage(HttpServletRequest request, Writer writer, int code, String message) + throws IOException + { + if ( message == null ) + message = HttpSC.getMessage(code) ; + writer.write(format("Error %d: %s\n", code, message)) ; + + Throwable th = (Throwable)request.getAttribute("javax.servlet.error.exception"); + while(th!=null) + { + writer.write("\n"); + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + th.printStackTrace(pw); + pw.flush(); + writer.write(sw.getBuffer().toString()); + writer.write("\n"); + th = th.getCause(); + } + } +} http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/FusekiServletContextListener.java ---------------------------------------------------------------------- diff --git a/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/FusekiServletContextListener.java b/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/FusekiServletContextListener.java new file mode 100644 index 0000000..f1c9642 --- /dev/null +++ b/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/FusekiServletContextListener.java @@ -0,0 +1,43 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.server; + +import javax.servlet.ServletContextEvent ; +import javax.servlet.ServletContextListener ; + +public class FusekiServletContextListener implements ServletContextListener { + // This could do the initialization. + private final SPARQLServer sparqlServer ; + public FusekiServletContextListener(SPARQLServer sparqlServer) { + this.sparqlServer = sparqlServer ; + } + + @Override + public void contextInitialized(ServletContextEvent sce) { +// Fuseki.serverLog.info("contextInitialized") ; +// for ( DatasetRef dsRef : sparqlServer.getDatasets() ) +// Fuseki.serverLog.info("Dataset: "+dsRef.getName()) ; + } + + @Override + public void contextDestroyed(ServletContextEvent sce) { +// Fuseki.serverLog.info("contextDestroyed") ; + } +} + http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/FusekiVocab.java ---------------------------------------------------------------------- diff --git a/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/FusekiVocab.java b/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/FusekiVocab.java new file mode 100644 index 0000000..d4d4e54 --- /dev/null +++ b/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/FusekiVocab.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.server; + +import org.apache.jena.fuseki.FusekiException ; +import org.apache.jena.iri.IRI ; +import org.apache.jena.riot.system.IRIResolver ; + +import com.hp.hpl.jena.rdf.model.Property ; +import com.hp.hpl.jena.rdf.model.Resource ; +import com.hp.hpl.jena.rdf.model.ResourceFactory ; + +public class FusekiVocab +{ + public static String NS = "http://jena.apache.org/fuseki#" ; + + public static final Resource tServer = resource("Server") ; + + public static final Property pServices = property("services") ; + public static final Property pServiceName = property("name") ; + + public static final Property pServiceQueryEP = property("serviceQuery") ; + public static final Property pServiceUpdateEP = property("serviceUpdate") ; + public static final Property pServiceUploadEP = property("serviceUpload") ; + public static final Property pServiceReadWriteGraphStoreEP = property("serviceReadWriteGraphStore") ; + public static final Property pServiceReadgraphStoreEP = property("serviceReadGraphStore") ; + + public static final Property pAllowTimeoutOverride = property("allowTimeoutOverride"); + public static final Property pMaximumTimeoutOverride = property("maximumTimeoutOverride"); + + private static Resource resource(String localname) { return ResourceFactory.createResource(iri(localname)) ; } + private static Property property(String localname) { return ResourceFactory.createProperty(iri(localname)) ; } + + private static String iri(String localname) + { + String uri = NS+localname ; + IRI iri = IRIResolver.parseIRI(uri) ; + if ( iri.hasViolation(true) ) + throw new FusekiException("Bad IRI: "+iri) ; + if ( ! iri.isAbsolute() ) + throw new FusekiException("Bad IRI: "+iri) ; + + return uri ; + } +} + http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/SPARQLServer.java ---------------------------------------------------------------------- diff --git a/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/SPARQLServer.java b/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/SPARQLServer.java new file mode 100644 index 0000000..7448eb2 --- /dev/null +++ b/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/SPARQLServer.java @@ -0,0 +1,484 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.server ; + +import static java.lang.String.format ; +import static org.apache.jena.fuseki.Fuseki.serverLog ; + +import java.io.FileInputStream ; +import java.util.* ; + +import javax.servlet.DispatcherType ; +import javax.servlet.http.HttpServlet ; + +import org.apache.jena.atlas.lib.FileOps ; +import org.apache.jena.fuseki.Fuseki ; +import org.apache.jena.fuseki.FusekiException ; +import org.apache.jena.fuseki.HttpNames ; +import org.apache.jena.fuseki.mgt.ActionDataset ; +import org.apache.jena.fuseki.mgt.MgtFunctions ; +import org.apache.jena.fuseki.mgt.PageNames ; +import org.apache.jena.fuseki.servlets.* ; +import org.apache.jena.fuseki.validation.DataValidator ; +import org.apache.jena.fuseki.validation.IRIValidator ; +import org.apache.jena.fuseki.validation.QueryValidator ; +import org.apache.jena.fuseki.validation.UpdateValidator ; +import org.apache.jena.riot.WebContent ; +import org.eclipse.jetty.http.MimeTypes ; +import org.eclipse.jetty.security.* ; +import org.eclipse.jetty.security.authentication.BasicAuthenticator ; +import org.eclipse.jetty.server.Connector ; +import org.eclipse.jetty.server.Server ; +import org.eclipse.jetty.server.nio.BlockingChannelConnector ; +import org.eclipse.jetty.servlet.DefaultServlet ; +import org.eclipse.jetty.servlet.ServletContextHandler ; +import org.eclipse.jetty.servlet.ServletHolder ; +import org.eclipse.jetty.servlets.GzipFilter ; +import org.eclipse.jetty.util.security.Constraint ; +import org.eclipse.jetty.xml.XmlConfiguration ; + +import com.hp.hpl.jena.sparql.mgt.ARQMgt ; +import com.hp.hpl.jena.sparql.util.Utils ; + +/** + * 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.server.ServerConfig}. + * + */ +public class SPARQLServer { + static { + Fuseki.init() ; + } + + private ServerConfig serverConfig ; + + private Server server = null ; + private static List<String> epDataset = Arrays.asList("*") ; + + /** + * Default constructor which requires a {@link org.apache.jena.fuseki.server.ServerConfig} + * object as input. We use this config to specify (verbose) logging, enable compression + * etc. + * @param config + */ + public SPARQLServer(ServerConfig config) { + this.serverConfig = config ; + // Currently server-wide. + Fuseki.verboseLogging = config.verboseLogging ; + + // GZip compression + // Note that regardless of this setting we'll always leave it turned off + // for the servlets + // where it makes no sense to have it turned on e.g. update and upload + + ServletContextHandler context = buildServer(serverConfig.jettyConfigFile, config.enableCompression) ; + configureDatasets(context) ; + } + + private void configureDatasets(ServletContextHandler context) { + // Build them all. + for (DatasetRef dsDesc : serverConfig.datasets) + configureOneDataset(context, dsDesc, serverConfig.enableCompression) ; + + } + + /** + * Initialize the {@link SPARQLServer} instance. + */ + public void start() { + String now = Utils.nowAsString() ; + serverLog.info(format("%s %s %s", Fuseki.NAME, Fuseki.VERSION, Fuseki.BUILD_DATE)) ; + // 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 = server.getConnectors()[0].getHost() ; + if ( host != null ) + serverLog.info("Incoming connections limited to " + host) ; + serverLog.info(format("Started %s on port %d", now, server.getConnectors()[0].getPort())) ; + + try { + server.start() ; + } catch (java.net.BindException ex) { + serverLog.error("SPARQLServer: 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) ; + } + + ServletContextHandler context = (ServletContextHandler)server.getHandler() ; + } + + /** + * Stop the {@link SPARQLServer} instance. + */ + public void stop() { + String now = Utils.nowAsString() ; + serverLog.info(format("Stopped %s on port %d", now, server.getConnectors()[0].getPort())) ; + try { + server.stop() ; + } catch (Exception ex) { + Fuseki.serverLog.warn("SPARQLServer: Exception while stopping server: " + ex.getMessage(), ex) ; + } + removeJMX() ; + } + + /** + * Get the Jetty instance. + * @return Server + */ + public Server getServer() { + return server ; + } + + /** + * Get the datasets associated with the server. + * @return returns the datasets via {@link org.apache.jena.fuseki.server.ServerConfig#datasets} + */ + public List<DatasetRef> getDatasets() { + return serverConfig.datasets ; + } + + /** + * Obtain the {@link org.apache.jena.fuseki.server.ServerConfig} + * @return ServerConfig + */ + public ServerConfig getServerConfig() { + return serverConfig ; + } + + // Later : private and in constructor. + private ServletContextHandler buildServer(String jettyConfig, boolean enableCompression) { + if ( jettyConfig != null ) { + // --jetty-config=jetty-fuseki.xml + // for detailed configuration of the server using Jetty features. + server = configServer(jettyConfig) ; + } else + server = defaultServerConfig(serverConfig.port, serverConfig.loopback) ; + // Keep the server to a maximum number of threads. + // server.setThreadPool(new QueuedThreadPool(ThreadPoolSize)) ; + + ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS) ; + context.setErrorHandler(new FusekiErrorHandler()) ; + context.addEventListener(new FusekiServletContextListener(this)); + + // Increase form size. + context.getServletContext().getContextHandler().setMaxFormContentSize(10 * 1000 * 1000) ; + + // Wire up authentication if appropriate + if ( jettyConfig == null && serverConfig.authConfigFile != null ) { + 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", serverConfig.authConfigFile) ; + loginService.setIdentityService(identService) ; + + securityHandler.setLoginService(loginService) ; + securityHandler.setAuthenticator(new BasicAuthenticator()) ; + + context.setSecurityHandler(securityHandler) ; + + serverLog.debug("Basic Auth Configuration = " + serverConfig.authConfigFile) ; + } + + // Wire up context handler to server + server.setHandler(context) ; + + // Constants. Add RDF types. + MimeTypes mt = new MimeTypes() ; + mt.addMimeMapping("rdf", WebContent.contentTypeRDFXML + ";charset=utf-8") ; + mt.addMimeMapping("ttl", WebContent.contentTypeTurtle + ";charset=utf-8") ; + mt.addMimeMapping("nt", WebContent.contentTypeNTriples + ";charset=ascii") ; + mt.addMimeMapping("nq", WebContent.contentTypeNQuads + ";charset=ascii") ; + mt.addMimeMapping("trig", WebContent.contentTypeTriG + ";charset=utf-8") ; + + // mt.addMimeMapping("tpl", "text/html;charset=utf-8") ; + context.setMimeTypes(mt) ; + server.setHandler(context) ; + + serverLog.debug("Pages = " + serverConfig.pages) ; + + boolean installManager = true ; + boolean installServices = true ; + + String validationRoot = "/validate" ; + + // Should all services be /_/.... or some such? + + if ( installManager || installServices ) { + // TODO Respect port. + if ( serverConfig.pagesPort != serverConfig.port ) + serverLog.warn("Not supported yet - pages on a different port to services") ; + if ( serverConfig.pages != null ) { + if ( ! FileOps.exists(serverConfig.pages) ) + serverLog.warn("No pages directory - "+serverConfig.pages) ; + String base = serverConfig.pages ; + Map<String, Object> data = new HashMap<String, Object>() ; + data.put("mgt", new MgtFunctions()) ; + SimpleVelocityServlet templateEngine = new SimpleVelocityServlet(base, data) ; + addServlet(context, templateEngine, "*.tpl", false) ; + } + } + + if ( installManager ) { + // Action when control panel selects a dataset. + HttpServlet datasetChooser = new ActionDataset() ; + addServlet(context, datasetChooser, PageNames.actionDatasetNames, false) ; + } + + if ( installServices ) { + // Validators + HttpServlet validateQuery = new QueryValidator() ; + HttpServlet validateUpdate = new UpdateValidator() ; + HttpServlet validateData = new DataValidator() ; + HttpServlet validateIRI = new IRIValidator() ; + + HttpServlet dumpService = new DumpServlet() ; + HttpServlet generalQueryService = new SPARQL_QueryGeneral() ; + + addServlet(context, validateQuery, validationRoot + "/query", false) ; + addServlet(context, validateUpdate, validationRoot + "/update", false) ; + addServlet(context, validateData, validationRoot + "/data", false) ; + addServlet(context, validateIRI, validationRoot + "/iri", false) ; + + // general query processor. + addServlet(context, generalQueryService, HttpNames.ServiceGeneralQuery, enableCompression) ; + } + + if ( installManager || installServices ) { + String[] files = {"fuseki.html", "index.html"} ; + context.setWelcomeFiles(files) ; + addContent(context, "/", serverConfig.pages) ; + } + + return context ; + } + + /** Experimental - off by default. The überservlet sits on the dataset name and handles all requests. + * Includes direct naming and quad access to the dataset. + */ + public static boolean überServlet = false ; + + private static List<String> ListOfEmptyString = Arrays.asList("") ; + + private void configureOneDataset(ServletContextHandler context, DatasetRef dsDesc, boolean enableCompression) { + String datasetPath = dsDesc.name ; + if ( datasetPath.equals("/") ) + datasetPath = "" ; + else + if ( !datasetPath.startsWith("/") ) + datasetPath = "/" + datasetPath ; + + if ( datasetPath.endsWith("/") ) + datasetPath = datasetPath.substring(0, datasetPath.length() - 1) ; + + dsDesc.init() ; + + DatasetRegistry.get().put(datasetPath, dsDesc) ; + serverLog.info(format("Dataset path = %s", datasetPath)) ; + + HttpServlet sparqlQuery = new SPARQL_QueryDataset() ; + HttpServlet sparqlUpdate = new SPARQL_Update() ; + HttpServlet sparqlUpload = new SPARQL_Upload() ; + HttpServlet sparqlHttpR = new SPARQL_REST_R() ; + HttpServlet sparqlHttpRW = new SPARQL_REST_RW() ; + HttpServlet sparqlDataset = new SPARQL_UberServlet.AccessByConfig() ; + + if ( !überServlet ) { + // If uberserver, these are unnecessary but can be used. + // If just means the überservlet isn't handling these operations. + addServlet(context, datasetPath, sparqlQuery, dsDesc.query, enableCompression) ; + addServlet(context, datasetPath, sparqlUpdate, dsDesc.update, false) ; + addServlet(context, datasetPath, sparqlUpload, dsDesc.upload, false) ; // No point - no results of any size. + addServlet(context, datasetPath, sparqlHttpR, dsDesc.readGraphStore, enableCompression) ; + addServlet(context, datasetPath, sparqlHttpRW, dsDesc.readWriteGraphStore, enableCompression) ; + // This adds direct operations on the dataset itself. + // addServlet(context, datasetPath, sparqlDataset, + // ListOfEmptyString, enableCompression) ; + } else { + // This is the servlet that analyses requests and dispatches them to + // the appropriate servlet. + // SPARQL Query, SPARQL Update -- handles dataset?query= + // dataset?update= + // Graph Store Protocol (direct and indirect naming) if enabled. + // GET/PUT/POST on the dataset itself. + // It also checks for a request that looks like a service request + // and passes it + // on to the service (this takes precedence over direct naming). + addServlet(context, datasetPath, sparqlDataset, epDataset, enableCompression) ; + } + + // Add JMX beans to record daatset and it's services. + addJMX(dsDesc) ; + } + + private static Server configServer(String jettyConfig) { + try { + serverLog.info("Jetty server config file = " + jettyConfig) ; + Server server = new Server() ; + XmlConfiguration configuration = new XmlConfiguration(new FileInputStream(jettyConfig)) ; + configuration.configure(server) ; + return server ; + } 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 static Server defaultServerConfig(int port, boolean loopback) { + // Server, with one NIO-based connector, large input buffer size (for + // long URLs, POSTed forms (queries, updates)). + Server server = new Server() ; + + // Using "= new SelectChannelConnector() ;" on Darwin (OS/X) causes + // problems + // with initialization not seen (thread scheduling?) in Joseki. + + // BlockingChannelConnector is better for pumping large responses back + // but there have been observed problems with DirectMemory allocation + // (-XX:MaxDirectMemorySize=1G does not help) + // Connector connector = new SelectChannelConnector() ; + + // Connector and specific settings. + BlockingChannelConnector bcConnector = new BlockingChannelConnector() ; + // bcConnector.setUseDirectBuffers(false) ; + + Connector connector = bcConnector ; + // Ignore. If set, then if this goes off, it keeps going off + // and you get a lot of log messages. + connector.setMaxIdleTime(0) ; // Jetty outputs a lot of messages if this + // goes off. + if ( loopback ) + connector.setHost("localhost"); + connector.setPort(port) ; + // Some people do try very large operations ... + connector.setRequestHeaderSize(64 * 1024) ; + connector.setRequestBufferSize(5 * 1024 * 1024) ; + connector.setResponseBufferSize(5 * 1024 * 1024) ; + server.addConnector(connector) ; + return server ; + } + + private static void addContent(ServletContextHandler context, String pathSpec, String pages) { + DefaultServlet staticServlet = new DefaultServlet() ; + ServletHolder staticContent = new ServletHolder(staticServlet) ; + staticContent.setInitParameter("resourceBase", pages) ; + + // Note we set GZip to false for static content because the Jetty + // DefaultServlet has + // a built-in GZip capability that is better for static content than the + // mechanism the + // GzipFilter uses for dynamic content + addServlet(context, staticContent, pathSpec, false) ; + } + + private void addServlet(ServletContextHandler context, String datasetPath, HttpServlet servlet, + ServiceRef serviceRef, boolean enableCompression) { + addServlet(context, datasetPath, servlet, serviceRef.endpoints, enableCompression) ; + } + + // SHARE + private static void addServlet(ServletContextHandler context, String datasetPath, HttpServlet servlet, + List<String> pathSpecs, boolean enableCompression) { + for (String pathSpec : pathSpecs) { + if ( pathSpec.equals("") ) { + // "" is special -- add as "base" and "base/" + addServlet(context, servlet, datasetPath + "/", enableCompression) ; + addServlet(context, servlet, datasetPath, enableCompression) ; + continue ; + } + + if ( pathSpec.endsWith("/") ) + pathSpec = pathSpec.substring(0, pathSpec.length() - 1) ; + if ( pathSpec.startsWith("/") ) + pathSpec = pathSpec.substring(1, pathSpec.length()) ; + addServlet(context, servlet, datasetPath + "/" + pathSpec, enableCompression) ; + } + } + + private static void addServlet(ServletContextHandler context, HttpServlet servlet, String pathSpec, + boolean enableCompression) { + ServletHolder holder = new ServletHolder(servlet) ; + addServlet(context, holder, pathSpec, enableCompression) ; + } + + private static void addServlet(ServletContextHandler context, ServletHolder holder, String pathSpec, + boolean enableCompression) { + if ( serverLog.isDebugEnabled() ) { + if ( enableCompression ) + serverLog.debug("Add servlet @ " + pathSpec + " (with gzip)") ; + else + serverLog.debug("Add servlet @ " + pathSpec) ; + } + context.addServlet(holder, pathSpec) ; + + if ( enableCompression ) + context.addFilter(GzipFilter.class, pathSpec, EnumSet.allOf(DispatcherType.class)) ; + } + + private void addJMX() { + DatasetRegistry registry = DatasetRegistry.get() ; + for (String ds : registry.keys()) { + DatasetRef dsRef = registry.get(ds) ; + addJMX(dsRef) ; + } + } + + private void addJMX(DatasetRef dsRef) { + String x = dsRef.name ; + // if ( x.startsWith("/") ) + // x = x.substring(1) ; + ARQMgt.register(Fuseki.PATH + ".dataset:name=" + x, dsRef) ; + // For all endpoints + for (ServiceRef sRef : dsRef.getServiceRefs()) { + ARQMgt.register(Fuseki.PATH + ".dataset:name=" + x + "/" + sRef.name, sRef) ; + } + } + + private void removeJMX() { + DatasetRegistry registry = DatasetRegistry.get() ; + for (String ds : registry.keys()) { + DatasetRef ref = registry.get(ds) ; + } + } + + private void removeJMX(DatasetRef dsRef) { + String x = dsRef.getName() ; + ARQMgt.unregister(Fuseki.PATH + ".dataset:name=" + x) ; + for (ServiceRef sRef : dsRef.getServiceRefs()) { + ARQMgt.unregister(Fuseki.PATH + ".dataset:name=" + x + "/" + sRef.name) ; + } + } +} http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/ServerConfig.java ---------------------------------------------------------------------- diff --git a/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/ServerConfig.java b/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/ServerConfig.java new file mode 100644 index 0000000..4e0b865 --- /dev/null +++ b/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/ServerConfig.java @@ -0,0 +1,55 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.server; + +import java.util.List ; + +/** This represents a configuration of a SPARQL server. + */ + +public class ServerConfig +{ + public ServerConfig() {} + + /** Port to run the server service on */ + public int port ; + /** Port for the management interface : -1 for no management interface */ + public int mgtPort ; + /** Port for the pages UI : this can be the same as the services port. */ + public int pagesPort ; + /** 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 ; + /** The list of datasets */ + public List<DatasetRef> datasets ; + /** Enable Accept-Encoding compression. Set to false by default.*/ + public boolean enableCompression = false ; + + /** Enable additional logging */ + public boolean verboseLogging = false ; + /** + * Authentication config file used to setup Jetty Basic auth, if a Jetty config file was set this is ignored since Jetty config allows much more complex auth methods to be implemented + */ + public String authConfigFile ; + +} + http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/ServiceMXBean.java ---------------------------------------------------------------------- diff --git a/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/ServiceMXBean.java b/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/ServiceMXBean.java new file mode 100644 index 0000000..11c7330 --- /dev/null +++ b/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/ServiceMXBean.java @@ -0,0 +1,32 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.server; + +public interface ServiceMXBean +{ + String getName() ; + + long getRequests() ; + long getRequestsGood() ; + long getRequestsBad() ; + +// void enable() ; +// void disable() ; +} + http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/ServiceRef.java ---------------------------------------------------------------------- diff --git a/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/ServiceRef.java b/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/ServiceRef.java new file mode 100644 index 0000000..6236050 --- /dev/null +++ b/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/ServiceRef.java @@ -0,0 +1,63 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.server; + +import java.util.ArrayList ; +import java.util.List ; + +/** Configuration of an individual service */ +public class ServiceRef implements ServiceMXBean, Counters +{ + public final String name ; + + // Service-level counters. + private final CounterSet counters = new CounterSet() ; + @Override + public CounterSet getCounters() { return counters ; } + + /** Endpoints (as absolute path URLs) */ + public List<String> endpoints = new ArrayList<String>() ; + + // Attach counters to services or datasets + // Can we have a counter of the same name on different services? + // Cost : number of maps. + // +ve: Multiple services with the same name counter + + public ServiceRef(String serviceName) { + this.name = serviceName ; + } + + public boolean isActive() { return endpoints.isEmpty() ; } + + @Override + public String getName() { return name ; } + + @Override public long getRequests() { + return counters.value(CounterName.Requests) ; + } + @Override + public long getRequestsGood() { + return counters.value(CounterName.RequestsGood) ; + } + @Override + public long getRequestsBad() { + return counters.value(CounterName.RequestsBad) ; + } +} + http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/ActionErrorException.java ---------------------------------------------------------------------- diff --git a/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/ActionErrorException.java b/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/ActionErrorException.java new file mode 100644 index 0000000..6c5ebe9 --- /dev/null +++ b/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/ActionErrorException.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.servlets; + +class ActionErrorException extends RuntimeException +{ + final Throwable exception ; + final String message ; + final int rc ; + ActionErrorException(Throwable ex, String message, int rc) + { + this.exception = ex ; + this.message = message ; + this.rc = rc ; + } +} http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/ConcurrencyPolicyMRSW.java ---------------------------------------------------------------------- diff --git a/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/ConcurrencyPolicyMRSW.java b/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/ConcurrencyPolicyMRSW.java new file mode 100644 index 0000000..259453d --- /dev/null +++ b/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/ConcurrencyPolicyMRSW.java @@ -0,0 +1,113 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.servlets; + +import java.util.ConcurrentModificationException ; +import java.util.concurrent.atomic.AtomicLong ; + +import org.apache.jena.fuseki.Fuseki ; +import org.slf4j.Logger ; + +public final class ConcurrencyPolicyMRSW +{ + static private Logger log = Fuseki.requestLog ; //org.slf4j.LoggerFactory.getLogger(ConcurrencyPolicyMRSW.class) ; + static private final boolean logging = false ; //log.isDebugEnabled() ; + + // This is a simplified version of ConcurrencyPolicyMRSW from TDB. + private final AtomicLong readCounter = new AtomicLong(0) ; + private final AtomicLong writeCounter = new AtomicLong(0) ; + static private final AtomicLong policyCounter = new AtomicLong(0) ; + + public ConcurrencyPolicyMRSW() + { policyCounter.incrementAndGet() ; } + + // Loggin -inside the operation. + + //@Override + public void startRead() + { + readCounter.getAndIncrement() ; + log() ; + checkConcurrency() ; + } + + //@Override + public void finishRead() + { + log() ; + readCounter.decrementAndGet() ; + checkConcurrency() ; + } + + //@Override + public void startUpdate() + { + writeCounter.getAndIncrement() ; + log() ; + checkConcurrency() ; + } + + //@Override + public void finishUpdate() + { + log() ; + writeCounter.decrementAndGet() ; + checkConcurrency() ; + } + + private synchronized void checkConcurrency() + { + long R = readCounter.get() ; + long W = writeCounter.get() ; + long id = policyCounter.get(); + if ( R > 0 && W > 0 ) + policyError(id, R, W) ; + if ( W > 1 ) + policyError(id, R, W) ; + } + + private void log() + { + if ( ! logging ) + return ; + long R , W , id ; + synchronized(this) + { + R = readCounter.get() ; + W = writeCounter.get() ; + id = policyCounter.get(); + } + log.info(format(id, R, W)) ; + } + + private static void policyError(long id, long R, long W) + { + policyError(format(id, R, W)) ; + } + + private static void policyError(String message) + { + throw new ConcurrentModificationException(message) ; + } + + private static String format(long id, long R, long W) + { + return String.format("(lock=%d) Reader = %d, Writer = %d", id, R, W) ; + } +} http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/DumpServlet.java ---------------------------------------------------------------------- diff --git a/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/DumpServlet.java b/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/DumpServlet.java new file mode 100644 index 0000000..7ece249 --- /dev/null +++ b/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/DumpServlet.java @@ -0,0 +1,313 @@ +/* + * 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. + */ + +/** A servlet that dumps its request + */ + +// Could be neater - much, much neater! +package org.apache.jena.fuseki.servlets; + +import java.io.BufferedReader ; +import java.io.IOException ; +import java.io.PrintWriter ; +import java.io.StringWriter ; +import java.util.Date ; +import java.util.Enumeration ; +import java.util.Locale ; +import java.util.Properties ; + +import javax.servlet.ServletContext ; +import javax.servlet.http.Cookie ; +import javax.servlet.http.HttpServlet ; +import javax.servlet.http.HttpServletRequest ; +import javax.servlet.http.HttpServletResponse ; + +public class DumpServlet extends HttpServlet +{ + private static final long serialVersionUID = 99L; // Serilizable. + + + public DumpServlet() + { + + } + + @Override + public void init() + { + return ; + } + + @Override + public void doGet(HttpServletRequest req, HttpServletResponse resp) + { + try { + PrintWriter out = resp.getWriter() ; + resp.setContentType("text/html"); + + String now = new Date().toString() ; + + // HEAD + out.println("<html>") ; + out.println("<head>") ; + out.println("<Title>Dump @ "+now+"</Title>") ; + // Reduce the desire to cache it. + out.println("<meta CONTENT=now HTTP-EQUIV=expires>") ; + out.println("</head>") ; + + // BODY + out.println("<body>") ; + out.println("<pre>") ; + + out.println("Dump : "+now); + out.println() ; + out.println("==== Request"); + out.println() ; + out.print(dumpRequest(req)) ; + out.println() ; + + out.println(">>>> Body"); + out.println() ; + printBody(out, req) ; + out.println("<<<< Body"); + + out.println("==== ServletContext"); + out.println() ; + out.print(dumpServletContext()); + out.println() ; + + out.println("==== Environment"); + out.println() ; + out.print(dumpEnvironment()); + out.println() ; + + out.println("</pre>") ; + + out.println("</body>") ; + out.println("</html>") ; + out.flush() ; + } catch (IOException e) + { } + } + + // This resets the input stream + + static public String dumpRequest(HttpServletRequest req) + { + StringWriter sw = new StringWriter() ; + try( PrintWriter pw = new PrintWriter(sw) ) { + // Standard environment + pw.println("Method: "+req.getMethod()); + pw.println("getContentLength: "+Integer.toString(req.getContentLength())); + pw.println("getContentType: "+req.getContentType()); + pw.println("getRequestURI: "+req.getRequestURI()); + pw.println("getRequestURL: "+req.getRequestURL()); + pw.println("getContextPath: "+req.getContextPath()); + pw.println("getServletPath: "+req.getServletPath()); + pw.println("getPathInfo: "+req.getPathInfo()); + pw.println("getPathTranslated: "+req.getPathTranslated()); + pw.println("getQueryString: "+req.getQueryString()); + pw.println("getProtocol: "+req.getProtocol()); + pw.println("getScheme: "+req.getScheme()); + pw.println("getServerName: "+req.getServerName()); + pw.println("getServerPort: "+req.getServerPort()); + pw.println("getRemoteUser: "+req.getRemoteUser()); + pw.println("getRemoteAddr: "+req.getRemoteAddr()); + pw.println("getRemoteHost: "+req.getRemoteHost()); + pw.println("getRequestedSessionId: "+req.getRequestedSessionId()); + { + Cookie c[] = req.getCookies() ; + if ( c == null ) + pw.println("getCookies: <none>"); + else + { + for ( Cookie aC : c ) + { + pw.println( "Cookie: " + aC.getName() ); + pw.println( " value: " + aC.getValue() ); + pw.println( " version: " + aC.getVersion() ); + pw.println( " comment: " + aC.getComment() ); + pw.println( " domain: " + aC.getDomain() ); + pw.println( " maxAge: " + aC.getMaxAge() ); + pw.println( " path: " + aC.getPath() ); + pw.println( " secure: " + aC.getSecure() ); + pw.println(); + } + } + } + + { + // To do: create a string for the output so can send to console and return it. + Enumeration<String> en = req.getHeaderNames() ; + + for ( ; en.hasMoreElements() ; ) + { + String name = en.nextElement() ; + String value = req.getHeader(name) ; + pw.println("Head: "+name + " = " + value) ; + } + } + + Enumeration<String> en2 = req.getAttributeNames() ; + if ( en2.hasMoreElements() ) + pw.println(); + for ( ; en2.hasMoreElements() ; ) + { + String name = en2.nextElement() ; + String value = req.getAttribute(name).toString() ; + pw.println("Attr: "+name + " = " + value) ; + } + + // Note that doing this on a form causes the forms content (body) to be read + // and parsed as form variables. +// en = req.getParameterNames() ; +// if ( en.hasMoreElements() ) +// pw.println(); +// for ( ; en.hasMoreElements() ; ) +// { +// String name = (String)en.nextElement() ; +// String value = req.getParameter(name) ; +// pw.println("Param: "+name + " = " + value) ; +// } + + + +// MultiMap<String, String> map = WebLib.parseQueryString(req) ; +// for ( String name : map.keys() ) +// for ( String value : map.get(name) ) +// pw.println("Param: "+name + " = " + value) ; + + Enumeration<Locale> en = req.getLocales() ; + if ( en.hasMoreElements() ) + pw.println(); + for ( ; en.hasMoreElements() ; ) + { + String name = en.nextElement().toString() ; + pw.println("Locale: "+name) ; + } + + pw.println() ; + pw.flush(); + //printBody(pw, req) ; + return sw.toString() ; + } + + } + + static void printBody(PrintWriter pw, HttpServletRequest req) throws IOException + { + BufferedReader in = req.getReader() ; + if ( req.getContentLength() > 0 ) + // Need +2 because last line may not have a CR/LF on it. + in.mark(req.getContentLength()+2) ; + else + // This is a dump - try to do something that works, even if inefficient. + in.mark(100*1024) ; + + while(true) + { + String x = in.readLine() ; + if ( x == null ) + break ; + x = x.replaceAll("&", "&") ; + x = x.replaceAll("<", "<") ; + x = x.replaceAll(">", ">") ; + pw.println(x) ; + } + try { in.reset() ;} catch (IOException e) { System.out.println("DumpServlet: Reset of content failed: "+e) ; } + } + + /** + * <code>dumpEnvironment</code> + * @return String that is the HTML of the System properties as name/value pairs. + * The values are with single quotes independent of whether or not the value has + * single quotes in it. + */ + static public String dumpEnvironment() + { + Properties properties = System.getProperties(); + + StringWriter sw = new StringWriter() ; + try(PrintWriter pw = new PrintWriter(sw) ) { + Enumeration<Object> en = properties.keys(); + while(en.hasMoreElements()) + { + String key = en.nextElement().toString(); + pw.println(key+": '"+properties.getProperty(key)+"'"); + } + pw.println() ; + pw.flush() ; + return sw.toString() ; + } + } + + public String dumpServletContext() + { + StringWriter sw = new StringWriter() ; + try(PrintWriter pw = new PrintWriter(sw)) { + + ServletContext sc = getServletContext(); + pw.println("majorVersion: '"+sc.getMajorVersion()+"'"); + pw.println("minorVersion: '"+sc.getMinorVersion()+"'"); + pw.println("contextName: '"+sc.getServletContextName()+"'"); + pw.println("servletInfo: '"+getServletInfo()+"'"); + pw.println("serverInfo: '"+sc.getServerInfo()+"'"); + + { + Enumeration<String> en = sc.getInitParameterNames(); + if (en != null) { + pw.println("initParameters: "); + while(en.hasMoreElements()) + { + String key = en.nextElement(); + pw.println(key+": '"+sc.getInitParameter(key)+"'"); + } + } + } + + { + Enumeration<String> en = sc.getAttributeNames(); + if (en != null) { + pw.println("attributes: "); + while(en.hasMoreElements()) + { + String key = en.nextElement(); + pw.println(key+": '"+sc.getAttribute(key)+"'"); + } + } + } + pw.println() ; + pw.close() ; + } + return sw.toString() ; + } + + + @Override + public void doPost(HttpServletRequest req, HttpServletResponse resp) + { + doGet(req, resp) ; + } + + + @Override + public String getServletInfo() + { + return "Dump"; + } +} http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/HttpAction.java ---------------------------------------------------------------------- diff --git a/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/HttpAction.java b/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/HttpAction.java new file mode 100644 index 0000000..39663dd --- /dev/null +++ b/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/HttpAction.java @@ -0,0 +1,290 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.servlets; + +import static com.hp.hpl.jena.query.ReadWrite.READ ; +import static com.hp.hpl.jena.query.ReadWrite.WRITE ; + +import java.util.HashMap ; +import java.util.Map ; + +import javax.servlet.http.HttpServletRequest ; +import javax.servlet.http.HttpServletResponse ; + +import org.apache.jena.atlas.logging.Log ; +import org.apache.jena.atlas.web.MediaType ; +import org.apache.jena.fuseki.DEF ; +import org.apache.jena.fuseki.conneg.ConNeg ; +import org.apache.jena.fuseki.server.DatasetRef ; +import org.apache.jena.fuseki.server.ServiceRef ; + +import com.hp.hpl.jena.query.ReadWrite ; +import com.hp.hpl.jena.sparql.SystemARQ ; +import com.hp.hpl.jena.sparql.core.DatasetGraph ; +import com.hp.hpl.jena.sparql.core.DatasetGraphWithLock ; +import com.hp.hpl.jena.sparql.core.DatasetGraphWrapper ; +import com.hp.hpl.jena.sparql.core.Transactional ; + +/** + * HTTP action that represents the user request lifecycle. Its state is handled in the + * {@link SPARQL_ServletBase#executeLifecycle(HttpAction)} method. + */ +public class HttpAction +{ + public final long id ; + public final boolean verbose ; + + // Phase two items - set and valida after the datasetRef is known. + private DatasetGraph dsg ; // The data + public DatasetRef dsRef ; + public ServiceRef srvRef ; + + private Transactional transactional ; + private boolean isTransactional; + private DatasetGraph activeDSG ; // Set when inside begin/end. + private ReadWrite activeMode ; // Set when inside begin/end. + + private boolean startTimeIsSet = false ; + private boolean finishTimeIsSet = false ; + + private long startTime = -2 ; + private long finishTime = -2 ; + + // Incoming + //public final + + // Outcome. + int statusCode = -1 ; + String message = null ; + int contentLength = -1 ; + String contentType = null ; + + // Cleared to archive: + Map <String, String> headers = new HashMap<String, String>() ; + public HttpServletRequest request; + public HttpServletResponseTracker response ; + + /** + * Creates a new HTTP Action, using the HTTP request and response, and a given ID. + * + * @param id given ID + * @param request HTTP request + * @param response HTTP response + * @param verbose verbose flag + */ + public HttpAction(long id, HttpServletRequest request, HttpServletResponse response, boolean verbose) { + this.id = id ; + this.request = request ; + this.response = new HttpServletResponseTracker(this, response) ; + // Should this be set when setDataset is called from the dataset context? + // Currently server-wide, e.g. from the command line. + this.verbose = verbose ; + } + + /** + * <p>Sets the action dataset. Setting a {@link DatasetRef} will replace any existing DatasetRef, as well as + * as the {@link DatasetGraph} of the current HTTP Action.</p> + * + * <p>Once it has updated its members, the HTTP Action will change its transactional state and + * {@link Transactional} instance according to its base dataset graph.</p> + * + * @param desc {@link DatasetRef} + * @see Transactional + * @see DatasetGraphWrapper + */ + public void setDataset(DatasetRef desc) { + this.dsRef = desc ; + this.dsg = desc.dataset ; + DatasetGraph basedsg = unwrap(dsg) ; + + if ( isTransactional(basedsg) && isTransactional(dsg) ) { + // Use transactional if it looks safe - abort is necessary. + transactional = (Transactional)dsg ; + isTransactional = true ; + } else { + // Unsure if safe + transactional = new DatasetGraphWithLock(dsg) ; + // No real abort. + isTransactional = false ; + } + } + + /** + * Returns <code>true</code> iff the given {@link DatasetGraph} is an instance of {@link Transactional}, + * <code>false otherwise</code>. + * + * @param dsg a {@link DatasetGraph} + * @return <code>true</code> iff the given {@link DatasetGraph} is an instance of {@link Transactional}, + * <code>false otherwise</code> + */ + private static boolean isTransactional(DatasetGraph dsg) { + return (dsg instanceof Transactional) ; + } + + /** + * A {@link DatasetGraph} may contain other <strong>wrapped DatasetGraph's</strong>. This method will return + * the first instance (including the argument to this method) that <strong>is not</strong> an instance of + * {@link DatasetGraphWrapper}. + * + * @param dsg a {@link DatasetGraph} + * @return the first found {@link DatasetGraph} that is not an instance of {@link DatasetGraphWrapper} + */ + private static DatasetGraph unwrap(DatasetGraph dsg) { + while (dsg instanceof DatasetGraphWrapper) { + dsg = ((DatasetGraphWrapper)dsg).getWrapped() ; + } + return dsg ; + } + + /** + * Sets the {@link ServiceRef}. + * + * @param srvRef a {@link ServiceRef} + */ + public void setService(ServiceRef srvRef) { + this.srvRef = srvRef ; + } + + /** + * Returns whether or not the underlying DatasetGraph is fully transactional (supports rollback). + * @return <code>true</code> if the underlying DatasetGraph is fully transactional (supports rollback), + * <code>false</code> otherwise. + */ + public boolean isTransactional() { + return isTransactional ; + } + + public void beginRead() { + activeMode = READ ; + transactional.begin(READ) ; + activeDSG = dsg ; + dsRef.startTxn(READ) ; + } + + public void endRead() { + dsRef.finishTxn(READ) ; + activeMode = null ; + transactional.end() ; + activeDSG = null ; + } + + public void beginWrite() { + transactional.begin(WRITE) ; + activeMode = WRITE ; + activeDSG = dsg ; + dsRef.startTxn(WRITE) ; + } + + public void commit() { + transactional.commit() ; + activeDSG = null ; + } + + public void abort() { + try { transactional.abort() ; } + catch (Exception ex) { + // Some datasets claim to be transactional but + // don't provide a real abort. We tried to avoid + // them earlier but even if they sneek through, + // we try to continue server operation. + Log.warn(this, "Exception during abort (operation attempts to continue): "+ex.getMessage()) ; + } + activeDSG = null ; + } + + public void endWrite() { + dsRef.finishTxn(WRITE) ; + activeMode = null ; + + if ( transactional.isInTransaction() ) { + Log.warn(this, "Transaction still active in endWriter - no commit or abort seen (forced abort)") ; + try { + transactional.abort() ; + } catch (RuntimeException ex) { + Log.warn(this, "Exception in forced abort (trying to continue)", ex) ; + } + } + transactional.end() ; + activeDSG = null ; + } + + public final DatasetGraph getActiveDSG() { + return activeDSG ; + } + + public final DatasetRef getDatasetRef() { + return dsRef ; + } + + /** Reduce to a size that can be kept around for sometime */ + public void minimize() { + this.request = null ; + this.response = null ; + } + + public void setStartTime() { + if ( startTimeIsSet ) + Log.warn(this, "Start time reset") ; + startTimeIsSet = true ; + this.startTime = System.nanoTime() ; + } + + public void setFinishTime() { + if ( finishTimeIsSet ) + Log.warn(this, "Finish time reset") ; + finishTimeIsSet = true ; + this.finishTime = System.nanoTime() ; + } + + public HttpServletRequest getRequest() { return request ; } + + public HttpServletResponseTracker getResponse() { return response ; } + + /** Return the recorded time taken in milliseconds. + * {@linkplain #setStartTime} and {@linkplain #setFinishTime} + * must have been called. + */ + public long getTime() + { + if ( ! startTimeIsSet ) + Log.warn(this, "Start time not set") ; + if ( ! finishTimeIsSet ) + Log.warn(this, "Finish time not set") ; + return (finishTime-startTime)/(1000*1000) ; + } + + public void sync() { + SystemARQ.sync(dsg) ; + } + + public static MediaType contentNegotationRDF(HttpAction action) { + MediaType mt = ConNeg.chooseContentType(action.request, DEF.rdfOffer, DEF.acceptRDFXML) ; + 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 ; + } + + public static MediaType contentNegotationQuads(HttpAction action) { + return ConNeg.chooseContentType(action.request, DEF.quadsOffer, DEF.acceptNQuads) ; + } +} http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/HttpServletResponseTracker.java ---------------------------------------------------------------------- diff --git a/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/HttpServletResponseTracker.java b/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/HttpServletResponseTracker.java new file mode 100644 index 0000000..c39e728 --- /dev/null +++ b/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/HttpServletResponseTracker.java @@ -0,0 +1,140 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.servlets; + +import java.io.IOException ; + +import javax.servlet.http.HttpServletResponse ; +import javax.servlet.http.HttpServletResponseWrapper ; + +import org.apache.jena.atlas.logging.Log ; + +/** Intercepting wrapper so we can track the response settings for logging purposes */ + +public class HttpServletResponseTracker extends HttpServletResponseWrapper +{ + private final HttpAction action ; + + public HttpServletResponseTracker(HttpAction action, HttpServletResponse response) + { + super(response) ; + this.action = action ; + } + + @Override + public void sendError(int sc, String msg) throws IOException + { + action.statusCode = sc ; + action.message = msg ; + super.sendError(sc, msg) ; + } + + @Override + public void sendError(int sc) throws IOException + { + action.statusCode = sc ; + action.message = null ; + super.sendError(sc) ; + } + + @Override + public void setHeader(String name, String value) + { + super.setHeader(name, value) ; + action.headers.put(name, value) ; + } + + @Override + public void addHeader(String name, String value) + { + Log.warn(this, "Unexpected addHeader - not recorded in log") ; + super.addHeader(name, value) ; + } + @Override + public void setStatus(int sc) + { + action.statusCode = sc ; + action.message = null ; + super.setStatus(sc) ; + } + + @Override + @Deprecated + public void setStatus(int sc, String sm) + { + action.statusCode = sc ; + action.message = sm ; + super.setStatus(sc, sm) ; + } + + @Override + public void setContentLength(int len) + { + action.contentLength = len ; + super.setContentLength(len) ; + } + + @Override + public void setContentType(String type) + { + action.contentType = type ; + super.setContentType(type) ; + } + + // From HttpServletResponse +// public void addCookie(Cookie cookie) {} +// public boolean containsHeader(String name) {} +// public String encodeURL(String url) { } +// public String encodeRedirectURL(String url) {} +// public String encodeUrl(String url) {} +// public String encodeRedirectUrl(String url) {} +// public void sendError(int sc, String msg) throws IOException +// public void sendError(int sc) throws IOException +// public void sendRedirect(String location) throws IOException {} +// public void setDateHeader(String name, long date) {} +// public void addDateHeader(String name, long date) {} +// public void setHeader(String name, String value) +// public void addHeader(String name, String value) +// public void setIntHeader(String name, int value) {} +// public void addIntHeader(String name, int value) {} +// public void setStatus(int sc) +// public void setStatus(int sc, String sm) +// public void sendRedirect(String location) throws IOException {} +// public void setDateHeader(String name, long date) {} +// public void addDateHeader(String name, long date) {} + + // From ServletResponse. +// public ServletResponse getResponse() {} +// public void setResponse(ServletResponse response) {} +// public void setCharacterEncoding(String charset) {} +// public String getCharacterEncoding() {} +// public ServletOutputStream getOutputStream() throws IOException {} +// public PrintWriter getWriter() throws IOException {} +// public void setContentLength(int len) {} +// public void setContentType(String type) {} +// public String getContentType() { +// public void setBufferSize(int size) {} +// public int getBufferSize() {} +// public void flushBuffer() throws IOException {} +// public boolean isCommitted() {} +// public void reset() {} +// public void resetBuffer() {} +// public void setLocale(Locale loc) {} +// public Locale getLocale() {} +} http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/NullOutputStream.java ---------------------------------------------------------------------- diff --git a/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/NullOutputStream.java b/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/NullOutputStream.java new file mode 100644 index 0000000..63e6562 --- /dev/null +++ b/jena-fuseki/src/main/java/org/apache/jena/fuseki/servlets/NullOutputStream.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.servlets; + +import java.io.* ; + +/** +* Code needed to implement an OutputStream that does nothing. +*/ + + +public class NullOutputStream extends /*Filter*/OutputStream +{ + public NullOutputStream() + { + } + + // The OutputStream operations + @Override + public void close() { /* .close() ;*/ } + @Override + public void flush() { /* .flush() ;*/ } + + // Need to implement this one. + @Override + public void write(int b) { /* .write(b) ;*/ } + @Override + public void write(byte b[]) { /* this.write(b, 0, b.length) ; */} + + // Good to implement this one. + @Override + public void write(byte[] b, int off, int len) + { + // Work function + } + +}
