From cf50a98b731591470ed11df5e9135a5c85ba3ce5 Mon Sep 17 00:00:00 2001 From: Andy Seaborne Date: Sat, 6 Aug 2016 14:49:36 +0100 Subject: [PATCH 1/9] JENA-1221: Embedded Fuseki server --- jena-fuseki2/jena-fuseki-embedded/pom.xml | 131 +++++++++ .../fuseki/embedded/FusekiEmbeddedServer.java | 257 ++++++++++++++++++ .../fuseki/embedded/TS_EmbeddedFuseki.java | 29 ++ .../fuseki/embedded/TestEmbeddedFuseki.java | 25 ++ jena-fuseki2/pom.xml | 1 + 5 files changed, 443 insertions(+) create mode 100644 jena-fuseki2/jena-fuseki-embedded/pom.xml create mode 100644 jena-fuseki2/jena-fuseki-embedded/src/main/java/org/apache/jena/fuseki/embedded/FusekiEmbeddedServer.java create mode 100644 jena-fuseki2/jena-fuseki-embedded/src/test/java/org/apache/jena/fuseki/embedded/TS_EmbeddedFuseki.java create mode 100644 jena-fuseki2/jena-fuseki-embedded/src/test/java/org/apache/jena/fuseki/embedded/TestEmbeddedFuseki.java diff --git a/jena-fuseki2/jena-fuseki-embedded/pom.xml b/jena-fuseki2/jena-fuseki-embedded/pom.xml new file mode 100644 index 00000000000..72fc04e7d2e --- /dev/null +++ b/jena-fuseki2/jena-fuseki-embedded/pom.xml @@ -0,0 +1,131 @@ + + + + + + 4.0.0 + Apache Jena - Fuseki Embedded Server + jena-fuseki-embedded + + + org.apache.jena + jena-fuseki + 2.4.1-SNAPSHOT + + + jar + + + + org.apache.jena + jena-fuseki-core + 2.4.1-SNAPSHOT + + + + org.slf4j + slf4j-log4j12 + + + log4j + log4j + + + + org.apache.jena + jena-text + + + org.apache.jena + jena-spatial + + + + + + + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + package + + jar-no-fork + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + **/TS_*.java + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + false + true + + + + + + + + diff --git a/jena-fuseki2/jena-fuseki-embedded/src/main/java/org/apache/jena/fuseki/embedded/FusekiEmbeddedServer.java b/jena-fuseki2/jena-fuseki-embedded/src/main/java/org/apache/jena/fuseki/embedded/FusekiEmbeddedServer.java new file mode 100644 index 00000000000..91614530128 --- /dev/null +++ b/jena-fuseki2/jena-fuseki-embedded/src/main/java/org/apache/jena/fuseki/embedded/FusekiEmbeddedServer.java @@ -0,0 +1,257 @@ +/* + * 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.embedded; + +import java.util.HashMap ; +import java.util.List ; +import java.util.Map ; + +import org.apache.jena.fuseki.Fuseki ; +import org.apache.jena.fuseki.FusekiConfigException ; +import org.apache.jena.fuseki.FusekiException ; +import org.apache.jena.fuseki.FusekiLogging ; +import org.apache.jena.fuseki.build.FusekiBuilder ; +import org.apache.jena.fuseki.build.FusekiConfig ; +import org.apache.jena.fuseki.jetty.FusekiErrorHandler1 ; +import org.apache.jena.fuseki.server.* ; +import org.apache.jena.fuseki.servlets.FusekiFilter ; +import org.apache.jena.sparql.core.DatasetGraph ; +import org.eclipse.jetty.server.HttpConnectionFactory ; +import org.eclipse.jetty.server.Server ; +import org.eclipse.jetty.server.ServerConnector ; +import org.eclipse.jetty.servlet.FilterHolder ; +import org.eclipse.jetty.servlet.ServletContextHandler ; + +/** + * Embedded Fuseki server. This is a Fuseki server running with a precofigured set of + * datasets and services. + * There is no admin UI. + *

+ * To create a embedded sever, use {@link FusekiEmbeddedServer} ({@link #make} is a + * packaging of a call to {@link FusekiEmbeddedServer} for the case of one dataset, + * responding to localhost only). + *

+ * The application should call {@link #start()} to actually start the server + * (it wil run in the background : see {@link #join}). + *

Example: + *

+ *      DatasetGraph dsg = ... ;
+ *      FusekiEmbeddedServer server = FusekiEmbeddedServer.create()
+ *          .setPort(1234)
+ *          .add("/ds", dsg)
+ *          .build() ;
+ *       server.start() ;
+ * 
+ * Compact form (use the builder pattern above to get more flexibility): + *
+ *    FusekiEmbeddedServer.make(1234, "/ds", dsg).start() ;
+ * 
+ * + */ +public class FusekiEmbeddedServer { + static { + //FusekiEnv.mode = FusekiEnv.INIT.EMBEDDED ; + // Stop anything accidently resetting Fuseki server logging. + FusekiLogging.allowLoggingReset(false) ; + } + + /** Construct a Fuseki server for one dataset. + * It only responds to localhost. + * The returned server has not been started */ + static public FusekiEmbeddedServer make(int port, String name, DatasetGraph dsg) { + return create() + .setPort(port) + .setLoopback(true) + .add(name, dsg) + .build() ; + } + + public static Builder create() { + return new Builder() ; + } + + public final Server server ; + private int port ; + + public FusekiEmbeddedServer(Server server) { + this.server = server ; + port = ((ServerConnector)server.getConnectors()[0]).getPort() ; + } + + /** Get the underlying Jetty server which has also been set up. + * Adding new servlets is posisble with care. + */ + public Server getJettyServer() { + return server ; + } + + /** Start the server - the server continues to run afetr thsi call returns. + * To synchronise with the server stopping, call {@link #join}. + */ + public void start() { + try { server.start(); } + catch (Exception e) { throw new FusekiException(e) ; } + if ( port == 0 ) + port = ((ServerConnector)server.getConnectors()[0]).getLocalPort() ; + Fuseki.serverLog.info("Start Fuseki (port="+port+")"); + } + + /** Stop the server. */ + public void stop() { + Fuseki.serverLog.info("Stop Fuseki (port="+port+")"); + try { server.stop(); } + catch (Exception e) { throw new FusekiException(e) ; } + } + + /** Wait for the server to exit. This call is blocking. */ + public void join() { + try { server.join(); } + catch (Exception e) { throw new FusekiException(e) ; } + } + + /** FusekiEmbeddedServer.Builder */ + public static class Builder { + // Keeping this allows accumulation of data access points for one name. + private Map map = new HashMap<>() ; + private int port = 3333 ; + private boolean loopback = false ; + private String path = "/" ; + + /* Set the port to run on */ + public Builder setPort(int port) { + this.port = port ; + return this ; + } + + /* Context path to Fuseki. If it's "/" then Fuseki URL look like + * "http://host:port/dataset/query" else "http://host:port/path/dataset/query" + */ + public Builder setContextPath(String path) { + this.path = path ; + return this ; + } + + /** Restrict the server to only respoding to the localhost interface. */ + public Builder setLoopback(boolean loopback) { + this.loopback = loopback; + return this ; + } + + /* Add the dataset with given name and a default set of services including update */ + public Builder add(String name, DatasetGraph dsg) { + return add(name, dsg, true) ; + } + + /* Add the dataset with given name and a default set of services. */ + public Builder add(String name, DatasetGraph dsg, boolean allowUpdate) { + DataService dSrv = FusekiBuilder.buildDataService(dsg, allowUpdate) ; + return add(name, dSrv) ; + } + + /* Add a data service that includes dataset and service names.*/ + public Builder add(String name, DataService dataService) { + return add$(name, dataService) ; + } + + /* Add an operation, specifing it's endpoint name. + * This adds endpoints to any existing data service already setup by the builder. + */ + public Builder add(String name, DatasetGraph dsg, OperationName opName, String epName) { + DataService dSrv = map.get(name) ; + if ( dSrv == null ) { + dSrv = new DataService(dsg) ; + map.put(name, dSrv) ; + } + dSrv.addEndpoint(opName, epName); + return this ; + } + + private Builder add$(String name, DataService dataService) { + DataService dSrv = map.get(name) ; + if ( dSrv != null ) { + DatasetGraph dsg1 = dSrv.getDataset() ; + DatasetGraph dsg2 = dataService.getDataset() ; + if ( dsg1 != dsg2 ) // Object identity + throw new FusekiConfigException("Attempt to add a DataService for a different dataset: "+name) ; + dSrv.getOperations() ; + } else + map.put(name, dataService) ; + return this ; + } + + /** Read and parse a Fuseki services/datasets file. + *

+ * The application is responsible for ensuring a correct classpath. For example, + * including a dependency on {@code jena-text} if the configuration file + * includes a text index. + */ + public Builder parseConfigFile(String filename) { + List x = FusekiConfig.readConfigurationFile(filename) ; + // Unbundle so that they accumulate. + x.forEach(dap-> add(dap.getName(), dap.getDataService())) ; + return this ; + } + + /** Build a server according to the current description */ + public FusekiEmbeddedServer build() { + map.forEach((name, dSrv) -> { + DataAccessPoint dap = new DataAccessPoint(name, dSrv) ; + DataAccessPointRegistry.get().put(name, dap) ; + }) ; + Server server = fusekiServer(port, path, loopback) ; + return new FusekiEmbeddedServer(server) ; + } + + /** build process */ + private static Server fusekiServer(int port, String contextPath, boolean loopback) { + if ( contextPath == null || contextPath.isEmpty() ) + contextPath = "/" ; + ServletContextHandler context = new ServletContextHandler() ; + // The Fuseki server-wide setup. + DataAccessPointRegistry.set(context.getServletContext(), new DataAccessPointRegistry()); + FusekiFilter ff = new FusekiFilter() ; + FilterHolder h = new FilterHolder(ff) ; + context.setContextPath(contextPath); + context.addFilter(h, "/*", null); + context.setDisplayName(Fuseki.servletRequestLogName); + context.setErrorHandler(new FusekiErrorHandler1()); + Server server = jettyServer(port, loopback) ; + server.setHandler(context); + return server ; + } + + /** Jetty build process */ + private static Server jettyServer(int port, boolean loopback) { + Server server = new Server() ; + HttpConnectionFactory f1 = new HttpConnectionFactory() ; + // Some people do try very large operations ... really, should use POST. + f1.getHttpConfiguration().setRequestHeaderSize(512 * 1024); + f1.getHttpConfiguration().setOutputBufferSize(5 * 1024 * 1024) ; + // Do not add "Server: Jetty(....) when not a development system. + if ( ! Fuseki.outputJettyServerHeader ) + f1.getHttpConfiguration().setSendServerVersion(false) ; + ServerConnector connector = new ServerConnector(server, f1) ; + connector.setPort(port) ; + server.addConnector(connector); + if ( loopback ) + connector.setHost("localhost"); + return server ; + } + } +} \ No newline at end of file diff --git a/jena-fuseki2/jena-fuseki-embedded/src/test/java/org/apache/jena/fuseki/embedded/TS_EmbeddedFuseki.java b/jena-fuseki2/jena-fuseki-embedded/src/test/java/org/apache/jena/fuseki/embedded/TS_EmbeddedFuseki.java new file mode 100644 index 00000000000..866200cb4d0 --- /dev/null +++ b/jena-fuseki2/jena-fuseki-embedded/src/test/java/org/apache/jena/fuseki/embedded/TS_EmbeddedFuseki.java @@ -0,0 +1,29 @@ +/* + * 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.embedded; + +import org.junit.runner.RunWith ; +import org.junit.runners.Suite ; +import org.junit.runners.Suite.SuiteClasses ; + +@RunWith(Suite.class) +@SuiteClasses({ + TestEmbeddedFuseki.class +}) +public class TS_EmbeddedFuseki { } diff --git a/jena-fuseki2/jena-fuseki-embedded/src/test/java/org/apache/jena/fuseki/embedded/TestEmbeddedFuseki.java b/jena-fuseki2/jena-fuseki-embedded/src/test/java/org/apache/jena/fuseki/embedded/TestEmbeddedFuseki.java new file mode 100644 index 00000000000..da9e6584c1f --- /dev/null +++ b/jena-fuseki2/jena-fuseki-embedded/src/test/java/org/apache/jena/fuseki/embedded/TestEmbeddedFuseki.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.embedded; + +import org.junit.Test ; + +public class TestEmbeddedFuseki { + @Test public void dummy() {} +} diff --git a/jena-fuseki2/pom.xml b/jena-fuseki2/pom.xml index 11728eee217..7b6f1264050 100644 --- a/jena-fuseki2/pom.xml +++ b/jena-fuseki2/pom.xml @@ -70,6 +70,7 @@ jena-fuseki-core + jena-fuseki-embedded jena-fuseki-war jena-fuseki-server apache-jena-fuseki From 7f4baef0d6bb5fac246cc70b71710a0555275eb5 Mon Sep 17 00:00:00 2001 From: Andy Seaborne Date: Sun, 7 Aug 2016 11:41:03 +0100 Subject: [PATCH 2/9] Refactor code to enable local access to JSON stats building. --- .../apache/jena/fuseki/mgt/ActionStats.java | 47 +++++++------------ 1 file changed, 18 insertions(+), 29 deletions(-) diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/mgt/ActionStats.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/mgt/ActionStats.java index b44e678c1c6..fc95331b229 100644 --- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/mgt/ActionStats.java +++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/mgt/ActionStats.java @@ -30,6 +30,7 @@ import javax.servlet.http.HttpServletResponse ; import org.apache.jena.atlas.json.JsonBuilder ; +import org.apache.jena.atlas.json.JsonObject ; import org.apache.jena.atlas.json.JsonValue ; import org.apache.jena.fuseki.server.* ; import org.apache.jena.fuseki.servlets.HttpAction ; @@ -44,21 +45,20 @@ public class ActionStats extends ActionContainerItem @Override protected JsonValue execGetContainer(HttpAction action) { action.log.info(format("[%d] GET stats all", action.id)) ; + return generateStats(action.getDataAccessPointRegistry()) ; + } + + public static JsonObject generateStats(DataAccessPointRegistry registry) { JsonBuilder builder = new JsonBuilder() ; builder.startObject("top") ; - builder.key(JsonConst.datasets) ; builder.startObject("datasets") ; - for ( String ds : action.getDataAccessPointRegistry().keys() ) { - DataAccessPoint access = action.getDataAccessPointRegistry().get(ds) ; - statsDataset(builder, access) ; - } + registry.forEach((name, access)->statsDataset(builder, access)); builder.finishObject("datasets") ; - builder.finishObject("top") ; - return builder.build() ; + return builder.build().getAsObject() ; } - + @Override protected JsonValue execGetItem(HttpAction action) { action.log.info(format("[%d] GET stats dataset %s", action.id, action.getDatasetName())) ; @@ -75,13 +75,19 @@ protected JsonValue execGetItem(HttpAction action) { builder.finishObject("TOP") ; return builder.build() ; } - + + public static JsonObject generateStats(DataAccessPoint access) { + JsonBuilder builder = new JsonBuilder() ; + statsDataset(builder, access) ; + return builder.build().getAsObject() ; + } + private void statsDataset(JsonBuilder builder, String name, DataAccessPointRegistry registry) { DataAccessPoint access = registry.get(name) ; statsDataset(builder, access); } - private void statsDataset(JsonBuilder builder, DataAccessPoint access) { + private static void statsDataset(JsonBuilder builder, DataAccessPoint access) { // Object started builder.key(access.getName()) ; DataService dSrv = access.getDataService() ; @@ -91,29 +97,13 @@ private void statsDataset(JsonBuilder builder, DataAccessPoint access) { 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 map = MultiMap.createMapList() ; -// for ( OperationName operName : dSrv.getOperations() ) { -// List 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 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. - + // Endpoint names are unique for a given service. builder.key(endpoint.getEndpoint()) ; builder.startObject() ; @@ -126,10 +116,9 @@ private void statsDataset(JsonBuilder builder, DataAccessPoint access) { } builder.finishObject("endpoints") ; builder.finishObject("counters") ; - } - private void operationCounters(JsonBuilder builder, Endpoint operation) { + private static 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()) ; From 9b243604937625447a426d027639e021f86650c4 Mon Sep 17 00:00:00 2001 From: Andy Seaborne Date: Sun, 7 Aug 2016 11:58:54 +0100 Subject: [PATCH 3/9] Convenience operation to help walking JSON structures. --- .../java/org/apache/jena/atlas/json/JsonObject.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/jena-arq/src/main/java/org/apache/jena/atlas/json/JsonObject.java b/jena-arq/src/main/java/org/apache/jena/atlas/json/JsonObject.java index b83d1cb19e5..b7e89dbd6e0 100644 --- a/jena-arq/src/main/java/org/apache/jena/atlas/json/JsonObject.java +++ b/jena-arq/src/main/java/org/apache/jena/atlas/json/JsonObject.java @@ -54,11 +54,6 @@ public boolean hasKey(Object key) { return map.containsKey(key) ; } -// @Override -// public boolean containsValue(Object value) { -// return map.containsValue(value) ; -// } - public Set keys() { return map.keySet() ; } @@ -71,6 +66,11 @@ public JsonValue get(String key) { return map.get(key) ; } + /** For walking structures */ + public JsonObject getObj(String key) { + return get(key).getAsObject() ; + } + public boolean isEmpty() { return map.isEmpty() ; } From 4e20f283e09e2e2978f85364b61354c78487f23e Mon Sep 17 00:00:00 2001 From: Andy Seaborne Date: Sun, 7 Aug 2016 12:00:55 +0100 Subject: [PATCH 4/9] Add adding of /$/stats. Don't assume a global DataAccessPointRegistry. --- .../server/DataAccessPointRegistry.java | 9 ++- .../fuseki/embedded/FusekiEmbeddedServer.java | 69 +++++++++++++------ 2 files changed, 53 insertions(+), 25 deletions(-) diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/DataAccessPointRegistry.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/DataAccessPointRegistry.java index eee14eaa2e8..533b49e5084 100644 --- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/DataAccessPointRegistry.java +++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/DataAccessPointRegistry.java @@ -50,18 +50,21 @@ public void print(String string) { }) ; } - // To be removed ... + // TODO To be removed ... private static DataAccessPointRegistry singleton = new DataAccessPointRegistry() ; + // Still used by ServerTest and FusekiEmbeddedServer (but nowhere else) public static DataAccessPointRegistry get() { return singleton ; } - private static final String attrNameRegistry = "jena.apache.org/fuseki/dataAccessPointRegistry" ; + private static final String attrNameRegistry = "jena-fuseki:dataAccessPointRegistry" ; // Policy for the location of the server-wide DataAccessPointRegistry public static DataAccessPointRegistry get(ServletContext cxt) { //return (DataAccessPointRegistry)cxt.getAttribute(attrName) ; return singleton ; } - public static void set(ServletContext cxt, DataAccessPointRegistry registry) { + public static void set(ServletContext cxt, DataAccessPointRegistry registry) { + // Temporary until get() removed completely. + singleton = registry ; cxt.setAttribute(attrNameRegistry, registry) ; } } diff --git a/jena-fuseki2/jena-fuseki-embedded/src/main/java/org/apache/jena/fuseki/embedded/FusekiEmbeddedServer.java b/jena-fuseki2/jena-fuseki-embedded/src/main/java/org/apache/jena/fuseki/embedded/FusekiEmbeddedServer.java index 91614530128..cd3b0cbef9e 100644 --- a/jena-fuseki2/jena-fuseki-embedded/src/main/java/org/apache/jena/fuseki/embedded/FusekiEmbeddedServer.java +++ b/jena-fuseki2/jena-fuseki-embedded/src/main/java/org/apache/jena/fuseki/embedded/FusekiEmbeddedServer.java @@ -22,6 +22,8 @@ import java.util.List ; import java.util.Map ; +import javax.servlet.ServletContext ; + import org.apache.jena.fuseki.Fuseki ; import org.apache.jena.fuseki.FusekiConfigException ; import org.apache.jena.fuseki.FusekiException ; @@ -29,7 +31,11 @@ import org.apache.jena.fuseki.build.FusekiBuilder ; import org.apache.jena.fuseki.build.FusekiConfig ; import org.apache.jena.fuseki.jetty.FusekiErrorHandler1 ; -import org.apache.jena.fuseki.server.* ; +import org.apache.jena.fuseki.mgt.ActionStats ; +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.server.OperationName ; import org.apache.jena.fuseki.servlets.FusekiFilter ; import org.apache.jena.sparql.core.DatasetGraph ; import org.eclipse.jetty.server.HttpConnectionFactory ; @@ -94,14 +100,19 @@ public FusekiEmbeddedServer(Server server) { port = ((ServerConnector)server.getConnectors()[0]).getPort() ; } - /** Get the underlying Jetty server which has also been set up. - * Adding new servlets is posisble with care. - */ + /** Get the underlying Jetty server which has also been set up. */ public Server getJettyServer() { return server ; } - /** Start the server - the server continues to run afetr thsi call returns. + /** Get the {@link ServletContext}. + * Adding new servlets is possible with care. + */ + public ServletContext getServletContext() { + return ((ServletContextHandler)server.getHandler()).getServletContext() ; + } + + /** Start the server - the server continues to run after this call returns. * To synchronise with the server stopping, call {@link #join}. */ public void start() { @@ -131,7 +142,8 @@ public static class Builder { private Map map = new HashMap<>() ; private int port = 3333 ; private boolean loopback = false ; - private String path = "/" ; + private boolean withStats = false ; + private String contextPath = "/" ; /* Set the port to run on */ public Builder setPort(int port) { @@ -143,7 +155,7 @@ public Builder setPort(int port) { * "http://host:port/dataset/query" else "http://host:port/path/dataset/query" */ public Builder setContextPath(String path) { - this.path = path ; + this.contextPath = path ; return this ; } @@ -153,6 +165,14 @@ public Builder setLoopback(boolean loopback) { return this ; } + /** Add the "/$/stats" servlet that responds with stats about the server, + * including counts of all calls made. + */ + public Builder enableStats(boolean withStats) { + this.withStats = withStats; + return this ; + } + /* Add the dataset with given name and a default set of services including update */ public Builder add(String name, DatasetGraph dsg) { return add(name, dsg, true) ; @@ -183,6 +203,7 @@ public Builder add(String name, DatasetGraph dsg, OperationName opName, String e } private Builder add$(String name, DataService dataService) { + name = DataAccessPoint.canonical(name) ; DataService dSrv = map.get(name) ; if ( dSrv != null ) { DatasetGraph dsg1 = dSrv.getDataset() ; @@ -210,33 +231,37 @@ public Builder parseConfigFile(String filename) { /** Build a server according to the current description */ public FusekiEmbeddedServer build() { + DataAccessPointRegistry registry = new DataAccessPointRegistry() ; map.forEach((name, dSrv) -> { DataAccessPoint dap = new DataAccessPoint(name, dSrv) ; - DataAccessPointRegistry.get().put(name, dap) ; + registry.put(name, dap) ; }) ; - Server server = fusekiServer(port, path, loopback) ; + ServletContextHandler handler = buildServletContext(contextPath, registry) ; + if ( withStats ) + handler.addServlet(ActionStats.class, "/$/stats") ; + DataAccessPointRegistry.set(handler.getServletContext(), registry) ; + Server server = jettyServer(port, loopback) ; + server.setHandler(handler); return new FusekiEmbeddedServer(server) ; } - /** build process */ - private static Server fusekiServer(int port, String contextPath, boolean loopback) { + /** Build a ServletContextHandler with the Fuseki router : {@link FusekiFilter} */ + private static ServletContextHandler buildServletContext(String contextPath, DataAccessPointRegistry registry) { if ( contextPath == null || contextPath.isEmpty() ) contextPath = "/" ; + else if ( !contextPath.startsWith("/") ) + contextPath = "/" + contextPath ; ServletContextHandler context = new ServletContextHandler() ; - // The Fuseki server-wide setup. - DataAccessPointRegistry.set(context.getServletContext(), new DataAccessPointRegistry()); FusekiFilter ff = new FusekiFilter() ; FilterHolder h = new FilterHolder(ff) ; - context.setContextPath(contextPath); - context.addFilter(h, "/*", null); - context.setDisplayName(Fuseki.servletRequestLogName); - context.setErrorHandler(new FusekiErrorHandler1()); - Server server = jettyServer(port, loopback) ; - server.setHandler(context); - return server ; + context.setContextPath(contextPath) ; + context.addFilter(h, "/*", null) ; + context.setDisplayName(Fuseki.servletRequestLogName) ; + context.setErrorHandler(new FusekiErrorHandler1()) ; + return context ; } - - /** Jetty build process */ + + /** Jetty server */ private static Server jettyServer(int port, boolean loopback) { Server server = new Server() ; HttpConnectionFactory f1 = new HttpConnectionFactory() ; From 870b3626f5359a771c2c554c501fda6bc2da86be Mon Sep 17 00:00:00 2001 From: Andy Seaborne Date: Sun, 7 Aug 2016 12:33:16 +0100 Subject: [PATCH 5/9] Remove incorrect comment. --- .../src/test/java/org/apache/jena/fuseki/TS_Fuseki.java | 1 - 1 file changed, 1 deletion(-) diff --git a/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/TS_Fuseki.java b/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/TS_Fuseki.java index e7a09ee79a6..9424eb8145f 100644 --- a/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/TS_Fuseki.java +++ b/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/TS_Fuseki.java @@ -59,7 +59,6 @@ public class TS_Fuseki extends ServerTest FusekiLogging.setLogging(); FusekiEnv.setEnvironment() ; - // Occasionally log4j.properties gets out of step. LogCtl.setLevel("org.apache.shiro", "WARN") ; LogCtl.setLevel("org.eclipse.jetty", "WARN"); From cd7f4420d16384a1f31cfc3d6e60df059abbf89d Mon Sep 17 00:00:00 2001 From: Andy Seaborne Date: Sun, 7 Aug 2016 13:53:28 +0100 Subject: [PATCH 6/9] JENA-1221: Some tests for the Fuseki embedded server. --- .../fuseki/embedded/FusekiEmbeddedServer.java | 3 +- .../fuseki/embedded/TS_EmbeddedFuseki.java | 21 ++- .../fuseki/embedded/TestEmbeddedFuseki.java | 147 +++++++++++++++++- 3 files changed, 167 insertions(+), 4 deletions(-) diff --git a/jena-fuseki2/jena-fuseki-embedded/src/main/java/org/apache/jena/fuseki/embedded/FusekiEmbeddedServer.java b/jena-fuseki2/jena-fuseki-embedded/src/main/java/org/apache/jena/fuseki/embedded/FusekiEmbeddedServer.java index cd3b0cbef9e..49184eb2ff4 100644 --- a/jena-fuseki2/jena-fuseki-embedded/src/main/java/org/apache/jena/fuseki/embedded/FusekiEmbeddedServer.java +++ b/jena-fuseki2/jena-fuseki-embedded/src/main/java/org/apache/jena/fuseki/embedded/FusekiEmbeddedServer.java @@ -27,7 +27,6 @@ import org.apache.jena.fuseki.Fuseki ; import org.apache.jena.fuseki.FusekiConfigException ; import org.apache.jena.fuseki.FusekiException ; -import org.apache.jena.fuseki.FusekiLogging ; import org.apache.jena.fuseki.build.FusekiBuilder ; import org.apache.jena.fuseki.build.FusekiConfig ; import org.apache.jena.fuseki.jetty.FusekiErrorHandler1 ; @@ -74,7 +73,7 @@ public class FusekiEmbeddedServer { static { //FusekiEnv.mode = FusekiEnv.INIT.EMBEDDED ; // Stop anything accidently resetting Fuseki server logging. - FusekiLogging.allowLoggingReset(false) ; + //FusekiLogging.allowLoggingReset(false) ; } /** Construct a Fuseki server for one dataset. diff --git a/jena-fuseki2/jena-fuseki-embedded/src/test/java/org/apache/jena/fuseki/embedded/TS_EmbeddedFuseki.java b/jena-fuseki2/jena-fuseki-embedded/src/test/java/org/apache/jena/fuseki/embedded/TS_EmbeddedFuseki.java index 866200cb4d0..c0e06d26d24 100644 --- a/jena-fuseki2/jena-fuseki-embedded/src/test/java/org/apache/jena/fuseki/embedded/TS_EmbeddedFuseki.java +++ b/jena-fuseki2/jena-fuseki-embedded/src/test/java/org/apache/jena/fuseki/embedded/TS_EmbeddedFuseki.java @@ -18,6 +18,10 @@ package org.apache.jena.fuseki.embedded; +import org.apache.jena.atlas.logging.LogCtl ; +import org.apache.jena.fuseki.Fuseki ; +import org.apache.jena.fuseki.FusekiLogging ; +import org.junit.BeforeClass ; import org.junit.runner.RunWith ; import org.junit.runners.Suite ; import org.junit.runners.Suite.SuiteClasses ; @@ -26,4 +30,19 @@ @SuiteClasses({ TestEmbeddedFuseki.class }) -public class TS_EmbeddedFuseki { } +public class TS_EmbeddedFuseki { + @BeforeClass public static void setupForFusekiServer() { + FusekiLogging.setLogging(); + LogCtl.setLevel(Fuseki.serverLogName, "WARN"); + LogCtl.setLevel(Fuseki.actionLogName, "WARN"); + LogCtl.setLevel(Fuseki.requestLogName, "WARN"); + LogCtl.setLevel("org.eclipse.jetty", "WARN"); + + // Shouldn't see these in the embedded server. +// LogCtl.setLevel("org.apache.shiro", "WARN") ; +// LogCtl.setLevel(Fuseki.configLogName, "WARN"); +// LogCtl.setLevel(Fuseki.adminLogName, "WARN"); +// LogCtl.setLevel(Fuseki.builderLogName, "WARN"); +// LogCtl.setLevel(Fuseki.servletRequestLogName,"WARN"); + } +} diff --git a/jena-fuseki2/jena-fuseki-embedded/src/test/java/org/apache/jena/fuseki/embedded/TestEmbeddedFuseki.java b/jena-fuseki2/jena-fuseki-embedded/src/test/java/org/apache/jena/fuseki/embedded/TestEmbeddedFuseki.java index da9e6584c1f..2e2ff7bd6de 100644 --- a/jena-fuseki2/jena-fuseki-embedded/src/test/java/org/apache/jena/fuseki/embedded/TestEmbeddedFuseki.java +++ b/jena-fuseki2/jena-fuseki-embedded/src/test/java/org/apache/jena/fuseki/embedded/TestEmbeddedFuseki.java @@ -18,8 +18,153 @@ package org.apache.jena.fuseki.embedded; +import static org.junit.Assert.assertEquals ; +import static org.junit.Assert.assertFalse ; +import static org.junit.Assert.assertTrue ; + +import java.io.OutputStream ; + +import org.apache.http.HttpEntity ; +import org.apache.http.entity.ContentProducer ; +import org.apache.http.entity.EntityTemplate ; +import org.apache.jena.atlas.web.ContentType ; +import org.apache.jena.atlas.web.TypedInputStream ; +import org.apache.jena.fuseki.server.DataAccessPointRegistry ; +import org.apache.jena.fuseki.server.DataService ; +import org.apache.jena.fuseki.server.OperationName ; +import org.apache.jena.graph.Graph ; +import org.apache.jena.query.QueryExecution ; +import org.apache.jena.query.QueryExecutionFactory ; +import org.apache.jena.query.ResultSet ; +import org.apache.jena.query.ResultSetFormatter ; +import org.apache.jena.riot.RDFDataMgr ; +import org.apache.jena.riot.RDFFormat ; +import org.apache.jena.riot.RDFLanguages ; +import org.apache.jena.riot.web.HttpOp ; +import org.apache.jena.sparql.core.DatasetGraph ; +import org.apache.jena.sparql.core.DatasetGraphFactory ; +import org.apache.jena.sparql.core.Quad ; +import org.apache.jena.sparql.graph.GraphFactory ; +import org.apache.jena.sparql.sse.SSE ; +import org.apache.jena.system.Txn ; +import org.apache.jena.update.UpdateExecutionFactory ; +import org.apache.jena.update.UpdateFactory ; +import org.apache.jena.update.UpdateRequest ; import org.junit.Test ; public class TestEmbeddedFuseki { - @Test public void dummy() {} + + @Test public void embedded_01() { + DatasetGraph dsg = dataset() ; + FusekiEmbeddedServer server = FusekiEmbeddedServer.make(3330, "/ds", dsg) ; + assertTrue(DataAccessPointRegistry.get().isRegistered("/ds")) ; + server.start() ; + try (QueryExecution qExec = QueryExecutionFactory.sparqlService("http://localhost:3330/ds/query", "SELECT * { ?s ?p ?o}") ) { + ResultSet rs = qExec.execSelect() ; + assertFalse(rs.hasNext()) ; + } + server.stop() ; + } + + @Test public void embedded_02() { + // test order !!!! + DatasetGraph dsg = dataset() ; + FusekiEmbeddedServer server = FusekiEmbeddedServer.make(3330, "/ds2", dsg) ; + // But no /ds + assertEquals(1, DataAccessPointRegistry.get().size()) ; + assertTrue(DataAccessPointRegistry.get().isRegistered("/ds2")) ; + assertFalse(DataAccessPointRegistry.get().isRegistered("/ds")) ; + server.start() ; + server.stop() ; + } + + @Test public void embedded_03() { + DatasetGraph dsg = dataset() ; + FusekiEmbeddedServer server = FusekiEmbeddedServer.create() + .setPort(3331) + .add("/ds1", dsg) + .build() ; + server.start() ; + // Add while live. + Txn.execWrite(dsg, ()->{ + Quad q = SSE.parseQuad("(_ :s :p _:b)") ; + dsg.add(q); + }) ; + try (QueryExecution qExec = QueryExecutionFactory.sparqlService("http://localhost:3331/ds1/query", "SELECT * { ?s ?p ?o}") ) { + ResultSet rs = qExec.execSelect() ; + int x = ResultSetFormatter.consume(rs) ; + assertEquals(1, x) ; + } + server.stop() ; + } + + + @Test public void embedded_04() { + DatasetGraph dsg = dataset() ; + Txn.execWrite(dsg, ()->{ + Quad q = SSE.parseQuad("(_ :s :p _:b)") ; + dsg.add(q); + }) ; + + // A service with just being able to do quads operations + // That is, GET, POST, PUT on "/data" in N-quads and TriG. + DataService dataService = new DataService(dsg) ; + dataService.addEndpoint(OperationName.Quads_RW, ""); + dataService.addEndpoint(OperationName.Query, ""); + dataService.addEndpoint(OperationName.Update, ""); + + FusekiEmbeddedServer server = FusekiEmbeddedServer.create() + .setPort(3332) + .add("/data", dataService) + .build() ; + server.start() ; + + // Put data in. + String data = "(graph (:s :p 1) (:s :p 2) (:s :p 3))" ; + Graph g = SSE.parseGraph(data) ; + HttpEntity e = graphToHttpEntity(g) ; + HttpOp.execHttpPut("http://localhost:3332/data", e) ; + + // Get data out. + try ( TypedInputStream in = HttpOp.execHttpGet("http://localhost:3332/data") ) { + Graph g2 = GraphFactory.createDefaultGraph() ; + RDFDataMgr.read(g2, in, RDFLanguages.contentTypeToLang(in.getContentType())) ; + assertTrue(g.isIsomorphicWith(g2)) ; + } + // Query. + try (QueryExecution qExec = QueryExecutionFactory.sparqlService("http://localhost:3332/data", "SELECT * { ?s ?p ?o}") ) { + ResultSet rs = qExec.execSelect() ; + int x = ResultSetFormatter.consume(rs) ; + assertEquals(3, x) ; + } + // Update + UpdateRequest req = UpdateFactory.create("CLEAR DEFAULT") ; + UpdateExecutionFactory.createRemote(req, "http://localhost:3332/data").execute(); + // Query again. + try (QueryExecution qExec = QueryExecutionFactory.sparqlService("http://localhost:3332/data", "SELECT * { ?s ?p ?o}") ) { + ResultSet rs = qExec.execSelect() ; + int x = ResultSetFormatter.consume(rs) ; + assertEquals(0, x) ; + } + server.stop() ; + } + + /** Create an HttpEntity for the graph */ + protected static HttpEntity graphToHttpEntity(final Graph graph) { + final RDFFormat syntax = RDFFormat.TURTLE_BLOCKS ; + ContentProducer producer = new ContentProducer() { + @Override + public void writeTo(OutputStream out) { + RDFDataMgr.write(out, graph, syntax) ; + } + } ; + EntityTemplate entity = new EntityTemplate(producer) ; + ContentType ct = syntax.getLang().getContentType() ; + entity.setContentType(ct.getContentType()) ; + return entity ; + } + + private DatasetGraph dataset() { + return DatasetGraphFactory.createTxnMem() ; + } } From 176fb1331cb3c32725de7aefb708348c79c8bfa4 Mon Sep 17 00:00:00 2001 From: Andy Seaborne Date: Sun, 7 Aug 2016 14:19:19 +0100 Subject: [PATCH 7/9] JENA-1221: More tests --- .../fuseki/embedded/TS_EmbeddedFuseki.java | 3 +- .../fuseki/embedded/TestEmbeddedFuseki.java | 182 +++++++++++++----- .../testing/FusekiEmbedded/config.ttl | 18 ++ 3 files changed, 156 insertions(+), 47 deletions(-) create mode 100644 jena-fuseki2/jena-fuseki-embedded/testing/FusekiEmbedded/config.ttl diff --git a/jena-fuseki2/jena-fuseki-embedded/src/test/java/org/apache/jena/fuseki/embedded/TS_EmbeddedFuseki.java b/jena-fuseki2/jena-fuseki-embedded/src/test/java/org/apache/jena/fuseki/embedded/TS_EmbeddedFuseki.java index c0e06d26d24..c66149d3727 100644 --- a/jena-fuseki2/jena-fuseki-embedded/src/test/java/org/apache/jena/fuseki/embedded/TS_EmbeddedFuseki.java +++ b/jena-fuseki2/jena-fuseki-embedded/src/test/java/org/apache/jena/fuseki/embedded/TS_EmbeddedFuseki.java @@ -36,12 +36,13 @@ public class TS_EmbeddedFuseki { LogCtl.setLevel(Fuseki.serverLogName, "WARN"); LogCtl.setLevel(Fuseki.actionLogName, "WARN"); LogCtl.setLevel(Fuseki.requestLogName, "WARN"); + LogCtl.setLevel(Fuseki.adminLogName, "WARN"); LogCtl.setLevel("org.eclipse.jetty", "WARN"); // Shouldn't see these in the embedded server. // LogCtl.setLevel("org.apache.shiro", "WARN") ; // LogCtl.setLevel(Fuseki.configLogName, "WARN"); -// LogCtl.setLevel(Fuseki.adminLogName, "WARN"); + // LogCtl.setLevel(Fuseki.builderLogName, "WARN"); // LogCtl.setLevel(Fuseki.servletRequestLogName,"WARN"); } diff --git a/jena-fuseki2/jena-fuseki-embedded/src/test/java/org/apache/jena/fuseki/embedded/TestEmbeddedFuseki.java b/jena-fuseki2/jena-fuseki-embedded/src/test/java/org/apache/jena/fuseki/embedded/TestEmbeddedFuseki.java index 2e2ff7bd6de..d96faa08c19 100644 --- a/jena-fuseki2/jena-fuseki-embedded/src/test/java/org/apache/jena/fuseki/embedded/TestEmbeddedFuseki.java +++ b/jena-fuseki2/jena-fuseki-embedded/src/test/java/org/apache/jena/fuseki/embedded/TestEmbeddedFuseki.java @@ -20,14 +20,18 @@ import static org.junit.Assert.assertEquals ; import static org.junit.Assert.assertFalse ; +import static org.junit.Assert.assertNotNull ; +import static org.junit.Assert.assertNull ; import static org.junit.Assert.assertTrue ; import java.io.OutputStream ; +import java.util.function.Consumer ; import org.apache.http.HttpEntity ; import org.apache.http.entity.ContentProducer ; import org.apache.http.entity.EntityTemplate ; import org.apache.jena.atlas.web.ContentType ; +import org.apache.jena.atlas.web.HttpException ; import org.apache.jena.atlas.web.TypedInputStream ; import org.apache.jena.fuseki.server.DataAccessPointRegistry ; import org.apache.jena.fuseki.server.DataService ; @@ -50,32 +54,35 @@ import org.apache.jena.update.UpdateExecutionFactory ; import org.apache.jena.update.UpdateFactory ; import org.apache.jena.update.UpdateRequest ; +import org.apache.jena.web.HttpSC ; import org.junit.Test ; public class TestEmbeddedFuseki { + private static final String DIR = "testing/FusekiEmbedded/" ; + @Test public void embedded_01() { DatasetGraph dsg = dataset() ; FusekiEmbeddedServer server = FusekiEmbeddedServer.make(3330, "/ds", dsg) ; assertTrue(DataAccessPointRegistry.get().isRegistered("/ds")) ; server.start() ; - try (QueryExecution qExec = QueryExecutionFactory.sparqlService("http://localhost:3330/ds/query", "SELECT * { ?s ?p ?o}") ) { + query("http://localhost:3330/ds/query", "SELECT * { ?s ?p ?o}", qExec-> { ResultSet rs = qExec.execSelect() ; assertFalse(rs.hasNext()) ; - } + }) ; server.stop() ; } - + @Test public void embedded_02() { - // test order !!!! DatasetGraph dsg = dataset() ; FusekiEmbeddedServer server = FusekiEmbeddedServer.make(3330, "/ds2", dsg) ; // But no /ds assertEquals(1, DataAccessPointRegistry.get().size()) ; assertTrue(DataAccessPointRegistry.get().isRegistered("/ds2")) ; assertFalse(DataAccessPointRegistry.get().isRegistered("/ds")) ; - server.start() ; - server.stop() ; + try { + server.start() ; + } finally { server.stop() ; } } @Test public void embedded_03() { @@ -85,17 +92,18 @@ public class TestEmbeddedFuseki { .add("/ds1", dsg) .build() ; server.start() ; - // Add while live. - Txn.execWrite(dsg, ()->{ - Quad q = SSE.parseQuad("(_ :s :p _:b)") ; - dsg.add(q); - }) ; - try (QueryExecution qExec = QueryExecutionFactory.sparqlService("http://localhost:3331/ds1/query", "SELECT * { ?s ?p ?o}") ) { - ResultSet rs = qExec.execSelect() ; - int x = ResultSetFormatter.consume(rs) ; - assertEquals(1, x) ; - } - server.stop() ; + try { + // Add while live. + Txn.execWrite(dsg, ()->{ + Quad q = SSE.parseQuad("(_ :s :p _:b)") ; + dsg.add(q); + }) ; + query("http://localhost:3331/ds1/query", "SELECT * { ?s ?p ?o}", qExec->{ + ResultSet rs = qExec.execSelect() ; + int x = ResultSetFormatter.consume(rs) ; + assertEquals(1, x) ; + }) ; + } finally { server.stop() ; } } @@ -118,37 +126,113 @@ public class TestEmbeddedFuseki { .add("/data", dataService) .build() ; server.start() ; - - // Put data in. - String data = "(graph (:s :p 1) (:s :p 2) (:s :p 3))" ; - Graph g = SSE.parseGraph(data) ; - HttpEntity e = graphToHttpEntity(g) ; - HttpOp.execHttpPut("http://localhost:3332/data", e) ; - - // Get data out. - try ( TypedInputStream in = HttpOp.execHttpGet("http://localhost:3332/data") ) { - Graph g2 = GraphFactory.createDefaultGraph() ; - RDFDataMgr.read(g2, in, RDFLanguages.contentTypeToLang(in.getContentType())) ; - assertTrue(g.isIsomorphicWith(g2)) ; - } - // Query. - try (QueryExecution qExec = QueryExecutionFactory.sparqlService("http://localhost:3332/data", "SELECT * { ?s ?p ?o}") ) { - ResultSet rs = qExec.execSelect() ; - int x = ResultSetFormatter.consume(rs) ; - assertEquals(3, x) ; - } - // Update - UpdateRequest req = UpdateFactory.create("CLEAR DEFAULT") ; - UpdateExecutionFactory.createRemote(req, "http://localhost:3332/data").execute(); - // Query again. - try (QueryExecution qExec = QueryExecutionFactory.sparqlService("http://localhost:3332/data", "SELECT * { ?s ?p ?o}") ) { - ResultSet rs = qExec.execSelect() ; - int x = ResultSetFormatter.consume(rs) ; - assertEquals(0, x) ; - } + try { + // Put data in. + String data = "(graph (:s :p 1) (:s :p 2) (:s :p 3))" ; + Graph g = SSE.parseGraph(data) ; + HttpEntity e = graphToHttpEntity(g) ; + HttpOp.execHttpPut("http://localhost:3332/data", e) ; + + // Get data out. + try ( TypedInputStream in = HttpOp.execHttpGet("http://localhost:3332/data") ) { + Graph g2 = GraphFactory.createDefaultGraph() ; + RDFDataMgr.read(g2, in, RDFLanguages.contentTypeToLang(in.getContentType())) ; + assertTrue(g.isIsomorphicWith(g2)) ; + } + // Query. + query("http://localhost:3332/data", "SELECT * { ?s ?p ?o}", qExec->{ + ResultSet rs = qExec.execSelect() ; + int x = ResultSetFormatter.consume(rs) ; + assertEquals(3, x) ; + }) ; + // Update + UpdateRequest req = UpdateFactory.create("CLEAR DEFAULT") ; + UpdateExecutionFactory.createRemote(req, "http://localhost:3332/data").execute(); + // Query again. + query("http://localhost:3332/data", "SELECT * { ?s ?p ?o}", qExec-> { + ResultSet rs = qExec.execSelect() ; + int x = ResultSetFormatter.consume(rs) ; + assertEquals(0, x) ; + }) ; + } finally { server.stop() ; } + } + + @Test public void embedded_05() { + DatasetGraph dsg = dataset() ; + FusekiEmbeddedServer server = FusekiEmbeddedServer.create() + .setPort(3330) + .add("/ds0", dsg) + .build() ; + server.start() ; + try { + // No stats + String x = HttpOp.execHttpGetString("http://localhost:3330/$/stats") ; + assertNull(x) ; + } finally { server.stop() ; } + } + + @Test public void embedded_06() { + DatasetGraph dsg = dataset() ; + FusekiEmbeddedServer server = FusekiEmbeddedServer.create() + .setPort(3330) + .add("/ds0", dsg) + .enableStats(true) + .build() ; + server.start() ; + // No stats + String x = HttpOp.execHttpGetString("http://localhost:3330/$/stats") ; + assertNotNull(x) ; server.stop() ; } - + + // Context path. + @Test public void embedded_07() { + DatasetGraph dsg = dataset() ; + FusekiEmbeddedServer server = FusekiEmbeddedServer.create() + .setPort(3330) + .setContextPath("/ABC") + .add("/ds", dsg) + .build() ; + server.start() ; + try { + String x1 = HttpOp.execHttpGetString("http://localhost:3330/ds") ; + assertNull(x1) ; + String x2 = HttpOp.execHttpGetString("http://localhost:3330/ABC/ds") ; + assertNotNull(x2) ; + } finally { server.stop() ; } + } + + @Test public void embedded_08() { + DatasetGraph dsg = dataset() ; + FusekiEmbeddedServer server = FusekiEmbeddedServer.create() + .setPort(3330) + .parseConfigFile(DIR+"config.ttl") + .build() ; + server.start() ; + try { + query("http://localhost:3330/FuTest", "SELECT * {}", x->{}) ; + } finally { server.stop() ; } + } + + @Test public void embedded_09() { + DatasetGraph dsg = dataset() ; + FusekiEmbeddedServer server = FusekiEmbeddedServer.create() + .setPort(3330) + .setContextPath("/ABC") + .parseConfigFile(DIR+"config.ttl") + .build() ; + server.start() ; + try { + try { + query("http://localhost:3330/FuTest", "ASK{}", x->{}) ; + } catch (HttpException ex) { + assertEquals(HttpSC.METHOD_NOT_ALLOWED_405, ex.getResponseCode()) ; + } + + query("http://localhost:3330/ABC/FuTest","ASK{}",x->{}) ; + } finally { server.stop() ; } + } + /** Create an HttpEntity for the graph */ protected static HttpEntity graphToHttpEntity(final Graph graph) { final RDFFormat syntax = RDFFormat.TURTLE_BLOCKS ; @@ -167,4 +251,10 @@ public void writeTo(OutputStream out) { private DatasetGraph dataset() { return DatasetGraphFactory.createTxnMem() ; } + + private static void query(String URL, String query, Consumer body) { + try (QueryExecution qExec = QueryExecutionFactory.sparqlService(URL, query) ) { + body.accept(qExec); + } + } } diff --git a/jena-fuseki2/jena-fuseki-embedded/testing/FusekiEmbedded/config.ttl b/jena-fuseki2/jena-fuseki-embedded/testing/FusekiEmbedded/config.ttl new file mode 100644 index 00000000000..5a7f84af210 --- /dev/null +++ b/jena-fuseki2/jena-fuseki-embedded/testing/FusekiEmbedded/config.ttl @@ -0,0 +1,18 @@ +@prefix : <#> . +@prefix fuseki: . +@prefix rdf: . +@prefix rdfs: . +@prefix ja: . +@prefix tdb: . + +<#serviceInMemory> rdf:type fuseki:Service; + rdfs:label "test"; + fuseki:name "FuTest"; + fuseki:serviceQuery "query"; + fuseki:serviceUpdate "update"; + fuseki:serviceUpload "upload" ; + fuseki:dataset <#dataset> ; +. + +<#dataset> rdf:type ja:RDFDataset; +. From 1acfc242385545a28c583c9c23c30be9ae627013 Mon Sep 17 00:00:00 2001 From: Andy Seaborne Date: Sun, 7 Aug 2016 18:52:34 +0100 Subject: [PATCH 8/9] Switch default port to 3330. --- .../org/apache/jena/fuseki/embedded/FusekiEmbeddedServer.java | 2 +- .../org/apache/jena/fuseki/embedded/TestEmbeddedFuseki.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/jena-fuseki2/jena-fuseki-embedded/src/main/java/org/apache/jena/fuseki/embedded/FusekiEmbeddedServer.java b/jena-fuseki2/jena-fuseki-embedded/src/main/java/org/apache/jena/fuseki/embedded/FusekiEmbeddedServer.java index 49184eb2ff4..59c1364806c 100644 --- a/jena-fuseki2/jena-fuseki-embedded/src/main/java/org/apache/jena/fuseki/embedded/FusekiEmbeddedServer.java +++ b/jena-fuseki2/jena-fuseki-embedded/src/main/java/org/apache/jena/fuseki/embedded/FusekiEmbeddedServer.java @@ -139,7 +139,7 @@ public void join() { public static class Builder { // Keeping this allows accumulation of data access points for one name. private Map map = new HashMap<>() ; - private int port = 3333 ; + private int port = 3330 ; private boolean loopback = false ; private boolean withStats = false ; private String contextPath = "/" ; diff --git a/jena-fuseki2/jena-fuseki-embedded/src/test/java/org/apache/jena/fuseki/embedded/TestEmbeddedFuseki.java b/jena-fuseki2/jena-fuseki-embedded/src/test/java/org/apache/jena/fuseki/embedded/TestEmbeddedFuseki.java index d96faa08c19..83413422933 100644 --- a/jena-fuseki2/jena-fuseki-embedded/src/test/java/org/apache/jena/fuseki/embedded/TestEmbeddedFuseki.java +++ b/jena-fuseki2/jena-fuseki-embedded/src/test/java/org/apache/jena/fuseki/embedded/TestEmbeddedFuseki.java @@ -63,7 +63,7 @@ public class TestEmbeddedFuseki { @Test public void embedded_01() { DatasetGraph dsg = dataset() ; - FusekiEmbeddedServer server = FusekiEmbeddedServer.make(3330, "/ds", dsg) ; + FusekiEmbeddedServer server = FusekiEmbeddedServer.create().add("/ds", dsg).build() ; assertTrue(DataAccessPointRegistry.get().isRegistered("/ds")) ; server.start() ; query("http://localhost:3330/ds/query", "SELECT * { ?s ?p ?o}", qExec-> { From ff28bc6968a38cdb9eb3ce50c3466cc85b654217 Mon Sep 17 00:00:00 2001 From: Andy Seaborne Date: Sun, 7 Aug 2016 22:26:02 +0100 Subject: [PATCH 9/9] Add Dataset variations. --- .../fuseki/embedded/FusekiEmbeddedServer.java | 42 +++++++++++++++---- .../fuseki/embedded/TestEmbeddedFuseki.java | 17 ++++++-- 2 files changed, 46 insertions(+), 13 deletions(-) diff --git a/jena-fuseki2/jena-fuseki-embedded/src/main/java/org/apache/jena/fuseki/embedded/FusekiEmbeddedServer.java b/jena-fuseki2/jena-fuseki-embedded/src/main/java/org/apache/jena/fuseki/embedded/FusekiEmbeddedServer.java index 59c1364806c..aa206fa89ba 100644 --- a/jena-fuseki2/jena-fuseki-embedded/src/main/java/org/apache/jena/fuseki/embedded/FusekiEmbeddedServer.java +++ b/jena-fuseki2/jena-fuseki-embedded/src/main/java/org/apache/jena/fuseki/embedded/FusekiEmbeddedServer.java @@ -36,6 +36,7 @@ import org.apache.jena.fuseki.server.DataService ; import org.apache.jena.fuseki.server.OperationName ; import org.apache.jena.fuseki.servlets.FusekiFilter ; +import org.apache.jena.query.Dataset ; import org.apache.jena.sparql.core.DatasetGraph ; import org.eclipse.jetty.server.HttpConnectionFactory ; import org.eclipse.jetty.server.Server ; @@ -172,11 +173,22 @@ public Builder enableStats(boolean withStats) { return this ; } + /* Add the dataset with given name and a default set of services including update */ + public Builder add(String name, Dataset ds) { + return add(name, ds.asDatasetGraph()) ; + } + /* Add the dataset with given name and a default set of services including update */ public Builder add(String name, DatasetGraph dsg) { return add(name, dsg, true) ; } + /* Add the dataset with given name and a default set of services. */ + public Builder add(String name, Dataset ds, boolean allowUpdate) { + return add(name, ds.asDatasetGraph(), allowUpdate) ; + } + + /* Add the dataset with given name and a default set of services. */ public Builder add(String name, DatasetGraph dsg, boolean allowUpdate) { DataService dSrv = FusekiBuilder.buildDataService(dsg, allowUpdate) ; @@ -191,6 +203,13 @@ public Builder add(String name, DataService dataService) { /* Add an operation, specifing it's endpoint name. * This adds endpoints to any existing data service already setup by the builder. */ + public Builder add(String name, Dataset ds, OperationName opName, String epName) { + return add(name, ds.asDatasetGraph(), opName, epName) ; + } + + /* Add an operation, specifing it's endpoint name. + * This adds endpoints to any existing data service already setup by the builder. + */ public Builder add(String name, DatasetGraph dsg, OperationName opName, String epName) { DataService dSrv = map.get(name) ; if ( dSrv == null ) { @@ -203,15 +222,20 @@ public Builder add(String name, DatasetGraph dsg, OperationName opName, String e private Builder add$(String name, DataService dataService) { name = DataAccessPoint.canonical(name) ; - DataService dSrv = map.get(name) ; - if ( dSrv != null ) { - DatasetGraph dsg1 = dSrv.getDataset() ; - DatasetGraph dsg2 = dataService.getDataset() ; - if ( dsg1 != dsg2 ) // Object identity - throw new FusekiConfigException("Attempt to add a DataService for a different dataset: "+name) ; - dSrv.getOperations() ; - } else - map.put(name, dataService) ; + if ( map.containsKey(name) ) + throw new FusekiConfigException("Attempt to add a DataService for a different dataset: "+name) ; + map.put(name, dataService) ; + + // Merge endpoints : too complicated +// DataService dSrv = map.get(name) ; +// if ( dSrv != null ) { +// DatasetGraph dsg1 = dSrv.getDataset() ; +// DatasetGraph dsg2 = dataService.getDataset() ; +// if ( dsg1 != dsg2 ) // Object identity +// throw new FusekiConfigException("Attempt to add a DataService for a different dataset: "+name) ; +// dSrv.getOperations() ; +// } else +// map.put(name, dataService) ; return this ; } diff --git a/jena-fuseki2/jena-fuseki-embedded/src/test/java/org/apache/jena/fuseki/embedded/TestEmbeddedFuseki.java b/jena-fuseki2/jena-fuseki-embedded/src/test/java/org/apache/jena/fuseki/embedded/TestEmbeddedFuseki.java index 83413422933..cbc0adb7411 100644 --- a/jena-fuseki2/jena-fuseki-embedded/src/test/java/org/apache/jena/fuseki/embedded/TestEmbeddedFuseki.java +++ b/jena-fuseki2/jena-fuseki-embedded/src/test/java/org/apache/jena/fuseki/embedded/TestEmbeddedFuseki.java @@ -37,10 +37,7 @@ import org.apache.jena.fuseki.server.DataService ; import org.apache.jena.fuseki.server.OperationName ; import org.apache.jena.graph.Graph ; -import org.apache.jena.query.QueryExecution ; -import org.apache.jena.query.QueryExecutionFactory ; -import org.apache.jena.query.ResultSet ; -import org.apache.jena.query.ResultSetFormatter ; +import org.apache.jena.query.* ; import org.apache.jena.riot.RDFDataMgr ; import org.apache.jena.riot.RDFFormat ; import org.apache.jena.riot.RDFLanguages ; @@ -73,6 +70,18 @@ public class TestEmbeddedFuseki { server.stop() ; } + @Test public void embedded_01a() { + Dataset ds = DatasetFactory.createTxnMem() ; + FusekiEmbeddedServer server = FusekiEmbeddedServer.create().add("/ds", ds).build() ; + assertTrue(DataAccessPointRegistry.get().isRegistered("/ds")) ; + server.start() ; + query("http://localhost:3330/ds/query", "SELECT * { ?s ?p ?o}", qExec-> { + ResultSet rs = qExec.execSelect() ; + assertFalse(rs.hasNext()) ; + }) ; + server.stop() ; + } + @Test public void embedded_02() { DatasetGraph dsg = dataset() ; FusekiEmbeddedServer server = FusekiEmbeddedServer.make(3330, "/ds2", dsg) ;