Preface

Aranuka is a framework which allows user to persist information stored in Java objects into a topic map without the need to implement the necessary calls to the used Topic Maps engine. The mapping between the Java model and the topic map is defined directly in the related model classes via Java-Annotation. The Annotations hold all information needed by Aranuka which makes it very easy to use Aranuka with an existing model or to change the model or mapping.

This documentation describes the general concept and architecture of Aranuka as well as shows how to use it and provides tutorials.

Aranuka

1. Architecture

Aranuka maps Java POJOs to topics of a topic map. It provides a set of annotations and a facility to use and configure Aranuka. It does not provide its own Topic Maps engine. Instead it uses connectors which use a specific Topic Maps engine. Currently Aranuka provides three conntectors:

images/arch.svg

To use Aranuka it is necessary to use one of the connectors.

2. Aranuka Annotations

For every construct an annotation exists. The following sections describe the annotations and their properties.

2.1. Topic

Use the Topic annotation to create a mapping to a topic type. Every class annotated with Topic represents a topic type in the topic map. This annotation has the following property:

EXAMPLE: The base locator "http://test.de" is set. A topic type for a class de.test.Person would get the automatically generated subject identifier "class:de/test/Person"

2.2. Id

The annotation is used for class properties which tells_Aranuka_, that the value of this property is a topic identifier. The type of the property must be either a String or an integer value. If it is the first the value has to be a valid IRI. In case of the latter an URI is generated using the subject identifier of the topic type and adds the value of the number.

This annotation has the following property:

The type of the property can also be a collection of String or Integer. This indicated, that the topic has more than one identifier of this type. It is also possible to have more than one property with an identifier annotation. If this is the case they must not have the same type.

2.3. Name

Use this annotation to specify that a value of a field of type String or a collection of String represents a or many names of a specific type. This annotation has the following property:

2.4. Occurrence

Use this annotation to specify that a value of a field is persisted as occurrence. The field must have either have a primitive type or java.util.Date. The datatype of the occurrence is based on the fields type. This annotation has the following property:

2.5. Association

The Association annotation is the most complex annotation. It us used to specify an association between the containing instance and the instance which is the value of the field. The type of the field must be either boolean which indicates a unary association or another class annotated with Topic or AssociationContainer. This annotation has the following properties:

Their are three types of associations:

If multiple instances are needed it is possible to use Collections of the types. For every instance in the collection an association will be created.

2.6. AssociationContainer

An association container is a class which field represent players in a n-nary association. Every player has to be annotated with Role.

The AssociationContainer is used to annotate a class like Topic.

2.7. Role

The Role annotation is used to specify the role type of a value of the field in an AssociationContainer. This annotation has the following property:

3. Using Aranuka

3.1. Configuration

After annotating the domain model Aranuke needs some configuration parameters to create a session. This configuration is done with an instance of de.topicmapslab.aranuka.Configuration.

Configuration conf = new Configuration();

After instantiating the configuration it is necessary to add all annotated classes to the configuration, like:

conf.addClass(Person.class);

The next step is to set some properties for the topic map. Configuration properties are:

Backends which support the memory backend need the property:

The database backends need informations about the connection. The properties are:

Note
Right now MaJorToM supports only posgresql.
Note
Ontopia is able to connect to much more DBMS. It is possible to configure the engine using the its own properties. If the property "net.ontopia.topicmaps.impl.rdbms.Database" is found the MaJorToM properties will be ignored. For more information refer to the Ontopia docs.
conf.setProperty(IProperties.CONNECTOR_CLASS, "de.topicmapslab.aranuka.tinytim.connectors.TinyTiMConnector");
conf.setProperty(IProperties.BASE_LOCATOR, "http://docs.aranuka.de/documentation/");
Note
It is possible to load the property files from a file and set them at once using the method: conf.setProperties(properties)

If qnames were used to specify the identifiers for the types the qname identifier, called prefix, and the URI should be set to the configuration. It is also possible to specify new prefixes which will be used for identifiers of the prefixes. Add a prefix definition like:

conf.addPrefix("tp", "http://test.de/");
Note
The base locator is used to resolve the prefix base_locator. If another prefix is desired it is possible to overwrite the prefix by adding a new prefix with the base locator URI.

The memory connectors of Aranuka store their topic maps into a file. This can be either XTM or CTM. The filename is also set in the configuration:

conf.setProperty(IProperties.FILENAME, "/path/to/file.ctm");
Note
The serializer is chosen based on the file suffix. If it’s ctm the ctm serializer is used. If it’s xtm the xtm2.0 serializer is used.

It is possible to change the filename property after the session is created. This means the topic map is stored in the new file overwrting any existent file.

3.1.1. Naming the types

Every type used in the annotations only has a subject identifier. For a easier understanding of the type, the user might want to add names to the types. This is possible via the configuration.

conf.addName(id, name)

Alternatively create a map and fill it using the subject identifier of the type as key and its name as value. After filling the map add it to the configuration with:

conf.setNameMap(nameMap)
Note
If you use setNameMap all names put in the configuration via addName will be overwritten.

3.2. Session

After the configuration is finished a session can be retrieved from the configuration instance:

conf.getSession(false);

The parameter asks if the classes annotations should be parsed before creating the session (false) or lazy when the class is used the first time (true).

Note
If the filename of the configuration points to an existing topic map it will be loaded and the topics.

3.2.1. Persist Topics

To persist an instance of an annotated class the session class provides the method persist. Every instance must have a property annotated with Id. Persist checks if a topic identified by the value of this property exists and updates the other properties and association according to the values of the instance. If no topic exists a new one is created.

3.2.2. Retrieve Topics

Of course it is possible to get instances from the topic map. The session provides two ways to retrieve an instance from the topic map. The first retrieves all instances from a specific type. The method:

getAll(Class<?> clazz)

The parameter of this method is the class object of the annotated class. This method returns a set of instances which represent the topics of that type.

The other way is to retrieve a instance by identifier. The session provides three methods one for each kind of Topic Maps identifier and returns either null if no topic with the given identifier exists or the instance representing the topic.

getByItemIdentifier(String)
getBySubjectIdentifier(String)
getBySubjectLocator(String)
Note
The given identifier must be resolved. QNames are not allowed at the moment.

With version 1.1.0 you can execute TMQL queries to retrieve instances. Use the method

getObjectsByQuery(String tmqlQuery)
Note
The result must be one or a list of topics which are mapped to an object. You can find the TMQL documentation at http://docs.topicmapslab.de/tmql4j/ .

If you want to know how many topics of a specific type exist in the topic map use the method

count(Class<?>)

3.2.3. Persist the Topic Map

The session provides the following method:

flushTopicMap()

The memory connectors serialize the file according to the filename set in the configuration.

3.2.4. Clear the Topic Map

With version 1.0.1 Aranuka provides a method to clear the topic map inside the session. This can come handy especially in unit tests. To clear the method call:

clearTopicMap()

4. Example

In the following sections an Aranuka application will be developed. The task of the application is to store Persons and their addresses into a Topic Map.

Note
This tutorial presumes a project which provides the aranuka-runtime library, an aranuka connector with its dependencies and an implementation of the slf4j-api library (version 1.6.1). You can find an example in the aranuka code repository.

4.1. Maven

To manage the dependencies of Aranuka it is advised to use maven, or an maven repository using dependency mangement tool (like ivy). In this example Maven2 is used. To add the Aranuka dependency to the example project it is necessary to add the topic maps lab maven repository to the pom.xml.

The dependency entry for Aranuka depends on the connector to use. In this tutorial the tinyTiM connector is used.

The pom for the example looks like:

<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">
        <parent>
                <artifactId>aranuka</artifactId>
                <groupId>de.topicmapslab</groupId>
                <version>1.0.1-SNAPSHOT</version>
        </parent>
  <modelVersion>4.0.0</modelVersion>
  <groupId>de.topicmapslab.aranuka</groupId>
  <artifactId>aranuka-example</artifactId>
  <version>${parent.project.version}</version>
  <build>
                <plugins>
                        <plugin>
                                <groupId>org.apache.maven.plugins</groupId>
                                <artifactId>maven-compiler-plugin</artifactId>
                                <version>2.0.2</version>
                                <configuration>
                                        <source>1.6</source>
                                        <target>1.6</target>
                                </configuration>
                        </plugin>
                </plugins>
        </build>
  <dependencies>
    <dependency>
      <groupId>de.topicmapslab.aranuka</groupId>
      <artifactId>aranuka-connector-tinytim</artifactId>
      <version>1.0.1</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>de.topicmapslab.aranuka</groupId>
      <artifactId>aranuka-runtime</artifactId>
      <version>1.0.1</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>1.6.1</version>
        <type>jar</type>
        <scope>compile</scope>
    </dependency>
  </dependencies>
</project>

4.2. The Model

The first thing to do is the specification and annotation of the model. Every person has an identifier, first name, last name and an address.

An address contains an id, the street name, house number, zip code and the name of the country.

Note
In this example the country is just a property of the address. A better Topic Maps modelling would be to create another class for the country and connects it via an association.

The class for the person could look like the following:

@Topic(subject_identifier="ex:person")
public class Person {

        @Id(type=IdType.SUBJECT_IDENTIFIER)
        private String id;

        @Name(type="ex:firstname")
        private String firstName;

        @Name(type="ex:lastname")
        private String lastName;

        @Association(type="ex:lives",
                played_role="ex:habitant",
                other_role="ex:place",
                persistOnCascade=true)
        private Address address;

        public String getId() {
                return id;
        }

        public void setId(String id) {
                this.id = id;
        }

        public String getFirstName() {
                return firstName;
        }

        public void setFirstName(String firstName) {
                this.firstName = firstName;
        }

        public String getLastName() {
                return lastName;
        }

        public void setLastName(String lastName) {
                this.lastName = lastName;
        }

        public Address getAddress() {
                return address;
        }

        public void setAddress(Address address) {
                this.address = address;
        }
}

The most complex annotation is Association. The attribute persistOnCascade means that if true the instance is persisted when the person is persisted. If persistOnCascade is false only a stub with the identifier of the topic is persisted.

The following is the implementation of Address:

@Topic(subject_identifier="ex:address")
public class Address {

        @Id(type=IdType.ITEM_IDENTIFIER)
        private int id;

        @Occurrence(type="ex:zipcode")
        private String zipCode;

        @Occurrence(type="ex:city")
        private String city;

        @Occurrence(type="ex:street")
        private String street;

        @Occurrence(type="ex:number")
        private String number;

        public int getId() {
                return id;
        }

        public void setId(int id) {
                this.id = id;
        }

        public String getZipCode() {
                return zipCode;
        }

        public void setZipCode(String zipCode) {
                this.zipCode = zipCode;
        }

        public String getCity() {
                return city;
        }

        public void setCity(String city) {
                this.city = city;
        }

        public String getStreet() {
                return street;
        }

        public void setStreet(String street) {
                this.street = street;
        }

        public String getNumber() {
                return number;
        }

        public void setNumber(String number) {
                this.number = number;
        }
}

The classes are simple classes with a default constructor and mutator and accessor methods for every property. In addition the properties and the class itself are annotated with the annotations of Aranuka. The Topic annotation specifies that the class should be mapped to a topic with the given identifier. This topic is used as topic type for every instance of the class. The identifier in this example uses qnames as subject identifier. This produces identifier, which are easier to understand and memorise. Alternatively the subject identifier can be any IRI. If qnames are used, the qnames identifier, called prefix must be specified. More to that later. The types specified in the annotation also have no other charactersitic. In the configuration it is possible to add a name to the type, which looks better in any Topic Maps browser.

4.3. The Configuration

The next step is to generate a small application which uses the model.

Create a class Test with a method main. The first step is the instantiation of the Configuration class:

Configuration conf = new Configuration();

// set classname of connector
conf.setProperty(IProperties.CONNECTOR_CLASS, MaJorToMEngineConnector.class.getName());

// set backend - not needed for tinytimn but necessary for majortom and ontopia
conf.setProperty(IProperties.BACKEND, "memory");

The next step is to add the annotated classes which should be persisted:

conf.addClass(Person.class);
conf.addClass(Address.class);

Now the configuration knows which classes to parse.

To create a Topic Map the engine needs an IRI which identifies the topic map. This IRI is called the base locator. The base locator is set via properties of the connection.

conf.setProperty(IProperties.BASE_LOCATOR, "http://example.aranuka.org/");

Last but not least is a file name necessary where to store the serialised topic map. The type of serialisation is based on the filenames suffix.

conf.setProperty(IProperties.FILENAME, "/tmp/test.ctm");
Note
If the file already exists it will be loaded into the topic map.

The annotations use QNames and therefore it is necessary to set a prefix:

conf.addPrefix("ex", "http://aranuka.example.org/");

To set some names for the specified types do:

conf.addName("ex:person", "Person");
conf.addName("ex:firstname", "First Name");
conf.addName("ex:lastname", "Surname");
conf.addName("ex:lives", "lives");
conf.addName("ex:habitant", "Habitant");
conf.addName("ex:place", "Place");

conf.addName("ex:address", "Address");
conf.addName("ex:zipcode", "Zip Code");
conf.addName("ex:street", "Street");
conf.addName("ex:city", "City");
conf.addName("ex:number", "House Number");

The first parameter of addName is the specified identifier for the type. The second parameter is the name for the topic. This name has the default name type specified by the TMDM.

Note
It is advised to set names for the types, but it is not mandatory.

4.4. The session

The session is used to persist instances into the topic map, flush the topic map and to retrieve instances from the topic map.

First get a session from the configuration:

Session session = conf.getSession(false);

now we can retrieve all instances of a specific type with:

Set<Object> persons = session.getAll(Person.class);

// persons.size() == 0
System.out.println(persons.size());

To add a new person create a person class with an address:

Address a = new Address();
a.setId(1);
a.setZipCode("00815");
a.setCity("Example City");
a.setStreet("Example Street");
a.setNumber("1");

Person p = new Person();
p.setId("ex:max");
p.setFirstName("Max");
p.setLastName("Powers");
p.setAddress(a);

return p;

Again a QName for the id is used. This QName is resolved when persisting the topic. If the topic is retrieved Aranuka recognises the prefix and sets the identifier to the short version. Inside the topic map the dientifier is resolved. After that the person is persisted with:

Person p = createPerson();
session.persist(p);

It is not necessary to persist the attribute instance because of the persistOnCascade annotation property.

To serialise the topic map call:

session.flushTopicMap();

Run the application and you should find the ctm file in the tmp directory which has the following content:

# Generated by the CTM Topic Map Writer.
%encoding "UTF-8"
%version 1.0


# prefixes

%prefix ex  <http://aranuka.example.org/>



# topic definitions
^<http://aranuka.example.org/address/1>
         isa ex:address;
         ex:zipcode :  "00815";
         ex:street :  "Example Street";
         ex:city :  "Example City";
         ex:number :  "1".

ex:firstname
         -  <http://psi.topicmaps.org/iso13250/model/topic-name> : "First Name".

ex:city
         -  <http://psi.topicmaps.org/iso13250/model/topic-name> : "City".

ex:street
         -  <http://psi.topicmaps.org/iso13250/model/topic-name> : "Street".

ex:place
         -  <http://psi.topicmaps.org/iso13250/model/topic-name> : "Place".

ex:person
         -  <http://psi.topicmaps.org/iso13250/model/topic-name> : "Person".

ex:address
         -  <http://psi.topicmaps.org/iso13250/model/topic-name> : "Address".

ex:lives
         -  <http://psi.topicmaps.org/iso13250/model/topic-name> : "lives".

ex:zipcode
         -  <http://psi.topicmaps.org/iso13250/model/topic-name> : "Zip Code".

ex:number
         -  <http://psi.topicmaps.org/iso13250/model/topic-name> : "House Number".

ex:lastname
         -  <http://psi.topicmaps.org/iso13250/model/topic-name> : "Surname".

ex:max
         isa ex:person;
         -  ex:firstname : "Max";
         -  ex:lastname : "Powers".

ex:habitant
         -  <http://psi.topicmaps.org/iso13250/model/topic-name> : "Habitant".

# association definitions
ex:lives (
         ex:place : ^<http://aranuka.example.org/address/1>,
         ex:habitant : ex:max
)
Note
Starting the application again, the application should print 1 instead of 0 because it loads the previously persisted topic map.

Glossary

Connector

A connector is the bridge between Aranuka and an existing topic map. A connector create a topic map and persists it.

Field Container

A field is a property of a class. This class is called the field container.

QName

A QName provides a way to shorten a URI. A QName has the structure id:localpart. For every used id a URI must be specified. If a QName is used the id will be resolved with the specified URI and adds the local part.