mulgara - semantic store

skip navigation

SHOW SITE NAV
fixed
fluid
straight

SOFA

SOFA (Simple Ontology Framework API) is a simple but powerful ontology API that allows for inter-operation between several different ontology description formats. Additionally, SOFA is not tied down to a particular storage layer and can easily be integrated into any application that requires an ontology manager. Due to the structure of the API, virtually any Java object can be used to model ontology datatype nodes, allowing the model to be as complex or simple as necessary. Features of the API include:

Visit the SOFA documentation Web site for additional information such as a Getting Started guide and an API reference guide.

 

Prerequisites

The following prerequisites should be met before this SOFA tutorial can be compiled and run:

 

Getting Started

Before creating an ontology, you need to consider its purpose, even if you are just extending an existing one. Questions you need to ask include:

For the purpose of this tutorial we will extend a Camera ontology to include a mobile phone camera concept, and a new relation describing the standard of the phone (GSM or CDMA).

Note - We are using a slightly modified version of the camera.owl ontology named kamera.owl. This is because of a bug in the way that SOFA handles namespaces, which will be fixed in a later version of Mulgara.

The code for this example, plus the kamera.owl, is in the Resources directory of the Mulgara directory.

The SOFA API documentation, including the Mulgara classes that provide persistence storage for SOFA objects, is located in <Mulgara install>/docs/api/sofa.

 

Creating the Ontology

The following class is a simple class that creates and displays data about the CameraPhone ontology, which is an extension of the Camera ontology. This code is available as CameraPhoneOntologyApp.java in the the Resources directory of your Mulgara installation.

package org.mulgara.sofa.example;

import java.util.*;
import java.util.Collection;
import java.net.*;
import java.io.*;

import net.java.dev.sofa.*;
import net.java.dev.sofa.impl.*;
import net.java.dev.sofa.model.*;
import org.mulgara.server.Session;
import org.mulgara.server.SessionFactory;
import org.mulgara.server.driver.SessionFactoryFinder;
import org.mulgara.client.jrdf.AbstractGraphFactory;
import org.mulgara.store.jrdf.JRDFGraph;
import org.mulgara.store.xa.XADatabaseImpl;
import org.mulgara.store.*;
import org.mulgara.sofa.*;
import org.mulgara.sofa.serialize.owl.*;
import org.jrdf.graph.*;

public class CameraPhoneOntologyApp {

public static void main(String[] args) {

// Create a new Camera Ontology Application
CameraPhoneOntologyApp app = new CameraPhoneOntologyApp();

// SOFA Ontology object
Ontology ontology = null;

try {

System.out.println("Creating empty Ontology");
String ontologyURI = "http://www.xfront.com/owl/ontologies/camera/";

// Create in memory based Ontology
ontology = OntoConnector.getInstance().createOntology(ontologyURI);

// uncomment this to..
// Create Ontology on the client (communicates with Mulgara server)
//ontology = OntoConnector.getInstance().createOntology(
// app.createClientOntologyModel(), ontologyURI);

// uncomment this to..
// Create Ontology on the server (same JVM)
//ontology = OntoConnector.getInstance().createOntology(
// app.createServerOntologyModel(), ontologyURI);

// Populate the ontology data
app.loadCameraOntology(ontology);

} catch (Exception exception) {

System.out
.println("Failed to create the ontolgy due to the following exception:");
exception.printStackTrace();
}

try {

// Create the ontology data
app.populateOntology(ontology);
} catch (Exception exception) {

System.out
.println("Failed to populate the ontolgy due to the following exception:");
exception.printStackTrace();
}
}


/**
* Loads the example Camera ontology into the supplied ontology object.
*
* @param onto
* @throws Exception
*/
public void loadCameraOntology(Ontology onto) throws Exception {

System.out.println("Loading Camera Ontology");
OWLReader.getReader().read(onto, "file:Resources/kamera.owl");

System.out.println("Loaded Ontology");

// get the Digital Camera Thing, a Concept is like an OWL Class,
// Thing is more like an Instance
Concept digiCam = onto.getConcept("Digital");

// Create a new type of camera: camera phone

// create phone first
Concept phone = onto.createConcept("Phone");
// give it a property/relation of GSM or CDMA
Relation standard = onto.createRelation("standard");
Set standards = new HashSet();
standards.add("GSM");
standards.add("CDMA");
phone.setRestrictionOn(standard, standards, 1, 2); // 1=minCard, 2=maxCard

// make phone a sub class of purchaseable item
Concept purchaseableItem = onto.getConcept("PurchaseableItem");
phone.addSuperConcept(purchaseableItem);

// create camera phone
Concept cameraPhone = onto.createConcept("CameraPhone");
cameraPhone.addSuperConcept(phone);
cameraPhone.addSuperConcept(digiCam);

// Show super classes
System.out.println("SUPER CLASSES");
Concept superConcept = null;
Collection superConcepts = cameraPhone.getSuperConcepts(true);

// test for N superclasses
System.out.println("Number of superConcepts found: " + superConcepts.size());

// test a phone is our superclass
System.out.println("Found phone concept in list of super concepts: "
+ superConcepts.contains(phone));

for (Iterator sc = superConcepts.iterator(); sc.hasNext();) {
superConcept = (Concept) sc.next();

System.out.println(superConcept.getId());
}

// show properties, including super properties 'true'
System.out.println("PROPERTIES");

for (Iterator ri = cameraPhone.definedRelations(true); ri.hasNext();) {
Relation relation = (Relation) ri.next();

System.out.println(relation.getId());
}

// test camera phones have 'standard'
System.out.println("CameraPhone contains the 'standard' relation: "
+ cameraPhone.hasDefinedRelation(standard, true));

// Write new ontology to Standard out
OWLWriter.getWriter().write(onto, System.out);
}

// Misc supporting methods removed
}

An analysis of the class is as follows:

import java.util.*;
import java.util.Collection;
import java.net.*;
import java.io.*;

import net.java.dev.sofa.*;
import net.java.dev.sofa.impl.*;
import net.java.dev.sofa.model.*;
import org.mulgara.server.Session;
import org.mulgara.server.SessionFactory;
import org.mulgara.server.driver.SessionFactoryFinder;
import org.mulgara.client.jrdf.AbstractGraphFactory;
import org.mulgara.store.jrdf.JRDFGraph;
import org.mulgara.store.xa.XADatabaseImpl;
import org.mulgara.store.*;
import org.mulgara.sofa.*;
import org.mulgara.sofa.serialize.owl.*;
import org.jrdf.graph.*;

In order to use the SOFA API, the net.java.dev.sofa packages need to be imported, along with the org.mulgara.sofa.serialize.owl packages to allow for reading OWL files.

System.out.println("Creating empty Ontology");
String ontologyURI = "http://www.xfront.com/owl/ontologies/camera/";

// Create in memory based Ontology
ontology = OntoConnector.getInstance().createOntology(ontologyURI);

Ontologies created or loaded during a session are maintained in a pool by a singleton instance of the OntoConnector class and are identified by the namespace given during construction. Namespace identifiers should be unique within a session and in valid URI format. Ontologies created in this way are memory based, which is fine for ontology viewing. SOFA provides another ontology creation method, createOntology(OntologyModel, String), that produces an ontology that is backed by the given OntologyModel, which can be a Mulgara model. See the Changing the Backing of an Ontology section for more information.

System.out.println("Loading Camera Ontology");
OWLReader.getReader().read(onto,"file:Resources/kamera.owl");

Often an ontology is specified in a file of some format, so there are classes available that will read them in and store the data in the given ontology. In this case, the OWL file, kamera.owl, contains the camera ontology and we want to read it into the ontology we created previously. We use the org.mulgara.sofa.serialize.owl.OWLReader class to populate our ontology model. Note that this OWLReader is an updated version of the one built in to SOFA (net.java.dev.sofa.serialize.owl.OWLReader) which uses an outdated version of Jena to parse RDF.

// get the Digital Camera Thing, a Concept is like an OWL Class,
// Thing is more like an Instance
Concept digiCam = onto.getConcept("Digital");

After the ontology is loaded, all concepts, relations and instances are instantly available for browsing and manipulating. To retrieve any of these objects, use the getConcept(String), getRelation(String) and getThing(String) (Thing is another way of referring to instances) methods in the Ontology class. All objects in the ontology have unique names and these are used to retrieve the values as objects.

// create phone first
Concept phone = onto.createConcept("Phone");

Since we are adding a new concept and relation to the ontology, we need to create the concept we want to add by invoking the createConcept(String) method found in the Ontology class. The string passed in is used as the identifier for the concept when the ontology is queried. There are similar methods available for relations and instances.

// give it a property/relation of GSM or CDMA
Relation standard = onto.createRelation("standard");
Set standards = new HashSet();
standards.add("GSM");
standards.add("CDMA");
phone.setRestrictionOn(standard, standards, 1,2); // 1=minCard, 2=maxCard

Since we are creating our phone concept, we should add the standard relation to it as well, which can be one or both of two possibilities: GSM or CDMA. As mentioned in the previous step, there are similar methods to the createConcept(String) method available that allows us to create a new relation, giving it a unique identifier. The two aforementioned standards are strict and have no other possibilities, so we need to create a set of options that the relation can select from when assigning values using the java.util.Collection class or subclasses. When assigning the value of the relation we can have one or both of the values so the cardinality needs to be set for the relation using the setRestrictionOn(Relation, Set, int, int) method available in the Concept class. This adds the relation to the concept along with the set of values and the minimum and maximum cardinalities, respectively. If the restriction does not contain an enumeration of values and is only required to restrict the number of relation instances of that type that can exist for a concept, then the setRestrictionOn(Relation, int, int) method should be used instead.

// make phone a sub class of purchaseable item
Concept purchaseableItem = onto.getConcept("PurchaseableItem");
phone.addSuperConcept(purchaseableItem);

// create camera phone
Concept cameraPhone = onto.createConcept("CameraPhone");
cameraPhone.addSuperConcept(phone);
cameraPhone.addSuperConcept(digiCam);

Quite often a concept is a subconcept of another type, like a phone is a subconcept of a purchasable item. To add a concept as a super concept of another, use the addSuperConcept(Concept) method found in the Concept class. Alternately, there exists a method called addSubConcept(Concept) which does the opposite and adds the given concept as a subconcept. A concept can be added as a subconcept to different superconcepts, which is useful because we want a 'CameraPhone' concept to be both a camera and a phone subconcept.

// Show super classes
System.out.println("SUPER CLASSES");
Concept superConcept = null;
Collection superConcepts = cameraPhone.getSuperConcepts(true);

// test for N superclasses
System.out.println("Number of superConcepts found: " + superConcepts.size());

// test a phone is our superclasses
System.out.println("Found phone concept in list of super concepts: "
+ superConcepts.contains(phone));

for (Iterator sc = superConcepts.iterator(); sc.hasNext();) {
  superConcept = (Concept) sc.next();

System.out.println(superConcept.getId());
}

At some point in working with an ontology you might want to discover the superconcepts or subconcepts of a particular concept. This can be done using the getSuperConcepts(boolean) and getSubConcepts(boolean) methods. A boolean of false retrieves only the direct superconcepts or subconcepts while a boolean of true retrieves all the indirect ones. Retrieving the indirect concepts is also called inferencing. In the CameraPhone example, you expect the list of superconcepts to be Phone, Camera, Digital and PurchaseableItem. From this you can determine that the phone is both a digital camera and a phone concept (direct superconcepts) as well as being a camera and a purchaseable item concept (indirect superconcepts).

// show properties, including super properties 'true'
System.out.println("PROPERTIES");

for (Iterator ri = cameraPhone.definedRelations(true); ri.hasNext();) {
Relation relation = (Relation) ri.next();

System.out.println(relation.getId());
}

// test camera phones have 'standard'
System.out.println("CameraPhone contains the 'standard' relation: "
+ cameraPhone.hasDefinedRelation(standard, true));

Just as you can navigate the direct and indirect super and sub concepts, you can navigate the indirect and direct domains to find out the values of all relations for a particular concept. To retrieve the relations, the definedRelations(boolean) is available in the Concept class. The boolean value works in much the same way as for super and subconcept listing where false lists only the relations for the concept and true lists the concept relation and any superconcept relations. Therefore, you expect to find the standard relation in a camera phone inherited from the phone concept.

// Write new ontology to Standard out
OWLWriter.getWriter().write(onto, System.out);

Once the ontology is created or modified, you might want to save it to disk for use in another application. Use the write(Ontology, OutputStream) method available in the OWLWriter class.

 

Changing the Backing of an Ontology

The standard memory model of SOFA's ontology object is acceptable for tasks such as viewing small ontologies. However, data stored within an ontology can get very large very quickly so it becomes less feasible to use memory as a storage facility. Also you might change the ontology and then want to save these changes.

You can use Mulgara to store the data as well as to provide a structured querying interface, allowing you to search the ontology for particular pieces of information and infer relationships. In order to do this you need to construct an OntologyModel object that plugs into the Mulgara framework and uses that as the backing instead of memory. To create a Mulgara backed ontology:

  1. Comment out the memory based ontology code:

    // Create in memory based Ontology
    ontology = OntoConnector.getInstance().createOntology(ontologyURI);

  2. Then uncomment either the ontology on the client or server code:

    // uncomment this to..
    // Create Ontology on the client (communicates with Mulgara server)
    //ontology = OntoConnector.getInstance().createOntology(
    // app.createClientOntologyModel(), ontologyURI);

    Note - If you use the client based ontology, the Mulgara server must be running.

    // uncomment this to..
    // Create Ontology on the server (same JVM)
    //ontology = OntoConnector.getInstance().createOntology(
    // app.createServerOntologyModel(), ontologyURI);

    Note - If you use the server based ontology, you must shutdown Mulgara, and include the Mulgara server jar in your Java class path. See the Compiling and Running the Example section for more information.

An analysis of the code is as follows:

ontology = OntoConnector.getInstance().createOntology(app.createServerOntologyModel(), ontologyURI);

The code section before this is concerned with initializing a Mulgara backed JRDF graph and using this graph, you can use an implementation of the ontology model to create our ontology. Inside the OntologyJRDFModel implementation are the hooks to direct all ontology manipulations towards the Mulgara backed JRDF graph. It is important to note that the constructor for the ontology is slightly different in that the model to use is passed in also. Omitting this parameter defaults the backing to the memory based model.

 

Populating the Ontology

Once an ontology is written, data is required to give it meaning. To achieve this use the populateOntology(Ontology ontology) method, which looks like the following:

public void populateOntology(Ontology ontology) throws Exception {

// Retrieve the CameraPhone concept
Concept cameraPhone = ontology.getConcept("CameraPhone");

// Create a CameraPhone instance called 'Nokia'
Thing mobile = cameraPhone.createInstance("Nokia");

// Retrieve the Digital concept
Concept digital = ontology.getConcept("Digital");

// Create an instance of the digital camera called 'Olympus'
Thing camera = digital.createInstance("Olympus");

// Retrieve the 'standard' relation
Relation standardRelation = ontology.getRelation("standard");

// Retrieve the 'lens' relation
Relation lensRelation = ontology.getRelation("lens");

// Set the lens and standard type for the mobile phone
mobile.add(standardRelation, "CDMA");
mobile.add(lensRelation, "CompanyX");

// Set the lens for the camera
camera.add(lensRelation, "Carl Zeiss");

System.out.println("Listing standards for mobile phone:");

// Iterate through the standards of the phone
for (Iterator iterator = mobile.list(standardRelation).iterator();
iterator.hasNext(); ) {

// Print the next standard
System.out.println(iterator.next());
}

System.out.println("Listing lenses for mobile phone:");

// Iterate through the lenses of the phone
for (Iterator iterator = mobile.list(lensRelation).iterator(); iterator.hasNext(); ) {

// Print the next lens
System.out.println(iterator.next());
}

System.out.println("Listing lenses for camera:");

// Iterate through the lenses of the camera
for (Iterator iterator = camera.list(lensRelation).iterator(); iterator.hasNext(); ) {

// Print the next lens
System.out.println(iterator.next());
}

System.out.println("All Things:");

// Iterate through all 'Things' in the ontology framework
for (Iterator iterator = ontology.things(); iterator.hasNext(); ) {

System.out.println("\t" + ((Thing) iterator.next()).getId());
}
}

An analysis of the population method is as follows:

// Retrieve the CameraPhone concept
Concept cameraPhone = ontology.getConcept("CameraPhone");

// Create a CameraPhone instance called 'Nokia'
Thing mobile = cameraPhone.createInstance("Nokia");

// Retrieve the Digital concept
Concept digital = ontology.getConcept("Digital");

// Create an instance of the digital camera called 'Olympus'
Thing camera = digital.createInstance("Olympus");

Adding and retrieving concepts in an ontology are not really useful on their own unless you can create objects with meaningful data based on the concept's structure. Every object and datatype within an ontology can be traced down to a common base object, Thing, and this is what instances are created as. To create a new instance of a concept, invoke the createInstance(String) method, passing in the value of the name of the instance as a parameter. The resulting object represents a new instance of that concept with the data set as the string. Since we created a CameraPhone ontology, let's create an instance of it to represent a Nokia phone with a camera, and an Olympus digital camera.

// Retrieve the 'standard' relation
Relation standardRelation = ontology.getRelation("standard");

// Retrieve the 'lens' relation
Relation lensRelation = ontology.getRelation("lens");

// Set the lens and standard type for the mobile phone
mobile.add(standardRelation, "CDMA");
mobile.add(lensRelation, "CompanyX");

// Set the lens for the camera
camera.add(lensRelation, "Carl Zeiss");

Concepts are rarely useful on their own as ontologies are used to describe the concepts using relations. So the next step is to add some data about various relations on the concepts. There are several ways to add data to an instance of a concept and these are the set(Relation, String), add(Relation, String), setAll(Relation, String[]), and addAll(Relation, String[]) methods. These associate one or more string values with the given relation for the concept. The add methods are for adding a new relation value or values to the current list while the set methods replace the current value with the new value or values. Be sure to keep this in mind when creating new relations else data may be lost. As seen during the creation method (see the Creating the Ontology section), a subconcept inherits the relations of its superconcepts so we are able to set not only the standard, but the lens type and other information on our camera phone. Remember that the phone can only have a standard value of either one of or both of CDMA or GSM.

System.out.println("Listing standards for mobile phone:");

// Iterate through the standards of the phone
for (Iterator iterator = mobile.list(standardRelation).iterator();
iterator.hasNext(); ) {

// Print the next standard
System.out.println(iterator.next());
}

System.out.println("Listing lenses for mobile phone:");

// Iterate through the lenses of the phone
for (Iterator iterator = mobile.list(lensRelation).iterator(); iterator.hasNext(); ) {

// Print the next lens
System.out.println(iterator.next());
}

System.out.println("Listing lenses for camera:");

// Iterate through the lenses of the camera
for (Iterator iterator = camera.list(lensRelation).iterator(); iterator.hasNext(); ) {

// Print the next lens
System.out.println(iterator.next());
}

After adding data, you will probably want to retrieve it again for a search operation or some form of query. To retrieve the list of values for a particular relation of a concept, use the list(Relation) method, which returns a java.util.Collection object which can be iterated through. The above code lists the values we inserted previously for each relation. Again, the indirect relations lens and standard are picked up as a relation of a superconcept for CameraPhone concepts.

System.out.println("All Things:");

// Iterate through all 'Things' in the ontology framework
for (Iterator iterator = ontology.things(); iterator.hasNext(); ) {

System.out.println("\t" + ((Thing) iterator.next()).getId());
}

Now that some concepts are added to the ontology you can view that they are indeed part of it by using the things() method, which return an iterator object. This iterates over the entire collection of objects in the ontology, concepts, relations, instances and other content alike. Similarly, there are concepts(), and relations() methods for iteration over the concepts and relations of the ontology respectively. Unlike the things() method, they do not return instances and values of concepts and relations. That is, you see CameraPhone, but not Nokia in the concepts iterator.

 

Compiling and Running the Example

To run the example:

  1. Retrieve it from the Resources directory of your Mulgara installation and place it in a directory hierarchy like the one shown below:

    example/sofa/java/org/mulgara/sofa/example/CameraPhoneOntologyApp.java

  2. Compile it from the directory in which you created the above hierarchy:

    javac -classpath <Mulgara directory>/Resources/driver-2.1.jar
    example/sofa/java/org/mulgara/sofa/example/CameraPhoneOntologyApp.java

  3. Depending on the ontology implementation you are running (that is, a memory, client or server based ontology), there are different requirements for a Mulgara server:
    • If you are using the memory based ontology:

      // Create in memory based Ontology
      ontology = OntoConnector.getInstance().createOntology(ontologyURI);

    • If you are using the client based ontology:

      // Create Ontology on the client (communicates with Mulgara server)
      ontology = OntoConnector.getInstance().createOntology(
      app.createClientOntologyModel(), ontologyURI);

      Note - If you use the client based ontology, you must have a running Mulgara server as the example tries to connect it. See the Starting and Stopping Mulgara section for more information.

    • If you are using the server based ontology:

      // Create Ontology on the server (same JVM)
      ontology = OntoConnector.getInstance().createOntology(
      app.createServerOntologyModel(), ontologyURI);

      Note - If you use the server based ontology, you must shutdown the Mulgara server, and include the Mulgara server jar in your Java class path. See the next point for more information.

  4. Depending on the ontology implementation you are running (that is, a memory, client or server based ontology), run the example application from the Mulgara installation directory, with one of the following commands:
    • If you are using the memory or client based ontology:

      java -classpath <Mulgara directory>/Resources/driver-2.1.jar:example/sofa/java
      org.mulgara.sofa.example.CameraPhoneOntologyApp

    • If you are using the server based ontology:

      java -classpath <Mulgara directory>/Resources/driver-1.1.0.jar:example/sofa/java:<Mulgara directory>/mulgara-1.0.0.jar
      org.mulgara.sofa.example.CameraPhoneOntologyApp

 

Integration

Integration depends on which of the three ontology models you decide to use. The following table outlines the advantages and disadvantages of each ontology model.

In Memory Ontology Model

Advantages

Disadvantages

Very fast

Not persisted to disk

Small footprint

Not transaction safe

 

Not scalable

 

 

Embedded Server Ontology Model

Advantages

Disadvantages

Simple deployment

Not suitable for client/server applications

Persistent datastore

Slower than in memory ontology

Full Mulgara server advantages

 

Scalable

 

 

 

Client/Server Ontology Model

Advantages

Disadvantages

Client/server architecture means client can be on different machine to server

Slower than memory and embedded ontology model

Persistent datastore

 

Full Mulgara server advantages

 

Scalable

 

In most cases simply including the driver.jar when compiling and running is sufficient for using the SOFA API. When using the client server ontology model a Mulgara server must be running.

Valid XHTML 1.0 TransitionalValid CSS 3.0!