http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/conneg/WebLib.java ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/conneg/WebLib.java b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/conneg/WebLib.java new file mode 100644 index 0000000..38dc265 --- /dev/null +++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/conneg/WebLib.java @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.conneg; + +import java.util.Enumeration ; + +import javax.servlet.http.HttpServletRequest ; + +import org.apache.jena.riot.web.HttpNames ; + +public class WebLib +{ + /** Split a string, removing whitespace around the split string. + * e.g. Use in splitting HTTP accept/content-type headers. + */ + public static String[] split(String s, String splitStr) + { + String[] x = s.split(splitStr,2) ; + for ( int i = 0 ; i < x.length ; i++ ) + { + x[i] = x[i].trim() ; + } + return x ; + } + + /** Migrate to WebLib */ + public static String getAccept(HttpServletRequest httpRequest) + { + // There can be multiple accept headers -- note many tools don't allow these to be this way (e.g. wget, curl) + Enumeration<String> en = httpRequest.getHeaders(HttpNames.hAccept) ; + if ( ! en.hasMoreElements() ) + return null ; + StringBuilder sb = new StringBuilder() ; + String sep = "" ; + for ( ; en.hasMoreElements() ; ) + { + String x = en.nextElement() ; + sb.append(sep) ; + sep = ", " ; + sb.append(x) ; + } + return sb.toString() ; + } +}
http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/jetty/FusekiErrorHandler.java ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/jetty/FusekiErrorHandler.java b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/jetty/FusekiErrorHandler.java new file mode 100644 index 0000000..40361de --- /dev/null +++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/jetty/FusekiErrorHandler.java @@ -0,0 +1,95 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.jetty; + +import static java.lang.String.format ; + +import java.io.* ; + +import javax.servlet.http.HttpServletRequest ; +import javax.servlet.http.HttpServletResponse ; + +import org.apache.jena.atlas.io.IO ; +import org.apache.jena.fuseki.Fuseki ; +import org.apache.jena.fuseki.servlets.ServletOps ; +import org.apache.jena.web.HttpSC ; +import org.eclipse.jetty.http.HttpMethod ; +import org.eclipse.jetty.http.MimeTypes ; +import org.eclipse.jetty.server.Request ; +import org.eclipse.jetty.server.Response ; +import org.eclipse.jetty.server.handler.ErrorHandler ; + +public class FusekiErrorHandler extends ErrorHandler +{ + public FusekiErrorHandler() {} + + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException + { + String method = request.getMethod(); + + if ( !method.equals(HttpMethod.GET.asString()) + && !method.equals(HttpMethod.POST.asString()) + && !method.equals(HttpMethod.HEAD.asString()) ) + return ; + + response.setContentType(MimeTypes.Type.TEXT_PLAIN_UTF_8.asString()) ; + ServletOps.setNoCache(response) ; + + ByteArrayOutputStream bytes = new ByteArrayOutputStream(1024) ; + try ( Writer writer = IO.asUTF8(bytes) ) { + String reason=(response instanceof Response)?((Response)response).getReason():null; + handleErrorPage(request, writer, response.getStatus(), reason) ; + + if ( ! Fuseki.VERSION.equalsIgnoreCase("development") && + ! Fuseki.VERSION.equals("${project.version}") ) + { + writer.write("\n") ; + writer.write("\n") ; + writer.write(format("Fuseki - version %s (Build date: %s)\n", Fuseki.VERSION, Fuseki.BUILD_DATE)) ; + } + writer.flush(); + response.setContentLength(bytes.size()) ; + // Copy :-( + response.getOutputStream().write(bytes.toByteArray()) ; + } + } + + @Override + protected void handleErrorPage(HttpServletRequest request, Writer writer, int code, String message) + throws IOException + { + if ( message == null ) + message = HttpSC.getMessage(code) ; + writer.write(format("Error %d: %s\n", code, message)) ; + + Throwable th = (Throwable)request.getAttribute("javax.servlet.error.exception"); + while(th!=null) + { + writer.write("\n"); + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + th.printStackTrace(pw); + pw.flush(); + writer.write(sw.getBuffer().toString()); + writer.write("\n"); + th = th.getCause(); + } + } +} http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/jetty/JettyFuseki.java ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/jetty/JettyFuseki.java b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/jetty/JettyFuseki.java new file mode 100644 index 0000000..94c9f33 --- /dev/null +++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/jetty/JettyFuseki.java @@ -0,0 +1,267 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.jetty ; + +import static java.lang.String.format ; +import static org.apache.jena.fuseki.Fuseki.serverLog ; + +import java.io.FileInputStream ; + +import org.apache.jena.atlas.lib.FileOps ; +import org.apache.jena.atlas.logging.LogCtl ; +import org.apache.jena.fuseki.Fuseki ; +import org.apache.jena.fuseki.FusekiException ; +import org.apache.jena.fuseki.mgt.MgtJMX ; +import org.eclipse.jetty.security.* ; +import org.eclipse.jetty.security.authentication.BasicAuthenticator ; +import org.eclipse.jetty.server.* ; +import org.eclipse.jetty.servlet.ServletContextHandler ; +import org.eclipse.jetty.util.security.Constraint ; +import org.eclipse.jetty.webapp.WebAppContext ; +import org.eclipse.jetty.xml.XmlConfiguration ; + +import com.hp.hpl.jena.sparql.util.Utils ; + +/** Standalone server, not run as a WAR file. + * Used in testing and development. + * + * SPARQLServer is the Jena server instance which wraps/utilizes + * {@link org.eclipse.jetty.server.Server}. This class provides + * immediate access to the {@link org.eclipse.jetty.server.Server#start()} and + * {@link org.eclipse.jetty.server.Server#stop()} commands as well as obtaining + * instances of the server and server configuration. Finally we can obtain + * instances of {@link org.apache.jena.fuseki.jetty.JettyServerConfig}. + */ +public class JettyFuseki { + // Jetty specific. + // This class is becoming less important - it now sets up a Jetty server for in-process use + // either for the command line in development + // and in testing but not direct webapp deployments. + static { Fuseki.init() ; } + + public static JettyFuseki instance = null ; + + private ServerConnector serverConnector = null ; + // If a separate ... + private ServerConnector mgtConnector = null ; + + private JettyServerConfig serverConfig ; + + // The jetty server. + private Server server = null ; + + // webapp setup - standard maven layout + public static String contextpath = "/" ; + public static final String resourceBase1 = "webapp" ; // Standalone jar + public static final String resourceBase2 = "src/main/webapp" ; // Development + + /** + * Default setup which requires a {@link org.apache.jena.fuseki.jetty.JettyServerConfig} + * object as input. We use this config to pass in the command line arguments for dataset, + * name etc. + * @param config + */ + + public static void initializeServer(JettyServerConfig config) { + // Currently server-wide. + Fuseki.verboseLogging = config.verboseLogging ; + instance = new JettyFuseki(config) ; + } + + /** Build a Jetty server using the development files for the webapp + * No command line configuration. + */ + public static Server create(int port) { + return create("/", port) ; + } + + public static Server create(String contextPath, int port) { + Server server = new Server(port) ; + WebAppContext webapp = createWebApp(contextPath) ; + server.setHandler(webapp) ; + return server ; + } + + private JettyFuseki(JettyServerConfig config) { + this.serverConfig = config ; + + buildServerWebapp(serverConfig.contextPath, serverConfig.jettyConfigFile, config.enableCompression) ; + + if ( mgtConnector == null ) + mgtConnector = serverConnector ; + } + + /** + * Initialize the {@link JettyFuseki} instance. + */ + public void start() { + + String version = Fuseki.VERSION ; + String buildDate = Fuseki.BUILD_DATE ; + + if ( version != null && version.equals("${project.version}") ) + version = null ; + if ( buildDate != null && buildDate.equals("${build.time.xsd}") ) + buildDate = Utils.nowAsXSDDateTimeString() ; + + if ( version != null && buildDate != null ) + serverLog.info(format("%s %s %s", Fuseki.NAME, version, buildDate)) ; + // This does not get set usefully for Jetty as we use it. + // String jettyVersion = org.eclipse.jetty.server.Server.getVersion() ; + // serverLog.info(format("Jetty %s",jettyVersion)) ; + + String host = serverConnector.getHost() ; + if ( host != null ) + serverLog.info("Incoming connections limited to " + host) ; + + try { + server.start() ; + } catch (java.net.BindException ex) { + serverLog.error("SPARQLServer (port="+serverConnector.getPort()+"): Failed to start server: " + ex.getMessage()) ; + System.exit(1) ; + } catch (Exception ex) { + serverLog.error("SPARQLServer: Failed to start server: " + ex.getMessage(), ex) ; + System.exit(1) ; + } + String now = Utils.nowAsString() ; + serverLog.info(format("Started %s on port %d", now, serverConnector.getPort())) ; + } + + /** + * Sync with the {@link JettyFuseki} instance. + * Returns only if the server exits cleanly + */ + public void join() { + try { + server.join() ; + } catch (InterruptedException ex) { } + } + + /** + * Stop the {@link JettyFuseki} instance. + */ + public void stop() { + String now = Utils.nowAsString() ; + serverLog.info(format("Stopped %s on port %d", now, serverConnector.getPort())) ; + try { + server.stop() ; + } catch (Exception ex) { + Fuseki.serverLog.warn("SPARQLServer: Exception while stopping server: " + ex.getMessage(), ex) ; + } + MgtJMX.removeJMX() ; + } + + public static WebAppContext createWebApp(String contextPath) { + WebAppContext webapp = new WebAppContext(); + webapp.getServletContext().getContextHandler().setMaxFormContentSize(10 * 1000 * 1000) ; + String resourceBase = null ; + if ( /*resourceBase == null &&*/ FileOps.exists(resourceBase1) ) + resourceBase = resourceBase1 ; + if ( resourceBase == null && FileOps.exists(resourceBase2) ) + resourceBase = resourceBase2 ; + if ( resourceBase == null ) + Fuseki.serverLog.warn("Can't find resourceBase (tried "+resourceBase1+" and "+resourceBase2+")") ; + + webapp.setDescriptor(resourceBase+"/WEB-INF/web.xml"); + webapp.setResourceBase(resourceBase); + webapp.setContextPath(contextPath); + + //-- Jetty setup for the ServletContext logger. + // The name of the Jetty-allocated slf4j/log4j logger is + // the display name or, if null, the context path name. + // It is set, without checking for a previous call of setLogger in "doStart" + // which happens during server startup. + // This the name of the ServletContext logger as well + webapp.setDisplayName(Fuseki.servletRequestLogName); + LogCtl.set(Fuseki.servletRequestLogName, "WARN"); + + webapp.setParentLoaderPriority(true); // Normal Java classloader behaviour. + webapp.setErrorHandler(new FusekiErrorHandler()) ; + return webapp ; + } + + private void buildServerWebapp(String contextPath, String jettyConfig, boolean enableCompression) { + if ( jettyConfig != null ) + // --jetty-config=jetty-fuseki.xml + // for detailed configuration of the server using Jetty features. + configServer(jettyConfig) ; + else + defaultServerConfig(serverConfig.port, serverConfig.loopback) ; + WebAppContext webapp = createWebApp(contextPath) ; + server.setHandler(webapp) ; + // Replaced by Shiro. + if ( jettyConfig == null && serverConfig.authConfigFile != null ) + security(webapp, serverConfig.authConfigFile) ; + } + + private static void security(ServletContextHandler context, String authfile) { + Constraint constraint = new Constraint() ; + constraint.setName(Constraint.__BASIC_AUTH) ; + constraint.setRoles(new String[]{"fuseki"}) ; + constraint.setAuthenticate(true) ; + + ConstraintMapping mapping = new ConstraintMapping() ; + mapping.setConstraint(constraint) ; + mapping.setPathSpec("/*") ; + + IdentityService identService = new DefaultIdentityService() ; + + ConstraintSecurityHandler securityHandler = new ConstraintSecurityHandler() ; + securityHandler.addConstraintMapping(mapping) ; + securityHandler.setIdentityService(identService) ; + + HashLoginService loginService = new HashLoginService("Fuseki Authentication", authfile) ; + loginService.setIdentityService(identService) ; + + securityHandler.setLoginService(loginService) ; + securityHandler.setAuthenticator(new BasicAuthenticator()) ; + + context.setSecurityHandler(securityHandler) ; + + serverLog.debug("Basic Auth Configuration = " + authfile) ; + } + + private void configServer(String jettyConfig) { + try { + serverLog.info("Jetty server config file = " + jettyConfig) ; + server = new Server() ; + XmlConfiguration configuration = new XmlConfiguration(new FileInputStream(jettyConfig)) ; + configuration.configure(server) ; + serverConnector = (ServerConnector)server.getConnectors()[0] ; + } catch (Exception ex) { + serverLog.error("SPARQLServer: Failed to configure server: " + ex.getMessage(), ex) ; + throw new FusekiException("Failed to configure a server using configuration file '" + jettyConfig + "'") ; + } + } + + private void defaultServerConfig(int port, boolean loopback) { + server = new Server() ; + ConnectionFactory f1 = new HttpConnectionFactory() ; + ConnectionFactory f2 = new SslConnectionFactory() ; + + ServerConnector connector = new ServerConnector(server, f1) ; //, f2) ; + connector.setPort(port); + server.addConnector(connector); + + if ( loopback ) + connector.setHost("localhost"); + connector.setPort(port) ; + serverConnector = connector ; + } +} http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/jetty/JettyServerConfig.java ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/jetty/JettyServerConfig.java b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/jetty/JettyServerConfig.java new file mode 100644 index 0000000..71012ef --- /dev/null +++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/jetty/JettyServerConfig.java @@ -0,0 +1,51 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.jetty; + + +/** Configuration of the Jetty server when run from the command line directly, + * not as a webapp/war file. + */ + +public class JettyServerConfig +{ + public JettyServerConfig() {} + + /** Port to run the server service on */ + public int port ; + /** Port to run the server service on */ + public String contextPath ; + /** Jetty config file - if null, use the built-in configuration of Jetty */ + public String jettyConfigFile = null ; + /** Listen only on the loopback (localhost) interface */ + public boolean loopback = false ; + /** The local directory for serving the static pages */ + public String pages ; + /** Enable Accept-Encoding compression. Set to false by default.*/ + public boolean enableCompression = false ; + + /** Enable additional logging */ + public boolean verboseLogging = false ; + /** + * Authentication config file used to setup Jetty Basic auth, if a Jetty config file was set this is ignored since Jetty config allows much more complex auth methods to be implemented + */ + public String authConfigFile ; + +} + http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionAsyncTask.java ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionAsyncTask.java b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionAsyncTask.java new file mode 100644 index 0000000..8d8a080 --- /dev/null +++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionAsyncTask.java @@ -0,0 +1,70 @@ +/** + * 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.mgt; + +import javax.servlet.http.HttpServletRequest ; +import javax.servlet.http.HttpServletResponse ; + +import org.apache.jena.atlas.json.JsonValue ; +import org.apache.jena.atlas.lib.InternalErrorException ; +import org.apache.jena.fuseki.async.AsyncPool ; +import org.apache.jena.fuseki.async.AsyncTask ; +import org.apache.jena.fuseki.servlets.HttpAction ; +import org.apache.jena.fuseki.servlets.ServletOps ; + +/** Base helper class for creating async tasks on "items", based on POST */ +public abstract class ActionAsyncTask extends ActionItem +{ + // ?? Better as a library (mixin) so can be used outside ActionItem + private static AsyncPool asyncPool = AsyncPool.get() ; + + public ActionAsyncTask() { super() ; } + + @Override + final + protected void doGet(HttpServletRequest request, HttpServletResponse response) { + ServletOps.errorMethodNotAllowed(METHOD_GET); + } + + @Override + final + protected JsonValue execGetItem(HttpAction action) { + throw new InternalErrorException("GET for AsyncTask -- Should not be here!") ; + } + + @Override + final + protected JsonValue execPostItem(HttpAction action) { + Runnable task = createRunnable(action) ; + AsyncTask aTask = Async.execASyncTask(action, AsyncPool.get(), "backup", task) ; + Async.setLocationHeader(action, aTask); + return Async.asJson(aTask) ; + } + + public static AsyncTask execASyncTask(HttpAction action, AsyncPool asyncPool, String displayName, Runnable task) { + AsyncTask atask = Async.asyncTask(asyncPool, displayName, action.getDataService(), task) ; + Async.setLocationHeader(action, atask); + JsonValue v = Async.asJson(atask) ; + ServletOps.sendJsonReponse(action, v); + return atask ; + } + + protected abstract Runnable createRunnable(HttpAction action) ; +} + http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionBackup.java ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionBackup.java b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionBackup.java new file mode 100644 index 0000000..34f134e --- /dev/null +++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionBackup.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.mgt; + +import static java.lang.String.format ; + +import javax.servlet.http.HttpServletRequest ; +import javax.servlet.http.HttpServletResponse ; + +import org.apache.jena.fuseki.servlets.HttpAction ; +import org.apache.jena.fuseki.servlets.ServletOps ; +import org.slf4j.Logger ; +import org.slf4j.LoggerFactory ; + +import com.hp.hpl.jena.sparql.core.DatasetGraph ; + +public class ActionBackup extends ActionAsyncTask +{ + public ActionBackup() { super() ; } + + // Only POST + @Override + protected void doPost(HttpServletRequest request, HttpServletResponse response) { + doCommon(request, response); + } + + @Override + protected Runnable createRunnable(HttpAction action) { + String name = action.getDatasetName() ; + if ( name == null ) { + action.log.error("Null for dataset name in item request") ; + ServletOps.errorOccurred("Null for dataset name in item request"); + return null ; + } + action.log.info(format("[%d] Backup dataset %s", action.id, name)) ; + return new BackupTask(action) ; + } + + static class BackupTask implements Runnable { + static private Logger log = LoggerFactory.getLogger("Backup") ; + + private final long actionId ; + private final DatasetGraph dataset ; + private final String datasetName ; + + public BackupTask(HttpAction action) { + this.actionId = action.id ; + action.getDataAccessPoint() ; + action.getDataAccessPoint().getDataService() ; + action.getDataAccessPoint().getDataService().getDataset() ; + this.dataset = action.getDataAccessPoint().getDataService().getDataset() ; + this.datasetName = action.getDatasetName() ; + } + + @Override + public void run() { + try { + String backupFilename = Backup.chooseFileName(datasetName) ; + log.info(format("[%d] >>>> Start backup %s -> %s", actionId, datasetName, backupFilename)) ; + Backup.backup(dataset, backupFilename) ; + log.info(format("[%d] <<<< Finish backup %s -> %s", actionId, datasetName, backupFilename)) ; + } catch (Exception ex) { + log.info(format("[%d] **** Exception in backup", actionId), ex) ; + } + } + } +} + \ No newline at end of file http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionContainerItem.java ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionContainerItem.java b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionContainerItem.java new file mode 100644 index 0000000..134afc4 --- /dev/null +++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionContainerItem.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.mgt; + +import org.apache.jena.atlas.json.JsonValue ; +import org.apache.jena.fuseki.servlets.HttpAction ; +import org.apache.jena.fuseki.servlets.ServletOps ; +import org.apache.jena.web.HttpSC ; + +/** Base for actions that are container and also have action on items */ +public abstract class ActionContainerItem extends ActionCtl { + + public ActionContainerItem() { super() ; } + + @Override + final + protected void perform(HttpAction action) { + String method = action.request.getMethod() ; + if ( method.equals(METHOD_GET) ) + execGet(action) ; + else if ( method.equals(METHOD_POST) ) + execPost(action) ; + else if ( method.equals(METHOD_DELETE) ) + execDelete(action) ; + else + ServletOps.error(HttpSC.METHOD_NOT_ALLOWED_405) ; + } + + protected void execGet(HttpAction action) { + JsonValue v ; + if ( isContainerAction(action) ) + v = execGetContainer(action) ; + else + v = execGetItem(action) ; + + ServletOps.sendJsonReponse(action, v); + } + + /** GET request on the container - respond with JSON, or null for plain 200 */ + protected abstract JsonValue execGetContainer(HttpAction action) ; + /** GET request on an item in the container - repond with JSON, or null for plain 200 */ + protected abstract JsonValue execGetItem(HttpAction action) ; + + protected void execPost(HttpAction action) { + JsonValue v ; + if ( isContainerAction(action) ) + v = execPostContainer(action) ; + else + v = execPostItem(action) ; + + ServletOps.sendJsonReponse(action, v); + } + + /** POST request on an item in the container - respond with JSON, or null for plain 200 */ + protected abstract JsonValue execPostContainer(HttpAction action) ; + /** POST request on an item in the container - respond with JSON, or null for plain 200 */ + protected abstract JsonValue execPostItem(HttpAction action) ; + + + /** DELETE request */ + protected void execDelete(HttpAction action) { + if ( isContainerAction(action) ) + execDeleteContainer(action) ; + else + execDeleteItem(action) ; + ServletOps.success(action) ; + } + + /** DELETE request on an item in the container */ + protected void execDeleteContainer(HttpAction action) { + ServletOps.errorMethodNotAllowed(METHOD_DELETE, "DELETE applied to a container") ; + } + + /** DELETE request on an item in the container */ + protected void execDeleteItem(HttpAction action) { + ServletOps.errorMethodNotAllowed(METHOD_DELETE) ; + } +} http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionCtl.java ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionCtl.java b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionCtl.java new file mode 100644 index 0000000..20073d9 --- /dev/null +++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionCtl.java @@ -0,0 +1,97 @@ +/* + * 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.mgt; + +import org.apache.jena.fuseki.Fuseki ; +import org.apache.jena.fuseki.server.DataAccessPoint ; +import org.apache.jena.fuseki.server.DataAccessPointRegistry ; +import org.apache.jena.fuseki.server.DataService ; +import org.apache.jena.fuseki.servlets.ActionBase ; +import org.apache.jena.fuseki.servlets.HttpAction ; +import org.apache.jena.fuseki.servlets.ServletOps ; + +/** Control/admin request lifecycle */ +public abstract class ActionCtl extends ActionBase +{ + protected ActionCtl() { super(Fuseki.adminLog) ; } + + @Override + final + protected void execCommonWorker(HttpAction action) { + DataAccessPoint dataAccessPoint ; + DataService dSrv ; + + String datasetUri = mapRequestToDatasetName(action) ; + if ( datasetUri != null ) { + dataAccessPoint = DataAccessPointRegistry.get().get(datasetUri) ; + if ( dataAccessPoint == null ) { + ServletOps.errorNotFound("Not found: "+datasetUri) ; + return ; + } + } + else { + // This is a placeholder when creating new DatasetRefs + // and also if addressing a container, not a dataset + dataAccessPoint = null ; + dSrv = DataService.serviceOnlyDataService() ; + } + + action.setControlRequest(dataAccessPoint, datasetUri) ; + action.setEndpoint(null, null) ; // No operation or service name. + executeAction(action) ; + } + + protected String mapRequestToDatasetName(HttpAction action) { + return extractItemName(action) ; + } + + // Execute - no stats. + // Intercept point for the UberServlet + protected void executeAction(HttpAction action) { + executeLifecycle(action) ; + } + + // This is the service request lifecycle. + final + protected void executeLifecycle(HttpAction action) + { + startRequest(action) ; + try { + perform(action) ; + } + finally { + finishRequest(action) ; + } + } + + final + protected boolean isContainerAction(HttpAction action) { + return (action.getDataAccessPoint() == null ) ; + } + + protected abstract void perform(HttpAction action) ; + +// /** Map request to uri in the registry. +// * null means no mapping done (passthrough). +// */ +// protected String mapRequestToDataset(HttpAction action) +// { +// return ActionLib.mapRequestToDataset(action.request.getRequestURI()) ; +// } +} http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionDatasets.java ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionDatasets.java b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionDatasets.java new file mode 100644 index 0000000..a623c28 --- /dev/null +++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionDatasets.java @@ -0,0 +1,400 @@ +/** + * 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.mgt; + +import static java.lang.String.format ; + +import java.io.* ; +import java.util.HashMap ; +import java.util.Iterator ; +import java.util.Map ; + +import javax.servlet.ServletException ; +import javax.servlet.ServletOutputStream ; +import javax.servlet.http.HttpServletRequest ; +import javax.servlet.http.HttpServletResponse ; + +import org.apache.jena.atlas.io.IO ; +import org.apache.jena.atlas.json.JsonBuilder ; +import org.apache.jena.atlas.json.JsonValue ; +import org.apache.jena.atlas.lib.InternalErrorException ; +import org.apache.jena.atlas.lib.StrUtils ; +import org.apache.jena.atlas.web.ContentType ; +import org.apache.jena.fuseki.FusekiLib ; +import org.apache.jena.fuseki.build.Builder ; +import org.apache.jena.fuseki.build.Template ; +import org.apache.jena.fuseki.build.TemplateFunctions ; +import org.apache.jena.fuseki.server.* ; +import org.apache.jena.fuseki.servlets.* ; +import org.apache.jena.riot.Lang ; +import org.apache.jena.riot.RDFDataMgr ; +import org.apache.jena.riot.RDFLanguages ; +import org.apache.jena.riot.WebContent ; +import org.apache.jena.riot.system.StreamRDF ; +import org.apache.jena.riot.system.StreamRDFLib ; +import org.apache.jena.web.HttpSC ; + +import com.hp.hpl.jena.datatypes.xsd.XSDDatatype ; +import com.hp.hpl.jena.graph.Node ; +import com.hp.hpl.jena.graph.NodeFactory ; +import com.hp.hpl.jena.query.Dataset ; +import com.hp.hpl.jena.query.ReadWrite ; +import com.hp.hpl.jena.rdf.model.* ; +import com.hp.hpl.jena.shared.uuid.JenaUUID ; +import com.hp.hpl.jena.sparql.core.DatasetGraph ; +import com.hp.hpl.jena.sparql.core.Quad ; +import com.hp.hpl.jena.sparql.util.FmtUtils ; +import com.hp.hpl.jena.tdb.transaction.DatasetGraphTransaction ; +import com.hp.hpl.jena.update.UpdateAction ; +import com.hp.hpl.jena.update.UpdateFactory ; +import com.hp.hpl.jena.update.UpdateRequest ; + +public class ActionDatasets extends ActionContainerItem { + + private static Dataset system = SystemState.getDataset() ; + private static DatasetGraphTransaction systemDSG = SystemState.getDatasetGraph() ; + + static private Property pServiceName = FusekiVocab.pServiceName ; + static private Property pStatus = FusekiVocab.pStatus ; + + private static final String paramDatasetName = "dbName" ; + private static final String paramDatasetType = "dbType" ; + private static final String tDatabasetTDB = "tdb" ; + private static final String tDatabasetMem = "mem" ; + + public ActionDatasets() { super() ; } + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) { + doCommon(request, response); + } + + @Override + protected void doPost(HttpServletRequest request, HttpServletResponse response) { + doCommon(request, response); + } + + @Override + protected void doDelete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + doCommon(request, response); + } + + // ---- GET : return details of dataset or datasets. + @Override + protected JsonValue execGetContainer(HttpAction action) { + action.log.info(format("[%d] GET datasets", action.id)) ; + JsonBuilder builder = new JsonBuilder() ; + builder.startObject("D") ; + builder.key(JsonConst.datasets) ; + JsonDescription.arrayDatasets(builder, DataAccessPointRegistry.get()); + builder.finishObject("D") ; + return builder.build() ; + } + + @Override + protected JsonValue execGetItem(HttpAction action) { + action.log.info(format("[%d] GET dataset %s", action.id, action.getDatasetName())) ; + JsonBuilder builder = new JsonBuilder() ; + DataAccessPoint dsDesc = DataAccessPointRegistry.get().get(action.getDatasetName()) ; + if ( dsDesc == null ) + ServletOps.errorNotFound("Not found: dataset "+action.getDatasetName()); + JsonDescription.describe(builder, dsDesc) ; + return builder.build() ; + } + + // ---- POST + + @Override + protected JsonValue execPostContainer(HttpAction action) { + JenaUUID uuid = JenaUUID.generate() ; + String newURI = uuid.asURI() ; + Node gn = NodeFactory.createURI(newURI) ; + + ContentType ct = FusekiLib.getContentType(action) ; + + boolean committed = false ; + system.begin(ReadWrite.WRITE) ; + try { + Model model = system.getNamedModel(gn.getURI()) ; + StreamRDF dest = StreamRDFLib.graph(model.getGraph()) ; + + if ( WebContent.isHtmlForm(ct) ) + assemblerFromForm(action, dest) ; + else if ( WebContent.isMultiPartForm(ct) ) + assemblerFromUpload(action, dest) ; + else + assemblerFromBody(action, dest) ; + + // Keep a persistent copy. + String filename = FusekiServer.dirFileArea.resolve(uuid.asString()).toString() ; + try ( OutputStream outCopy = new FileOutputStream(filename) ) { + RDFDataMgr.write(outCopy, model, Lang.TURTLE) ; + } + + Statement stmt = getOne(model, null, pServiceName, null) ; + if ( stmt == null ) { + StmtIterator sIter = model.listStatements(null, pServiceName, (RDFNode)null ) ; + if ( ! sIter.hasNext() ) + ServletOps.errorBadRequest("No name given in description of Fuseki service") ; + sIter.next() ; + if ( sIter.hasNext() ) + ServletOps.errorBadRequest("Multiple names given in description of Fuseki service") ; + throw new InternalErrorException("Inconsistent: getOne didn't fail the second time") ; + } + + if ( ! stmt.getObject().isLiteral() ) + ServletOps.errorBadRequest("Found "+FmtUtils.stringForRDFNode(stmt.getObject())+" : Service names are strings, then used to build the external URI") ; + + Resource subject = stmt.getSubject() ; + Literal object = stmt.getObject().asLiteral() ; + + if ( object.getDatatype() != null && ! object.getDatatype().equals(XSDDatatype.XSDstring) ) + action.log.warn(format("[%d] Service name '%s' is not a string", action.id, FmtUtils.stringForRDFNode(object))); + + String datasetName = object.getLexicalForm() ; + String datasetPath = DataAccessPoint.canonical(datasetName) ; + action.log.info(format("[%d] Create database : name = %s", action.id, datasetPath)) ; + + if ( DataAccessPointRegistry.get().isRegistered(datasetPath) ) + // And abort. + ServletOps.error(HttpSC.CONFLICT_409, "Name already registered "+datasetPath) ; + + model.removeAll(null, pStatus, null) ; + model.add(subject, pStatus, FusekiVocab.stateActive) ; + + // Need to be in Resource space at this point. + DataAccessPoint ref = Builder.buildDataAccessPoint(subject) ; + DataAccessPointRegistry.register(datasetPath, ref) ; + action.getResponse().setContentType(WebContent.contentTypeTextPlain); + ServletOutputStream out = action.getResponse().getOutputStream() ; + out.println("That went well") ; + ServletOps.success(action) ; + system.commit(); + committed = true ; + + } catch (IOException ex) { IO.exception(ex); } + finally { + if ( ! committed ) system.abort() ; + system.end() ; + } + return null ; + } + + @Override + protected JsonValue execPostItem(HttpAction action) { + String name = action.getDatasetName() ; + if ( name == null ) + name = "''" ; + action.log.info(format("[%d] POST dataset %s", action.id, name)) ; + + if ( action.getDataAccessPoint() == null ) + ServletOps.errorNotFound("Not found: dataset "+action.getDatasetName()); + + DataService dSrv = action.getDataService() ; + if ( dSrv == null ) + // If not set explicitly, take from DataAccessPoint + dSrv = action.getDataAccessPoint().getDataService() ; + + String s = action.request.getParameter("state") ; + if ( s == null || s.isEmpty() ) + ServletOps.errorBadRequest("No state change given") ; + + // setDatasetState is a transaction on the persistent state of the server. + if ( s.equalsIgnoreCase("active") ) { + action.log.info(format("[%d] REBUILD DATASET %s", action.id, name)) ; + setDatasetState(name, FusekiVocab.stateActive) ; + dSrv.goActive() ; + // DatasetGraph dsg = ???? ; + //dSrv.activate(dsg) ; + //dSrv.activate() ; + } else if ( s.equalsIgnoreCase("offline") ) { + action.log.info(format("[%d] OFFLINE DATASET %s", action.id, name)) ; + DataAccessPoint access = action.getDataAccessPoint() ; + //access.goOffline() ; + dSrv.goOffline() ; // Affects the target of the name. + setDatasetState(name, FusekiVocab.stateOffline) ; + //dSrv.offline() ; + } else if ( s.equalsIgnoreCase("unlink") ) { + action.log.info(format("[%d] UNLINK ACCESS NAME %s", action.id, name)) ; + DataAccessPoint access = action.getDataAccessPoint() ; + ServletOps.errorNotImplemented("unlink: dataset"+action.getDatasetName()); + //access.goOffline() ; + // Registry? + } + else + ServletOps.errorBadRequest("State change operation '"+s+"' not recognized"); + return null ; + } + + private void assemblerFromBody(HttpAction action, StreamRDF dest) { + bodyAsGraph(action, dest) ; + } + + private void assemblerFromForm(HttpAction action, StreamRDF dest) { + String dbType = action.getRequest().getParameter(paramDatasetType) ; + String dbName = action.getRequest().getParameter(paramDatasetName) ; + Map<String, String> params = new HashMap<>() ; + params.put(Template.NAME, dbName) ; + FusekiServer.addGlobals(params); + + action.log.info(format("[%d] Create database : name = %s, type = %s", action.id, dbName, dbType )) ; + if ( dbType == null || dbName == null ) + ServletOps.errorBadRequest("Required parameters: dbName and dbType"); + if ( ! dbType.equals(tDatabasetTDB) && ! dbType.equals(tDatabasetMem) ) + ServletOps.errorBadRequest(format("dbType can be only '%s' or '%s'", tDatabasetTDB, tDatabasetMem)) ; + + String template = null ; + if ( dbType.equalsIgnoreCase(tDatabasetTDB)) + template = TemplateFunctions.templateFile(Template.templateTDBFN, params) ; + if ( dbType.equalsIgnoreCase(tDatabasetMem)) + template = TemplateFunctions.templateFile(Template.templateMemFN, params) ; + RDFDataMgr.parse(dest, new StringReader(template), "http://base/", Lang.TTL) ; + } + + private void assemblerFromUpload(HttpAction action, StreamRDF dest) { + Upload.fileUploadWorker(action, dest); + } + + // ---- DELETE + + @Override + protected void execDeleteItem(HttpAction action) { +// if ( isContainerAction(action) ) { +// ServletOps.errorBadRequest("DELETE only applies to a specific dataset.") ; +// return ; +// } + + // Does not exist? + String name = action.getDatasetName() ; + if ( name == null ) + name = "" ; + action.log.info(format("[%d] DELETE ds=%s", action.id, name)) ; + + if ( ! DataAccessPointRegistry.get().isRegistered(name) ) + ServletOps.errorNotFound("No such dataset registered: "+name); + + systemDSG.begin(ReadWrite.WRITE) ; + boolean committed = false ; + try { + // Here, go offline. + // Need to reference count operations when they drop to zero + // or a timer goes off, we delete the dataset. + + DataAccessPoint ref = DataAccessPointRegistry.get().get(name) ; + // Redo check inside transaction. + if ( ref == null ) + ServletOps.errorNotFound("No such dataset registered: "+name); + + // Name to graph + Quad q = getOne(SystemState.getDatasetGraph(), null, null, pServiceName.asNode(), null) ; + if ( q == null ) + ServletOps.errorBadRequest("Failed to find dataset for '"+name+"'"); + Node gn = q.getGraph() ; + + action.log.info("SHUTDOWN NEEDED"); + DataAccessPointRegistry.get().remove(name) ; + systemDSG.deleteAny(gn, null, null, null) ; + systemDSG.commit() ; + committed = true ; + ServletOps.success(action) ; + } finally { + if ( ! committed ) systemDSG.abort() ; + systemDSG.end() ; + } + } + + // Persistent state change. + private void setDatasetState(String name, Resource newState) { + boolean committed = false ; + system.begin(ReadWrite.WRITE) ; + try { + String dbName = name ; + if ( dbName.startsWith("/") ) + dbName = dbName.substring(1) ; + + String update = StrUtils.strjoinNL + (SystemState.PREFIXES, + "DELETE { GRAPH ?g { ?s fu:status ?state } }", + "INSERT { GRAPH ?g { ?s fu:status "+FmtUtils.stringForRDFNode(newState)+" } }", + "WHERE {", + " GRAPH ?g { ?s fu:name '"+dbName+"' ; ", + " fu:status ?state .", + " }", + "}" + ) ; + UpdateRequest req = UpdateFactory.create(update) ; + UpdateAction.execute(req, system); + system.commit(); + committed = true ; + } finally { + if ( ! committed ) system.abort() ; + system.end() ; + } + } + + // ---- Auxilary functions + + private static Quad getOne(DatasetGraph dsg, Node g, Node s, Node p, Node o) { + Iterator<Quad> iter = dsg.findNG(g, s, p, o) ; + if ( ! iter.hasNext() ) + return null ; + Quad q = iter.next() ; + if ( iter.hasNext() ) + return null ; + return q ; + } + + private static Statement getOne(Model m, Resource s, Property p, RDFNode o) { + StmtIterator iter = m.listStatements(s, p, o) ; + if ( ! iter.hasNext() ) + return null ; + Statement stmt = iter.next() ; + if ( iter.hasNext() ) + return null ; + return stmt ; + } + + // XXX Merge with Upload.incomingData + + private static void bodyAsGraph(HttpAction action, StreamRDF dest) { + HttpServletRequest request = action.request ; + String base = ActionLib.wholeRequestURL(request) ; + ContentType ct = FusekiLib.getContentType(request) ; + Lang lang = RDFLanguages.contentTypeToLang(ct.getContentType()) ; + if ( lang == null ) { + ServletOps.errorBadRequest("Unknown content type for triples: " + ct) ; + return ; + } + InputStream input = null ; + try { input = request.getInputStream() ; } + catch (IOException ex) { IO.exception(ex) ; } + + int len = request.getContentLength() ; +// if ( verbose ) { +// if ( len >= 0 ) +// alog.info(format("[%d] Body: Content-Length=%d, Content-Type=%s, Charset=%s => %s", action.id, len, +// ct.getContentType(), ct.getCharset(), lang.getName())) ; +// else +// alog.info(format("[%d] Body: Content-Type=%s, Charset=%s => %s", action.id, ct.getContentType(), +// ct.getCharset(), lang.getName())) ; +// } + dest.prefix("root", base+"#"); + ActionSPARQL.parse(action, dest, input, lang, base) ; + } +} http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionItem.java ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionItem.java b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionItem.java new file mode 100644 index 0000000..72d3c65 --- /dev/null +++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionItem.java @@ -0,0 +1,45 @@ +/** + * 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.mgt; + +import org.apache.jena.atlas.json.JsonValue ; +import org.apache.jena.fuseki.servlets.HttpAction ; +import org.apache.jena.fuseki.servlets.ServletOps ; +import org.apache.jena.web.HttpSC ; + +/** Action on items in a container, but not the container itself */ +public abstract class ActionItem extends ActionContainerItem +{ + public ActionItem() { super() ; } + + @Override + final + protected JsonValue execGetContainer(HttpAction action) { + ServletOps.error(HttpSC.METHOD_NOT_ALLOWED_405) ; + return null ; + } + + @Override + final + protected JsonValue execPostContainer(HttpAction action) { + ServletOps.error(HttpSC.METHOD_NOT_ALLOWED_405) ; + return null ; + } +} + http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionLogs.java ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionLogs.java b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionLogs.java new file mode 100644 index 0000000..c4d6579 --- /dev/null +++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionLogs.java @@ -0,0 +1,59 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.mgt; + +import static org.apache.jena.riot.WebContent.charsetUTF8 ; +import static org.apache.jena.riot.WebContent.contentTypeTextPlain ; + +import java.io.IOException ; + +import javax.servlet.ServletOutputStream ; +import javax.servlet.http.HttpServletRequest ; +import javax.servlet.http.HttpServletResponse ; + +import org.apache.jena.fuseki.servlets.HttpAction ; +import org.apache.jena.fuseki.servlets.ServletOps ; + +public class ActionLogs extends ActionCtl +{ + public ActionLogs() { super() ; } + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) { + doCommon(req, resp); + } + + @Override + protected void perform(HttpAction action) { + execGet(action) ; + } + + protected void execGet(HttpAction action) { + try { + HttpServletResponse response = action.response ; + ServletOutputStream out = response.getOutputStream() ; + response.setContentType(contentTypeTextPlain) ; + response.setCharacterEncoding(charsetUTF8) ; + out.println("Not implemented yet") ; + out.println() ; + out.flush() ; + ServletOps.success(action); + } catch (IOException ex) { ServletOps.errorOccurred(ex) ; } + } +} http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionPing.java ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionPing.java b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionPing.java new file mode 100644 index 0000000..2426af6 --- /dev/null +++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionPing.java @@ -0,0 +1,71 @@ +/** + * 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.mgt; + +import static org.apache.jena.riot.WebContent.charsetUTF8 ; +import static org.apache.jena.riot.WebContent.contentTypeTextPlain ; + +import java.io.IOException ; + +import javax.servlet.ServletOutputStream ; +import javax.servlet.http.HttpServlet ; +import javax.servlet.http.HttpServletRequest ; +import javax.servlet.http.HttpServletResponse ; + +import org.apache.jena.fuseki.Fuseki ; +import org.apache.jena.fuseki.servlets.ServletOps ; +import org.apache.jena.web.HttpSC ; + +public class ActionPing extends HttpServlet +{ + // Ping is special. + // To avoid excessive logging and id allocation for a "noise" operation, + // this is a raw servlet. + public ActionPing() { super() ; } + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) { + doCommon(req, resp); + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) { + doCommon(req, resp); + } + + + @Override + protected void doHead(HttpServletRequest req, HttpServletResponse resp) { + doCommon(req, resp); + } + + protected void doCommon(HttpServletRequest request, HttpServletResponse response) { + try { + ServletOps.setNoCache(response) ; + ServletOutputStream out = response.getOutputStream() ; + response.setContentType(contentTypeTextPlain); + response.setCharacterEncoding(charsetUTF8) ; + response.setStatus(HttpSC.OK_200); + } catch (IOException ex) { + Fuseki.serverLog.warn("ping :: IOException :: "+ex.getMessage()); + } + } +} + + http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionServerStatus.java ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionServerStatus.java b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionServerStatus.java new file mode 100644 index 0000000..d8f5b1e --- /dev/null +++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionServerStatus.java @@ -0,0 +1,114 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.mgt; + +import static org.apache.jena.riot.WebContent.charsetUTF8 ; +import static org.apache.jena.riot.WebContent.contentTypeJSON ; + +import java.io.IOException ; + +import javax.servlet.ServletOutputStream ; +import javax.servlet.http.HttpServletRequest ; +import javax.servlet.http.HttpServletResponse ; + +import org.apache.jena.atlas.json.JSON ; +import org.apache.jena.atlas.json.JsonBuilder ; +import org.apache.jena.atlas.json.JsonValue ; +import org.apache.jena.fuseki.Fuseki ; +import org.apache.jena.fuseki.server.DataAccessPointRegistry ; +import org.apache.jena.fuseki.servlets.HttpAction ; +import org.apache.jena.fuseki.servlets.ServletOps ; + +/** Description of datasets for a server */ +public class ActionServerStatus extends ActionCtl +{ + public ActionServerStatus() { super() ; } + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) { + doCommon(req, resp) ; + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) { + doCommon(req, resp) ; + } + + @Override + protected void perform(HttpAction action) { + try { + description(action) ; + ServletOps.success(action) ; + } catch (IOException e) { + ServletOps.errorOccurred(e) ; + } + } + + private void description(HttpAction action) throws IOException { + ServletOutputStream out = action.response.getOutputStream() ; + action.response.setContentType(contentTypeJSON); + action.response.setCharacterEncoding(charsetUTF8) ; + + JsonBuilder builder = new JsonBuilder() ; + builder.startObject() ; + describeServer(builder, action.request.getLocalPort()) ; + describeDatasets(builder) ; + builder.finishObject() ; + + JsonValue v = builder.build() ; + JSON.write(out, v) ; + out.println() ; + out.flush() ; + } + + private void describeServer(JsonBuilder builder, int requestPort) { + String versionStr = Fuseki.VERSION ; + String builtDateStr = Fuseki.BUILD_DATE ; + if ( versionStr == null || versionStr.startsWith("${") ) + versionStr = "Development" ; + if ( builtDateStr == null || builtDateStr.startsWith("${") ) + builtDateStr = "Unknown" ; + +// builder +// .key(JsonConst.server) +// .startObject() +// .key(JsonConst.port).value(port) +// .finishObject() ; + builder + .key(JsonConst.admin) + .startObject() + .key(JsonConst.port).value(requestPort) + .finishObject() ; + + builder + .key(JsonConst.version).value(versionStr) + .key(JsonConst.built).value(builtDateStr) + .key(JsonConst.startDT).value(Fuseki.serverStartedAt()) + .key(JsonConst.uptime).value(Fuseki.serverUptimeSeconds()) + ; + + } + + private void describeDatasets(JsonBuilder builder) { + builder.key(JsonConst.datasets) ; + JsonDescription.arrayDatasets(builder, DataAccessPointRegistry.get()); + } + +} + http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionSleep.java ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionSleep.java b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionSleep.java new file mode 100644 index 0000000..e53eb9a --- /dev/null +++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionSleep.java @@ -0,0 +1,98 @@ +/** + * 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.mgt; + +import static java.lang.String.format ; + +import javax.servlet.http.HttpServletRequest ; +import javax.servlet.http.HttpServletResponse ; + +import org.apache.jena.atlas.json.JsonValue ; +import org.apache.jena.atlas.lib.Lib ; +import org.apache.jena.fuseki.async.AsyncPool ; +import org.apache.jena.fuseki.async.AsyncTask ; +import org.apache.jena.fuseki.servlets.HttpAction ; +import org.apache.jena.fuseki.servlets.ServletOps ; +import org.slf4j.Logger ; + +/** A task that kicks off a asynchornous operation that simply waits and exits. For testing. */ +public class ActionSleep extends ActionCtl /* Not ActionAsyncTask - that is a container */ +{ + public ActionSleep() { super() ; } + + // And only POST + @Override + protected void doPost(HttpServletRequest request, HttpServletResponse response) { + doCommon(request, response); + } + + @Override + protected void perform(HttpAction action) { + Runnable task = createRunnable(action) ; + AsyncTask aTask = Async.execASyncTask(action, AsyncPool.get(), "sleep", task) ; + JsonValue v = Async.asJson(aTask) ; + Async.setLocationHeader(action, aTask); + ServletOps.sendJsonReponse(action, v); + } + + protected Runnable createRunnable(HttpAction action) { + String name = action.getDatasetName() ; + if ( name == null ) { +// action.log.error("Null for dataset name in item request") ; +// ServletOps.errorOccurred("Null for dataset name in item request"); +// return null ; + name = "''" ; + } + + String interval = action.request.getParameter("interval") ; + int sleepMilli = 5000 ; + if ( interval != null ) + try { + sleepMilli = Integer.parseInt(interval) ; + } catch (NumberFormatException ex) { + action.log.error(format("[%d] NumberFormatException: %s", action.id, interval)) ; + } + action.log.info(format("[%d] Sleep %s %d ms", action.id, name, sleepMilli)) ; + return new SleepTask(action, sleepMilli) ; + } + + static class SleepTask implements Runnable { + private final Logger log ; + private final long actionId ; + private final int sleepMilli ; + + public SleepTask(HttpAction action, int sleepMilli ) { + this.log = action.log ; + this.actionId = action.id ; + this.sleepMilli = sleepMilli ; + } + + @Override + public void run() { + try { + log.info(format("[%d] >> Sleep start", actionId)) ; + Lib.sleep(sleepMilli) ; + log.info(format("[%d] << Sleep finish", actionId)) ; + } catch (Exception ex) { + log.info(format("[%d] **** Exception", actionId), ex) ; + } + } + } +} + http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionStats.java ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionStats.java b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionStats.java new file mode 100644 index 0000000..490bce2 --- /dev/null +++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionStats.java @@ -0,0 +1,214 @@ +/** + * 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.mgt; + +import static java.lang.String.format ; +import static org.apache.jena.riot.WebContent.charsetUTF8 ; +import static org.apache.jena.riot.WebContent.contentTypeTextPlain ; + +import java.io.IOException ; +import java.util.Iterator ; +import java.util.List ; + +import javax.servlet.ServletOutputStream ; +import javax.servlet.http.HttpServletRequest ; +import javax.servlet.http.HttpServletResponse ; + +import org.apache.jena.atlas.json.JsonBuilder ; +import org.apache.jena.atlas.json.JsonValue ; +import org.apache.jena.fuseki.server.* ; +import org.apache.jena.fuseki.servlets.HttpAction ; + +public class ActionStats extends ActionContainerItem +{ + // XXX Use ActionContainerItem + public ActionStats() { super() ; } + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) { + doCommon(req, resp); + } + + // This does not consult the system database for dormant etc. + @Override + protected JsonValue execGetContainer(HttpAction action) { + action.log.info(format("[%d] GET stats all", action.id)) ; + JsonBuilder builder = new JsonBuilder() ; + builder.startObject("top") ; + + builder.key(JsonConst.datasets) ; + builder.startObject("datasets") ; + for ( String ds : DataAccessPointRegistry.get().keys() ) + statsDataset(builder, ds) ; + builder.finishObject("datasets") ; + + builder.finishObject("top") ; + return builder.build() ; + } + + @Override + protected JsonValue execGetItem(HttpAction action) { + action.log.info(format("[%d] GET stats dataset %s", action.id, action.getDatasetName())) ; + + JsonBuilder builder = new JsonBuilder() ; + String datasetPath = DataAccessPoint.canonical(action.getDatasetName()) ; + builder.startObject("TOP") ; + + builder.key(JsonConst.datasets) ; + builder.startObject("datasets") ; + statsDataset(builder, datasetPath) ; + builder.finishObject("datasets") ; + + builder.finishObject("TOP") ; + return builder.build() ; + } + + private void statsDataset(JsonBuilder builder, String ds) { + // Object started + builder.key(ds) ; + + DataAccessPoint access = DataAccessPointRegistry.get().get(ds) ; + DataService dSrv = access.getDataService() ; + builder.startObject("counters") ; + + builder.key(CounterName.Requests.name()).value(dSrv.getCounters().value(CounterName.Requests)) ; + builder.key(CounterName.RequestsGood.name()).value(dSrv.getCounters().value(CounterName.RequestsGood)) ; + builder.key(CounterName.RequestsBad.name()).value(dSrv.getCounters().value(CounterName.RequestsBad)) ; + + + // Build the operation -> endpoint list map. + +// MultiMap<OperationName, Endpoint> map = MultiMap.createMapList() ; +// for ( OperationName operName : dSrv.getOperations() ) { +// List<Endpoint> endpoints = access.getDataService().getOperation(operName) ; +// for ( Endpoint endpoint : endpoints ) +// map.put(operName, endpoint) ; +// } + + + builder.key(JsonConst.endpoints).startObject("endpoints") ; + + for ( OperationName operName : dSrv.getOperations() ) { + List<Endpoint> endpoints = access.getDataService().getOperation(operName) ; +// System.err.println(operName+" : "+endpoints.size()) ; +// for ( Endpoint endpoint : endpoints ) +// System.err.println(" "+endpoint.getEndpoint()) ; + + for ( Endpoint endpoint : endpoints ) { + + // Endpoint names are unique but not services. + + builder.key(endpoint.getEndpoint()) ; + builder.startObject() ; + + operationCounters(builder, endpoint); + builder.key(JsonConst.operation).value(operName.name()) ; + builder.key(JsonConst.description).value(operName.getDescription()) ; + + builder.finishObject() ; + } + } + builder.finishObject("endpoints") ; + builder.finishObject("counters") ; + + } + + private void operationCounters(JsonBuilder builder, Endpoint operation) { + for (CounterName cn : operation.getCounters().counters()) { + Counter c = operation.getCounters().get(cn) ; + builder.key(cn.name()).value(c.value()) ; + } + } + + private void statsTxt(HttpServletResponse resp) throws IOException + { + ServletOutputStream out = resp.getOutputStream() ; + resp.setContentType(contentTypeTextPlain); + resp.setCharacterEncoding(charsetUTF8) ; + + Iterator<String> iter = DataAccessPointRegistry.get().keys().iterator() ; + while(iter.hasNext()) + { + String ds = iter.next() ; + DataAccessPoint desc = DataAccessPointRegistry.get().get(ds) ; + statsTxt(out, desc) ; + if ( iter.hasNext() ) + out.println() ; + } + out.flush() ; + } + + private void statsTxt(ServletOutputStream out, DataAccessPoint desc) throws IOException + { + DataService dSrv = desc.getDataService() ; + out.println("Dataset: "+desc.getName()) ; + out.println(" Requests = "+dSrv.getCounters().value(CounterName.Requests)) ; + out.println(" Good = "+dSrv.getCounters().value(CounterName.RequestsGood)) ; + out.println(" Bad = "+dSrv.getCounters().value(CounterName.RequestsBad)) ; + + out.println(" SPARQL Query:") ; + out.println(" Request = "+counter(dSrv, OperationName.Query, CounterName.Requests)) ; + out.println(" Good = "+counter(dSrv, OperationName.Query, CounterName.RequestsGood)) ; + out.println(" Bad requests = "+counter(dSrv, OperationName.Query, CounterName.RequestsBad)) ; + out.println(" Timeouts = "+counter(dSrv, OperationName.Query, CounterName.QueryTimeouts)) ; + out.println(" Bad exec = "+counter(dSrv, OperationName.Query, CounterName.QueryExecErrors)) ; + out.println(" IO Errors = "+counter(dSrv, OperationName.Query, CounterName.QueryIOErrors)) ; + + out.println(" SPARQL Update:") ; + out.println(" Request = "+counter(dSrv, OperationName.Update, CounterName.Requests)) ; + out.println(" Good = "+counter(dSrv, OperationName.Update, CounterName.RequestsGood)) ; + out.println(" Bad requests = "+counter(dSrv, OperationName.Update, CounterName.RequestsBad)) ; + out.println(" Bad exec = "+counter(dSrv, OperationName.Update, CounterName.UpdateExecErrors)) ; + + out.println(" Upload:") ; + out.println(" Requests = "+counter(dSrv, OperationName.Upload, CounterName.Requests)) ; + out.println(" Good = "+counter(dSrv, OperationName.Upload, CounterName.RequestsGood)) ; + out.println(" Bad = "+counter(dSrv, OperationName.Upload, CounterName.RequestsBad)) ; + + out.println(" SPARQL Graph Store Protocol:") ; + out.println(" GETs = "+gspValue(dSrv, CounterName.HTTPget)+ " (good="+gspValue(dSrv, CounterName.HTTPgetGood)+"/bad="+gspValue(dSrv, CounterName.HTTPGetBad)+")") ; + out.println(" PUTs = "+gspValue(dSrv, CounterName.HTTPput)+ " (good="+gspValue(dSrv, CounterName.HTTPputGood)+"/bad="+gspValue(dSrv, CounterName.HTTPputBad)+")") ; + out.println(" POSTs = "+gspValue(dSrv, CounterName.HTTPpost)+ " (good="+gspValue(dSrv, CounterName.HTTPpostGood)+"/bad="+gspValue(dSrv, CounterName.HTTPpostBad)+")") ; + out.println(" DELETEs = "+gspValue(dSrv, CounterName.HTTPdelete)+ " (good="+gspValue(dSrv, CounterName.HTTPdeleteGood)+"/bad="+gspValue(dSrv, CounterName.HTTPdeleteBad)+")") ; + out.println(" HEADs = "+gspValue(dSrv, CounterName.HTTPhead)+ " (good="+gspValue(dSrv, CounterName.HTTPheadGood)+"/bad="+gspValue(dSrv, CounterName.HTTPheadBad)+")") ; + } + + private long counter(DataService dSrv, OperationName opName, CounterName cName) { + return 0 ; + } + + private long gspValue(DataService dSrv, CounterName cn) { + return counter(dSrv, OperationName.GSP, cn) + + counter(dSrv, OperationName.GSP_R, cn) ; + } + + // We shouldn't get here - no doPost above. + + @Override + protected JsonValue execPostContainer(HttpAction action) { + throw new InternalError(METHOD_POST+" container") ; + } + + @Override + protected JsonValue execPostItem(HttpAction action) { + throw new InternalError(METHOD_POST+" item") ; + } +} + + http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionTasks.java ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionTasks.java b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionTasks.java new file mode 100644 index 0000000..97f8027 --- /dev/null +++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/ActionTasks.java @@ -0,0 +1,125 @@ +/** + * 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.mgt; +import static java.lang.String.format ; + +import javax.servlet.http.HttpServletRequest ; +import javax.servlet.http.HttpServletResponse ; + +import org.apache.jena.atlas.json.JsonBuilder ; +import org.apache.jena.atlas.json.JsonValue ; +import org.apache.jena.fuseki.Fuseki ; +import org.apache.jena.fuseki.async.AsyncPool ; +import org.apache.jena.fuseki.async.AsyncTask ; +import org.apache.jena.fuseki.servlets.ActionBase ; +import org.apache.jena.fuseki.servlets.HttpAction ; +import org.apache.jena.fuseki.servlets.ServletOps ; +import org.apache.jena.web.HttpSC ; + +public class ActionTasks extends ActionBase //ActionContainerItem +{ + private static AsyncPool[] pools = { AsyncPool.get() } ; + + public ActionTasks() { super(Fuseki.serverLog) ; } + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) { + doCommon(request, response); + } + + @Override + protected void doPost(HttpServletRequest request, HttpServletResponse response) { + doCommon(request, response); + } + + private static String prefix = "/" ; + + @Override + protected void execCommonWorker(HttpAction action) { + String name = extractItemName(action) ; + if ( name != null ) { + if ( name.startsWith(prefix)) + name = name.substring(prefix.length()) ; + else + log.warn("Unexpected task name : "+name) ; + } + + String method = action.request.getMethod() ; + if ( method.equals(METHOD_GET) ) + execGet(action, name) ; + else if ( method.equals(METHOD_POST) ) + execPost(action, name) ; + else + ServletOps.error(HttpSC.METHOD_NOT_ALLOWED_405) ; + } + + private void execGet(HttpAction action, String name) { + if ( name == null ) + log.info(format("[%d] Tasks", action.id)); + else + log.info(format("[%d] Task %s", action.id, name)); + + JsonValue responseBody = null ; + + if ( name == null ) { + JsonBuilder builder = new JsonBuilder() ; + builder.startArray() ; + + for ( AsyncPool pool : pools ) { + for ( AsyncTask aTask : pool.tasks() ) { + //builder.value(aTask.getTaskId()) ; + descOneTask(builder, aTask) ; + } + } + builder.finishArray() ; + responseBody = builder.build(); + } else { + for ( AsyncPool pool : pools ) { + // Assumes first is only. + AsyncTask aTask = pool.getTask(name) ; + if ( aTask != null ) { + JsonBuilder builder = new JsonBuilder() ; + descOneTask(builder, aTask); + responseBody = builder.build() ; + } + } + } + + if ( responseBody == null ) + ServletOps.errorNotFound("Task '"+name+"' not found") ; + ServletOps.setNoCache(action) ; + ServletOps.sendJsonReponse(action, responseBody); + } + + private void execPost(HttpAction action, String name) { + + } + + private static void descOneTask(JsonBuilder builder, AsyncTask aTask) { + builder.startObject("SingleTask") ; + builder.key(JsonConst.task).value(aTask.displayName()) ; + builder.key(JsonConst.taskId).value(aTask.getTaskId()) ; + if ( aTask.getStartPoint() != null ) + builder.key(JsonConst.started).value(aTask.getStartPoint()) ; + if ( aTask.getFinishPoint() != null ) + builder.key(JsonConst.finished).value(aTask.getFinishPoint()) ; + builder.finishObject("SingleTask") ; + } +} + http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/Async.java ---------------------------------------------------------------------- diff --git a/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/Async.java b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/Async.java new file mode 100644 index 0000000..1cbda48 --- /dev/null +++ b/jena-fuseki2/src/main/java/org/apache/jena/fuseki/mgt/Async.java @@ -0,0 +1,68 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.mgt; + +import org.apache.http.HttpHeaders ; +import org.apache.jena.atlas.json.JsonBuilder ; +import org.apache.jena.atlas.json.JsonValue ; +import org.apache.jena.fuseki.async.AsyncPool ; +import org.apache.jena.fuseki.async.AsyncTask ; +import org.apache.jena.fuseki.server.DataService ; +import org.apache.jena.fuseki.servlets.HttpAction ; + +public class Async +{ + public static AsyncTask asyncTask(AsyncPool asyncPool, String displayName, DataService dataService, Runnable task) { + AsyncTask asyncTask = asyncPool.submit(task, displayName, dataService) ; + return asyncTask ; + } + + public static JsonValue asJson(AsyncTask asyncTask) { + JsonBuilder builder = new JsonBuilder() ; + builder.startObject("outer") ; + builder.key(JsonConst.taskId).value(asyncTask.getTaskId()) ; + builder.finishObject("outer") ; + return builder.build() ; + } + + public static void setLocationHeader(HttpAction action, AsyncTask asyncTask) { + String x = action.getRequest().getRequestURI() ; + if ( ! x.endsWith("/") ) + x += "/" ; + x += asyncTask.getTaskId() ; + //String x = "/$/tasks/"+asyncTask.getTaskId() ; + action.getResponse().setHeader(HttpHeaders.LOCATION, x) ; + } + + public static AsyncTask execASyncTask(HttpAction action, AsyncPool asyncPool, String displayName, Runnable runnable) { + AsyncTask atask = Async.asyncTask(asyncPool, "backup", action.getDataService(), runnable) ; + Async.setLocationHeader(action, atask); + return atask ; + } + + // Combined does not work very well - e.g sleep does not set Location. +// public static AsyncTask execASyncTask(HttpAction action, AsyncPool asyncPool, String displayName, Runnable task) { +// AsyncTask atask = Async.asyncTask(asyncPool, displayName, action.getDataService(), task) ; +// Async.setLocationHeader(action, atask); +// JsonValue v = Async.asJson(atask) ; +// ServletOps.sendJsonReponse(action, v); +// return atask ; +// } +} +