http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki/src/main/java/org/apache/jena/fuseki/conneg/ConNeg.java ---------------------------------------------------------------------- diff --git a/jena-fuseki/src/main/java/org/apache/jena/fuseki/conneg/ConNeg.java b/jena-fuseki/src/main/java/org/apache/jena/fuseki/conneg/ConNeg.java new file mode 100644 index 0000000..25e6a4f --- /dev/null +++ b/jena-fuseki/src/main/java/org/apache/jena/fuseki/conneg/ConNeg.java @@ -0,0 +1,123 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.conneg; + +import static org.apache.jena.fuseki.HttpNames.hAcceptCharset ; + +import javax.servlet.http.HttpServletRequest ; + +import org.apache.jena.atlas.web.AcceptList ; +import org.apache.jena.atlas.web.MediaRange ; +import org.apache.jena.atlas.web.MediaType ; +import org.slf4j.Logger ; +import org.slf4j.LoggerFactory ; + +public class ConNeg +{ + private static Logger log = LoggerFactory.getLogger(ConNeg.class) ; + // See riot.ContentNeg (client side). + // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1 + + static public MediaType parse(String contentType) + { + try { + return MediaType.create(contentType) ; + } catch (RuntimeException ex) { return null ; } + } + + static public MediaType match(String headerString, AcceptList offerList) + { + AcceptList l = new AcceptList(headerString) ; + return AcceptList.match(l, offerList) ; + } + + /** Match a single media type against a header string */ + public static String match(String headerString, String mediaRangeStr) + { + AcceptList l = new AcceptList(headerString) ; + MediaRange aItem = new MediaRange(mediaRangeStr) ; // MediaType + MediaType m = l.match(aItem) ; + if ( m == null ) + return null ; + return m.toHeaderString() ; + } + + /*package*/ static String[] split(String s, String splitStr) + { + String[] x = s.split(splitStr,2) ; + for ( int i = 0 ; i < x.length ; i++ ) + { + x[i] = x[i].trim() ; + } + return x ; + } + + public static MediaType chooseCharset(HttpServletRequest httpRequest, + AcceptList myPrefs, + MediaType defaultMediaType) + { + String a = httpRequest.getHeader(hAcceptCharset) ; + if ( log.isDebugEnabled() ) + log.debug("Accept-Charset request: "+a) ; + + MediaType item = choose(a, myPrefs, defaultMediaType) ; + + if ( log.isDebugEnabled() ) + log.debug("Charset chosen: "+item) ; + + return item ; + } + + public static MediaType chooseContentType(HttpServletRequest httpRequest, + AcceptList myPrefs, + MediaType defaultMediaType) + { + String a = WebLib.getAccept(httpRequest) ; + if ( log.isDebugEnabled() ) + log.debug("Accept request: "+a) ; + + MediaType item = choose(a, myPrefs, defaultMediaType) ; + + if ( log.isDebugEnabled() ) + log.debug("Content type chosen: "+item) ; + + return item ; + } + + private static MediaType choose(String headerString, AcceptList myPrefs, + MediaType defaultMediaType) + { + if ( headerString == null ) + return defaultMediaType ; + + AcceptList headerList = new AcceptList(headerString) ; + + if ( myPrefs == null ) + { + MediaType i = headerList.first() ; + if ( i == null ) return defaultMediaType ; + return i ; + } + + MediaType i = AcceptList.match(headerList, myPrefs) ; + if ( i == null ) + return defaultMediaType ; + return i ; + } +}
http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki/src/main/java/org/apache/jena/fuseki/conneg/WebLib.java ---------------------------------------------------------------------- diff --git a/jena-fuseki/src/main/java/org/apache/jena/fuseki/conneg/WebLib.java b/jena-fuseki/src/main/java/org/apache/jena/fuseki/conneg/WebLib.java new file mode 100644 index 0000000..fdeb139 --- /dev/null +++ b/jena-fuseki/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.fuseki.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-fuseki/src/main/java/org/apache/jena/fuseki/mgt/ActionBackup.java ---------------------------------------------------------------------- diff --git a/jena-fuseki/src/main/java/org/apache/jena/fuseki/mgt/ActionBackup.java b/jena-fuseki/src/main/java/org/apache/jena/fuseki/mgt/ActionBackup.java new file mode 100644 index 0000000..c36e8be --- /dev/null +++ b/jena-fuseki/src/main/java/org/apache/jena/fuseki/mgt/ActionBackup.java @@ -0,0 +1,196 @@ +/** + * 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.concurrent.Callable ; +import java.util.concurrent.ExecutorService ; +import java.util.concurrent.Executors ; +import java.util.zip.GZIPOutputStream ; + +import javax.servlet.http.HttpServletRequest ; +import javax.servlet.http.HttpServletResponse ; + +import org.apache.jena.atlas.io.IO ; +import org.apache.jena.atlas.lib.FileOps ; +import org.apache.jena.atlas.logging.Log ; +import org.apache.jena.fuseki.FusekiException ; +import org.apache.jena.fuseki.FusekiLib ; +import org.apache.jena.fuseki.server.DatasetRef ; +import org.apache.jena.fuseki.server.DatasetRegistry ; +import org.apache.jena.fuseki.servlets.HttpAction ; +import org.apache.jena.fuseki.servlets.ServletBase ; +import org.apache.jena.riot.Lang ; +import org.apache.jena.riot.RDFDataMgr ; +import org.apache.jena.web.HttpSC ; + +import com.hp.hpl.jena.sparql.core.DatasetGraph ; +import com.hp.hpl.jena.sparql.util.Utils ; + +public class ActionBackup extends ServletBase +{ + public ActionBackup() { super() ; } + + // Limit to one backup at a time. + public static final ExecutorService backupService = Executors.newFixedThreadPool(1) ; + + @Override + protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException + { + String dataset = FusekiLib.safeParameter(request, "dataset") ; + if ( dataset == null ) + { + response.sendError(HttpSC.BAD_REQUEST_400, "Required parameter missing: ?dataset=") ; + return ; + } + + if ( ! dataset.startsWith("/") ) + dataset="/"+dataset ; + + // HttpSession session = request.getSession(true) ; + // session.setAttribute("dataset", dataset) ; + // session.setMaxInactiveInterval(15*60) ; // 10 mins + + boolean known = DatasetRegistry.get().isRegistered(dataset) ; + if (!known) + { + response.sendError(HttpSC.BAD_REQUEST_400, "No such dataset: " + dataset) ; + return ; + } + + long id = allocRequestId(request, response); + HttpAction action = new HttpAction(id, request, response, false) ; + DatasetRef ref = DatasetRegistry.get().get(dataset) ; + action.setDataset(ref); + scheduleBackup(action) ; + } + + static final String BackupArea = "backups" ; + + private void scheduleBackup(final HttpAction action) + { + String dsName = action.dsRef.name ; + final String ds = dsName.startsWith("/")? dsName : "/"+dsName ; + + String timestamp = Utils.nowAsString("yyyy-MM-dd_HH-mm-ss") ; + final String filename = BackupArea + ds + "_" + timestamp ; + FileOps.ensureDir(BackupArea) ; + + try { + final Callable<Boolean> task = new Callable<Boolean>() { + @Override + public Boolean call() throws Exception + { + log.info(format("[%d] Start backup %s to '%s'", action.id, ds, filename)) ; + action.beginRead() ; + try { + backup(action.getActiveDSG(), filename) ; + log.info(format("[%d] Finish backup %s to '%s'", action.id, ds, filename)) ; + } + catch ( RuntimeException ex ) + { + log.info(format("[%d] Exception during backup: ", action.id, ex.getMessage()), ex) ; + return Boolean.FALSE ; + } + finally { + action.endRead() ; + } + return Boolean.TRUE ; + }} ; + + log.info(format("[%d] Schedule backup %s to '%s'", action.id, ds, filename)) ; + backupService.submit(task) ; + } + //catch (FusekiException ex) + catch (RuntimeException ex) + { + log.warn("Unanticipated exception", ex) ; + try { action.response.sendError(HttpSC.INTERNAL_SERVER_ERROR_500, ex.getMessage()) ; } + catch (IOException e) { IO.exception(e) ; } + return ; + } + + successPage(action, "Backup scheduled - see server log for details") ; + } + + // Share with new ServletBase. + protected static void successPage(HttpAction action, String message) + { + try { + action.response.setContentType("text/html"); + action.response.setStatus(HttpSC.OK_200); + PrintWriter out = action.response.getWriter() ; + out.println("<html>") ; + out.println("<head>") ; + out.println("</head>") ; + out.println("<body>") ; + out.println("<h1>Success</h1>"); + if ( message != null ) + { + out.println("<p>") ; + out.println(message) ; + out.println("</p>") ; + } + out.println("</body>") ; + out.println("</html>") ; + out.flush() ; + } catch (IOException ex) { IO.exception(ex) ; } + } + + public static void backup(DatasetGraph dsg, String backupfile) + { + if ( ! backupfile.endsWith(".nq") ) + backupfile = backupfile+".nq" ; + + OutputStream out = null ; + try + { + if ( true ) + { + // This seems to achive about the same as "gzip -6" + // It's not too expensive in elapsed time but it's not zero cost. + // GZip, large buffer. + out = new FileOutputStream(backupfile+".gz") ; + out = new GZIPOutputStream(out, 8*1024) ; + out = new BufferedOutputStream(out) ; + } + else + { + out = new FileOutputStream(backupfile) ; + out = new BufferedOutputStream(out) ; + } + + RDFDataMgr.write(out, dsg,Lang.NQUADS) ; + out.close() ; + out = null ; + } + catch (FileNotFoundException e) + { + Log.warn(ActionBackup.class, "File not found: "+backupfile) ; + throw new FusekiException("File not found: "+backupfile) ; + } + catch (IOException e) { IO.exception(e) ; } + finally { + try { if (out != null) out.close() ; } + catch (IOException e) { /* ignore */ } + } + } +} http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki/src/main/java/org/apache/jena/fuseki/mgt/ActionDataset.java ---------------------------------------------------------------------- diff --git a/jena-fuseki/src/main/java/org/apache/jena/fuseki/mgt/ActionDataset.java b/jena-fuseki/src/main/java/org/apache/jena/fuseki/mgt/ActionDataset.java new file mode 100644 index 0000000..fc3d395 --- /dev/null +++ b/jena-fuseki/src/main/java/org/apache/jena/fuseki/mgt/ActionDataset.java @@ -0,0 +1,121 @@ +/* + * 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 java.io.IOException ; +import java.io.UnsupportedEncodingException ; + +import javax.servlet.ServletOutputStream ; +import javax.servlet.http.HttpServlet ; +import javax.servlet.http.HttpServletRequest ; +import javax.servlet.http.HttpServletResponse ; +import javax.servlet.http.HttpSession ; + +import org.apache.commons.codec.binary.Base64 ; +import org.apache.jena.fuseki.FusekiLib ; +import org.apache.jena.fuseki.HttpNames ; +import org.apache.jena.fuseki.server.DatasetRegistry ; +import org.apache.jena.web.HttpSC ; + +/** Log-in and choose dataset */ +public class ActionDataset extends HttpServlet +{ + @Override + protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException + { +// request.getRemoteUser() ; +// request.getUserPrincipal() ; + + String dataset = FusekiLib.safeParameter(request, "dataset") ; + HttpSession session = request.getSession(true) ; + session.setAttribute("dataset", dataset) ; + session.setMaxInactiveInterval(15*60) ; // 10 mins + + boolean known = DatasetRegistry.get().isRegistered(dataset) ; + if ( !known ) + { + response.sendError(HttpSC.BAD_REQUEST_400, "No such dataset: "+dataset) ; + return ; + } + + if ( true ) + { + // Redirect to GET page. + response.setHeader(HttpNames.hLocation, PageNames.pageAfterLogin) ; + response.setStatus(HttpSC.SEE_OTHER_303) ; + } + else + { + // Welcome style - but HTML inline :-( + response.setContentType("text/html"); + response.setStatus(HttpSC.OK_200) ; + ServletOutputStream out = response.getOutputStream() ; + out.print("<p>"+dataset+"("+known+")</p>") ; + + for ( String name : DatasetRegistry.get().keys() ) { + out.print("<li>") ; + out.print(name) ; + out.println("</li>") ; + } + out.println("</ul>") ; + out.println("<p><a href=\"info\">Next</a></p>") ; + } + +// Cookie cookie = new Cookie("org.apache.jena.fuseki.session", dataset) ; +// // 24 hours. +// cookie.setMaxAge(24*60*60) ; + + } + + /** + * This method returns true if the HttpServletRequest contains a valid + * authorisation header + * @param req The HttpServletRequest to test + * @return true if the Authorisation header is valid + */ + + private boolean authenticate(HttpServletRequest req) + { + String authhead=req.getHeader("Authorization"); + + if(authhead!=null) + { + byte[] up = Base64.decodeBase64(authhead.substring(6)) ; + // Decode the authorisation String + String usernpass ; + try + { + usernpass = new String(up, "ascii") ; + } catch (UnsupportedEncodingException e) + { + e.printStackTrace(); + usernpass = null ; + } + // Split the username from the password + String user=usernpass.substring(0,usernpass.indexOf(":")); + String password=usernpass.substring(usernpass.indexOf(":")+1); + + if (user.equals("user") && password.equals("pass")) + return true; + } + + return false; + } + +} http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki/src/main/java/org/apache/jena/fuseki/mgt/ManagementServer.java ---------------------------------------------------------------------- diff --git a/jena-fuseki/src/main/java/org/apache/jena/fuseki/mgt/ManagementServer.java b/jena-fuseki/src/main/java/org/apache/jena/fuseki/mgt/ManagementServer.java new file mode 100644 index 0000000..c884bde --- /dev/null +++ b/jena-fuseki/src/main/java/org/apache/jena/fuseki/mgt/ManagementServer.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 org.apache.jena.fuseki.Fuseki.serverLog ; + +import java.util.List ; + +import javax.servlet.http.HttpServlet ; + +import org.apache.jena.fuseki.Fuseki ; +import org.apache.jena.fuseki.server.FusekiErrorHandler ; +import org.apache.jena.fuseki.servlets.DumpServlet ; +import org.eclipse.jetty.server.Connector ; +import org.eclipse.jetty.server.Server ; +import org.eclipse.jetty.server.nio.SelectChannelConnector ; +import org.eclipse.jetty.servlet.ServletContextHandler ; +import org.eclipse.jetty.servlet.ServletHolder ; + +public class ManagementServer +{ + public static Server createManagementServer(int mgtPort) + { + Fuseki.serverLog.info("Adding management functions") ; + + // Separate Jetty server + Server server = new Server() ; + +// BlockingChannelConnector bcConnector = new BlockingChannelConnector() ; +// bcConnector.setUseDirectBuffers(false) ; +// Connector connector = bcConnector ; + + Connector connector = new SelectChannelConnector() ; + // Ignore idle time. + // If set, then if this goes off, it keeps going off and you get a lot of log messages. + connector.setMaxIdleTime(0) ; // Jetty outputs a lot of messages if this goes off. + connector.setPort(mgtPort); + server.addConnector(connector) ; + + ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); + context.setErrorHandler(new FusekiErrorHandler()) ; + server.setHandler(context); + + // Add the server control servlet + addServlet(context, new MgtCmdServlet(), "/mgt") ; + addServlet(context, new DumpServlet(), "/dump") ; + addServlet(context, new StatsServlet(), "/stats") ; + + return server ; + // Old plan +// // Development : server control panel. +// addServlet(context, new ServerServlet(), "/server") ; +// addServlet(context, new ActionBackup(), "/backup") ; + } + + // SHARE + private static void addServlet(ServletContextHandler context, String datasetPath, HttpServlet servlet, List<String> pathSpecs) + { + for ( String pathSpec : pathSpecs ) + { + if ( pathSpec.endsWith("/") ) + pathSpec = pathSpec.substring(0, pathSpec.length()-1) ; + if ( pathSpec.startsWith("/") ) + pathSpec = pathSpec.substring(1, pathSpec.length()) ; + addServlet(context, servlet, datasetPath+"/"+pathSpec) ; + } + } + + private static void addServlet(ServletContextHandler context, HttpServlet servlet, String pathSpec) + { + ServletHolder holder = new ServletHolder(servlet) ; + addServlet(context, holder, pathSpec) ; + } + + private static void addServlet(ServletContextHandler context, ServletHolder holder, String pathSpec) + { + serverLog.debug("Add servlet @ "+pathSpec) ; + context.addServlet(holder, pathSpec) ; + } + +} + http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki/src/main/java/org/apache/jena/fuseki/mgt/MgtCmdServlet.java ---------------------------------------------------------------------- diff --git a/jena-fuseki/src/main/java/org/apache/jena/fuseki/mgt/MgtCmdServlet.java b/jena-fuseki/src/main/java/org/apache/jena/fuseki/mgt/MgtCmdServlet.java new file mode 100644 index 0000000..5385e75 --- /dev/null +++ b/jena-fuseki/src/main/java/org/apache/jena/fuseki/mgt/MgtCmdServlet.java @@ -0,0 +1,169 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** A servlet that dumps its request + */ + +// Could be neater - much, much neater! + +package org.apache.jena.fuseki.mgt ; + +import java.io.IOException ; +import java.io.PrintWriter ; + +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.server.DatasetRef ; +import org.apache.jena.fuseki.server.SPARQLServer ; +import org.apache.jena.fuseki.server.ServiceRef ; +import org.apache.jena.web.HttpSC ; +import org.slf4j.Logger ; + +import com.hp.hpl.jena.Jena ; +import com.hp.hpl.jena.query.ARQ ; +import com.hp.hpl.jena.tdb.TDB ; + +/** Control functions for a Fuskei server */ + +public class MgtCmdServlet extends HttpServlet +{ + // Experimental - likely to change. + private static Logger log = Fuseki.serverLog ; + + public MgtCmdServlet() + { + + } + + @Override + public void init() + { + return ; + } + + public static String paramCmd = "cmd" ; + public static String cmdBackup = "backup" ; // &dataset=/datasetname + public static String cmdRestart = "restart" ; // Not implemented. + public static String cmdShutdown = "shutdown" ; // Server stops, no questions asked. (Not implemented) + + ActionBackup actionBackup = new ActionBackup() ; + + @Override + public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException + { + // Commands format: + // ?cmd=backup&<other args per command> + + String[] args = req.getParameterValues(paramCmd) ; + if ( args == null ) { + resp.setContentType("text/plain") ; + resp.setStatus(HttpSC.BAD_REQUEST_400) ; + + return ; + } + for ( String cmd : args ) { + if ( log.isInfoEnabled() ) + log.info("Management command: " + cmd) ; + + if ( cmd.equalsIgnoreCase(cmdBackup) ) { + actionBackup.doPost(req, resp) ; + continue ; + } + if ( cmd.equalsIgnoreCase(cmdRestart) ) { + + continue ; + } + if ( cmd.equalsIgnoreCase(cmdShutdown) ) { + Fuseki.getServer().stop() ; + continue ; + } + log.warn("Unrecognized command : " + cmd) ; + + } + } + + @Override + public void doGet(HttpServletRequest req, HttpServletResponse resp) + { + try { + // serverLog.info("Fuseki Server Config servlet") ; + + PrintWriter out = resp.getWriter() ; + resp.setContentType("text/plain") ; + SPARQLServer server = Fuseki.getServer() ; + + out.println("Software:") ; + String fusekiVersion = Fuseki.VERSION ; + if ( fusekiVersion.equals("${project.version}") ) + fusekiVersion = "(development)" ; + + out.printf(" %s %s\n", Fuseki.NAME, fusekiVersion) ; + out.printf(" %s %s\n", TDB.NAME, TDB.VERSION) ; + out.printf(" %s %s\n", ARQ.NAME, ARQ.VERSION) ; + out.printf(" %s %s\n", Jena.NAME, Jena.VERSION) ; + + // out.printf("Port: %s\n", + // server.getServer().getConnectors()[0].getPort()) ; + out.println() ; + + for ( DatasetRef dsRef : server.getDatasets() ) { + datasetRefDetails(out, dsRef) ; + out.println() ; + } + } + catch (IOException ex) {} + } + + private static void datasetRefDetails(PrintWriter out, DatasetRef dsRef) + { + if ( dsRef.name != null ) + out.println("Name = " + dsRef.name) ; + else + out.println("Name = <unset>") ; + + endpointDetail(out, "Query", dsRef, dsRef.query) ; + endpointDetail(out, "Update", dsRef, dsRef.update) ; + endpointDetail(out, "Upload", dsRef, dsRef.upload) ; + endpointDetail(out, "Graphs(Read)", dsRef, dsRef.readGraphStore) ; + endpointDetail(out, "Graphs(RW)", dsRef, dsRef.readWriteGraphStore) ; + } + + private static void endpointDetail(PrintWriter out, String label, DatasetRef dsRef, ServiceRef service) + { + boolean first = true ; + out.printf(" %-15s :: ", label) ; + + for ( String s : service.endpoints ) { + if ( !first ) + out.print(" , ") ; + first = false ; + s = "/" + dsRef.name + "/" + s ; + out.print(s) ; + } + out.println() ; + } + + @Override + public String getServletInfo() + { + return "Fuseki Control Servlet" ; + } +} http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki/src/main/java/org/apache/jena/fuseki/mgt/MgtFunctions.java ---------------------------------------------------------------------- diff --git a/jena-fuseki/src/main/java/org/apache/jena/fuseki/mgt/MgtFunctions.java b/jena-fuseki/src/main/java/org/apache/jena/fuseki/mgt/MgtFunctions.java new file mode 100644 index 0000000..e43b1e2 --- /dev/null +++ b/jena-fuseki/src/main/java/org/apache/jena/fuseki/mgt/MgtFunctions.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.mgt; + +import java.util.List ; + +import javax.servlet.http.HttpServletRequest ; +import javax.servlet.http.HttpSession ; + +import org.apache.jena.atlas.io.IndentedLineBuffer ; +import org.apache.jena.atlas.iterator.Iter ; +import org.apache.jena.fuseki.Fuseki ; +import org.apache.jena.fuseki.server.DatasetRef ; +import org.apache.jena.fuseki.server.DatasetRegistry ; +import org.apache.jena.fuseki.server.ServiceRef ; + +import com.hp.hpl.jena.shared.PrefixMapping ; +import com.hp.hpl.jena.sparql.core.DatasetGraph ; +import com.hp.hpl.jena.sparql.core.Prologue ; +import com.hp.hpl.jena.sparql.serializer.PrologueSerializer ; +import com.hp.hpl.jena.tdb.store.DatasetGraphTDB ; + +/** Avoid code in JSPs */ +public class MgtFunctions +{ + /** Return the name of the current dataset */ + public static String dataset(HttpServletRequest request, String dftValue) + { + String ds = dataset(request) ; + if ( ds == null ) + return dftValue ; + return ds ; + } + + /** Return the name of the current dataset */ + public static String dataset(HttpServletRequest request) + { + HttpSession session = request.getSession(false) ; + if ( session == null ) + return "No session"; + String ds = (String)session.getAttribute("dataset") ; + return ds ; + } + + /** Return the dataset description reference for currnet dataset */ + public static DatasetRef datasetDesc(HttpServletRequest request) + { + HttpSession session = request.getSession(false) ; + if ( session == null ) + return null ; + String ds = (String)session.getAttribute("dataset") ; + return DatasetRegistry.get().get(ds) ; + } + + /** Return lists of datasets */ + public static List<String> datasets(HttpServletRequest request) + { + return Iter.toList(DatasetRegistry.get().keys()) ; + } + + /** Return name of */ + public static String actionDataset(HttpServletRequest request) + { + return PageNames.actionDatasetNames ; + } + + // Service name getters ... + + /** Return a SPARQL query service name for the dataset */ + public static String serviceQuery(String dataset) + { + String dft = "sparql" ; + DatasetRef ref = getFromRegistry(dataset) ; + if ( ref == null ) + return dft ; + return serviceNameOrDefault(ref.query, dft) ; + } + + /** Return a SPARQL update service name for the dataset */ + public static String serviceUpdate(String dataset) + { + String dft = "update" ; + DatasetRef ref = getFromRegistry(dataset) ; + if ( ref == null ) + return dft ; + return serviceNameOrDefault(ref.update, dft) ; + } + + /** Return a SPARQL upload service name for the dataset */ + public static String serviceUpload(String dataset) + { + String dft = "upload" ; + DatasetRef ref = getFromRegistry(dataset) ; + if ( ref == null ) + return dft ; + return serviceNameOrDefault(ref.upload, dft) ; + } + + /** Return a SPARQL Graph Store Protocol (Read) service name for the dataset */ + public static String serviceGraphRead(String dataset) + { + String dft = "get" ; + DatasetRef ref = getFromRegistry(dataset) ; + if ( ref == null ) + return dft ; + return serviceNameOrDefault(ref.readGraphStore, dft) ; + } + + /** Return a SPARQL Graph Store Protocol (Read-Write) service name for the dataset */ + public static String serviceGraphReadWrite(String dataset) + { + String dft = "data" ; + DatasetRef ref = getFromRegistry(dataset) ; + if ( ref == null ) + return dft ; + return serviceNameOrDefault(ref.readWriteGraphStore, dft) ; + } + + private static DatasetRef getFromRegistry(String dataset) + { + DatasetRegistry registry = DatasetRegistry.get() ; + if ( registry == null ) + { + Fuseki.serverLog.warn("No dataset registry") ; + return null ; + } + + DatasetRef ref = registry.get(dataset) ; + if ( ref == null ) + Fuseki.serverLog.warn("Dataset not found: "+dataset) ; + return ref ; + } + + private static String serviceNameOrDefault(ServiceRef service, String defaultValue) + { + if ( service.endpoints.isEmpty() ) + return defaultValue ; + String x = service.endpoints.get(0) ; + if ( x.startsWith("/") ) + x = x.substring(1) ; + return x ; + } + + /** Return prefixes for the datasets, SPARQL syntax. */ + public static String prefixes(HttpServletRequest request) + { + String dsName = dataset(request) ; + DatasetRef desc = getFromRegistry(dsName) ; + if ( desc == null ) + return "<not found>" ; + DatasetGraph dsg = desc.dataset ; + + if ( dsg instanceof DatasetGraphTDB ) + { + PrefixMapping pmap = ((DatasetGraphTDB)dsg).getPrefixes().getPrefixMapping() ; + Prologue prologue = new Prologue(pmap) ; + IndentedLineBuffer buff = new IndentedLineBuffer() ; + PrologueSerializer.output(buff, prologue) ; + buff.append("\n") ; + return buff.asString() ; + } + return "" ; + } +} http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki/src/main/java/org/apache/jena/fuseki/mgt/PageNames.java ---------------------------------------------------------------------- diff --git a/jena-fuseki/src/main/java/org/apache/jena/fuseki/mgt/PageNames.java b/jena-fuseki/src/main/java/org/apache/jena/fuseki/mgt/PageNames.java new file mode 100644 index 0000000..4dc315b --- /dev/null +++ b/jena-fuseki/src/main/java/org/apache/jena/fuseki/mgt/PageNames.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.mgt; + +public class PageNames +{ + //public static final String pageControlPanel = "/control-panel.tpl" ; + + /** The dispatch URL for the place to go after login (0.2.1 - login = choose dataset) */ + public static final String pageAfterLogin = "/sparql.tpl" ; + + /** This is the full web dispatch URL: control-panel.tpl knowns + * the name indirectly because it gets it via velocity + * calling mgt.actionDataset. + */ + public static final String actionDatasetNames = "/$/datasets" ; +} http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki/src/main/java/org/apache/jena/fuseki/mgt/StatsServlet.java ---------------------------------------------------------------------- diff --git a/jena-fuseki/src/main/java/org/apache/jena/fuseki/mgt/StatsServlet.java b/jena-fuseki/src/main/java/org/apache/jena/fuseki/mgt/StatsServlet.java new file mode 100644 index 0000000..e165355 --- /dev/null +++ b/jena-fuseki/src/main/java/org/apache/jena/fuseki/mgt/StatsServlet.java @@ -0,0 +1,171 @@ +/** + * 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 java.io.IOException ; +import java.util.Iterator ; + +import javax.servlet.ServletOutputStream ; +import javax.servlet.http.HttpServlet ; +import javax.servlet.http.HttpServletRequest ; +import javax.servlet.http.HttpServletResponse ; + +import org.apache.jena.atlas.json.JSON ; +import org.apache.jena.atlas.json.JsonArray ; +import org.apache.jena.atlas.json.JsonObject ; +import org.apache.jena.fuseki.server.* ; +import org.apache.jena.riot.WebContent ; + +public class StatsServlet extends HttpServlet +{ + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) + //throws ServletException, IOException + { + try { + // Conneg etc. + statsJSON(req, resp) ; + } catch (IOException e) + { } + } + + private void statsJSON(HttpServletRequest req, HttpServletResponse resp) throws IOException + { + ServletOutputStream out = resp.getOutputStream() ; + resp.setContentType(WebContent.contentTypeJSON); + resp.setCharacterEncoding(WebContent.charsetUTF8) ; + + /* + * { "server" : .... + * "datasets" : { + * "ds1": { counters... } + * GSP stucture? + * + */ + + JsonObject obj = new JsonObject() ; + JsonObject datasets = new JsonObject() ; + JsonObject server = new JsonObject() ; + server.put("host", req.getLocalName()+":"+req.getLocalPort()) ; + + for ( String ds : DatasetRegistry.get().keys() ) + statsJSON(datasets, ds) ; + + obj.put("server", server) ; + obj.put("datasets", datasets) ; + + JSON.write(out, obj) ; + out.flush() ; + } + + private void statsJSON(JsonObject datasets, String ds) { + DatasetRef desc = DatasetRegistry.get().get(ds) ; + JsonObject stats = new JsonObject() ; + datasets.put(ds, stats) ; + stats.put(CounterName.Requests.name(), desc.getCounters().value(CounterName.Requests)) ; + stats.put(CounterName.RequestsGood.name(), desc.getCounters().value(CounterName.RequestsGood)) ; + stats.put(CounterName.RequestsBad.name(), desc.getCounters().value(CounterName.RequestsBad)) ; + JsonObject services = new JsonObject() ; + +// JsonArray endpoints = new JsonArray() ; +// services.put("endpoints", endpoints) ; +// JsonArray srvNames = new JsonArray() ; +// services.put("names", srvNames) ; + + // There can be several endpoints for one service. + for ( ServiceRef srvRef : desc.getServiceRefs() ) { + JsonObject epStats = new JsonObject() ; + statsJSON(epStats, srvRef) ; + services.put(srvRef.name, epStats) ; + JsonArray endpoints = new JsonArray() ; + epStats.put("endpoints", endpoints) ; + for ( String ep : srvRef.endpoints) { + endpoints.add(ep) ; + } + } + stats.put("services", services) ; + } + + private void statsJSON(JsonObject epStats, ServiceRef srvRef) { + for (CounterName cn : srvRef.getCounters().counters()) { + Counter c = srvRef.getCounters().get(cn) ; + epStats.put(cn.name(), c.value()) ; + } + } + + private void statsTxt(HttpServletResponse resp) throws IOException + { + ServletOutputStream out = resp.getOutputStream() ; + resp.setContentType(WebContent.contentTypeTextPlain); + resp.setCharacterEncoding(WebContent.charsetUTF8) ; + + Iterator<String> iter = DatasetRegistry.get().keys().iterator() ; + while(iter.hasNext()) + { + String ds = iter.next() ; + DatasetRef desc = DatasetRegistry.get().get(ds) ; + statsTxt(out, desc) ; + if ( iter.hasNext() ) + out.println() ; + } + out.flush() ; + } + private void statsTxt(ServletOutputStream out, DatasetRef desc) throws IOException + { + out.println("Dataset: "+desc.name) ; + out.println(" Requests = "+desc.getCounters().value(CounterName.Requests)) ; + out.println(" Good = "+desc.getCounters().value(CounterName.RequestsGood)) ; + out.println(" Bad = "+desc.getCounters().value(CounterName.RequestsBad)) ; + + out.println(" SPARQL Query:") ; + out.println(" Request = "+desc.query.getCounters().value(CounterName.Requests)) ; + out.println(" Good = "+desc.query.getCounters().value(CounterName.RequestsGood)) ; + out.println(" Bad requests = "+desc.query.getCounters().value(CounterName.RequestsBad)) ; + out.println(" Timeouts = "+desc.query.getCounters().value(CounterName.QueryTimeouts)) ; + out.println(" Bad exec = "+desc.query.getCounters().value(CounterName.QueryExecErrors)) ; + + out.println(" SPARQL Update:") ; + out.println(" Request = "+desc.update.getCounters().value(CounterName.Requests)) ; + out.println(" Good = "+desc.update.getCounters().value(CounterName.RequestsGood)) ; + out.println(" Bad requests = "+desc.update.getCounters().value(CounterName.RequestsBad)) ; + out.println(" Bad exec = "+desc.update.getCounters().value(CounterName.UpdateExecErrors)) ; + + out.println(" Upload:") ; + out.println(" Requests = "+desc.upload.getCounters().value(CounterName.Requests)) ; + out.println(" Good = "+desc.upload.getCounters().value(CounterName.RequestsGood)) ; + out.println(" Bad = "+desc.upload.getCounters().value(CounterName.RequestsBad)) ; + + out.println(" SPARQL Graph Store Protocol:") ; + out.println(" GETs = "+gspValue(desc, CounterName.GSPget)+ " (good="+gspValue(desc, CounterName.GSPgetGood)+"/bad="+gspValue(desc, CounterName.GSPgetBad)+")") ; + out.println(" PUTs = "+gspValue(desc, CounterName.GSPput)+ " (good="+gspValue(desc, CounterName.GSPputGood)+"/bad="+gspValue(desc, CounterName.GSPputBad)+")") ; + out.println(" POSTs = "+gspValue(desc, CounterName.GSPpost)+ " (good="+gspValue(desc, CounterName.GSPpostGood)+"/bad="+gspValue(desc, CounterName.GSPpostBad)+")") ; + out.println(" DELETEs = "+gspValue(desc, CounterName.GSPdelete)+ " (good="+gspValue(desc, CounterName.GSPdeleteGood)+"/bad="+gspValue(desc, CounterName.GSPdeleteBad)+")") ; + out.println(" HEADs = "+gspValue(desc, CounterName.GSPhead)+ " (good="+gspValue(desc, CounterName.GSPheadGood)+"/bad="+gspValue(desc, CounterName.GSPheadBad)+")") ; + } + + private long gspValue(DatasetRef desc, CounterName cn) { + long x1 = desc.readGraphStore.getCounters().value(cn) ; + long x2 = desc.readWriteGraphStore.getCounters().value(cn) ; + return x1+x2 ; + } + + +} + + http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki/src/main/java/org/apache/jena/fuseki/migrate/GraphLoadUtils.java ---------------------------------------------------------------------- diff --git a/jena-fuseki/src/main/java/org/apache/jena/fuseki/migrate/GraphLoadUtils.java b/jena-fuseki/src/main/java/org/apache/jena/fuseki/migrate/GraphLoadUtils.java new file mode 100644 index 0000000..da16863 --- /dev/null +++ b/jena-fuseki/src/main/java/org/apache/jena/fuseki/migrate/GraphLoadUtils.java @@ -0,0 +1,76 @@ +/* + * 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.migrate; + +import org.apache.jena.atlas.web.TypedInputStream ; +import org.apache.jena.fuseki.Fuseki ; +import org.apache.jena.riot.RDFDataMgr ; +import org.apache.jena.riot.system.StreamRDF ; +import org.apache.jena.riot.system.StreamRDFLib ; + +import com.hp.hpl.jena.graph.Factory ; +import com.hp.hpl.jena.graph.Graph ; +import com.hp.hpl.jena.rdf.model.Model ; +import com.hp.hpl.jena.rdf.model.ModelFactory ; + +/** A packaging of code to do a controlled read of a graph or model */ + +public class GraphLoadUtils +{ + // ---- Model level + + public static Model readModel(String uri, int limit) + { + Graph g = Factory.createGraphMem() ; + readUtil(g, uri, limit) ; + return ModelFactory.createModelForGraph(g) ; + } + + public static void loadModel(Model model, String uri, int limit) + { + Graph g = model.getGraph() ; + readUtil(g, uri, limit) ; + } + + // ---- Graph level + + public static Graph readGraph(String uri, int limit) + { + Graph g = Factory.createGraphMem() ; + readUtil(g, uri, limit) ; + return g ; + } + + public static void loadGraph(Graph g, String uri, int limit) + { + readUtil(g, uri, limit) ; + } + + // ** Worker. + private static void readUtil(Graph graph, String uri, int limit) + { + // We need to do this ourselves, not via riot, to use the webStreamManager + StreamRDF sink = StreamRDFLib.graph(graph) ; + sink = new SinkRDFLimited(sink, limit) ; + + TypedInputStream input = Fuseki.webStreamManager.open(uri) ; + RDFDataMgr.parse(sink, input, uri) ; + } +} http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki/src/main/java/org/apache/jena/fuseki/migrate/Registry.java ---------------------------------------------------------------------- diff --git a/jena-fuseki/src/main/java/org/apache/jena/fuseki/migrate/Registry.java b/jena-fuseki/src/main/java/org/apache/jena/fuseki/migrate/Registry.java new file mode 100644 index 0000000..ca30be1 --- /dev/null +++ b/jena-fuseki/src/main/java/org/apache/jena/fuseki/migrate/Registry.java @@ -0,0 +1,42 @@ +/* + * 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.migrate; + +import java.util.Collection ; +import java.util.HashMap ; +import java.util.Map ; + +public class Registry<T> +{ + protected Map<String, T> registry = new HashMap<String, T>() ; + + public Registry() {} + + public void put(String key, T value) { registry.put(key, value) ; } + + public T get(String key) { return registry.get(key) ; } + + public boolean isRegistered(String key) { return registry.containsKey(key) ; } + public void remove(String key) { registry.remove(key) ; } + public Collection<String> keys() { return registry.keySet() ; } + //public Iterator<String> keys() { return registry.keySet().iterator() ; } + + public int size() { return registry.size() ; } + public boolean isEmpty() { return registry.isEmpty() ; } +} http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki/src/main/java/org/apache/jena/fuseki/migrate/SinkRDFLimited.java ---------------------------------------------------------------------- diff --git a/jena-fuseki/src/main/java/org/apache/jena/fuseki/migrate/SinkRDFLimited.java b/jena-fuseki/src/main/java/org/apache/jena/fuseki/migrate/SinkRDFLimited.java new file mode 100644 index 0000000..514d756 --- /dev/null +++ b/jena-fuseki/src/main/java/org/apache/jena/fuseki/migrate/SinkRDFLimited.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.fuseki.migrate; + +import org.apache.jena.riot.system.StreamRDF ; +import org.apache.jena.riot.system.StreamRDFWrapper ; + +import com.hp.hpl.jena.graph.Triple ; +import com.hp.hpl.jena.sparql.core.Quad ; + +public class SinkRDFLimited extends StreamRDFWrapper +{ + private long count = 0 ; + private final long limit ; + + public SinkRDFLimited(StreamRDF output, long limit) + { + super(output) ; + this.limit = limit ; + } + + @Override + public void triple(Triple triple) + { + count++ ; + super.triple(triple) ; + } + + @Override + public void quad(Quad quad) + { + count++ ; + super.quad(quad) ; + } + + public long getCount() { return count ; } + public long getLimit() { return limit ; } +} + http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/Counter.java ---------------------------------------------------------------------- diff --git a/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/Counter.java b/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/Counter.java new file mode 100644 index 0000000..88d4d37 --- /dev/null +++ b/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/Counter.java @@ -0,0 +1,34 @@ +/** + * 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.concurrent.atomic.AtomicLong ; + +/** A statistics counter */ +public class Counter +{ + private AtomicLong counter = new AtomicLong(0) ; + + public Counter() {} + + public void inc() { counter.incrementAndGet() ; } + public void dec() { counter.decrementAndGet() ; } + public long value() { return counter.get() ; } +} + http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/CounterMXBean.java ---------------------------------------------------------------------- diff --git a/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/CounterMXBean.java b/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/CounterMXBean.java new file mode 100644 index 0000000..2de7658 --- /dev/null +++ b/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/CounterMXBean.java @@ -0,0 +1,25 @@ +/** + * 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 CounterMXBean +{ + long getValue() ; +} + http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/CounterName.java ---------------------------------------------------------------------- diff --git a/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/CounterName.java b/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/CounterName.java new file mode 100644 index 0000000..2952aa8 --- /dev/null +++ b/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/CounterName.java @@ -0,0 +1,83 @@ +/** + * 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; + +/** Names for all counters */ +public enum CounterName { + // There are generic names - apply to all services and datasets + // and specific ones. + + + // Total request received + Requests("requests"), + // .. of which some and "good" and some are "bad". + // #"good" + #"bad" roughly equals #"requests" + // except that the total is incremented at the start, and the outcome at the end. + // There may also be short term consistency issues. + RequestsGood("requests.good"), + RequestsBad("requests.bad") , + + // SPARQL Protocol - query and update - together with upload. + + // Query - standard and ... + QueryTimeouts("query.timeouts") , + QueryExecErrors("query.execerrors") , + + // Update - standard and ... + UpdateExecErrors("update.execerrors"), + + // Upload ... standard counters + + // Graph Store Protocol. + + // For each HTTP method + GSPget("gsp.get.requests") , + GSPgetGood("gsp.get.requests.good") , + GSPgetBad("gsp.get.requests.bad") , + + GSPpost("gsp.post.requests") , + GSPpostGood("gsp.post.requests.good") , + GSPpostBad("gsp.post.requests.bad") , + + GSPdelete("gsp.delete.requests") , + GSPdeleteGood("gsp.delete.requests.good") , + GSPdeleteBad("gsp.delete.requests.bad") , + + GSPput("gsp.put.requests") , + GSPputGood("gsp.put.requests.good") , + GSPputBad("gsp.put.requests.bad") , + + GSPhead("gsp.head.requests") , + GSPheadGood("gsp.head.requests.good") , + GSPheadBad("gsp.head.requests.bad") , + + GSPpatch("gsp.patch.requests") , + GSPpatchGood("gsp.patch.requests.good") , + GSPpatchBad("gsp.patch.requests.bad") , + + GSPoptions("gsp.options.requests") , + GSPoptionsGood("gsp.options.requests.good") , + GSPoptionsBad("gsp.options.requests.bad") , + + ; + + private String name ; + private CounterName(String name) { this.name = name ; } + +} http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/CounterSet.java ---------------------------------------------------------------------- diff --git a/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/CounterSet.java b/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/CounterSet.java new file mode 100644 index 0000000..7d0d622 --- /dev/null +++ b/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/CounterSet.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.server ; + +import java.util.Collection ; +import java.util.HashMap ; +import java.util.Map ; + +import org.slf4j.Logger ; +import org.slf4j.LoggerFactory ; + +/** A collection of counters */ +public class CounterSet { + private static Logger log = LoggerFactory.getLogger(CounterSet.class) ; + + private Map<CounterName, Counter> counters = new HashMap<CounterName, Counter>() ; + + public CounterSet() {} + + public Collection<CounterName> counters() { + return counters.keySet() ; + } + + public void inc(CounterName c) { + get(c).inc() ; + } + + public void dec(CounterName c) { + get(c).dec() ; + } + + public long value(CounterName c) { + return get(c).value() ; + } + + public void add(CounterName counterName) { + if ( counters.containsKey(counterName) ) { + log.warn("Duplicate counter in counter set: " + counterName) ; + return ; + } + counters.put(counterName, new Counter()) ; + } + + public boolean contains(CounterName cn) { + return counters.containsKey(cn) ; + } + + public Counter get(CounterName cn) { + Counter c = counters.get(cn) ; + if ( c == null ) + log.warn("No counter in counter set: " + cn) ; + return c ; + } +} http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/Counters.java ---------------------------------------------------------------------- diff --git a/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/Counters.java b/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/Counters.java new file mode 100644 index 0000000..4e5ca4b --- /dev/null +++ b/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/Counters.java @@ -0,0 +1,25 @@ +/** + * 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; + +/** Objects that have a counter set */ +public interface Counters { + public CounterSet getCounters() ; +} + http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/DatasetMXBean.java ---------------------------------------------------------------------- diff --git a/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/DatasetMXBean.java b/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/DatasetMXBean.java new file mode 100644 index 0000000..bf38229 --- /dev/null +++ b/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/DatasetMXBean.java @@ -0,0 +1,35 @@ +/** + * 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 DatasetMXBean +{ + String getName() ; + + long getRequests() ; + long getRequestsGood() ; + long getRequestsBad() ; + +// void enable() ; +// void disable() ; +// void setReadOnly() ; +// boolean getReadOnly() ; + +} + http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/DatasetRef.java ---------------------------------------------------------------------- diff --git a/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/DatasetRef.java b/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/DatasetRef.java new file mode 100644 index 0000000..040c759 --- /dev/null +++ b/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/DatasetRef.java @@ -0,0 +1,241 @@ +/* + * 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.* ; +import java.util.concurrent.atomic.AtomicLong ; + +import org.apache.jena.fuseki.Fuseki ; + +import com.hp.hpl.jena.query.ReadWrite ; +import com.hp.hpl.jena.sparql.core.DatasetGraph ; + +public class DatasetRef implements DatasetMXBean, Counters +{ + public String name = null ; + public DatasetGraph dataset = null ; + + public ServiceRef query = new ServiceRef("query") ; + public ServiceRef update = new ServiceRef("update") ; + public ServiceRef upload = new ServiceRef("upload") ; + public ServiceRef readGraphStore = new ServiceRef("gspRead") ; + public ServiceRef readWriteGraphStore = new ServiceRef("gspReadWrite") ; + + // Dataset-level counters. + private final CounterSet counters = new CounterSet() ; + @Override + public CounterSet getCounters() { return counters ; } + + private Map<String, ServiceRef> endpoints = new HashMap<String, ServiceRef>() ; + private List<ServiceRef> serviceRefs = new ArrayList<ServiceRef>() ; + private boolean initialized = false ; + + // Two step initiation (c.f. Builder pattern) + // Create object - incrementally set state - call init to calculate internal datastructures. + public DatasetRef() {} + public void init() { + if ( initialized ) + Fuseki.serverLog.warn("Already initialized: dataset = "+name) ; + initialized = true ; + initServices() ; + } + + @Override public String toString() { return "DatasetRef:'"+name+"'" ; } + + private void initServices() { + add(query) ; + add(update) ; + add(upload) ; + add(readGraphStore) ; + add(readWriteGraphStore) ; + addCounters() ; + } + + private void add(ServiceRef srvRef) { + serviceRefs.add(srvRef) ; + for ( String ep : srvRef.endpoints ) + endpoints.put(ep, srvRef) ; + } + + public ServiceRef getServiceRef(String service) { + if ( ! initialized ) + Fuseki.serverLog.error("Not initialized: dataset = "+name) ; + if ( service.startsWith("/") ) + service = service.substring(1, service.length()) ; + return endpoints.get(service) ; + } + + public Collection<ServiceRef> getServiceRefs() { + return serviceRefs ; + } + + /** Counter of active read transactions */ + public AtomicLong activeReadTxn = new AtomicLong(0) ; + + /** Counter of active write transactions */ + public AtomicLong activeWriteTxn = new AtomicLong(0) ; + + /** Cumulative counter of read transactions */ + public AtomicLong totalReadTxn = new AtomicLong(0) ; + + /** Cumulative counter of writer transactions */ + public AtomicLong totalWriteTxn = new AtomicLong(0) ; + +// /** Count of requests received - anyzservice */ +// public AtomicLong countServiceRequests = new AtomicLong(0) ; +// /** Count of requests received that fail in some way */ +// public AtomicLong countServiceRequestsBad = new AtomicLong(0) ; +// /** Count of requests received that fail in some way */ +// public AtomicLong countServiceRequestsOK = new AtomicLong(0) ; +// +// // SPARQL Query +// +// /** Count of SPARQL Queries successfully executed */ +// public AtomicLong countQueryOK = new AtomicLong(0) ; +// /** Count of SPARQL Queries with syntax errors */ +// public AtomicLong countQueryBadSyntax = new AtomicLong(0) ; +// /** Count of SPARQL Queries with timeout on execution */ +// public AtomicLong countQueryTimeout = new AtomicLong(0) ; +// /** Count of SPARQL Queries with execution errors (not timeouts) */ +// public AtomicLong countQueryBadExecution = new AtomicLong(0) ; + + public void startTxn(ReadWrite mode) + { + switch(mode) + { + case READ: + activeReadTxn.getAndIncrement() ; + totalReadTxn.getAndIncrement() ; + break ; + case WRITE: + activeWriteTxn.getAndIncrement() ; + totalWriteTxn.getAndIncrement() ; + break ; + } + } + + public void finishTxn(ReadWrite mode) + { + switch(mode) + { + case READ: + activeReadTxn.decrementAndGet() ; + break ; + case WRITE: + activeWriteTxn.decrementAndGet() ; + break ; + } + } + + //TODO Need to be able to set this from the config file. + public boolean allowDatasetUpdate = false; + + public boolean allowTimeoutOverride = false; + public long maximumTimeoutOverride = Long.MAX_VALUE; + + public boolean isReadOnly() + { + return ! allowDatasetUpdate && + ! update.isActive() && + ! upload.isActive() && + ! readWriteGraphStore.isActive() + ; + } + + // MBean + + @Override + public String getName() { return name ; } + + @Override public long getRequests() { + return counters.value(CounterName.Requests) ; + } + + @Override + public long getRequestsGood() { + return counters.value(CounterName.RequestsGood) ; + } + @Override + public long getRequestsBad() { + return counters.value(CounterName.RequestsBad) ; + } + + private void addCounters() { + getCounters().add(CounterName.Requests) ; + getCounters().add(CounterName.RequestsGood) ; + getCounters().add(CounterName.RequestsBad) ; + + query.getCounters().add(CounterName.Requests) ; + query.getCounters().add(CounterName.RequestsGood) ; + query.getCounters().add(CounterName.RequestsBad) ; + query.getCounters().add(CounterName.QueryTimeouts) ; + query.getCounters().add(CounterName.QueryExecErrors) ; + + update.getCounters().add(CounterName.Requests) ; + update.getCounters().add(CounterName.RequestsGood) ; + update.getCounters().add(CounterName.RequestsBad) ; + update.getCounters().add(CounterName.UpdateExecErrors) ; + + upload.getCounters().add(CounterName.Requests) ; + upload.getCounters().add(CounterName.RequestsGood) ; + upload.getCounters().add(CounterName.RequestsBad) ; + + addCountersForGSP(readWriteGraphStore.getCounters(), false) ; + if ( readGraphStore != readWriteGraphStore ) + addCountersForGSP(readGraphStore.getCounters(), true) ; + } + + private void addCountersForGSP(CounterSet cs, boolean readWrite) { + cs.add(CounterName.Requests) ; + cs.add(CounterName.RequestsGood) ; + cs.add(CounterName.RequestsBad) ; + + cs.add(CounterName.GSPget) ; + cs.add(CounterName.GSPgetGood) ; + cs.add(CounterName.GSPgetBad) ; + + cs.add(CounterName.GSPhead) ; + cs.add(CounterName.GSPheadGood) ; + cs.add(CounterName.GSPheadBad) ; + + // Add anyway. + // if ( ! readWrite ) + // return ; + + cs.add(CounterName.GSPput) ; + cs.add(CounterName.GSPputGood) ; + cs.add(CounterName.GSPputBad) ; + + cs.add(CounterName.GSPpost) ; + cs.add(CounterName.GSPpostGood) ; + cs.add(CounterName.GSPpostBad) ; + + cs.add(CounterName.GSPdelete) ; + cs.add(CounterName.GSPdeleteGood) ; + cs.add(CounterName.GSPdeleteBad) ; + + cs.add(CounterName.GSPpatch) ; + cs.add(CounterName.GSPpatchGood) ; + cs.add(CounterName.GSPpatchBad) ; + + cs.add(CounterName.GSPoptions) ; + cs.add(CounterName.GSPoptionsGood) ; + cs.add(CounterName.GSPoptionsBad) ; + } +} http://git-wip-us.apache.org/repos/asf/jena/blob/36855e1b/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/DatasetRegistry.java ---------------------------------------------------------------------- diff --git a/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/DatasetRegistry.java b/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/DatasetRegistry.java new file mode 100644 index 0000000..152e8cd --- /dev/null +++ b/jena-fuseki/src/main/java/org/apache/jena/fuseki/server/DatasetRegistry.java @@ -0,0 +1,30 @@ +/* + * 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.migrate.Registry ; + +public class DatasetRegistry extends Registry<DatasetRef> +{ + private static DatasetRegistry singleton = new DatasetRegistry() ; + + public static DatasetRegistry get() { return singleton ; } + + private DatasetRegistry() {} +}