Connection API

Overview

Mulgara now supports a new interface called a Connection. This will be replacing the current ItqlInterpreterBean interface as the full suite of utilities becomes available.

The existing ItqlInterpreterBean interface provides a completely automated connection mechanism, based on the URLs of graphs referenced in TQL queries. While convenient, this has several undesirable effects:

  • All queries issued to this interface must contain at least one URL for a graph on the server that will handle the query.
  • Arbitrary graph names are not permitted. Graph names must follow a particular format, and will include the hostname of the server containing the graph.
  • Queries against graphs must be changed when the server hosting the graph changes.
  • Connections to a server cannot be cached.
  • Distributed queries (queries where the requested data comes from more than one server) are sent to an arbitrary server and not necessarily the most desirable one.
  • Query languages which use a default graph name (like SPARQL) cannot identify a server if no graph names are given.

The Connection interface avoids these issues by establishing a link to a specified server. This link can be to a server running in the current process, over inter-process-communication (IPC) to a server running on the same machine, or across a network. Queries and commands can be sent along this link, and all operations will be performed by the server that has been connected to.

The Connection interface and its supporting classes are found in the org.mulgara.connection package.

Connection Factory

While Connections can be created directly, the ConnectionFactory class attempts to manage connections to ensure that the correct type is created, and reduce network overhead. The connections returned by a ConnectionFactory are only managed by that factory, and will not interfere with connections from a separate factory instance.

A Connection can be established based on the URL of a database (in the form protocol://host/service) or by wrapping a Session instance. The Session interface has existed in Mulgara for some time, and it may be more appropriate for existing code to use its internal Session instances to create a new Connection.

When the ConnectionFactory is used to obtain a connection based on the database URL, the factory will first check to see if it has previously established a Connection to that database. If it has, and the connection is no longer in use, then it will try to re-use the resources associated with that previous connection that are cached in the factory. This will avoid setting up a new remote session with the database, which in turn should reduce network overhead. The ConnectionFactory is designed to support concurrent access by multiple clients in different threads; if two clients simultaneously get a Connection to the same database, the Connection they receive will be distinct and commands issued on one will not interfere with commands issued on the other. Note, however, that the Connection itself is not designed for concurrent access.

When the ConnectionFactory is used to obtain a connection based on an existing Session instance, no caching is performed. It is assumed that the user will take responsibility for managing their own Session since they are using a lower-level API.

For the moment, the only supported protocol is rmi. Other protocols, including http are expected shortly.

Using a Connection

Commands
Once a Connection has been created, commands can be sent on it using the execute method. Alternatively, commands also have their own execute method that can be applied to a connection, with the same effect.

The following is an example of using a Connection to create a graph, and load a file into that graph:

 1     // define the server we want to connect to
 2     URI serverURI = new URI("rmi://localhost/server1");
 3     // define the file we want to load
 4     URI dataFile = new File("data.rdf").toURI();
 5     // define the name of the graph to load the data into
 6     URI myData = new URI("test:data");
 7 
 8     // Create a factory, and connect to the server
 9     [[ConnectionFactory]] factory = new [[ConnectionFactory]]();
10     Connection connection = factory.newConnection(serverUri);
11 
12     // execute a CREATE command
13     connection.execute(new [[CreateGraph]](myData));
14     // execute a LOAD command
15     connection.execute(new Load(dataFile, myData, true));
16 
17     // cleaning up the connection allows the network resources to be re-used
18     connection.close();

Executing a command returns a String containing a message related to the success of the command. If commands are kept, then it is also possible to retrieve the last message from a command with the getResultMessage method.

Queries

Queries are a type of command that return an Answer instead of a message string. Queries can be created using either the TQL or SPARQL parsers:

 1     // define the server we want to connect to
 2     URI serverURI = new URI("rmi://localhost/server1");
 3 
 4     // Create a factory, and connect to the server
 5     [[ConnectionFactory]] factory = new [[ConnectionFactory]]();
 6     Connection connection = factory.newConnection(serverUri);
 7 
 8     // Use a SPARQL query
 9     Query query = new [[SparqlInterpreter]]().parseQuery(
10                   "SELECT * FROM <test:data> WHERE { ?s ?p ?o }");
11     Answer answer = connection.execute(query);
12 
13     // display the result
14     answer.beforeFirst();
15     while (answer.next()) {
16       System.out.print(" " + answer.getObject(0));
17       System.out.print(" " + answer.getObject(1));
18       System.out.println(" " + answer.getObject(2));
19     }
20     answer.close();
21 
22     connection.close();

A working example program for issuing SPARQL queries is provided with the source code. It can be found on the following path:
tools/src/org/mulgara/tools/Sparql.java

Closing Connections

When a Connection is no longer in use, it should be either closed or disposed in order to release the resources. Close a connection that was obtained from a ConnectionFactory using the database URL by calling the Connection.close() method. This will release the resources associated with the connection back to the factory, where they may be re-used for subsequent connections. If a connection was obtained based on an existing Session or was created directly (not using the factory) then close it by calling the Connection.dispose() method. This will destroy the underlying resources for that connection, both in the local process and in the remote database, including the Session itself. Once close() or dispose() has been called on a Connection and further attempts to execute commands or queries on that connection will cause an exception to be thrown. Some care should be taken when using the Connection.close() method; if autocommit is turned off on a connection when the connection is closed (but not disposed) then the write-lock will not be released and any modifications made on that connection will remain uncommitted. Also, authentication credentials are stored in the Session that backs a Connection, so if you are using a ConnectionFactory in a multi-user environment then you should use the dispose() method to ensure that credentials are not shared between users.

Finally, the ConnectionFactory provides a closeAll() method that disposes of all resources associated with the factory. Exercise caution when using this method, as it will dispose of both cached resources and any resources which might still be in use by active connections created by the factory. This method is intended to be used when an application is shutting down and releasing all of its resources.

 1     // define the server we want to connect to
 2     URI serverURI = new URI("rmi://localhost/server1");
 3 
 4     // Create a SPARQL query
 5     Query query = new [[SparqlInterpreter]]().parseQuery(
 6                   "SELECT * FROM <test:data> WHERE { ?s ?p ?o }");
 7 
 8     // Create a factory, and connect to the server
 9     [[ConnectionFactory]] factory = new [[ConnectionFactory]]();
10     Connection connection1 = factory.newConnection(serverUri);
11 
12     // This query will execute successfully.
13     connection1.execute(query);
14 
15     // Return the session for this connection to the factory for re-use
16     connection1.close();
17 
18     // This call will throw an exception since the connection was closed
19     connection1.execute(query);
20 
21     // The previous connection was closed, so this will re-use the cached session
22     Connection connection2 = factory.newConnection(serverUri);
23 
24     // connection2 is still in use, so this will cause a new session to be created
25     Connection connection3 = factory.newConnection(serverUri);
26 
27     // This will close the session for this connection; it will not be re-used
28     connection2.dispose();
29 
30     // Clears the cache and closes the session for connection3
31     factory.closeAll();