Grouping Concepts

So far in the previous chapter we have been looking at Mapping from a single property perspective. However it is not possible to directly manipulate a single property in the current version of Topaz. For this you need to define something called an Entity.

Entity

An entity is the smallest unit that an application can manipulate using Topaz. It can be thought of as a single node in an RDF graph. (Except Blank Nodes - See note on Blank Nodes below.)

In RDF terms, instances of an Entity represents Nodes in the RDF graph. Additional properties defined on the entities are the rdf:type and the named graph in which instances of the entity can be found. Entities that have an rdf:type specified also has equivalence in the RDFS and OWL domains. See [TBD] for a full comparison of how these are related.

See @Entity for configuring an Entity. It should be noted that in java POJO mapping case, any java class passed to the preload() method in SessionFactory becomes registered as an Entity in the SessionFactory?. See Configuring Application Metadata for more details.

Blank Nodes

Topaz cannot map properties grouped under a blank node into an object. This is because an update semantics is unclear for Blank nodes. (Note: Topaz currently does updates by deleting old statements and inserting new statements.)

[TBD - is this right?]

However the functionality from a higher level application perspective is easily achieved by identifier generators. See GeneratedValue

rdf:type handling

Topaz does special handling of rdf:type properties that are configured on the Entity. All instances of an entity will have the same rdf:type values and therefore Topaz does not map these value into an instance property. Instead this is pre-mapped/configured in the Entity metadata by the application.

rdf:type has special handling in 3 places in Topaz:

  • when a new instance is persisted, Topaz will write out the predefined rdf:type values also
  • when a load of an entity is requested, the rdf:type values are checked to ensure that the node that was loaded represents this entity
  • rdf:type is used in sub-class determination as explained below

Named Graph Handling

Most triple-stores are really quad stores and applications have come to rely on named graph as a partition for grouping rdf statements. Therefore it is entirely possible to even apply semantic meaning for graphs by an application. An example is the permission service in Ambra.

Briefly, the permission service relies on the named graph 'grants' for permission grants and 'revokes' for permission revokes. The only difference between them is really the named graph in which they are defined.

Using Topaz this can be modeled as:

@Entity
class Permission { 
  @Id
  String getResource() {...}
  void setResource(String resource) {...}
  @PredicateMap
  Map<String, List<String>> getPermissions() {...}
  void setPermissions(Map<String, List<String>> setPermissions() {...}
}

@Entity(graph="grants")
class Grants extends Permission {
}

@Entity(graph="revokes")
class Revokes extends Permission {
}

The partitioning by named graphs works in Topaz as long as the graphs are static and small in number. This is because as you saw above, the graphs are part of the metadata of an Entity.

Dynamic named graphs

If your application relies heavily on dynamic run-time named graph usage, currently the only way to support that is by adding/manipulating the metadata stored in the SessionFactory. See the sections on @Entity and @Graph for help with dynamic runtime configurations. Essentially what you would have to do is to bind the same Java class to more than one entity:

@Entity
class Permission { 
  @Id
  String getResource() {...}
  void setResource(String resource) {...}
  @PredicateMap
  Map<String, List<String>> getPermissions() {...}
  void setPermissions(Map<String, List<String>> setPermissions() {...}
}

   // Let the annotation parser set up everything for Permission.class
   sessionFactory.preload(Permission.class);

   // Configure the graphs:
   sessionFactory.addGraph(new GraphConfig("grants", URI.create("local:///topazproject#grants")));
   sessionFactory.addGraph(new GraphConfig("revokes", URI.create("local:///topazproject#revokes")));


   // Now define 'Grants' entity with graph = 'grants' and as a sub-class of 'Permissions'
   sessionFactory.addDefinition(new EntityDefinition("Grants", Collections.emptySet(), 
                                                     "grants", Collections.singletonSet("Permission")));

   // Bind it to the same Permission.class. Note that 'suppressAliases' is true here.
   sessionFactory.getClassBinding("Grants").bind(EntityMode.POJO, new ClassBinder(Permission.class, true));


   // Similarly for 'Revokes' ...
   ...

   // Validate and build metadata
   sessionFactory.validate();

   // Now the application code can look up grants and revokes for a resource:

    Permission grants = session.get("Grants", "my:resource:42");
    Permission revokes = session.get("Revokes", "my:resource:42");
   

Loading and Saving Entities

Applications work with Topaz sessions to load and save instances of the entity (or collectively the group of statements with the same subject-uri.

For example:

   Article a = session.get("Article", "article:23");
   Article b = session.get(Article.class, "article:23");

Both the above statements will load all properties that are grouped under the "Article" entity for the subject-uri "article:23". Additionally the load will be from the graph configured for this Entity.

Similarly the following example:

   Article a = new Article();
   a.setId("article:42");
   a.setProperty1(value1);
   ...
   ...
   ...

   session.saveOrUpdate(a);

This will save all of the properties that are grouped under the "Article" entity for the subject-uri "article:42" and that are set on the Artcile.class instance 'a'. Additionally the save will be to the graph configured for this Entity.

Entity in Queries

In OQL, Entity also identifies a graph/group from which a selection is made:

For example:

  select a from Article a where ....;

The 'from' clause identifies the Entity or group from which to load.

Inheritance

Entity inheritance works much the same way as Java class inheritance. An entity can have more than one super-entity. In Java terms this is equivalent to multiple interfaces being implemented by a class.

Sub-classes inherit all of the properties of the super-classes. Additionally sub-classes inherit the graph definition and the rdf:type definitions from the super-classes.

rdf:type inheritance

The set of effective rdf:type values of a sub-class is the union of the declared rdf:type values and the effective rdf:type values of all of it's declared super-classes. Both the declared set and the effective set are maintained by Topaz on the metadata for this Entity. (See getTypes() and getAllTypes() in the org.topazproject.otm.ClassMetadata?)

For example:

@Entity(graph="animals", types = {animals:Animal})
class Animal {
...
}

@Entity(types = {animals:Cat})
class Cat extends Animal {
...
}

'Animal' has {'animals:Animal'} as both the declared and effewctive rdf:type values set. Where as 'Cat' has {'animals:Cat'} as the declared set and {'animals:Animal', 'animals:Cat'} as the effective set.

Topaz writes out the effective set of rdf:types always when an instance is persisted. Whereas queries will only test for the declared sets to check the existence of Entities.

eg:

  select a from Animal a ...;
  select c from Cat c ...;

The first query tests for {'animals:Animal'} and the second {'animals:Cat'}.

That is there is no inferencing done in the query for Animal to test for 'animals:Cat' and include those in the result set.

There is no need for inferencing when working with data that is persisted by Topaz. eg. When Cat instances are persisted with Topaz the effective set {'animals:Animal', 'animals:Cat'} is what gets written oput.

However when working with existing data, the application must run this inferencing and insert the effective set before Topaz can work with it. In TQL this can be achieved by executing the following:

   insert select $s <rdf:type> <animals:Animal> from <...> where
   $s <rdf:type> <animals:Cat> into <...>;

Graph inheritance

Graph values are handled differently. There can only be one graph that an Entity is associated with in Topaz. So the declared graph value will always override the values declared in the super-classes.

When a graph is not configured on an entity, Topaz expects the union of the graphs from the super-classes to be a singleton set or an empty set. ie. Conflicting graph definitions will result in an error being flagged during the configuration validation step.

For example, given these definitions:

@Entity(graph="animals", types = {animals:Animal})
class Animal {
...
}

@Entity(types = {animals:Cat})
class Cat extends Animal {
...
}

The named graph 'animals' is inherited by Cat.

Property Overrides

Normally during inheritance, all of the properties of the super-classes are added to the set of properties declared on this Entity to form an effective property set.

However there is also the case of superseded property definitions that Topaz supports. Most notable use case is the support for java generics in POJO mappings. (see below)

But in its most general form any attribute of a property may be superseded in a sub-class. This more or less translates to any attribute in @Predicate being supersedable. superseded property then becomes a 'referenced' property in this property definition in the absence of any declared 'referenced' properties. (See ref attribute in @Predicate)

When Topaz builds the effective set of properties for an entity, the superseded properties are discarded and are replaced with the superseding properties declared in this entity.

Java Generics support

Generics support in Topaz is a special case of superseded property definitions. As you have seen from above sections, the declared data-type of a property is used by the annotation parser to make numerous decisions about the property:

  • literal vs resource reference
  • data-type in case of literals and associated entity in case of resource reference

These determinations then supersede the ones from the genric base class. In addition an application may explicitly control the superseded properties by having an annotated superseded getter/setter method declaration in the sub-class.

Sub-Class Resolving

During load from the TripleStore, Topaz creates an instance of an entity based on the properties it finds in the !Triplestore. Just as in Java, a sub-class entity instance is assignment compatible to any of its super entities.

For example, given these definitions:

@Entity(graph="animals", types = {animals:Animal})
class Animal {
...
}

@Entity(types = {animals:Cat})
class Cat extends Animal {
...
}

And given the following graph:

Then the following assertions would hold:

   Animal a = session.get(Animal.class, "cat:1");
   assert a instanceof Cat;

Application Defined SubClass Resolver

In the above exanple. Topaz makes the subclass resolving decision purely based on the rdf:type values. For cases where that is not sufficient, an application needs to define a subclass resolver. (See @SubClassResolver)

SubClass Resolvers get to see the entire forward and reverse mapped properties and their values and therefore can decide on a sub-class based on any criteria.

For example, given the additional class definition:

@Entity
class BlackCat extends Cat {
...
}

And the following graph:

A SubClass Resolver can then make use of the <cat:1> <animal:color> 'black' statement and determine that the required instance is that of a 'BlackCat?'.

See @SubClassResolver for an example on how to define a sub-class resolver.

Containment

We saw in the inheritance section how sub-classes inherit the properties of the super-class. However inheritance is not the only way in which a group of properties can be re-used. An example is the usage of a group of properties that define a postal-address:

@UriPrefix("address:")
class Address {
  String getStreet() {...}
  @Predicate
  void setStreet(String s) {...}

  String getCity() {...}
  @Predicate
  void setCity(String s) {...}

  String getState() {...}
  @Predicate
  void setState(String s) {...}

  String getZip() {...}
  @Predicate
  void setZip(String s) {...}

  String getCountry() {...}
  @Predicate
  void setCountry(String s) {...}
}  


@Entity 
class Office {
  ...
  ...
  @Embedded
  Address getAddress() {...}
  void setAddress(Address address) {...}

}

@Entity 
class Home {
  ...
  ...
  @Embedded
  Address getAddress() {...}
  void setAddress(Address address) {...}

}

@Entity
class Applicant {

  @Embedded
  Address getAddress() {...}
  void setAddress(Address address) {...}

}

rdf:type from contained entity

Currently Topaz ignores any rdf:type values defined in the contained entity. Therefore it is better to leave out any rdf:type definitions for the containing entity to define. However this may change in a future version.

Named graph from contained entity

Since Topaz allows graph definitions at a property level, any graph definitions on the contained entity is only applied to properties within that entity.

For example, if the 'Address' entity defined a graph named 'address' for its properties, then all usages of 'Address' in any containing class, will continue to load and save the properties of Address from the named graph 'address'.

ie. the graph definitions of the contained graph cannot currently be overridden. However this may change in a future version.

Superseding properties from a contained entity

Similar to graph definition, the property definitions in a contained property cannot be superseded in the containing entity. This behavior also may change in a future version.

Association

Association is strictly not a property grouping concept in the same lines as inheritance and containment. The difference is now we are traversing to a different graph node.

For example:

can be expressed in POJO mapping as:

@Entity
class Book {
  ...
  ...

  @Predicate(uri="dc:title")
  String getTitle() {...}
  void setTitle(String title) { ... }

  @Predicate(uri="dc:creator")
  Set<Author> getAuthors() {...}
  void setAuthors(Set<Author> authors) { ... }
}

@Entity
class Author {
  ...
  ...

  @Predicate(uri="foaf:name")
  String getName() {...}
  void setName(String name) { ... }
}

Alternatively from a graph representation point of view we could define the authors property for Book as:

@Entity
class Book {
  ...
  ...

  @Predicate(uri="dc:creator", type=Predicate.PropType.OBJECT)
  Set<String> getAuthors() {...}
  void setAuthors(Set<String> authors) { ... }
}

The differences are:

  • the first form is called an Association where as the second is merely an Object property definition with no clear rdfs:range specified.
  • the second form therefore is more dynamic in the sense it is not limited to instances of Author objects. In this case, the application needs to make an explicit call to session.get(Author.class, id) to load instances of Author objects.
  • the first form also is considered an implicit filter for nodes that match instances of Author objects. WARN: The filtering is done at load time and the nodes that are filtered out are discarded. This means any change that causes the list of associations to be modified (eg. adding/deleting Authors) will cause the new list to replace the existing list from the triple-store. This means any statements that were filtered out by the implicit filter will also be removed. Most often that is the desired behavior, but there could be cases where the original filtered out statements need to be preserved. This option is slated for a future version of Topaz.

Fetch options

The associated object is loaded by Topaz on an as needed basis (called lazy loading). It is possible to change the loading option to eager by setting the fetch option in @Predicate annotation (or manually by setting the FetchType value in the RdfDefinition configured for this property).

WARN: For Lazy loading to succeed, the Topaz Session must be open and a Transaction must be active. The load is performed on the first access to a property of the object. This means a get or set of a property is the one that is triggering the load. This is something to watch out for when passing lazy loaded POJO objects to a View component in an MVC framework, for example. Accessing a lazy loaded property that is yet to be loaded outside of a Transaction scope will result in an OtmException being thrown. To mitigate this an application has three options:

  • define associations as eager fetched
  • manually touch properties needed by view components
  • use an Open Session in View paradigm where a Session and Transaction is kept alive even when the view is being built

Cascade options

The FetchType option controls how associations are loaded when Topaz is asked to load an object. Similarly updates on an object can cascade down to the Associations. This is explained in the Transitive Persistence section.