JENA-1221: Merge commit 'refs/pull/162/head' of github.com:apache/jena
This closes #162.
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 b83d1cb..b7e89db 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 @@
return map.containsKey(key) ;
}
-// @Override
-// public boolean containsValue(Object value) {
-// return map.containsValue(value) ;
-// }
-
public Set<String> keys() {
return map.keySet() ;
}
@@ -71,6 +66,11 @@
return map.get(key) ;
}
+ /** For walking structures */
+ public JsonObject getObj(String key) {
+ return get(key).getAsObject() ;
+ }
+
public boolean isEmpty() {
return map.isEmpty() ;
}
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 b44e678..fc95331 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 @@
@Override
protected JsonValue execGetContainer(HttpAction action) {
action.log.info(format("[%d] GET stats all", action.id)) ;
- JsonBuilder builder = new JsonBuilder() ;
- builder.startObject("top") ;
-
- builder.key(JsonConst.datasets) ;
- builder.startObject("datasets") ;
- for ( String ds : action.getDataAccessPointRegistry().keys() ) {
- DataAccessPoint access = action.getDataAccessPointRegistry().get(ds) ;
- statsDataset(builder, access) ;
- }
- builder.finishObject("datasets") ;
-
- builder.finishObject("top") ;
- return builder.build() ;
+ return generateStats(action.getDataAccessPointRegistry()) ;
}
+ public static JsonObject generateStats(DataAccessPointRegistry registry) {
+ JsonBuilder builder = new JsonBuilder() ;
+ builder.startObject("top") ;
+ builder.key(JsonConst.datasets) ;
+ builder.startObject("datasets") ;
+ registry.forEach((name, access)->statsDataset(builder, access));
+ builder.finishObject("datasets") ;
+ builder.finishObject("top") ;
+ 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 @@
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 @@
builder.key(CounterName.RequestsGood.name()).value(dSrv.getCounters().value(CounterName.RequestsGood)) ;
builder.key(CounterName.RequestsBad.name()).value(dSrv.getCounters().value(CounterName.RequestsBad)) ;
-
- // Build the operation -> endpoint list map.
-
-// MultiMap<OperationName, Endpoint> map = MultiMap.createMapList() ;
-// for ( OperationName operName : dSrv.getOperations() ) {
-// List<Endpoint> endpoints = access.getDataService().getOperation(operName) ;
-// for ( Endpoint endpoint : endpoints )
-// map.put(operName, endpoint) ;
-// }
-
-
builder.key(JsonConst.endpoints).startObject("endpoints") ;
for ( OperationName operName : dSrv.getOperations() ) {
List<Endpoint> endpoints = access.getDataService().getOperation(operName) ;
-// System.err.println(operName+" : "+endpoints.size()) ;
-// for ( Endpoint endpoint : endpoints )
-// System.err.println(" "+endpoint.getEndpoint()) ;
for ( Endpoint endpoint : endpoints ) {
-
- // Endpoint names are unique but not services.
-
+ // Endpoint names are unique for a given service.
builder.key(endpoint.getEndpoint()) ;
builder.startObject() ;
@@ -126,10 +116,9 @@
}
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()) ;
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 eee14ea..533b49e 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 @@
}) ;
}
- // 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-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 e7a09ee..9424eb8 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 @@
FusekiLogging.setLogging();
FusekiEnv.setEnvironment() ;
- // Occasionally log4j.properties gets out of step.
LogCtl.setLevel("org.apache.shiro", "WARN") ;
LogCtl.setLevel("org.eclipse.jetty", "WARN");
diff --git a/jena-fuseki2/jena-fuseki-embedded/pom.xml b/jena-fuseki2/jena-fuseki-embedded/pom.xml
new file mode 100644
index 0000000..72fc04e
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-embedded/pom.xml
@@ -0,0 +1,131 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+ <name>Apache Jena - Fuseki Embedded Server</name>
+ <artifactId>jena-fuseki-embedded</artifactId>
+
+ <parent>
+ <groupId>org.apache.jena</groupId>
+ <artifactId>jena-fuseki</artifactId>
+ <version>2.4.1-SNAPSHOT</version>
+ </parent>
+
+ <packaging>jar</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.jena</groupId>
+ <artifactId>jena-fuseki-core</artifactId>
+ <version>2.4.1-SNAPSHOT</version>
+ <!-- No specific logging - leave to the application -->
+ <exclusions>
+ <exclusion>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-log4j12</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>log4j</groupId>
+ <artifactId>log4j</artifactId>
+ </exclusion>
+ <!-- -->
+ <exclusion>
+ <groupId>org.apache.jena</groupId>
+ <artifactId>jena-text</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.apache.jena</groupId>
+ <artifactId>jena-spatial</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+
+ <!-- Logging examples -->
+ <!-- java.util.logging.
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-jdk14</artifactId>
+ <version>1.7.21</version>
+ </dependency>
+ -->
+
+ <!-- Log4j
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-log4j12</artifactId>
+ <version>1.7.21</version>
+ </dependency>
+
+ <dependency>
+ <groupId>log4j</groupId>
+ <artifactId>log4j</artifactId>
+ <version>1.2.17</version>
+ </dependency>
+ -->
+
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-source-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>attach-sources</id>
+ <phase>package</phase>
+ <goals>
+ <goal>jar-no-fork</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <includes>
+ <include>**/TS_*.java</include>
+ </includes>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-dependency-plugin</artifactId>
+ <configuration>
+ <overWriteReleases>false</overWriteReleases>
+ <overWriteIfNewer>true</overWriteIfNewer>
+ </configuration>
+ </plugin>
+
+ </plugins>
+
+ </build>
+
+</project>
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 0000000..aa206fa
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-embedded/src/main/java/org/apache/jena/fuseki/embedded/FusekiEmbeddedServer.java
@@ -0,0 +1,305 @@
+/*
+ * 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 javax.servlet.ServletContext ;
+
+import org.apache.jena.fuseki.Fuseki ;
+import org.apache.jena.fuseki.FusekiConfigException ;
+import org.apache.jena.fuseki.FusekiException ;
+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.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.query.Dataset ;
+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.
+ * <p>
+ * 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).
+ * <p>
+ * The application should call {@link #start()} to actually start the server
+ * (it wil run in the background : see {@link #join}).
+ * <p>Example:
+ * <pre>
+ * DatasetGraph dsg = ... ;
+ * FusekiEmbeddedServer server = FusekiEmbeddedServer.create()
+ * .setPort(1234)
+ * .add("/ds", dsg)
+ * .build() ;
+ * server.start() ;
+ * </pre>
+ * Compact form (use the builder pattern above to get more flexibility):
+ * <pre>
+ * FusekiEmbeddedServer.make(1234, "/ds", dsg).start() ;
+ * </pre>
+ *
+ */
+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. */
+ public Server getJettyServer() {
+ return server ;
+ }
+
+ /** 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() {
+ 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<String, DataService> map = new HashMap<>() ;
+ private int port = 3330 ;
+ private boolean loopback = false ;
+ private boolean withStats = false ;
+ private String contextPath = "/" ;
+
+ /* 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.contextPath = 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 "/$/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, 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) ;
+ 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, 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 ) {
+ dSrv = new DataService(dsg) ;
+ map.put(name, dSrv) ;
+ }
+ dSrv.addEndpoint(opName, epName);
+ return this ;
+ }
+
+ private Builder add$(String name, DataService dataService) {
+ name = DataAccessPoint.canonical(name) ;
+ 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 ;
+ }
+
+ /** Read and parse a Fuseki services/datasets file.
+ * <p>
+ * 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<DataAccessPoint> 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() {
+ DataAccessPointRegistry registry = new DataAccessPointRegistry() ;
+ map.forEach((name, dSrv) -> {
+ DataAccessPoint dap = new DataAccessPoint(name, dSrv) ;
+ registry.put(name, dap) ;
+ }) ;
+ 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 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() ;
+ FusekiFilter ff = new FusekiFilter() ;
+ FilterHolder h = new FilterHolder(ff) ;
+ context.setContextPath(contextPath) ;
+ context.addFilter(h, "/*", null) ;
+ context.setDisplayName(Fuseki.servletRequestLogName) ;
+ context.setErrorHandler(new FusekiErrorHandler1()) ;
+ return context ;
+ }
+
+ /** Jetty server */
+ 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 0000000..c66149d
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-embedded/src/test/java/org/apache/jena/fuseki/embedded/TS_EmbeddedFuseki.java
@@ -0,0 +1,49 @@
+/*
+ * 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.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 ;
+
+@RunWith(Suite.class)
+@SuiteClasses({
+ TestEmbeddedFuseki.class
+})
+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(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.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
new file mode 100644
index 0000000..cbc0adb
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-embedded/src/test/java/org/apache/jena/fuseki/embedded/TestEmbeddedFuseki.java
@@ -0,0 +1,269 @@
+/*
+ * 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 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 ;
+import org.apache.jena.fuseki.server.OperationName ;
+import org.apache.jena.graph.Graph ;
+import org.apache.jena.query.* ;
+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.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.create().add("/ds", dsg).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_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) ;
+ // But no /ds
+ assertEquals(1, DataAccessPointRegistry.get().size()) ;
+ assertTrue(DataAccessPointRegistry.get().isRegistered("/ds2")) ;
+ assertFalse(DataAccessPointRegistry.get().isRegistered("/ds")) ;
+ try {
+ server.start() ;
+ } finally { server.stop() ; }
+ }
+
+ @Test public void embedded_03() {
+ DatasetGraph dsg = dataset() ;
+ FusekiEmbeddedServer server = FusekiEmbeddedServer.create()
+ .setPort(3331)
+ .add("/ds1", dsg)
+ .build() ;
+ server.start() ;
+ 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() ; }
+ }
+
+
+ @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() ;
+ 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 ;
+ 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() ;
+ }
+
+ private static void query(String URL, String query, Consumer<QueryExecution> 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 0000000..5a7f84a
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-embedded/testing/FusekiEmbedded/config.ttl
@@ -0,0 +1,18 @@
+@prefix : <#> .
+@prefix fuseki: <http://jena.apache.org/fuseki#> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix ja: <http://jena.hpl.hp.com/2005/11/Assembler#> .
+@prefix tdb: <http://jena.hpl.hp.com/2008/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;
+.
diff --git a/jena-fuseki2/pom.xml b/jena-fuseki2/pom.xml
index 11728ee..7b6f126 100644
--- a/jena-fuseki2/pom.xml
+++ b/jena-fuseki2/pom.xml
@@ -70,6 +70,7 @@
<modules>
<module>jena-fuseki-core</module>
+ <module>jena-fuseki-embedded</module>
<module>jena-fuseki-war</module>
<module>jena-fuseki-server</module>
<module>apache-jena-fuseki</module>