Object Composition

1. Introduction

An essential part of the KOMMA framework is the Object Composition mechanism which we want to introduce here.

The general idea for this feature originates with James Leigh of Elmo and AliBaba and has been improved upon with ASM code generation and InfiniSpan caching.

When you work with Entities in KOMMA, you are really working with generated Proxy objects that are composed from a number of Behaviour classes and offer, among others, bean-type interfaces to data in an RDF store.

This sounds complicated. We best discuss this in some detail.

2. Proxies

Let us start with how Entities come into being and what goes on behind the scenes.

If you create a KOMMA Entity, the framework

  1. instantiates a plain Java Object to be used as the Proxy

  2. collects all RDF types from all bound @Iri annotations to determine the list of relevant Behaviours

  3. adds every supported Java interface from all found behaviour classes

  4. generates code for the methods of all these interfaces, by creating Invocation Chains of the appropriate methods from each of the found behaviour classes.

There are a few behaviours inherently added by the framework to supply general functionality for handling the entities and the bean-type interface to the RDF store, but additional Behaviour classes can be bound as needed.

3. Behaviour

This interface is implemented by all classes that add some behaviour to a mapped entity. In steps 3 and 4 of generating the Proxy, ASM is used for merging the supported interfaces and method implementations from all bound Behaviour classes.

4. Precedence and Invocation Chain

When generating the code for the methods, the composition framework follows a partial ordering of the bound Behaviours, imposed on them by the @Precedes annotation. Each method is constructed as a chain of invocations, using this partial ordering, of all specific method implementations from the behaviours.

Suppose we have:

public abstract class SomeBasicThingSupport implements SomeBasicThing,
		Behaviour<SomeBasicThing>, ISomeThing {

	@Override
	public void doSomeThing() {
		// [...]
	}
}

@Precedes(SomeBasicThingSupport.class)
public abstract class SomeSpecificThingSupport implements SomeSpecificThing,
		Behaviour<SomeSpecificThing>, ISomeThing {

	@Override
	public void doSomeThing() {
		// [...]
	}
}

The result would be a generated doSomeThing() method on the Proxy object with an invocation chain of: SomeSpecificThingSupport.doSomeThing() → SomeBasicThingSupport.doSomeThing() which, when executed, would call the methods, in order from left to right (pseudo-code):

public class SomeThing__PROXY {
	// [...]
	public void doSomeThing() {
		SomeSpecificThingSupport__INSTANCE.doSomeThing();
		SomeBasicThingSupport__INSTANCE.doSomeThing();
	}
}

4.1. Importance of Return Values

After each invocation from the chain, a check is made against the return value (if any) to determine if the chain should proceed or be stopped, where a non-null value (or a boolean true) will cause it to be stopped, with the value used as final result for the method invocation as a whole.

5. Object-Triple-Mapping

Now, as has been stated above, as one of their prominent features, KOMMA entities receive a behaviour implementing all the mapped bean-type accessor methods. This is automagically generated from built-in functionality and is similar in concept to JPA/Hibernate, except it maps directly to RDF properties. It supports both single- and multi-valued properties and includes InfiniSpan caching for improved performance.

Let us look at a short snippet from one of our examples that maps the interface Person to an RDF type representing it and has accessor methods for name which it maps to an appropriate RDF property:

package net.enilink.komma.example.objectmapping.model;

import net.enilink.composition.annotations.Iri;

@Iri("http://enilink.net/examples/objectmapping#Person")
public interface Person {

	@Iri("http://enilink.net/examples/objectmapping#name")
	String getName();

	void setName(String name);

}

If we were to create such a Person entity and set a name on it:

manager.create(Person.class).setName("John Q. Doe");

we would end up with RDF statements like this:

_:node18hs02kdux1
	a <http://enilink.net/examples/objectmapping#Person> ;
	<http://enilink.net/examples/objectmapping#name> "John Q. Doe" .

For further information and an example regarding the details and application of such mappings, please refer to the basic object triple mapping example.