diff --git a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/jetty/JettyFuseki.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/jetty/JettyFuseki.java index 6580a81e985..7e8f40562f1 100644 --- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/jetty/JettyFuseki.java +++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/jetty/JettyFuseki.java @@ -23,11 +23,14 @@ import java.io.FileInputStream ; +import javax.servlet.ServletContext ; + import org.apache.jena.atlas.lib.DateTimeUtils ; import org.apache.jena.atlas.lib.FileOps ; import org.apache.jena.fuseki.Fuseki ; import org.apache.jena.fuseki.FusekiException ; import org.apache.jena.fuseki.mgt.MgtJMX ; +import org.apache.jena.fuseki.server.DataAccessPointRegistry ; import org.apache.jena.fuseki.server.FusekiEnv ; import org.eclipse.jetty.security.* ; import org.eclipse.jetty.security.authentication.BasicAuthenticator ; @@ -66,7 +69,9 @@ public class JettyFuseki { private JettyServerConfig serverConfig ; // The jetty server. + private Server server = null ; + private ServletContext servletContext = null ; // webapp setup - standard maven layout public static String contextpath = "/" ; @@ -218,6 +223,10 @@ public static String getenv(String name) { x = System.getProperty(name) ; return x ; } + + public DataAccessPointRegistry getDataAccessPointRegistry() { + return DataAccessPointRegistry.get(servletContext) ; + } private static String tryResourceBase(String maybeResourceBase, String currentResourceBase) { if ( currentResourceBase != null ) @@ -236,6 +245,7 @@ private void buildServerWebapp(String contextPath, String jettyConfig) { defaultServerConfig(serverConfig.port, serverConfig.loopback) ; WebAppContext webapp = createWebApp(contextPath) ; + servletContext = webapp.getServletContext() ; server.setHandler(webapp) ; // Replaced by Shiro. if ( jettyConfig == null && serverConfig.authConfigFile != null ) 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 533b49e5084..50b1f4df56e 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 @@ -21,6 +21,7 @@ import javax.servlet.ServletContext ; import org.apache.jena.atlas.lib.Registry ; +import org.apache.jena.atlas.logging.Log ; import org.apache.jena.fuseki.FusekiException ; public class DataAccessPointRegistry extends Registry @@ -49,22 +50,19 @@ public void print(String string) { }); }) ; } - - // 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 ; } + // The server DataAccessPointRegistry is held in the ServletContext for the server. + 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 ; + DataAccessPointRegistry registry = (DataAccessPointRegistry)cxt.getAttribute(attrNameRegistry) ; + if ( registry == null ) + Log.warn(DataAccessPointRegistry.class, "No registry for ServletContext") ; + return 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/main/java/org/apache/jena/fuseki/server/FusekiServerListener.java b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/FusekiServerListener.java index d944610cc18..6597fe80a28 100644 --- a/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/FusekiServerListener.java +++ b/jena-fuseki2/jena-fuseki-core/src/main/java/org/apache/jena/fuseki/server/FusekiServerListener.java @@ -48,13 +48,6 @@ public void contextInitialized(ServletContextEvent sce) { String x = servletContext.getContextPath() ; if ( ! x.isEmpty() ) Fuseki.configLog.info("Context path = "+x) ; -// String x = System.getProperty("user.dir") ; -// Path currentRelativePath = Paths.get(""); -// String s = currentRelativePath.toAbsolutePath().toString(); -// confLog.info("dir1 = "+x+" : dir2 = "+s) ; - - // Set the server wide state. - //servletContext.setAttribute(, DataAccessPointRegistry.get()) ; serverInitialization(servletContext) ; } @@ -72,6 +65,9 @@ private synchronized void serverInitialization(ServletContext servletContext) { return ; initialized = true ; + DataAccessPointRegistry registry = new DataAccessPointRegistry() ; + DataAccessPointRegistry.set(servletContext, registry); + try { FusekiServer.formatBaseArea() ; if ( ! FusekiServer.serverInitialized ) { @@ -88,7 +84,7 @@ private synchronized void serverInitialization(ServletContext servletContext) { } if ( initialSetup != null ) { - FusekiServer.initializeDataAccessPoints(DataAccessPointRegistry.get(servletContext), + FusekiServer.initializeDataAccessPoints(registry, initialSetup, FusekiServer.dirConfiguration.toString()) ; } else { Fuseki.serverLog.error("No configuration") ; diff --git a/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/ServerCtl.java b/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/ServerCtl.java index 9b0f6120e16..93537538d58 100644 --- a/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/ServerCtl.java +++ b/jena-fuseki2/jena-fuseki-core/src/test/java/org/apache/jena/fuseki/ServerCtl.java @@ -25,13 +25,11 @@ import java.io.IOException ; import java.net.ServerSocket ; import java.nio.file.Paths ; -import java.util.Collection ; import java.util.concurrent.atomic.AtomicInteger ; import org.apache.http.client.HttpClient ; import org.apache.http.impl.client.CloseableHttpClient ; import org.apache.jena.atlas.io.IO ; -import org.apache.jena.atlas.iterator.Iter ; import org.apache.jena.atlas.lib.FileOps ; import org.apache.jena.fuseki.jetty.JettyFuseki ; import org.apache.jena.fuseki.jetty.JettyServerConfig ; @@ -240,15 +238,13 @@ public static void setupServer(int port, String authConfigFile, String datasetPa } /*package*/ static void teardownServer() { - if ( server != null ) + if ( server != null ) { + // Clear out the registry. + server.getDataAccessPointRegistry().clear() ; + FileOps.clearAll(FusekiServer.dirConfiguration.toFile()) ; server.stop() ; + } server = null ; - // Clear out the registry. - Collection keys = Iter.toList(DataAccessPointRegistry.get().keys().iterator()) ; - for (String k : keys) - DataAccessPointRegistry.get().remove(k) ; - // Clear configuration directory. - FileOps.clearAll(FusekiServer.dirConfiguration.toFile()) ; } /*package*/ static JettyServerConfig make(int port, boolean allowUpdate, boolean listenLocal) { 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 452761ba8d4..97659191bc7 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 @@ -113,15 +113,23 @@ public ServletContext getServletContext() { return ((ServletContextHandler)server.getHandler()).getServletContext() ; } + /** Get the {@link DataAccessPointRegistry}. + * This method is intended for inspecting the registry. + */ + public DataAccessPointRegistry getDataAccessPointRegistry() { + return DataAccessPointRegistry.get(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() { + public FusekiEmbeddedServer 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+")"); + return this ; } /** Stop the server. */ diff --git a/jena-fuseki2/jena-fuseki-embedded/src/test/java/org/apache/jena/fuseki/embedded/FusekiTestServer.java b/jena-fuseki2/jena-fuseki-embedded/src/test/java/org/apache/jena/fuseki/embedded/FusekiTestServer.java new file mode 100644 index 00000000000..599fb7857e6 --- /dev/null +++ b/jena-fuseki2/jena-fuseki-embedded/src/test/java/org/apache/jena/fuseki/embedded/FusekiTestServer.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.embedded; + +import static org.apache.jena.fuseki.embedded.FusekiTestServer.ServerScope.CLASS ; +import static org.apache.jena.fuseki.embedded.FusekiTestServer.ServerScope.SUITE ; +import static org.apache.jena.fuseki.embedded.FusekiTestServer.ServerScope.TEST ; + +import java.io.IOException ; +import java.net.ServerSocket ; +import java.util.concurrent.atomic.AtomicInteger ; + +import org.apache.http.client.HttpClient ; +import org.apache.http.impl.client.CloseableHttpClient ; +import org.apache.jena.atlas.io.IO ; +import org.apache.jena.fuseki.FusekiException ; +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.modify.request.Target ; +import org.apache.jena.sparql.modify.request.UpdateDrop ; +import org.apache.jena.system.Txn ; +import org.apache.jena.update.Update ; +import org.apache.jena.update.UpdateExecutionFactory ; +import org.apache.jena.update.UpdateProcessor ; + +// NOT FINISHED + +/** + * Manage a single server for use with tests. It supports three modes: + * + * One server per individual test can be troublesome due to connections not closing down + * fast enough and can also be slow. + *

One server per test class is a good compromise. + *

The data in the server is always reset between tests in all modes. + *

+ * Using a connection pooling HttpClient (see {@link HttpOp#createPoolingHttpClient()}) is important, + * both for test performance and for reducing the TCP connection load on the operating system. + *

+ * Usage: + *

+ *

+ * In the test suite, put: + * + *

+ *  {@literal @BeforeClass} static public void beforeSuiteClass() { ServerCtl.ctlBeforeTestSuite(); } 
+ *  {@literal @AfterClass}  static public void afterSuiteClass()  { ServerCtl.ctlAfterTestSuite(); }
+ * 
+ *

+ * In the test class, put: + *

+ * {@literal @BeforeClass} public static void ctlBeforeClass() { ServerCtl.ctlBeforeClass(); }
+ * {@literal @AfterClass}  public static void ctlAfterClass()  { ServerCtl.ctlAfterClass(); }
+ * {@literal @Before}      public void ctlBeforeTest()         { ServerCtl.ctlBeforeTest(); }
+ * {@literal @After}       public void ctlAfterTest()          { ServerCtl.ctlAfterTest(); }
+ * 
+ */ +public class FusekiTestServer { + /* Cut&Paste versions: + + Test suite (TS_*) + @BeforeClass static public void beforeSuiteClass() { ServerCtl.ctlBeforeTestSuite(); } + @AfterClass static public void afterSuiteClass() { ServerCtl.ctlAfterTestSuite(); } + + Test class (Test*) + @BeforeClass public static void ctlBeforeClass() { ServerCtl.ctlBeforeClass(); } + @AfterClass public static void ctlAfterClass() { ServerCtl.ctlAfterClass(); } + @Before public void ctlBeforeTest() { ServerCtl.ctlBeforeTest(); } + @After public void ctlAfterTest() { ServerCtl.ctlAfterTest(); } + */ + + static HttpClient defaultHttpClient = HttpOp.getDefaultHttpClient(); + + // Note: it is important to cleanly close a PoolingHttpClient across server restarts + // otherwise the pooled connections remain for the old server. + + /*package : for import static */ enum ServerScope { SUITE, CLASS, TEST } + private static ServerScope serverScope = ServerScope.CLASS ; + private static int currentPort = choosePort() ; + + public static int port() { + return currentPort ; + } + + // Whether to use a transaction on the dataset or to use SPARQL Update. + static boolean CLEAR_DSG_DIRECTLY = true ; + static private DatasetGraph dsgTesting ; + + // reference count of start/stop server + private static AtomicInteger countServer = new AtomicInteger() ; + private static FusekiEmbeddedServer server = null ; + + public static final String urlRoot() { return "http://localhost:"+port()+"/" ; } + public static final String datasetPath() { return "/ds_test" ; } + public static final String urlDataset() { return "http://localhost:"+port()+datasetPath() ; } + + public static final String serviceUpdate() { return "http://localhost:"+port()+datasetPath()+"/update" ; } + public static final String serviceQuery() { return "http://localhost:"+port()+datasetPath()+"/query" ; } + public static final String serviceGSP() { return "http://localhost:"+port()+datasetPath()+"/data" ; } + + public static void ctlBeforeTestSuite() { + if ( serverScope == SUITE ) { + setPoolingHttpClient() ; + allocServer(); + } + } + + public static void ctlAfterTestSuite() { + if ( serverScope == SUITE ) { + freeServer(); + resetDefaultHttpClient() ; + } + } + + /** + * Setup for the tests by allocating a Fuseki instance to work with + */ + public static void ctlBeforeClass() { + if ( serverScope == CLASS ) { + setPoolingHttpClient() ; + allocServer(); + } + } + + /** + * Clean up after tests by de-allocating the Fuseki instance + */ + public static void ctlAfterClass() { + if ( serverScope == CLASS ) { + freeServer(); + resetDefaultHttpClient() ; + } + } + + /** + * Placeholder. + */ + public static void ctlBeforeTest() { + if ( serverScope == TEST ) { + setPoolingHttpClient() ; + allocServer(); + } + } + + /** + * Clean up after each test by resetting the Fuseki dataset + */ + public static void ctlAfterTest() { + if ( serverScope == TEST ) { + freeServer(); + resetDefaultHttpClient() ; + } else + resetServer(); + } + + /** Set a PoolingHttpClient */ + private static void setPoolingHttpClient() { + setHttpClient(HttpOp.createPoolingHttpClient()) ; + } + + /** Restore the original setup */ + private static void resetDefaultHttpClient() { + setHttpClient(defaultHttpClient); + } + + /** Set the HttpClient - close the old one if appropriate */ + /*package*/ static void setHttpClient(HttpClient newHttpClient) { + HttpClient hc = HttpOp.getDefaultHttpClient() ; + if ( hc instanceof CloseableHttpClient ) + IO.close((CloseableHttpClient)hc) ; + HttpOp.setDefaultHttpClient(newHttpClient) ; + } + + /*package*/ static void allocServer() { + if ( countServer.getAndIncrement() == 0 ) + setupServer(true) ; + } + + /*package*/ static void freeServer() { + if ( countServer.decrementAndGet() == 0 ) + teardownServer() ; + } + + /*package*/ static void setupServer(boolean updateable) { + dsgTesting = DatasetGraphFactory.createTxnMem() ; + server = + FusekiEmbeddedServer.create() + .setPort(port()) + .setLoopback(true) + .add(datasetPath(), dsgTesting) + .build(); + } + + /*package*/ static void teardownServer() { + if ( server != null ) + server.stop() ; + server = null ; + } + + /*package*/ static void resetServer() { + if (countServer.get() == 0) + throw new RuntimeException("No server started!"); + if ( CLEAR_DSG_DIRECTLY ) { + Txn.executeWrite(dsgTesting, ()->dsgTesting.clear()) ; + } else { + Update clearRequest = new UpdateDrop(Target.ALL) ; + UpdateProcessor proc = UpdateExecutionFactory.createRemote(clearRequest, serviceUpdate()) ; + try {proc.execute() ; } + catch (Throwable e) {e.printStackTrace(); throw e;} + } + } + + /** Choose an unused port for a server to listen on */ + public static int choosePort() { + try (ServerSocket s = new ServerSocket(0)) { + return s.getLocalPort(); + } catch (IOException ex) { + throw new FusekiException("Failed to find a port for tests!"); + } + } +} 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 c66149d3727..07b47282a3d 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 @@ -28,7 +28,8 @@ @RunWith(Suite.class) @SuiteClasses({ - TestEmbeddedFuseki.class + TestEmbeddedFuseki.class + , TestMultipleEmbedded.class }) public class TS_EmbeddedFuseki { @BeforeClass public static void setupForFusekiServer() { 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 6926fa661f1..4c5e866f671 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 @@ -61,7 +61,7 @@ public class TestEmbeddedFuseki { @Test public void embedded_01() { DatasetGraph dsg = dataset() ; FusekiEmbeddedServer server = FusekiEmbeddedServer.create().add("/ds", dsg).build() ; - assertTrue(DataAccessPointRegistry.get().isRegistered("/ds")) ; + assertTrue(server.getDataAccessPointRegistry().isRegistered("/ds")) ; server.start() ; query("http://localhost:3330/ds/query", "SELECT * { ?s ?p ?o}", qExec-> { ResultSet rs = qExec.execSelect() ; @@ -73,10 +73,11 @@ public class TestEmbeddedFuseki { @Test public void embedded_02() { DatasetGraph dsg = dataset() ; FusekiEmbeddedServer server = FusekiEmbeddedServer.make(3330, "/ds2", dsg) ; + DataAccessPointRegistry registry = server.getDataAccessPointRegistry() ; // But no /ds - assertEquals(1, DataAccessPointRegistry.get().size()) ; - assertTrue(DataAccessPointRegistry.get().isRegistered("/ds2")) ; - assertFalse(DataAccessPointRegistry.get().isRegistered("/ds")) ; + assertEquals(1, registry.size()) ; + assertTrue(registry.isRegistered("/ds2")) ; + assertFalse(registry.isRegistered("/ds")) ; try { server.start() ; } finally { server.stop() ; } @@ -261,11 +262,11 @@ public void writeTo(OutputStream out) { return entity ; } - private DatasetGraph dataset() { + /*package*/ static DatasetGraph dataset() { return DatasetGraphFactory.createTxnMem() ; } - private static void query(String URL, String query, Consumer body) { + /*package*/ 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/src/test/java/org/apache/jena/fuseki/embedded/TestMultipleEmbedded.java b/jena-fuseki2/jena-fuseki-embedded/src/test/java/org/apache/jena/fuseki/embedded/TestMultipleEmbedded.java new file mode 100644 index 00000000000..5ceab3ed82c --- /dev/null +++ b/jena-fuseki2/jena-fuseki-embedded/src/test/java/org/apache/jena/fuseki/embedded/TestMultipleEmbedded.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. + */ + +package org.apache.jena.fuseki.embedded; + +import static org.apache.jena.fuseki.embedded.TestEmbeddedFuseki.dataset ; +import static org.apache.jena.fuseki.embedded.TestEmbeddedFuseki.query ; +import static org.junit.Assert.assertEquals ; +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.fuseki.FusekiException ; +import org.apache.jena.graph.Graph ; +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.sparql.core.DatasetGraph ; +import org.apache.jena.sparql.core.Quad ; +import org.apache.jena.sparql.sse.SSE ; +import org.apache.jena.system.Txn ; +import org.junit.Test ; + +public class TestMultipleEmbedded { + + static Quad q1 = SSE.parseQuad("(_ :s :p 1)") ; + static Quad q2 = SSE.parseQuad("(_ :s :p 2)") ; + + // Two servers, same port -> bad. + @Test(expected=FusekiException.class) + public void multiple_01() { + DatasetGraph dsg = dataset() ; + + int port = FusekiTestServer.choosePort() ; + FusekiEmbeddedServer server1 = FusekiEmbeddedServer.create().setPort(port).add("/ds1", dsg).build() ; + // Same port - Bbad. + FusekiEmbeddedServer server2 = FusekiEmbeddedServer.create().setPort(port).add("/ds2", dsg).build() ; + + server1.start(); + + try { + server2.start(); + } catch (FusekiException ex) { + assertTrue(ex.getCause() instanceof java.net.BindException ) ; + throw ex ; + } finally { + try { server1.stop() ; } catch (Exception ex) {} + try { server2.stop() ; } catch (Exception ex) {} + } + } + + // Two servers, different ports -> good. + @Test + public void multiple_02() { + DatasetGraph dsg = dataset() ; + int port1 = FusekiTestServer.choosePort() ; + FusekiEmbeddedServer server1 = FusekiEmbeddedServer.create().setPort(port1).add("/ds1", dsg).build() ; + + // Different port - good + int port2 = FusekiTestServer.choosePort() ; + FusekiEmbeddedServer server2 = FusekiEmbeddedServer.create().setPort(port2).add("/ds2", dsg).build() ; + + try { + server1.start(); + server2.start(); + } finally { + try { server1.stop() ; } catch (Exception ex) {} + try { server2.stop() ; } catch (Exception ex) {} + } + } + + // Two servers, two datasets. + @Test + public void multiple_03() { + DatasetGraph dsg1 = dataset() ; + DatasetGraph dsg2 = dataset() ; + // Same name. + int port1 = FusekiTestServer.choosePort() ; + FusekiEmbeddedServer server1 = FusekiEmbeddedServer.create().setPort(port1).add("/ds", dsg1).build().start() ; + Txn.executeWrite(dsg1, ()->dsg1.add(q1)); + + int port2 = FusekiTestServer.choosePort() ; + FusekiEmbeddedServer server2 = FusekiEmbeddedServer.create().setPort(port2).add("/ds", dsg2).build().start() ; + Txn.executeWrite(dsg2, ()->dsg2.add(q2)); + + query("http://localhost:"+port1+"/ds/", "SELECT * {?s ?p 1}", qExec->{ + ResultSet rs = qExec.execSelect() ; + int x = ResultSetFormatter.consume(rs) ; + assertEquals(1, x) ; + }) ; + query("http://localhost:"+port2+"/ds/", "SELECT * {?s ?p 1}", qExec->{ + ResultSet rs = qExec.execSelect() ; + int x = ResultSetFormatter.consume(rs) ; + assertEquals(0, x) ; + }) ; + server1.stop(); + // server2 still running + query("http://localhost:"+port2+"/ds/", "SELECT * {?s ?p 2}", qExec->{ + ResultSet rs = qExec.execSelect() ; + int x = ResultSetFormatter.consume(rs) ; + assertEquals(1, x) ; + }) ; + server2.stop(); + } + + // Two servers, one dataset under two names. + @Test + public void multiple_04() { + DatasetGraph dsg = dataset() ; + + int port1 = FusekiTestServer.choosePort() ; + FusekiEmbeddedServer server1 = FusekiEmbeddedServer.create().setPort(port1).add("/ds1", dsg).build().start() ; + Txn.executeWrite(dsg, ()->dsg.add(q1)); + + int port2 = FusekiTestServer.choosePort() ; + FusekiEmbeddedServer server2 = FusekiEmbeddedServer.create().setPort(port2).add("/ds2", dsg).build().start() ; + Txn.executeWrite(dsg, ()->dsg.add(q2)); + + query("http://localhost:"+port1+"/ds1", "SELECT * {?s ?p ?o}", qExec->{ + ResultSet rs = qExec.execSelect() ; + int x = ResultSetFormatter.consume(rs) ; + assertEquals(2, x) ; + }) ; + query("http://localhost:"+port2+"/ds2", "SELECT * {?s ?p ?o}", qExec->{ + ResultSet rs = qExec.execSelect() ; + int x = ResultSetFormatter.consume(rs) ; + assertEquals(2, x) ; + }) ; + + server1.stop(); + server2.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 ; + } +} +