http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/FusekiServletContextListener.java ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/FusekiServletContextListener.java b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/FusekiServletContextListener.java new file mode 100644 index 0000000..88c7436 --- /dev/null +++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/FusekiServletContextListener.java @@ -0,0 +1,94 @@ +/** + * 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.ServletContext ; +import javax.servlet.ServletContextEvent ; +import javax.servlet.ServletContextListener ; + +import org.apache.jena.fuseki.Fuseki ; +import org.slf4j.Logger ; + +public class FusekiServletContextListener implements ServletContextListener { + private static Logger confLog = Fuseki.configLog ; + + public FusekiServletContextListener() { + confLog.debug("FusekiServletContextListener created") ; + } + + public static ServerInitialConfig initialSetup = null ; + + private volatile Boolean initialized = false ; + + @Override + public void contextInitialized(ServletContextEvent sce) { + ServletContext servletContext = sce.getServletContext() ; + String x = servletContext.getContextPath() ; + if ( ! x.isEmpty() ) + confLog.info("Context path = "+x) ; +// String x = System.getProperty("user.dir") ; +// Path currentRelativePath = Paths.get(""); +// String s = currentRelativePath.toAbsolutePath().toString(); +// confLog.info("dir1 = "+x+" : dir2 = "+s) ; + init() ; + } + + @Override + public void contextDestroyed(ServletContextEvent sce) {} + + public void init() { + if ( initialized ) + return ; + synchronized(initialized) + { + if ( initialized ) + return ; + initialized = true ; + + try { + FusekiServer.init() ; + if ( ! FusekiServer.serverInitialized ) { + Fuseki.serverLog.error("Failed to initialize : Server not running") ; + return ; + } + + if ( initialSetup == null ) { + initialSetup = new ServerInitialConfig() ; + String cfg = FusekiServer.FUSEKI_BASE.resolve(FusekiServer.DFT_CONFIG).toAbsolutePath().toString() ; + initialSetup.fusekiConfigFile = cfg ; + } + + if ( initialSetup != null ) { + FusekiServer.initializeDataAccessPoints(initialSetup, FusekiServer.dirConfiguration.toString()) ; + } else { + Fuseki.serverLog.error("No configuration") ; + System.exit(0) ; + } + } catch (Throwable th) { + Fuseki.serverLog.error("Exception in initialization", th) ; + throw th ; + } + } + } + + public static String chooseFusekiDirectory() { + return "" ; + } +} +
http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/FusekiVocab.java ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/FusekiVocab.java b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/FusekiVocab.java new file mode 100644 index 0000000..ddae32c --- /dev/null +++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/FusekiVocab.java @@ -0,0 +1,77 @@ +/* + * 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.* ; + +public class FusekiVocab +{ + public static String NS = "http://jena.apache.org/fuseki#" ; + private static Model model = ModelFactory.createDefaultModel() ; + + public static final Resource tServer = resource("Server") ; + + public static final Resource fusekiService = resource("Service") ; + + 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"); + + // Internal + + private static final String stateNameActive = DatasetStatus.ACTIVE.name ; + private static final String stateNameOffline = DatasetStatus.OFFLINE.name ; + private static final String stateNameClosing = DatasetStatus.CLOSING.name ; + private static final String stateNameClosed = DatasetStatus.CLOSED.name ; + + public static final Resource stateActive = resource(stateNameActive) ; + public static final Resource stateOffline = resource(stateNameOffline) ; + public static final Resource stateClosing = resource(stateNameClosing) ; + public static final Resource stateClosed = resource(stateNameClosed) ; + + public static final Property pStatus = property("status") ; + + private static Resource resource(String localname) { return model.createResource(iri(localname)) ; } + private static Property property(String localname) { return model.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-fuseki2/src/main/java/org/apache/jena/fuseki/server/OperationName.java ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/OperationName.java b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/OperationName.java new file mode 100644 index 0000000..0abcf20 --- /dev/null +++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/OperationName.java @@ -0,0 +1,37 @@ +/** + * 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 enum OperationName { + // Fixed names give the codebase some resilience. + + Query("SPARQL Query"), + Update("SPARQL Update"), + Upload("File Upload"), + GSP("Graph Store Protocol"), + GSP_R("Graph Store Protocol (Read)"), + Quads("HTTP Quads") + ; + + private final String description ; + private OperationName(String description) { this.description = description ; } + public String getDescription() { return description ; } + +} + http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/RequestLog.java ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/RequestLog.java b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/RequestLog.java new file mode 100644 index 0000000..db79d6a --- /dev/null +++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/RequestLog.java @@ -0,0 +1,148 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.server; + +import java.text.DateFormat ; +import java.text.SimpleDateFormat ; +import java.util.Collection ; +import java.util.Date ; +import java.util.Enumeration ; +import java.util.TimeZone ; + +import javax.servlet.http.HttpServletRequest ; +import javax.servlet.http.HttpServletResponse ; + +import org.apache.jena.atlas.logging.Log ; +import org.apache.jena.fuseki.servlets.HttpAction ; + +/** Create standard request logs (NCSA etc) */ +public class RequestLog { + /* + http://httpd.apache.org/docs/current/mod/mod_log_config.html +Common Log Format (CLF) + "%h %l %u %t \"%r\" %>s %b" +Common Log Format with Virtual Host + "%v %h %l %u %t \"%r\" %>s %b" +NCSA extended/combined log format + "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" + */ + /* + %l -- Identity or - + %u -- Remote user or - + %t -- Timestamp + "%r" -- Request line + %>s -- Final request status + %b -- Size in bytes + Headers. + %{}i for request header. + %{}o for response header. + */ + + private static DateFormat dateFormatter ; + static { + dateFormatter = new SimpleDateFormat("dd/MMM/yyyy:HH:mm:ss Z") ; + dateFormatter.setTimeZone(TimeZone.getTimeZone("UTC")); + } + + /** NCSA combined log format * + * LogFormat "%{X-Forwarded-For}i %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combinedfwd + * XXX.XXX.XXX.XXX - - [01/Feb/2014:03:19:09 +0000] "GET / HTTP/1.1" 200 6190 "-" "check_http/v1.4.16 (nagios-plugins 1.4.16)" + */ + public static String combinedNCSA(HttpAction action) { + StringBuilder builder = new StringBuilder() ; + HttpServletRequest request = action.request ; + HttpServletResponse response = action.response ; + + // Remote + String remote = get(request, "X-Forwarded-For", request.getRemoteAddr()) ; + builder.append(remote) ; + builder.append(" ") ; + + // %l %u : User identity (unrelaible) + builder.append("- - ") ; + + // %t + // Expensive? + builder.append("["); + // Better? + builder.append(dateFormatter.format(new Date())) ; + builder.append("] "); + + // "%r" + builder.append("\"") ; + builder.append(request.getMethod()) ; + builder.append(" ") ; + // No query string - they are long and logged readably elsewhere + builder.append(request.getRequestURI()) ; + builder.append("\"") ; + //%>s -- Final request status + builder.append(" ") ; + builder.append(response.getStatus()) ; + + //%b -- Size in bytes + builder.append(" ") ; + //String size = getField() + String size = get(response, "Content-Length", "-") ; + builder.append(size) ; + + // "%{Referer}i" + builder.append(" \"") ; + builder.append(get(request, "Referer", "")) ; + builder.append("\"") ; + // "%{User-Agent}i" + builder.append(" \"") ; + builder.append(get(request, "User-Agent", "")) ; + builder.append("\"") ; + + return builder.toString() ; + } + + private static String get(HttpServletRequest request, String name, String dft) { + String x = get(request, name) ; + if ( x == null ) + x = dft ; + return x ; + } + + private static String get(HttpServletRequest request, String name) { + Enumeration<String> en = request.getHeaders(name) ; + if ( ! en.hasMoreElements() ) return null ; + String x = en.nextElement() ; + if ( en.hasMoreElements() ) { + Log.warn(RequestLog.class, "Multiple request header values") ; + } + return x ; + } + + private static String get(HttpServletResponse response, String name, String dft) { + String x = get(response, name) ; + if ( x == null ) + x = dft ; + return x ; + } + + + private static String get(HttpServletResponse response, String name) { + Collection<String> en = response.getHeaders(name) ; + if ( en.isEmpty() )return null ; + if ( en.size() != 1 ) Log.warn(RequestLog.class, "Multiple response header values") ; + return response.getHeader(name) ; + } + +} http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/ServerInitialConfig.java ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/ServerInitialConfig.java b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/ServerInitialConfig.java new file mode 100644 index 0000000..67f5d26 --- /dev/null +++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/ServerInitialConfig.java @@ -0,0 +1,40 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.server; + +import java.util.HashMap ; +import java.util.Map ; + +import com.hp.hpl.jena.sparql.core.DatasetGraph ; + +/** Dataset setup (command line, config file) for a dataset (or several if config file) */ +public class ServerInitialConfig { + // Either this ... + public String templateFile = null ; + public Map<String,String> params = new HashMap<>() ; + public String datasetPath = null ; + public boolean allowUpdate = false ; + // Or this ... + public String fusekiConfigFile = null ; + + // Special case - directly pass in the dataset graphs - datasetPath must be given. + // This is not persistet across server restarts. + public DatasetGraph dsg = null ; + +} http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/ServiceMXBean.java ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/ServiceMXBean.java b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/ServiceMXBean.java new file mode 100644 index 0000000..11c7330 --- /dev/null +++ b/jena-fuseki2/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-fuseki2/src/main/java/org/apache/jena/fuseki/server/ShiroEnvironmentLoader.java ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/ShiroEnvironmentLoader.java b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/ShiroEnvironmentLoader.java new file mode 100644 index 0000000..29a633a --- /dev/null +++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/ShiroEnvironmentLoader.java @@ -0,0 +1,164 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.server; + +import java.io.IOException ; +import java.io.InputStream ; +import java.nio.file.Path ; +import java.nio.file.Paths ; + +import javax.servlet.ServletContext ; +import javax.servlet.ServletContextEvent ; +import javax.servlet.ServletContextListener ; + +import org.apache.jena.fuseki.Fuseki ; +import org.apache.shiro.config.ConfigurationException ; +import org.apache.shiro.io.ResourceUtils ; +import org.apache.shiro.web.env.EnvironmentLoader ; +import org.apache.shiro.web.env.ResourceBasedWebEnvironment ; +import org.apache.shiro.web.env.WebEnvironment ; +import org.slf4j.Logger ; + +import com.hp.hpl.jena.util.FileUtils ; + +/** A place to perform Fuseki-specific initialization of Apache Shiro. + * This means finding shiro.ini in multiple possible places, based on + * different deployment setups. + */ +public class ShiroEnvironmentLoader extends EnvironmentLoader implements ServletContextListener { + private static Logger confLog = Fuseki.configLog ; + private ServletContext servletContext ; + @Override + public void contextInitialized(ServletContextEvent sce) { + FusekiServer.init() ; + this.servletContext = sce.getServletContext() ; + try { + // Shiro. + initEnvironment(servletContext); + } catch (ConfigurationException ex) { + Fuseki.configLog.error("Shiro initialization failed: "+ex.getMessage()); + // Exit? + throw ex ; + } + + } + + @Override + public void contextDestroyed(ServletContextEvent sce) { + destroyEnvironment(sce.getServletContext()); + } + + /** + * Normal Shiro initialization only supports one location for an INI file. + * + * When given multiple multiple locations for the shiro.ini file, and + * if a {@linkplain ResourceBasedWebEnvironment}, check the list of configuration + * locations, testing whether the name identified an existing resource. + * For the first resource name found to exist, reset the {@linkplain ResourceBasedWebEnvironment} + * to name that resource alone so the normal Shiro initialization + */ + @Override + protected void customizeEnvironment(WebEnvironment environment) { + if ( environment instanceof ResourceBasedWebEnvironment ) { + ResourceBasedWebEnvironment env = (ResourceBasedWebEnvironment)environment ; + String[] locations = env.getConfigLocations() ; + String loc = huntForShiroIni(locations) ; + Fuseki.configLog.info("Shiro file: "+loc); + if (loc != null ) + locations = new String[] {loc} ; + env.setConfigLocations(locations); + } + } + + private static final String FILE = "file" ; + + //Siro needs a URL, or a resource name. + // TODO Log choice. + // TODO check file: works. + + /** Look for a Shiro ini file, or return null */ + private static String huntForShiroIni(String[] locations) { + for ( String loc : locations ) { + // If file:, look for that file. + // If a relative name without scheme, look in FUSEKI_BASE, FUSEKI_HOME, webapp. + String scheme = FileUtils.getScheme(loc) ; + + // Covers C:\\ as a "scheme name" + if ( scheme != null ) { + if ( scheme.equalsIgnoreCase(FILE)) { + // Test file: for exists + Path p = Paths.get(loc.substring(FILE.length()+1)) ; + if ( ! p.toFile().exists() ) + continue ; + // Fall through. + } + // Can't test - try + return loc ; + } + // No scheme . + Path p = Paths.get(loc) ; + String fn = resolve(FusekiServer.FUSEKI_BASE, p) ; + if ( fn != null ) + return "file://"+fn ; + fn = resolve(FusekiServer.FUSEKI_HOME, p) ; + if ( fn != null ) + return "file://"+fn ; + + // Try in webapp. + + try ( InputStream is = ResourceUtils.getInputStreamForPath(loc); ) { + boolean exists = (is != null ) ; + return loc ; + } catch (IOException e) { } + } + return null ; + } + + /** Directory + name -> filename if it exists */ + private static String resolve(Path dir, Path file) { + Path p = dir.resolve(file) ; + if ( p.toFile().exists() ) + return p.normalize().toString() ; + return null ; + } + +// /** +// * Test whether a name identified an existing resource +// * @param resource A String in Shiro-resource name format (e.g. URL scheme names) +// * @return True/false as to whether the resource can be found or not. +// */ +// +// private boolean resourceExists(String resource) { +// try { +// // See IniWebEnvironment.convertPathToIni +// if (!ResourceUtils.hasResourcePrefix(resource)) { +// //Sort out "path" and open as a webapp resource. +// resource = WebUtils.normalize(resource); +// URL url = servletContext.getResource(resource) ; +// return ( url == null ) ; +// } else { +// // Treat as a plain name. +// InputStream is = ResourceUtils.getInputStreamForPath(resource); +// boolean exists = (is != null ) ; +// is.close() ; +// return exists ; +// } +// } catch (IOException e) { return false ; } +// } +} http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/SystemState.java ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/SystemState.java b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/SystemState.java new file mode 100644 index 0000000..8351400 --- /dev/null +++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/server/SystemState.java @@ -0,0 +1,84 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.server; + +import org.apache.jena.atlas.lib.FileOps ; +import org.apache.jena.atlas.lib.StrUtils ; + +import com.hp.hpl.jena.query.Dataset ; +import com.hp.hpl.jena.tdb.TDB ; +import com.hp.hpl.jena.tdb.TDBFactory ; +import com.hp.hpl.jena.tdb.base.file.Location ; +import com.hp.hpl.jena.tdb.transaction.DatasetGraphTransaction ; + +public class SystemState { + private static String SystemDatabaseLocation ; + // Testing may reset this. + public static Location location ; + + private static Dataset dataset = null ; + private static DatasetGraphTransaction dsg = null ; + + public static Dataset getDataset() { + init() ; + return dataset ; + } + + public static DatasetGraphTransaction getDatasetGraph() { + init() ; + return dsg ; + } + + private static boolean initialized = false ; + private static void init() { + init$() ; + } + + public /* for testing */ static void init$() { + if ( initialized ) + return ; + initialized = true ; + + if ( location == null ) + location = new Location(FusekiServer.dirSystemDatabase.toString()) ; + + if ( ! location.isMem() ) + FileOps.ensureDir(location.getDirectoryPath()) ; + + dataset = TDBFactory.createDataset(location) ; + dsg = (DatasetGraphTransaction)(dataset.asDatasetGraph()) ; + dsg.getContext().set(TDB.symUnionDefaultGraph, false) ; + } + + public static String PREFIXES = StrUtils.strjoinNL + ("BASE <http://example/base#>", + "PREFIX ja: <http://jena.hpl.hp.com/2005/11/Assembler#>", + "PREFIX fu: <http://jena.apache.org/fuseki#>", + "PREFIX fuseki: <http://jena.apache.org/fuseki#>", + "PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>", + "PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>", + "PREFIX tdb: <http://jena.hpl.hp.com/2008/tdb#>", + "PREFIX sdb: <http://jena.hpl.hp.com/20087/sdb#>", + "PREFIX list: <http://jena.hpl.hp.com/ARQ/list#>", + "PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>", + "PREFIX apf: <http://jena.hpl.hp.com/ARQ/property#>", + "PREFIX afn: <http://jena.hpl.hp.com/ARQ/function#>", + "") ; +} + http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/ActionBase.java ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/ActionBase.java b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/ActionBase.java new file mode 100644 index 0000000..18d4c82 --- /dev/null +++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/ActionBase.java @@ -0,0 +1,263 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.servlets; + +import static java.lang.String.format ; + +import java.io.IOException ; +import java.util.Enumeration ; +import java.util.Map ; + +import javax.servlet.ServletException ; +import javax.servlet.http.HttpServletRequest ; +import javax.servlet.http.HttpServletResponse ; + +import org.apache.jena.atlas.RuntimeIOException ; +import org.apache.jena.fuseki.Fuseki ; +import org.apache.jena.riot.web.HttpNames ; +import org.apache.jena.web.HttpSC ; +import org.slf4j.Logger ; + +import com.hp.hpl.jena.query.ARQ ; +import com.hp.hpl.jena.query.QueryCancelledException ; +import com.hp.hpl.jena.sparql.util.Context ; + +/** General request lifecycle */ +public abstract class ActionBase extends ServletBase +{ + protected final Logger log ; + + protected ActionBase(Logger log) { + super() ; + this.log = log ; + } + + @Override + public void init() { +// log.info("["+Utils.className(this)+"] ServletContextName = "+getServletContext().getServletContextName()) ; +// log.info("["+Utils.className(this)+"] ContextPath = "+getServletContext().getContextPath()) ; + + //super.init() ; + } + + /** + * Common framework for handling HTTP requests. + * @param request + * @param response + */ + protected void doCommon(HttpServletRequest request, HttpServletResponse response) + { + try { + long id = allocRequestId(request, response); + + // Lifecycle + HttpAction action = allocHttpAction(id, request, response) ; + + printRequest(action) ; + action.setStartTime() ; + + // The response may be changed to a HttpServletResponseTracker + response = action.response ; + initResponse(request, response) ; + Context cxt = ARQ.getContext() ; + + try { + execCommonWorker(action) ; + } catch (QueryCancelledException ex) { + // Also need the per query info ... + String message = String.format("The query timed out (restricted to %s ms)", cxt.get(ARQ.queryTimeout)); + // Possibility :: response.setHeader("Retry-after", "600") ; // 5 minutes + ServletOps.responseSendError(response, HttpSC.SERVICE_UNAVAILABLE_503, message); + } catch (ActionErrorException ex) { + if ( ex.exception != null ) + ex.exception.printStackTrace(System.err) ; + // Log message done by printResponse in a moment. + if ( ex.message != null ) + ServletOps.responseSendError(response, ex.rc, ex.message) ; + else + ServletOps.responseSendError(response, ex.rc) ; + } catch (RuntimeIOException ex) { + log.warn(format("[%d] Runtime IO Exception (client left?) RC = %d : %s", id, HttpSC.INTERNAL_SERVER_ERROR_500, ex.getMessage()), ex) ; + ServletOps.responseSendError(response, HttpSC.INTERNAL_SERVER_ERROR_500, ex.getMessage()) ; + } catch (Throwable ex) { + // This should not happen. + //ex.printStackTrace(System.err) ; + log.warn(format("[%d] RC = %d : %s", id, HttpSC.INTERNAL_SERVER_ERROR_500, ex.getMessage()), ex) ; + ServletOps.responseSendError(response, HttpSC.INTERNAL_SERVER_ERROR_500, ex.getMessage()) ; + } + + action.setFinishTime() ; + printResponse(action) ; + archiveHttpAction(action) ; + } catch (Throwable th) { + log.error("Internal error", th) ; + } + } + + // ---- Operation lifecycle + + /** + * Returns a fresh HTTP Action for this request. + * @param id the Request ID + * @param request HTTP request + * @param response HTTP response + * @return a new HTTP Action + */ + protected HttpAction allocHttpAction(long id, HttpServletRequest request, HttpServletResponse response) { + // Need a way to set verbose logging on a per servlet and per request basis. + return new HttpAction(id, log, request, response, Fuseki.verboseLogging) ; + } + + /** + * Begin handling an {@link HttpAction} + * @param action + */ + protected final void startRequest(HttpAction action) { + action.startRequest() ; + } + + /** + * Stop handling an {@link HttpAction} + */ + protected final void finishRequest(HttpAction action) { + action.finishRequest() ; + } + + /** + * Archives the HTTP Action. + * @param action HTTP Action + * @see HttpAction#minimize() + */ + private void archiveHttpAction(HttpAction action) { + action.minimize() ; + } + + /** + * Execute this request, which maybe a admin operation or a client request. + * @param action HTTP Action + */ + protected abstract void execCommonWorker(HttpAction action) ; + + /** Extract the name after the container name (serverlet name). + * Returns "/name" or null + */ + protected static String extractItemName(HttpAction action) { +// action.log.info("context path = "+action.request.getContextPath()) ; +// action.log.info("pathinfo = "+action.request.getPathInfo()) ; +// action.log.info("servlet path = "+action.request.getServletPath()) ; + // if /name + // request.getServletPath() otherwise it's null + // if /* + // request.getPathInfo() ; otherwise it's null. + + // PathInfo is after the servlet name. + String x1 = action.request.getServletPath() ; + String x2 = action.request.getPathInfo() ; + + String pathInfo = action.request.getPathInfo() ; + if ( pathInfo == null || pathInfo.isEmpty() || pathInfo.equals("/") ) + // Includes calling as a container. + return null ; + String name = pathInfo ; + // pathInfo starts with a "/" + int idx = pathInfo.lastIndexOf('/') ; + if ( idx > 0 ) + name = name.substring(idx) ; + // Returns "/name" + return name ; + } + + @SuppressWarnings("unused") // ServletException + protected void doPatch(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, "HTTP PATCH not supported"); + } + + private void printRequest(HttpAction action) { + String url = ActionLib.wholeRequestURL(action.request) ; + String method = action.request.getMethod() ; + + log.info(format("[%d] %s %s", action.id, method, url)) ; + if ( action.verbose ) { + Enumeration<String> en = action.request.getHeaderNames() ; + for (; en.hasMoreElements();) { + String h = en.nextElement() ; + Enumeration<String> vals = action.request.getHeaders(h) ; + if ( !vals.hasMoreElements() ) + log.info(format("[%d] ", action.id, h)) ; + else { + for (; vals.hasMoreElements();) + log.info(format("[%d] %-20s %s", action.id, h, vals.nextElement())) ; + } + } + } + } + + private void initResponse(HttpServletRequest request, HttpServletResponse response) { + setCommonHeaders(response) ; + String method = request.getMethod() ; + // All GET and HEAD operations are sensitive to conneg so ... + if ( HttpNames.METHOD_GET.equalsIgnoreCase(method) || HttpNames.METHOD_HEAD.equalsIgnoreCase(method) ) + setVaryHeader(response) ; + } + + private void printResponse(HttpAction action) { + long time = action.getTime() ; + + HttpServletResponseTracker response = action.response ; + if ( action.verbose ) { + if ( action.contentType != null ) + log.info(format("[%d] %-20s %s", action.id, HttpNames.hContentType, action.contentType)) ; + if ( action.contentLength != -1 ) + log.info(format("[%d] %-20s %d", action.id, HttpNames.hContentLengh, action.contentLength)) ; + for (Map.Entry<String, String> e : action.headers.entrySet()) + log.info(format("[%d] %-20s %s", action.id, e.getKey(), e.getValue())) ; + } + + String timeStr = fmtMillis(time) ; + + if ( action.message == null ) + log.info(String.format("[%d] %d %s (%s) ", action.id, action.statusCode, + HttpSC.getMessage(action.statusCode), timeStr)) ; + else + log.info(String.format("[%d] %d %s (%s) ", action.id, action.statusCode, action.message, timeStr)) ; + } + + /** + * <p>Given a time point, return the time as a milli second string if it is less than 1000, + * otherwise return a seconds string.</p> + * <p>It appends a 'ms' suffix when using milli seconds, + * and <i>s</i> for seconds.</p> + * <p>For instance: </p> + * <ul> + * <li>10 emits 10 ms</li> + * <li>999 emits 999 ms</li> + * <li>1000 emits 1.000 s</li> + * <li>10000 emits 10.000 s</li> + * </ul> + * @param time the time in milliseconds + * @return the time as a display string + */ + + private static String fmtMillis(long time) { + // Millis only? seconds only? + if ( time < 1000 ) + return String.format("%,d ms", time) ; + return String.format("%,.3f s", time / 1000.0) ; + } +} http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/ActionErrorException.java ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/ActionErrorException.java b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/ActionErrorException.java new file mode 100644 index 0000000..c87cfe8 --- /dev/null +++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/ActionErrorException.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.servlets; + +public class ActionErrorException extends RuntimeException +{ + public final Throwable exception ; + public final String message ; + public final int rc ; + public ActionErrorException(Throwable ex, String message, int rc) + { + this.exception = ex ; + this.message = message ; + this.rc = rc ; + } +} http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/ActionLib.java ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/ActionLib.java b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/ActionLib.java new file mode 100644 index 0000000..182b46e --- /dev/null +++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/ActionLib.java @@ -0,0 +1,180 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.servlets; + +import javax.servlet.http.HttpServletRequest ; + +import org.apache.jena.atlas.web.AcceptList ; +import org.apache.jena.atlas.web.MediaType ; +import org.apache.jena.fuseki.DEF ; +import org.apache.jena.fuseki.conneg.ConNeg ; +import org.apache.jena.fuseki.server.DataAccessPoint ; +import org.apache.jena.fuseki.server.DataAccessPointRegistry ; + +/** Operations related to servlets */ + +public class ActionLib { + /** + * A possible implementation for {@link ActionSPARQL#mapRequestToDataset} + * that assumes the form /dataset/service. + * @param action the request + * @return the dataset + */ public static String mapRequestToDataset(HttpAction action) { + String uri = action.getActionURI() ; + return mapActionRequestToDataset(uri) ; + } + + /** Map request to uri in the registry. + * A possible implementation for mapRequestToDataset(String) + * that assumes the form /dataset/service + * Returning null means no mapping found. + * The URI must be the action URI (no contact path) + */ + + public static String mapActionRequestToDataset(String uri) { + // Chop off trailing part - the service selector + // e.g. /dataset/sparql => /dataset + int i = uri.lastIndexOf('/') ; + if ( i == -1 ) + return null ; + if ( i == 0 ) + { + // started with '/' - leave. + return uri ; + } + + return uri.substring(0, i) ; + } + + /** Calculate the operation , given action and data access point */ + public static String mapRequestToOperation(HttpAction action, DataAccessPoint dsRef) { + if ( dsRef == null ) + return "" ; + String uri = action.getActionURI() ; + String name = dsRef.getName(); + if ( name.length() >= uri.length() ) + return "" ; + return uri.substring(name.length()+1) ; // Skip the separating "/" + + } + + /** Implementation of mapRequestToDataset(String) that looks for + * the longest match in the registry. + * This includes use in direct naming GSP. + */ + public static String mapRequestToDatasetLongest$(String uri) + { + if ( uri == null ) + return null ; + + // This covers local, using the URI as a direct name for + // a graph, not just using the indirect ?graph= or ?default + // forms. + + String ds = null ; + for ( String ds2 : DataAccessPointRegistry.get().keys() ) { + if ( ! uri.startsWith(ds2) ) + continue ; + + if ( ds == null ) + { + ds = ds2 ; + continue ; + } + if ( ds.length() < ds2.length() ) + { + ds = ds2 ; + continue ; + } + } + return ds ; + } + + /** Calculate the fill URL including query string + * for the HTTP request. This may be quite long. + * @param request HttpServletRequest + * @return String The full URL, including query string. + */ + public static String wholeRequestURL(HttpServletRequest request) { + StringBuffer sb = request.getRequestURL() ; + String queryString = request.getQueryString() ; + if ( queryString != null ) { + sb.append("?") ; + sb.append(queryString) ; + } + return sb.toString() ; + } + + /* + * The context path can be: + * "" for the root context + * "/APP" for named contexts + * so: + * "/dataset/server" becomes "/dataset/server" + * "/APP/dataset/server" becomes "/dataset/server" + */ + public static String removeContextPath(HttpAction action) { + + return actionURI(action.request) ; + } + + public static String actionURI(HttpServletRequest request) { +// Log.info(this, "URI = '"+request.getRequestURI()) ; +// Log.info(this, "Context path = '"+request.getContextPath()+"'") ; +// Log.info(this, "Servlet path = '"+request.getServletPath()+"'") ; +// ServletContext cxt = this.getServletContext() ; +// Log.info(this, "ServletContext path = '"+cxt.getContextPath()+"'") ; + + String contextPath = request.getServletContext().getContextPath() ; + String uri = request.getRequestURI() ; + if ( contextPath == null ) + return uri ; + if ( contextPath.isEmpty()) + return uri ; + String x = uri ; + if ( uri.startsWith(contextPath) ) + x = uri.substring(contextPath.length()) ; + //log.info("uriWithoutContextPath: uri = "+uri+" contextPath="+contextPath+ "--> x="+x) ; + return x ; + } + + /** Negotiate the content-type and set the response headers */ + public static MediaType contentNegotation(HttpAction action, AcceptList myPrefs, + MediaType defaultMediaType) { + MediaType mt = ConNeg.chooseContentType(action.request, myPrefs, defaultMediaType) ; + if ( mt == null ) + return null ; + if ( mt.getContentType() != null ) + action.response.setContentType(mt.getContentType()) ; + if ( mt.getCharset() != null ) + action.response.setCharacterEncoding(mt.getCharset()) ; + return mt ; + } + + /** Negotiate the content-type for an RDF triples syntax and set the response headers */ + public static MediaType contentNegotationRDF(HttpAction action) { + return contentNegotation(action, DEF.rdfOffer, DEF.acceptRDFXML) ; + } + + /** Negotiate the content-type for an RDF quads syntax and set the response headers */ + public static MediaType contentNegotationQuads(HttpAction action) { + return contentNegotation(action, DEF.quadsOffer, DEF.acceptNQuads) ; + } +} + http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/ActionREST.java ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/ActionREST.java b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/ActionREST.java new file mode 100644 index 0000000..e2c7e4a --- /dev/null +++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/ActionREST.java @@ -0,0 +1,161 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.servlets; + +import java.io.IOException ; +import java.util.Locale ; + +import javax.servlet.ServletException ; +import javax.servlet.http.HttpServletRequest ; +import javax.servlet.http.HttpServletResponse ; + +import org.apache.jena.fuseki.server.CounterName ; + +/** Common point for operations that are "REST"ish (use GET/PUT etc as operations). */ +public abstract class ActionREST extends ActionSPARQL +{ + public ActionREST() + { super() ; } + + @Override + protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + // Direct all verbs to our common framework. + doCommon(request, response) ; + } + + @Override + protected void perform(HttpAction action) { + dispatch(action) ; + } + + private void dispatch(HttpAction action) { + HttpServletRequest req = action.request ; + HttpServletResponse resp = action.response ; + String method = req.getMethod().toUpperCase(Locale.ROOT) ; + + if (method.equals(METHOD_GET)) + doGet$(action); + else if (method.equals(METHOD_HEAD)) + doHead$(action); + else if (method.equals(METHOD_POST)) + doPost$(action); + else if (method.equals(METHOD_PATCH)) + doPatch$(action) ; + else if (method.equals(METHOD_OPTIONS)) + doOptions$(action) ; + else if (method.equals(METHOD_TRACE)) + //doTrace(action) ; + ServletOps.errorMethodNotAllowed("TRACE") ; + else if (method.equals(METHOD_PUT)) + doPut$(action) ; + else if (method.equals(METHOD_DELETE)) + doDelete$(action) ; + else + ServletOps.errorNotImplemented("Unknown method: "+method) ; + } + + // Counter wrappers + + // XXX Out of date - we now add HTTP counters to all endpoints. + + private final void doGet$(HttpAction action) { + incCounter(action.getEndpoint(), CounterName.HTTPget) ; + try { + doGet(action) ; + incCounter(action.getEndpoint(), CounterName.HTTPgetGood) ; + } catch ( ActionErrorException ex) { + incCounter(action.getEndpoint(), CounterName.HTTPGetBad) ; + throw ex ; + } + } + + private final void doHead$(HttpAction action) { + incCounter(action.getEndpoint(), CounterName.HTTPhead) ; + try { + doHead(action) ; + incCounter(action.getEndpoint(), CounterName.HTTPheadGood) ; + } catch ( ActionErrorException ex) { + incCounter(action.getEndpoint(), CounterName.HTTPheadBad) ; + throw ex ; + } + } + + private final void doPost$(HttpAction action) { + incCounter(action.getEndpoint(), CounterName.HTTPpost) ; + try { + doPost(action) ; + incCounter(action.getEndpoint(), CounterName.HTTPpostGood) ; + } catch ( ActionErrorException ex) { + incCounter(action.getEndpoint(), CounterName.HTTPpostBad) ; + throw ex ; + } + } + + private final void doPatch$(HttpAction action) { + incCounter(action.getEndpoint(), CounterName.HTTPpatch) ; + try { + doPatch(action) ; + incCounter(action.getEndpoint(), CounterName.HTTPpatchGood) ; + } catch ( ActionErrorException ex) { + incCounter(action.getEndpoint(), CounterName.HTTPpatchBad) ; + throw ex ; + } + } + + private final void doDelete$(HttpAction action) { + incCounter(action.getEndpoint(), CounterName.HTTPdelete) ; + try { + doDelete(action) ; + incCounter(action.getEndpoint(), CounterName.HTTPdeleteGood) ; + } catch ( ActionErrorException ex) { + incCounter(action.getEndpoint(), CounterName.HTTPdeleteBad) ; + throw ex ; + } + } + + private final void doPut$(HttpAction action) { + incCounter(action.getEndpoint(), CounterName.HTTPput) ; + try { + doPut(action) ; + incCounter(action.getEndpoint(), CounterName.HTTPputGood) ; + } catch ( ActionErrorException ex) { + incCounter(action.getEndpoint(), CounterName.HTTPputBad) ; + throw ex ; + } + } + + private final void doOptions$(HttpAction action) { + incCounter(action.getEndpoint(), CounterName.HTTPoptions) ; + try { + doOptions(action) ; + incCounter(action.getEndpoint(), CounterName.HTTPoptionsGood) ; + } catch ( ActionErrorException ex) { + incCounter(action.getEndpoint(), CounterName.HTTPoptionsBad) ; + throw ex ; + } + } + + protected abstract void doGet(HttpAction action) ; + protected abstract void doHead(HttpAction action) ; + protected abstract void doPost(HttpAction action) ; + protected abstract void doPatch(HttpAction action) ; + protected abstract void doDelete(HttpAction action) ; + protected abstract void doPut(HttpAction action) ; + protected abstract void doOptions(HttpAction action) ; +} http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/ActionSPARQL.java ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/ActionSPARQL.java b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/ActionSPARQL.java new file mode 100644 index 0000000..d26bc55 --- /dev/null +++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/ActionSPARQL.java @@ -0,0 +1,207 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.servlets; + +import static org.apache.jena.fuseki.server.CounterName.Requests ; +import static org.apache.jena.fuseki.server.CounterName.RequestsBad ; +import static org.apache.jena.fuseki.server.CounterName.RequestsGood ; + +import java.io.InputStream ; + +import org.apache.jena.atlas.RuntimeIOException ; +import org.apache.jena.fuseki.Fuseki ; +import org.apache.jena.fuseki.server.* ; +import org.apache.jena.riot.Lang ; +import org.apache.jena.riot.RDFDataMgr ; +import org.apache.jena.riot.ReaderRIOT ; +import org.apache.jena.riot.RiotException ; +import org.apache.jena.riot.system.ErrorHandler ; +import org.apache.jena.riot.system.ErrorHandlerFactory ; +import org.apache.jena.riot.system.StreamRDF ; + +import com.hp.hpl.jena.query.QueryCancelledException ; + +/** SPARQL request lifecycle */ +public abstract class ActionSPARQL extends ActionBase +{ + protected ActionSPARQL() { super(Fuseki.actionLog) ; } + + protected abstract void validate(HttpAction action) ; + protected abstract void perform(HttpAction action) ; + + /** + * Executes common tasks, including mapping the request to the right dataset, setting the dataset into the HTTP + * action, and retrieving the service for the dataset requested. Finally, it calls the + * {@link #executeAction(HttpAction)} method, which executes the HTTP Action life cycle. + * @param action HTTP Action + */ + @Override + protected void execCommonWorker(HttpAction action) { + DataAccessPoint dataAccessPoint ; + DataService dSrv ; + + String datasetUri = mapRequestToDataset(action) ; + if ( datasetUri != null ) { + dataAccessPoint = DataAccessPointRegistry.get().get(datasetUri) ; + if ( dataAccessPoint == null ) { + ServletOps.errorNotFound("No dataset for URI: "+datasetUri) ; + return ; + } + //dataAccessPoint. + dSrv = dataAccessPoint.getDataService() ; + if ( ! dSrv.isAcceptingRequests() ) { + ServletOps.errorNotFound("Dataset not active: "+datasetUri) ; + return ; + } + } else { + dataAccessPoint = null ; + dSrv = DataService.serviceOnlyDataService() ; + } + + String operationName = mapRequestToOperation(action, dataAccessPoint) ; + action.setRequest(dataAccessPoint, dSrv) ; + + //operationName = "" + + Endpoint op = dSrv.getOperation(operationName) ; + action.setEndpoint(op, operationName); + executeAction(action) ; + } + + /** Execute a SPARQL request. Statistics have not been adjusted at this point. + * + * @param action + */ + protected void executeAction(HttpAction action) { + executeLifecycle(action) ; + } + + /** + * Standard execution lifecycle for a SPARQL Request. + * <ul> + * <li>{@link #startRequest(HttpAction)}</li> + * <li>initial statistics,</li> + * <li>{@link #validate(HttpAction)} request,</li> + * <li>{@link #perform(HttpAction)} request,</li> + * <li>completion/error statistics,</li> + * <li>{@link #finishRequest(HttpAction)} + * </ul> + * + * @param action + */ + // This is the service request lifecycle. + final + protected void executeLifecycle(HttpAction action) { + startRequest(action) ; + // And also HTTP counter + CounterSet csService = action.getDataService().getCounters() ; + CounterSet csOperation = action.getEndpoint().getCounters() ; + + incCounter(csService, Requests) ; + incCounter(csOperation, Requests) ; + try { + // Either exit this via "bad request" on validation + // or in execution in perform. + try { + validate(action) ; + } catch (ActionErrorException ex) { + incCounter(csOperation, RequestsBad) ; + incCounter(csService, RequestsBad) ; + throw ex ; + } + + try { + perform(action) ; + // Success + incCounter(csOperation, RequestsGood) ; + incCounter(csService, RequestsGood) ; + } catch (ActionErrorException | QueryCancelledException | RuntimeIOException ex) { + incCounter(csOperation, RequestsBad) ; + incCounter(csService, RequestsBad) ; + throw ex ; + } + } finally { + finishRequest(action) ; + } + } + + /** + * Map request {@link HttpAction} to uri in the registry. + * A return of ull means no mapping done (passthrough). + * @param uri the URI + * @return the dataset + */ + protected String mapRequestToDataset(HttpAction action) { + return ActionLib.mapRequestToDataset(action) ; + } + + /** + * Map request to uri in the registry. null means no mapping done + * (passthrough). + */ + protected String mapRequestToOperation(HttpAction action, DataAccessPoint dataAccessPoint) { + return ActionLib.mapRequestToOperation(action, dataAccessPoint) ; + } + + /** Increment counter */ + protected static void incCounter(Counters counters, CounterName name) { + if ( counters == null ) return ; + incCounter(counters.getCounters(), name) ; + } + + /** Decrement counter */ + protected static void decCounter(Counters counters, CounterName name) { + if ( counters == null ) return ; + decCounter(counters.getCounters(), name) ; + } + + protected static void incCounter(CounterSet counters, CounterName name) { + if ( counters == null ) + return ; + try { + if ( counters.contains(name) ) + counters.inc(name) ; + } catch (Exception ex) { + Fuseki.serverLog.warn("Exception on counter inc", ex) ; + } + } + + protected static void decCounter(CounterSet counters, CounterName name) { + if ( counters == null ) + return ; + try { + if ( counters.contains(name) ) + counters.dec(name) ; + } catch (Exception ex) { + Fuseki.serverLog.warn("Exception on counter dec", ex) ; + } + } + + public static void parse(HttpAction action, StreamRDF dest, InputStream input, Lang lang, String base) { + try { + ReaderRIOT r = RDFDataMgr.createReader(lang) ; + if ( r == null ) + ServletOps.errorBadRequest("No parser for language '"+lang.getName()+"'") ; + ErrorHandler errorHandler = ErrorHandlerFactory.errorHandlerStd(action.log); + r.setErrorHandler(errorHandler); + r.read(input, base, null, dest, null) ; + } + catch (RiotException ex) { ServletOps.errorBadRequest("Parse error: "+ex.getMessage()) ; } + } +} http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/ConcurrencyPolicyMRSW.java ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/ConcurrencyPolicyMRSW.java b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/ConcurrencyPolicyMRSW.java new file mode 100644 index 0000000..1f86539 --- /dev/null +++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/ConcurrencyPolicyMRSW.java @@ -0,0 +1,113 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.servlets; + +import java.util.ConcurrentModificationException ; +import java.util.concurrent.atomic.AtomicLong ; + +import org.apache.jena.fuseki.Fuseki ; +import org.slf4j.Logger ; + +public final class ConcurrencyPolicyMRSW +{ + static private Logger log = Fuseki.actionLog ; //org.slf4j.LoggerFactory.getLogger(ConcurrencyPolicyMRSW.class) ; + static private final boolean logging = false ; //log.isDebugEnabled() ; + + // This is a simplified version of ConcurrencyPolicyMRSW from TDB. + private final AtomicLong readCounter = new AtomicLong(0) ; + private final AtomicLong writeCounter = new AtomicLong(0) ; + static private final AtomicLong policyCounter = new AtomicLong(0) ; + + public ConcurrencyPolicyMRSW() + { policyCounter.incrementAndGet() ; } + + // Loggin -inside the operation. + + //@Override + public void startRead() + { + readCounter.getAndIncrement() ; + log() ; + checkConcurrency() ; + } + + //@Override + public void finishRead() + { + log() ; + readCounter.decrementAndGet() ; + checkConcurrency() ; + } + + //@Override + public void startUpdate() + { + writeCounter.getAndIncrement() ; + log() ; + checkConcurrency() ; + } + + //@Override + public void finishUpdate() + { + log() ; + writeCounter.decrementAndGet() ; + checkConcurrency() ; + } + + private synchronized void checkConcurrency() + { + long R = readCounter.get() ; + long W = writeCounter.get() ; + long id = policyCounter.get(); + if ( R > 0 && W > 0 ) + policyError(id, R, W) ; + if ( W > 1 ) + policyError(id, R, W) ; + } + + private void log() + { + if ( ! logging ) + return ; + long R , W , id ; + synchronized(this) + { + R = readCounter.get() ; + W = writeCounter.get() ; + id = policyCounter.get(); + } + log.info(format(id, R, W)) ; + } + + private static void policyError(long id, long R, long W) + { + policyError(format(id, R, W)) ; + } + + private static void policyError(String message) + { + throw new ConcurrentModificationException(message) ; + } + + private static String format(long id, long R, long W) + { + return String.format("(lock=%d) Reader = %d, Writer = %d", id, R, W) ; + } +} http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/FusekiFilter.java ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/FusekiFilter.java b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/FusekiFilter.java new file mode 100644 index 0000000..7a79cd3 --- /dev/null +++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/FusekiFilter.java @@ -0,0 +1,87 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.servlets; + +import java.io.IOException ; + +import javax.servlet.* ; +import javax.servlet.http.HttpServletRequest ; +import javax.servlet.http.HttpServletResponse ; + +import org.apache.jena.fuseki.Fuseki ; +import org.apache.jena.fuseki.server.DataAccessPointRegistry ; +import org.slf4j.Logger ; + +/** Look at all requests and see if they match a registered dataset name; + * if they do, pass down to the uber servlet, which can dispatch any request + * for any service. + */ +public class FusekiFilter implements Filter { + private static Logger log = Fuseki.serverLog ; + private static SPARQL_UberServlet überServlet = new SPARQL_UberServlet.AccessByConfig() ; + + @Override + public void init(FilterConfig filterConfig) throws ServletException { +// log.info("Filter: ["+Utils.className(this)+"] ServletContextName = "+filterConfig.getServletContext().getServletContextName()) ; +// log.info("Filter: ["+Utils.className(this)+"] ContextPath = "+filterConfig.getServletContext().getContextPath()) ; + } + + private static final boolean LogFilter = false ; // Development debugging (can be excessive!) + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + try { + HttpServletRequest req = (HttpServletRequest)request ; + HttpServletResponse resp = (HttpServletResponse)response ; + + // Handle context path + String uri = ActionLib.actionURI(req) ; + String datasetUri = ActionLib.mapActionRequestToDataset(uri) ; + + // is it a long running operation? + // (this could be a separate filter) + + if ( LogFilter ) { + log.info("Filter: Request URI = "+req.getRequestURI()) ; + log.info("Filter: Action URI = "+uri) ; + log.info("Filter: Dataset URI = "+datasetUri) ; + } + + if ( datasetUri != null ) { + if ( DataAccessPointRegistry.get().isRegistered(datasetUri) ) { + if ( LogFilter ) + log.info("Filter: dispatch") ; + überServlet.doCommon(req, resp) ; + return ; + } + } + } catch (Exception ex) {} + + if ( LogFilter ) + log.info("Filter: pass to chain") ; + // Not found - continue. + chain.doFilter(request, response); + } + + @Override + public void destroy() {} + +} + http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/HttpAction.java ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/HttpAction.java b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/HttpAction.java new file mode 100644 index 0000000..1086848 --- /dev/null +++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/servlets/HttpAction.java @@ -0,0 +1,387 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.servlets; + +import static com.hp.hpl.jena.query.ReadWrite.READ ; +import static com.hp.hpl.jena.query.ReadWrite.WRITE ; + +import java.util.HashMap ; +import java.util.Map ; + +import javax.servlet.http.HttpServletRequest ; +import javax.servlet.http.HttpServletResponse ; + +import org.apache.jena.atlas.logging.Log ; +import org.apache.jena.fuseki.Fuseki ; +import org.apache.jena.fuseki.FusekiException ; +import org.apache.jena.fuseki.server.* ; +import org.slf4j.Logger ; + +import com.hp.hpl.jena.query.ReadWrite ; +import com.hp.hpl.jena.sparql.SystemARQ ; +import com.hp.hpl.jena.sparql.core.DatasetGraph ; +import com.hp.hpl.jena.sparql.core.DatasetGraphWithLock ; +import com.hp.hpl.jena.sparql.core.DatasetGraphWrapper ; +import com.hp.hpl.jena.sparql.core.Transactional ; + +/** + * HTTP action that represents the user request lifecycle. Its state is handled in the + * {@link ActionSPARQL#executeAction(HttpAction)} method. + */ +public class HttpAction +{ + public final long id ; + public final boolean verbose ; + public final Logger log ; + + // ---- + // Worth subclassing? Given this is allocated in the general lifecycle + // it would mean there are downcasts to the specific type. + + // -- Valid only for operational actions (e.g. SPARQL). + + public String endpointName = null ; // Endpoint name srv was found under + public Endpoint endpoint = null ; + private Transactional transactional = null ; + private boolean isTransactional = false ; + private DatasetGraph activeDSG = null ; // Set when inside begin/end. + private ReadWrite activeMode = null ; // Set when inside begin/end. + + // -- Valid only for administration actions. + + // -- Shared items (but exact meaning may differ) + /** Handle to dataset+services being acted on (maybe null) */ + private DataAccessPoint dataAccessPoint = null ; + private DataService dataService = null ; + private String datasetName = null ; // Dataset URI used (e.g. registry) + private DatasetGraph dsg = null ; + + // ---- + + private boolean startTimeIsSet = false ; + private boolean finishTimeIsSet = false ; + + private long startTime = -2 ; + private long finishTime = -2 ; + + // Outcome. + public int statusCode = -1 ; + public String message = null ; + public int contentLength = -1 ; + public String contentType = null ; + + // Cleared to archive: + public Map <String, String> headers = new HashMap<>() ; + public HttpServletRequest request; + public HttpServletResponseTracker response ; + private final String actionURI ; + private final String contextPath ; + + /** + * Creates a new HTTP Action, using the HTTP request and response, and a given ID. + * + * @param id given ID + * @param log Logger for this action + * @param request HTTP request + * @param response HTTP response + * @param verbose verbose flag + */ + public HttpAction(long id, Logger log, HttpServletRequest request, HttpServletResponse response, boolean verbose) { + this.id = id ; + this.log = log ; + this.request = request ; + this.response = new HttpServletResponseTracker(this, response) ; + // Should this be set when setDataset is called from the dataset context? + // Currently server-wide, e.g. from the command line. + this.verbose = verbose ; + this.contextPath = request.getServletContext().getContextPath() ; + this.actionURI = ActionLib.actionURI(request) ; + } + + /** Initialization after action creation during lifecycle setup. + * <p>Sets the action dataset. Setting will replace any existing {@link DataAccessPoint} and {@link DataService}, + * as the {@link DatasetGraph} of the current HTTP Action.</p> + * + * <p>Once it has updated its members, the HTTP Action will change its transactional state and + * {@link Transactional} instance according to its base dataset graph.</p> + * + * @param dataAccessPoint {@link DataAccessPoint} + * @param dService {@link DataService} + * @see Transactional + */ + + public void setRequest(DataAccessPoint dataAccessPoint, DataService dService) { + this.dataAccessPoint = dataAccessPoint ; + if ( dataAccessPoint != null ) + this.datasetName = dataAccessPoint.getName() ; + + if ( this.dataService != null ) + throw new FusekiException("Redefinition of DatasetRef in the request action") ; + + this.dataService = dService ; + if ( dService == null || dService.getDataset() == null ) + // Null does not happens for service requests, (it does for admin requests - call setControlRequest) + throw new FusekiException("Null DataService in the request action") ; + + this.dsg = dService.getDataset() ; + DatasetGraph basedsg = unwrap(dsg) ; + + if ( isTransactional(basedsg) && isTransactional(dsg) ) { + // Use transactional if it looks safe - abort is necessary. + transactional = (Transactional)dsg ; + isTransactional = true ; + } else { + // Unsure if safesetControlRef + transactional = new DatasetGraphWithLock(dsg) ; + // No real abort. + isTransactional = false ; + } + } + + public void setControlRequest(DataAccessPoint dataAccessPoint, String datasetUri) { + this.dataAccessPoint = dataAccessPoint ; + this.dataService = null ; + this.datasetName = datasetUri ; + } + + /** + * Returns <code>true</code> iff the given {@link DatasetGraph} is an instance of {@link Transactional}, + * <code>false otherwise</code>. + * + * @param dsg a {@link DatasetGraph} + * @return <code>true</code> iff the given {@link DatasetGraph} is an instance of {@link Transactional}, + * <code>false otherwise</code> + */ + private static boolean isTransactional(DatasetGraph dsg) { + return (dsg instanceof Transactional) ; + } + + /** + * A {@link DatasetGraph} may contain other <strong>wrapped DatasetGraph's</strong>. This method will return + * the first instance (including the argument to this method) that <strong>is not</strong> an instance of + * {@link DatasetGraphWrapper}. + * + * @param dsg a {@link DatasetGraph} + * @return the first found {@link DatasetGraph} that is not an instance of {@link DatasetGraphWrapper} + */ + private static DatasetGraph unwrap(DatasetGraph dsg) { + while (dsg instanceof DatasetGraphWrapper) { + dsg = ((DatasetGraphWrapper)dsg).getWrapped() ; + } + return dsg ; + } + + /** This is the requestURI with the context path removed. + * It should be used internally for dispatch. + */ + public String getActionURI() { + return actionURI ; + } + + /** Get the context path. + */ + public String getContextPath() { + return contextPath ; + } + + + /** Set the endpoint and endpoint name that this is an action for. + * @param srvRef {@link Endpoint} + * @param endpointName + */ + public void setEndpoint(Endpoint srvRef, String endpointName) { + this.endpoint = srvRef ; + this.endpointName = endpointName ; + } + + /** Get the endpoint for the action (may be null) . */ + public Endpoint getEndpoint() { + return endpoint ; + } + + /** + * Returns whether or not the underlying DatasetGraph is fully transactional (supports rollback) + */ + public boolean isTransactional() { + return isTransactional ; + } + + public void beginRead() { + activeMode = READ ; + transactional.begin(READ) ; + activeDSG = dsg ; + dataService.startTxn(READ) ; + } + + public void endRead() { + dataService.finishTxn(READ) ; + activeMode = null ; + transactional.end() ; + activeDSG = null ; + } + + public void beginWrite() { + transactional.begin(WRITE) ; + activeMode = WRITE ; + activeDSG = dsg ; + dataService.startTxn(WRITE) ; + } + + public void commit() { + transactional.commit() ; + activeDSG = null ; + } + + public void abort() { + try { transactional.abort() ; } + catch (Exception ex) { + // Some datasets claim to be transactional but + // don't provide a real abort. We tried to avoid + // them earlier but even if they sneek through, + // we try to continue server operation. + Log.warn(this, "Exception during abort (operation attempts to continue): "+ex.getMessage()) ; + } + activeDSG = null ; + } + + public void endWrite() { + dataService.finishTxn(WRITE) ; + activeMode = null ; + + if ( transactional.isInTransaction() ) { + Log.warn(this, "Transaction still active in endWriter - no commit or abort seen (forced abort)") ; + try { + transactional.abort() ; + } catch (RuntimeException ex) { + Log.warn(this, "Exception in forced abort (trying to continue)", ex) ; + } + } + transactional.end() ; + activeDSG = null ; + } + + public final void startRequest() + { + if ( dataAccessPoint != null ) + dataAccessPoint.startRequest(this) ; + } + + public final void finishRequest() { + if ( dataAccessPoint != null ) + dataAccessPoint.finishRequest(this) ; + // Standard logging goes here. + if ( Fuseki.requestLog != null ) { + String s = RequestLog.combinedNCSA(this) ; + Fuseki.requestLog.info(s); + } + } + + /** If inside the transaction for the action, return the active {@link DatasetGraph}, + * otherwise return null. + * @return Current active {@link DatasetGraph} + */ + public final DatasetGraph getActiveDSG() { + return activeDSG ; + } + + public final DataAccessPoint getDataAccessPoint() { + return dataAccessPoint; + } + +// public void setDataAccessPoint(DataAccessPoint dataAccessPoint) { +// this.dataAccessPoint = dataAccessPoint; +// } + + public final DataService getDataService() { + return dataService; + } + +// public final void setDataService(DataService dataService) { +// this.dataService = dataService; +// } + + public final String getDatasetName() { + return datasetName; + } + +// public void setDatasetName(String datasetName) { +// this.datasetName = datasetName; +// } + + /** Reduce to a size that can be kept around for sometime. + * Release resources like datasets that may be closed, reset etc. + */ + public void minimize() { + this.request = null ; + this.response = null ; + this.dsg = null ; + this.dataService = null ; + this.activeDSG = null ; + this.endpoint = null ; + } + + public void setStartTime() { + if ( startTimeIsSet ) + Log.warn(this, "Start time reset") ; + startTimeIsSet = true ; + this.startTime = System.nanoTime() ; + } + + /** Start time, in system nanos */ + public long getStartTime() { + if ( ! startTimeIsSet ) + Log.warn(this, "Start time is not set") ; + return startTime ; + } + + /** Start time, in system nanos */ + public long getFinishTime() { + if ( ! finishTimeIsSet ) + Log.warn(this, "Finish time is not set") ; + return finishTime ; + } + + public void setFinishTime() { + if ( finishTimeIsSet ) + Log.warn(this, "Finish time reset") ; + finishTimeIsSet = true ; + this.finishTime = System.nanoTime() ; + } + + public HttpServletRequest getRequest() { return request ; } + + public HttpServletResponseTracker getResponse() { return response ; } + + /** Return the recorded time taken in milliseconds. + * {@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) ; + } +}
