TopicMapProvider

TM4J supports the storage of processed topic maps in a number of different forms. The simplest form of storage is an in-memory storage model, other storage models include the Ozone object-oriented database; a variety of relational databases via the Hibernate object-relational mapping tool; and as a DOM representation of the XTM file. Each of these models must implement the TopicMapProvider interface which provides you with the way to access the parsed topic maps held in the storage model or to add new topic maps to the storage model. The TopicMapProvider can be seen as representing your "connection" to the backend storage model, once you have this "connection" object you can:

Creating the TopicMapProvider

In order to ensure consistent and correct initialisation of a back-end storage model, applications should not create instances of the TopicMapProvider interface directly. Instead you should either use the newTopicMapProvider() method of the TopicMapProviderFactory class to create the TopicMapProvider, or initialise a new instance of the TopicMapProviderFactory implementation provided by the backend storage model you are using. Table 3.3, “TopicMapProviderFactory Implemenatations in TM4J” shows the current TopicMapProviderFactory implementations shipped with TM4J.

Table 3.3. TopicMapProviderFactory Implemenatations in TM4J

Back-end

Implementation Class

In-memory

org.tm4j.topicmap.memory.TopicMapProviderFactoryImpl

Ozone OODBMS

org.tm4j.topicmap.ozone.OzoneTopicMapProviderFactoryImpl

Hibernate/JDBC

org.tm4j.topicmap.hibernate.TopicMapProviderFactoryImpl

The method newInstance on the class org.tm4j.topicmap.TopicMapProviderFactory implements a search mechanism which checks the runtime environment for configuration information specifying which concrete implementation should be instantiated. The search procedure is as follows:

  1. If the system property org.tm4j.topicmap.TopicMapProviderFactory is defined, then the value of this property must be the name of the concrete implementation class to be instantiated. If the value is specified but the class named cannot be loaded or, after loading is found not to be a concrete implementation of the TopicMapProviderFactory interface, then a TopicMapProviderFactoryConfigurationError will be raised.

  2. Search the CLASSPATH for the resource META-INF/services/org.tm4j.topicmap.TopicMapProviderFactory. If found, that resource is expected to contain a single line of text denoting the fully-qualified class name of the concrete implementation to be instantiated. If the value is found, but the class named cannot be loaded or, after loading is found not to be a concrete implementation of the TopicMapProviderFactory interface, then a TopicMapProviderFactoryConfigurationError will be raised.

  3. Instantiate an instance of the org.tm4j.topicmap.memory.TopicMapProviderFactoryImpl class.

Once a concrete implementation of the TopicMapProviderFactory class is retrieved, the method newTopicMapProvider() can be used to initialise the TopicMapProvider object which represents your connection to the back-end storage model. You can optionally invoke this method with a Java Properties object which contains parameters for initialisation of the connection to the back-end store.

Each storage model will accept and process a different set of properties. To find out what parameters are expected and processed by this method, check the documentation for the back-end specific implementation of this interface (e.g. org.tm4j.topicmap.memory.TopicMapProviderImpl). You may choose, for example to simply pass the system properties (defined by the user on the command line in to this method, giving the user complete control over the initialisation of the back-end, or you may elect to provide the user with some more convenient UI for providing this configuration information.

The following example shows how a connection to a user-specificed provider can be made. The example makes use of the default search path of the TopicMapProviderFactory class. This means that to specify the backend to be used, the user need only provide a value for the System property org.tm4j.topicmap.TopicMapProviderFactory. Also the System properties are passed into the newTopicMapProvider method, so any configuration parameters can also be specified by the user as System properties.

Example 3.1. Creating a user-specified TopicMapProvider

// The following should appear somewhere at the top of the source file
import org.tm4j.topicmap.*;

...

try
{
  // Ask the TopicMapProviderFactory for a concrete implementation
  TopicMapProviderFactory tmpf = TopicMapProviderFactory.newInstance();

  // Ask the factory for a TopicMapProvider
  // In this case the system properties are used to initialise the TopicMapProvider
  // Your application may get the back-end configuration properties from some other source
  TopicMapProvider provider = tmpf.newTopicMapProvider(System.getProperties());
}
catch(TopicMapProviderFactoryConfigurationError ex)
{
  // Report the exception
  System.err.println("Error in specification of provider factory: " + ex.toString());
}
catch(TopicMapProviderException ex)
{
  // Report the exception
  System.err.println("Error creating back-end connection: " + ex.toString());
}
	  

Example 3.2. Creating a specific backend connection

This example shows how to make use of the backend-specific TopicMapProviderFactory implementations. The only real difference here is in the creation of the TopicMapProviderFactory. This example also shows the use of TopicMapProvider configuration properties read from a file.

// The following should appear somewhere at the top of the source file
import org.tm4j.topicmap.*;
import org.tm4j.topicmap.hibernate.TopicMapProviderFactoryImpl;

...

try
{
  // Create an instance of the Hibernate TopicMapProviderFactory
  TopicMapProviderFactory tmpf = new TopicMapProviderFactoryImpl();

  // Ask the factory for a TopicMapProvider
  // In this case, the configuration properties are read from a file
  Properties providerProps = new Properties();
  providerProps.load(new FileInputStream("provider.properties"));
  
  TopicMapProvider provider = tmpf.newTopicMapProvider(providerProps);
}
catch(TopicMapProviderException ex)
{
  // Report the exception
  System.err.println("Error creating back-end connection: " + ex.toString());
}
catch(IOException ex)
{
  // Thrown if the properties file could not be read
  System.err.println("Failed to find or read provider.properties file: " + ex.toString());
}

Topic Map Provider Configuration Properties

As you have already seen in the previous code example, when creating a new TopicMapProvider instance, you must pass a Properties object to the newTopicMapProvider() method. This Properties object contains the configuration properties for the back-end. Typically these properties include the connection parameters such as the database to connect to and the username and password to use. The following sections detail the configuration properties expected by each of the different back-ends.

Common Configuration Properties

The following properties are recognised by all of the supported back-ends.

Table 3.4. Common TopicMapProviderFactory configuration properties

PropertyDescription
tm4j.noDefaultListenersIf defined with any value, then the default vetoable change listeners will not be applied to topic maps managed by the TopicMapProvider which is created. See the section called “Default Listeners” for more information about the default vetoable change listeners.
tm4j.listener.xWhere x is an integer value starting from 0 and increasing by 1. (e.g. tm4j.listener.0, tm4j.listener.1 etc.). This property specifies the name of a class which implements the interface org.tm4j.topicmap.utils.ConfigurableListener. For each property specified, a new instance of the specified class will be created and that instance will be added as a listener to all topic maps managed by the TopicMapProvider.
tm4j.static.merge

If defined with the value "true", then the topic maps created by this provider will use the static merging model which means that when two topics get merged, they become a single topic that cannot be "unmerged". The default value for this property is "false", which means that the provider will use the dynamic merging model under which topics that get merged are simply recorded as merged, but can be accessed and manipulated separately and if they are ever changed in such a way as to remove the reason for their merging, they will automatically "unmerge".

For more details on the two merging models, please refer to the section called “Topic Merging”.

Note

Currently this property is not yet supported by the Ozone backend implementation. This implementation will always perform dynamic merging (i.e. act as if the value of this property is "false").

tm4j.name.based.merge

This is another boolean property. If specified with the value "true", then topic maps created by this provider will default to implementing name-based merging (i.e. merging two topics when they have the base names with the same string value and the same scope). If specified with the value "false", then name-based merging is disabled. The default value for this property is "true".

tm4j.merge.references

A boolean property. If specified with the value "true", then when a topic map is imported by the provider, the provider will attempt to resolve all references to external topic maps made either by <mergeMap> elements or by <topicRef> elements and merge the referenced maps into the imported map. The default value for this property is "false".

tm4j.make.consistent

A boolean property. If specified with the value "true", then when a topic map is imported by the provider, the provider will apply all of the rules of XTM 1.0 for making the topic map consistent. That is, the provider will apply the rules to fully merge all external referenced topic maps; will ensure that there is only one Topic instance for every merged set of topics; and will eliminate dupliate names, occurrences and associations. For large maps, this processing can be extremely intensive and may take some time to complete, so this property should be used with caution. The default value for this property is "false".

Configuration of the In-Memory Topic Map Provider

Unlike the other providers, the in-memory topic map provider requires no authentication or database connection strings. Instead this backend reads the configuration properties to determine if any topic maps should be preloaded. By preloading specific topic maps, an in-memory backend can use XTM files as a form of persistent storage mechanism.

Note

For this to really be a "persistent" store, the application is responsible for ensuring that the topic maps read at start-up are written back to the XTM files when changes are made.

Each topic map to be preloaded should be specified in a property named tm4j.topicmap.x where x is an incrementing integer starting with 0. The value of this property should be the URL of the topic map file to be loaded. If the value of the property cannot be parsed as a URL, then the system will try to use the property as a local file name and load a file from there.

For each tm4j.topicmap.x property, two additional, optional properties may be specified. The value of the property tm4j.topicmap.base.x (if defined) is parsed as a URL and is used to define the base URL of the topic map loaded for tm4j.topicmap.x. The value of the property tm4j.topicmap.notation.x specifies the notation of the topic map to be parsed. This may be either "XTM" or "LTM". If this property is not specified, then by default a resource with an address ending in ".ltm" or ".txt" will be parsed as an LTM file and all other resources will be parsed as an XTM file.

Example 3.3. In-Memory Topic Map Provider Configuration

Given the following configuration properties:

tm4j.topicmap.1 = /data/topicmaps/mytopicmap.xtm
tm4j.topicmap.2 = /data/topicmaps/geolang.xtm
tm4j.topicmap.base.2 = http://www.example.com/topicmaps/geolang.xtm
tm4j.topicmap.3 = /data/topicmaps/orgchart.lt
tm4j.topicmap.base.3 = http://www.example.com/topicmaps/orgchart.lt
tm4j.topicmap.notation.3 = LTM
tm4j.topicmap.5 = /data/topicmaps/i_am_not_loaded.xtm

	    

the in-memory topic map provider will be created with three topic maps loaded. The first topic map loaded will be parsed as an XTM file from the file location /data/topicmaps/mytopicmap.xtm and will be processed with a base resource address generated from the full file path name (file://data/topicmaps/mytopicmap.xtm). The second topic map will be parsed as an XTM file from the file location /data/topicmaps/geolang.xtm and will be processed with a base resource address of http://www.example.com/topicmaps/geolang.xtm. The third topic map will be parsed as an LTM file from the file location /data/topicmaps/orgchart.lt and will be processed with a base resource address generated from the full file path name. The entry for the topic map /data/topicmaps/i_am_not_loaded.xtm is not processed by the in-memory topic map provider initialisation, because it is specified as the value of the property tm4j.topicmap.5 and there is no tm4j.topicmap.4 entry in the properties list.

Configuration of the Ozone Topic Map Provider

The Ozone topic map provider requires the Ozone database URL to be defined in the property dburl. The format of this database URL depends on whether the connection is to be made to a remote server (a server running in a different JVM) or to a "local" server which essentially causes the Ozone database to be opened and managed by the current JVM.

For a remote database, the Ozone database URL takes the form ozonedb:remote://host-address:host-port. Where host-address and host-port are the address and port number of the remote server to be connected to. The default port used by an Ozone server is port number 3333.

For a local database, the Ozone database URL takes the form ozonedb:local:database-dir. Where database-dir is the path to the top-level directory of the Ozone database to be opened.

Example 3.4. Ozone Topic Map Provider Configuration

The following configuration property set could be used to create a connection to an Ozone database server running on the local machine which holds the topic map data:

dburl=ozonedb:remote://localhost:3333

The following configuration property set could be used to access the topic map data held in a database at the file location /data/ozone/topicmaps without creating a client-server connection:

dburl=ozonedb:local:/data/ozone/topicmaps
Configuration of the Hibernate Topic Map Provider

The Hibernate topic map provider supports connection to a wide range of different relational databases via JDBC. A number of configuration parameters must be passed to this back-end's TopicMapProviderFactory in order to correctly establish the database connection. These are detailed in the following table.

Table 3.5. Configuration Parameters for the Hibernate TopicMapProviderFactory

PropertyDescription
hibernate.dialect The name of a Hibernate class which implements database-specific extensions. See below for a list of supported dialects.
hibernate.connection.driver_class The full Java class name of the JDBC driver to be used to connect to the database.
hibernate.connection.urlThe connection URL for the database.
hibernate.connection.usernameThe username to use for the database connection.
hibernate.connection.passwordThe password to use for the database connection.
hibernate.connection.pool_sizeThe maximum number of pooled connections.
hibernate.statement_cache.size The maximum number of cached PreparedStatements (must be 0 for Interbase)
hibernate.connection.isolationTransaction isolation level (optional)
hibernate.connection.xxxPass JDBC property xxx to DriverManager.getConnection()

Note

In addition to the parameters described above, the Hibernate back-end supports all of the other configuration properties provided by Hibernate, including use of other connection pooling mechansisms. For more information, please refer to the Hibernate documentation.

The following table shows the databases supported by Hibernate and the value to pass for the hibernate.dialect property.

Table 3.6. Databases supported by the Hibernate Back-end

Databasehibernate.dialect value
DB2net.sf.hibernate.dialect.DB2Dialect
MySQLnet.sf.hibernate.dialect.MySQLDialect
SAP DBnet.sf.hibernate.dialect.SAPDBDialect
Oraclenet.sf.hibernate.dialect.OracleDialect
Sybasenet.sf.hibernate.dialect.SybaseDialect
Progressnet.sf.hibernate.dialect.ProgressDialect
Mckoi SQLnet.sf.hibernate.dialect.McKoiDialect
Interbasenet.sf.hibernate.dialect.InterbaseDialect
Pointbasenet.sf.hibernate.dialect.PointbaseDialect
PostgreSQLnet.sf.hibernate.dialect.PostgreSQLDialect
HypersonicSQLnet.sf.hibernate.dialect.HSQLDialect
Microsoft SQL Servernet.sf.hibernate.dialect.SybaseDialect

Note

At the time of writing, only the MySQL database has been tested with the TM4J Hibernate back-end.

Example 3.5. Hibernate Topic Map Provider Configuration

The following properties show a typical simple Hibernate back-end configuration for connecting to a database called tm4j-test managed by a MySQL server process running on the local machine:

hibernate.dialect=net.sf.hibernate.dialect.MySQLDialect
hibernate.connection.driver_class=com.mysql.jdbc.Driver
hibernate.connection.url=jdbc:mysql://localhost/tm4j-test
hibernate.connection.username=topicmap_user
hibernate.connection.password=iluvtms

Getting Topic Maps From The Provider

Every topic map managed by a provider is identified by a Locator object. To retrieve the list of identifiers of topic maps known to the provider, call the method: getTopicMapBaseLocators(). This method returns a collection of Locator objects. To retrieve a specific TopicMap object, use the method: getTopicMap(Locator loc) passing in the base locator of the topic map you wish to retrieve.

Adding Topic Maps To The Provider

The org.tm4j.topicmap.source.TopicMapSource interface is used to represent the combination of data and processing needed to populate a topic map. The standard implementation of this interface provided by the class org.tm4j.topicmap.source.SerializedTopicMapSource represents a serialized file (in XTM or LTM syntax) and the parsing logic for the file. To import a topic map into a provider, simply create the TopicMapSource implementation that represents your data, and pass it to one of the addTopicMap() methods of the TopicMapProvider interface.

You may choose to import a topic map from a TopicMapSource into an existing TopicMap managed by the provider. This can be done by using the addTopicMap() methods that take the existing TopicMap object as a parameter. As the TopicMapSource is processed, it will be merged with the exising TopicMap.

Warning

Currently the Ozone implementation requires that you use only those addTopicMap() methods which take a TopicMapProvider as a parameter. For all other backends, the TopicMapSource can determine the provider, but the current Ozone implementation does not support this, meaning that the TopicMapProvider instance your application is using must be passed to the addTopicMap() method. This will be fixed in a future release.

Creating the SerializedTopicMapSource Instance

To import from a file, you must first create an instance of the SerializedTopicMapSource class. The class implementation provides a large number of constructors allowing you to provide a File, InputStream, Reader or String object as the source data.

In addition there are methods which also take a Locator object which, if specified, defines the base locator of the source - this is a locator relative to which all referneces that are not within the source file itself will be resolved. This can be useful when importing a topic map which uses relative URIs for occurrence references as by specifying the address of the original topic map file as the base locator, all of the occurrence references will be resolved to point to the resources correctly.

Finally, there are constructors which also take a TopicMapBuilder instance as a parameter. If specified, the TopicMapBuilder instance defines the type of source data being parsed. Use an org.tm4j.topicmap.utils.XTMBuilder to parse XTM files and an org.tm4j.topicmap.utils.LTMBuilder to parse LTM files. If you wish to extend TM4J to handle a new topic map syntax, you need only implement the TopicMapBuilder interface and then it too can be used here. If you do not provide a TopicMapBuilder instance as a parameter, but do specifiy a base locator, then the LTMBuilder will be used for those topic maps with a base locator ending with ".ltm" or ".txt" extensions, and an XTMBuilder will be used for all other files. If there is no base locator specified to provide a file name hint, then the XTMBuilder will be used by default.

Creating Topic Maps

The method createTopicMap() can be used to create a new TopicMap object in the back-end storage managed by the TopicMapProvider object. You must pass this method a Locator object which specifies the URI of the topic map resource itself. To get a Locator object you need to use the LocatorFactory provided by the getLocatorFactory method of TopicMapProvider. This is shown in the example below.

Example 3.6. Creating a Topic Map

TopicMapProviderFactory tmpf = new org.tm4j.topicmap.memory.TopicMapProviderFactoryImpl();
TopicMapProvider tmp;
		
//
// Create the TopicMapProvider (see "Creating The TopicMapProvider", above)
//
		
try
{
  Locator base = tmp.getLocatorFactory().createLocator("URI",
                                       "http://mycorp.com/xtm/mymap.xtm");
  TopicMap tm = tmp.createTopicMap(base);
}
catch(LocatorFactoryException ex)
{
  System.out.println("Error creating locator: " + ex.getMessage());
}
catch(TopicMapProviderException ex)
{
  System.out.println("Error creating new topic map: " + ex.getMessage());
}