This document describes Elmo 1.3 and its use with Sesame 2.2. Elmo is a JavaBean pool implementation for the Sesame RDF repository, providing static JavaBean interfaces to RDF resources. Specifically, Elmo is a subject-oriented RDF entity manager that allows JavaBeans to be cast to different roles providing a unique context specific view of the subject.
As systems become more integrated and provide diverse roles to a broader market, traditional object-oriented and component-oriented systems can become bloated with complex interfaces and behaviours that increase expenses for software maintenance. Integrated domain driven designs often require context-specific views of shared multi-dimensional resources. These resources are a challenge to model through polymorphism alone and require a higher level of concern management.
As a subject-oriented system, Elmo provides a way to group common behaviours and separate roles into unique interfaces and reusable classes. Domain models built with Elmo are simpler and are better able to express their design concept. This leads to lower maintenance costs and more flexible software.
Elmo is a role based Java persistent Bean pool. It provides a simple API to access ontology oriented data inside a Sesame repository through concepts and behaviours that may have been designed and developed independently.
Elmo provides a static typed interface to RDF properties within an RDF store. For example, the following code accesses FOAF properties from the RDF/XML file "foaf.rdf" and prints them to standard out.
ElmoModule module = new ElmoModule();
SesameManagerFactory factory = new SesameManagerFactory(module);
SesameManager manager = factory.createElmoManager();
File file = new File("foaf.rdf");
manager.getConnection().add(file, "", RDFFormat.RDFXML);
for (Person person : manager.findAll(Person.class)) {
System.out.print("Name: ");
System.out.println(person.getFoafNames());
} |
With Elmo's role based interface and component oriented design, software becomes more flexible and allows for novel opportunities for developing and modularizing domain driven designs. The domain model is simplified into independent concerns that are mixed together for multi-dimensional, inter-operating, or integrated applications.
Elmo is a subject-oriented programming library that addresses common problems in traditional object-oriented designs. With Elmo, application design and development can:
Create extensions to software without modifying original source;
Customize and integrate systems with reusable components;
Facilitate multiple team system development by dynamically integrating independent domain-models;
Permit decentralized design and development of concepts and behaviours;
Simplify code by capturing design patterns into reusable behaviours;
Maintain a 1:1 mapping between design concept and implementation.
OpenRDF Elmo is licensed under the BSD license and as such is free to use and modify as stated below.
Copyright (c) 2005-2008, James Leigh and Peter Mika All rights reserved, unless otherwise specified.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
Neither the name of openRDF.org nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Elmo stores all persistent data in a Sesame repository store. Sesame supports interchangeable repository stores. The one discussed in this user guide, MemoryStore, is a self-managed library and does not use a database server. The MemoryStore stores all values in memory and can save values to disk in a configured directory, it is suitable for storing 1 million values with half a gigabyte of memory. The NativeStore is another store that can scale much higher and stores values in the local file system under a configured directory. The RDBMS Store is an RDF store backed by an relational database that has advanced query processing. Please consult the Sesame User Guide for more details on backing RDF stores.
To use Elmo the following jars must be included in the class-path along with their dependencies.
Elmo 1.3 depends on the following libraries:
Sesame 2.2
javax-persistence-api
commons-beanutils 1.7.0
javassist 3.7.0
slf4j 1.5.0
Elmo also ships with the following optional jars.
Contains Elmo concepts for popular ontologies; just drop into your class-path and start using the concepts listed in the javadocs.
Self executable jar to create your own roles and ontologies. This jar has additional dependencies on commons-cli and optionally groovy, asm, and antrl if the elmo:groovy annotation is used.
Contains an RDF crawler that follows rdfs:seeAlso links in RDF documents.
Ready-to-deploy war file (downloaded separately) containing a web interface to the RDF crawler.
Self executable jar to find equivalent instances in large sets of data. This jar has additional dependencies on commons-codec and commons-httpclient.
Ready-to-deploy war file (downloaded separately) containing three example applications and all needed dependencies for use within a J2EE server.
Elmo and Sesame can be downloaded from openRDF.org and are also available as maven artifacts in the maven repository http://repo.aduna-software.org/maven2/releases . Below is a extract from a maven pom.xml.
<dependencies>
<dependency>
<groupId>org.openrdf.elmo</groupId>
<artifactId>elmo-sesame</artifactId>
<version>1.3</version>
</dependency>
<dependency>
<groupId>org.openrdf.sesame</groupId>
<artifactId>sesame-runtime</artifactId>
<version>2.2</version>
</dependency>
</dependencies>
<repositories>
<repository>
<id>aduna-opensource.releases</id>
<name>Aduna Open Source - Maven releases</name>
<url>http://repo.aduna-software.org/maven2/releases</url>
</repository>
</repositories> |
Elmo has three main levels of operation: ElmoManagerFactory, ElmoManager, and Entity. Normally, only one ElmoManagerFactory would exist for each domain model used with the repository and only one ElmoManager for each repository connection. An Entity would exist for each individual used within a connection. ElmoManagerFactory and ElmoManager are interfaces to the classes SesameManagerFactory and SesameManager. This is shown in the following class diagram.
The ElmoManagerFactory can create ElmoManagers. When ElmoManagers are created, they operate on a unique connection to the repository. Through this connection all the properties of created entities are read and updated.
Entities are referenced by a qualified-name (QName). Each qualified-name must include a namespaceURI and a localPart that is unique within the namespace. An example of a namespaceURI is "http://www.example.com/rdf/2008/data/". This namespaceURI will be referenced as "NS" throughout the examples in this document.
An ElmoModule can be created using its default constructor and must be created before instantiating an SesameManagerFactory. ElmoModule provides methods to register roles, datatypes, and factories, it is also used to load RDF datasets, set the operating context, and scan jars. These methods set the scope of the manager factory when instantiated.
Role registration is described in the Section called Registering Concepts & Behaviours. Datatype registration is described in the Section called Datatypes. Factories are described in the Section called Factory Classes. Dataset can be loaded into and replace a context. Each ElmoModule can include a primary context, which will contain all new statements added.
The SesameManagerFactory is created with a module and a repository. An existing repository can be passed to the manager factory or the factory can create (and shutdown) a repository itself. A repository can be created using a repository id to a remote sesame server (passing a URL), a local data configuration directory (passing a File dataDir), or an application id, used to locate the data directory. If no repository or repository id is provided an in-memory repository will be created with no persisted storage.
Elmo's entity manager interface provides common methods to designate, merge, find, rename, and remove entities. It can be created using the SesameManagerFactory class or accessed by framework specific notation. For command oriented applications, a new ElmoManager should be created for each command (or request). The overhead for creating a new ElmoManager is low and resources are shared by managers created in the same ElmoManagerFactory.
The manager provides a method to designate (or create) entities by a qualified-name (QName). Blank or anonymous entities can be created by using the overloaded designate method and will operate without a qualified-name. The designate method is called with a Java interface (concept), and the ElmoManager will create a new entity with the subject type of the given concept. Calling designate with a QName that is already used will cause the subject to take on multiple roles to satisfy each designate request. If the entity already implements the concept given, it is equivalent to calling find and casting the result.
Take an example of a company where an employee is an engineer, but also does sales. In Elmo this can be modelled as shown below where a subject can implement both a SalesRep and an Engineer at the same time, by calling designate with the same QName. This technique can be used to integrate and extend domain-models without modifying their source code.
assert SalesRep.class.isInterface(); assert Engineer.class.isInterface(); ElmoModule module = new ElmoModule(); module.addRole(Engineer.class); module.addRole(SalesRep.class); factory = new SesameManagerFactory(module); manager = factory.createElmoManager(); QName id = new QName(NS, "E340076"); manager.designate(SalesRep.class, id); Object john = manager.designate(Engineer.class, id); assert john instanceof SalesRep; assert john instanceof Engineer; |
Elmo supports a form of persistence ignorance. When a property is assigned to an object, created outside of the manager and directly implements a recorded concept, it will be merged into the repository. If the object has the method getQName, it will be merged with the QName returned. If it does not have this method or the result is null, it will be merged with a newly designated anonymous resource. Merging can also be done explicitly by using the merge method on the manager. This method will merge the object into the repository, returning the managed entity. Note that the managed entity will not extend the class, but only implement all the recorded concepts.
By default the interface java.util.List is registered as an interface for rdfs:Container. This means that any object created outside of the manager, which implements java.util.List, will be merged into the repository as a rdfs:Container when it is assigned to a property or merged explicitly.
The manager has a find method to directly load entities by a QName. Entities can also be retrieved by type or query, through findAll and createQuery methods respectively. In addition, the manager provides a method to remove the entity from the pool (RDF repository). This method will remove all references to the entity and its properties. If an entity includes another entity as its property value it must be removed separately, this includes anonymous entity, such as container or list entities.
The entities do not persist any values internally and all information is stored in the underlying repository connection. In the example below we can see that the entities john and jonny are separate Objects, but both retrieve their values from the same repository. The ElmoManager only persists concept properties with @rdf annotation on the getter method.
ElmoModule module = new ElmoModule();
module.addRole(Employee.class);
factory = new SesameManagerFactory(module);
manager = factory.createElmoManager();
QName id = new QName(NS, "E340076");
Employee john = manager.designate(Employee.class, id);
Employee jonny = manager.designate(Employee.class, id);
assert john.equals(jonny);
assert john != jonny;
john.setName("John");
assert jonny.getName().equals("John"); |
Entities are not serializable and are only valid during the life of the ElmoManager (usually just a transaction). The Entity interface, implemented by all entities returned from the manager, provides the getQName method to allow entities to be restored at a later time by another manager. Anonymous entities return null from their getQName method and cannot be restored using the find method. Shown below is the QName property of the Entity interface.
factory = new SesameManagerFactory(new ElmoModule()); manager = factory.createElmoManager(); QName id = new QName(NS, "E340076"); Entity john = manager.find(id); assertEquals(id, john.getQName()); // the subject john has the URI of // "http://www.example.com/rdf/2008/data/E340076" |
The ElmoManager can retrieve both SPARQL and SeRQL tuple queries of entity resources through the createQuery method of the ElmoManager. These results are then converted into Objects or arrays of Objects in an iterator. Only one query language can be configured for an ElmoManager at a time using the setQueryLanguage method of the SesameManagerFactory. SPARQL is the default query language, below is an example of have to use SeRQL.
ElmoModule module = new ElmoModule();
module.addRole(Employee.class);
factory = new SesameManagerFactory(module);
factory.setQueryLanguage(QueryLanguage.SERQL);
manager = factory.createElmoManager();
Employee john = manager.designate(Employee.class);
john.setName("John");
String queryStr = "SELECT emp FROM "
+ " {emp} <http://www.example.com/rdf/2008/model#name> {name}";
ElmoQuery<?> query = manager.createQuery(queryStr);
query.setParameter("name", "John");
for (Object emp : query) {
assert ((Employee) emp).getName().equals("John");
} |
The method setParameter of ElmoQuery can be used to assign literals or entities to variables in the query. To assign an entities by name, use the setQName method. To assign a class use the setType method.
In Elmo, a role is defined as a concept or behaviour. A concept, in Elmo, is a JavaBean interface with an @rdf annotations describing the subject type of the interface and the predicates of each mapped Bean property. Elmo ships with a collection of popular ontologies defined as Elmo concepts. They can be found in the elmo-concepts module.
Every @rdf annotated concept property must be a single or a set of entities and/or literals (sometimes referred to as value objects). These correspond to the RDF structure, where everything is a resource or a literal and properties have multiple values or a single value (where value is an entity or literal). Elmo uses Sesame, an RDF repository, for the persistence layer and all @rdf annotated concept properties are mapped to an RDF triple, consisting of subject, predicate, and value. RDF repositories are semi-structured. You can use any URI (or URL) you want, but it must be unique to that property or role and every persistent concept property must have a predicate. For more information about Sesame and RDF please consult the Sesame User Guide or FAQ.
The @rdf and @inverseOf annotations are placed on getter methods of Bean interfaces. They are placed on methods that have no parameters, a return type, and start with "get" or if a boolean "is". A setter method is not required, but if present it will also be implemented. These methods will be implemented to map to the RDF values in the repository. The return type can be any registered concept, literal, or java.util.Set of the above. When a setter method is not present the property is read-only. @inverseOf annotation is used to indicate inverse relations as show below. The @rdf annotation takes precedence over the @inverseOf annotation, if both are specified, and only the first URI is used for reading the values from the repository.
@rdf("http://www.example.com/rdf/2008/model#subteam")
public Set<Team> getSubteams();
public void setSubteams(Set<Team> subteams);
@inverseOf("http://www.example.com/rdf/2008/model#subteam")
public Team getParentTeam(); |
The localized annotation can be used on properties that return a String or a Set of Strings. When this annotation is present the Locale of the ElmoManager is associated with assigned values, and when retrieved the closest set of values for the Locale of the ElmoManager are returned.
If inferencing is enabled in the factory, through setInferencingEnabled, and the getter method of a concept property contains additional URI values, all changes to the property will also modify each listed predicate, and inverseOf predicates will be modified with the subject and value reversed.
public interface Team {
@rdf("http://www.example.com/rdf/2008/model#members")
public Set<Employee> getMemebers(); // read-only
@rdf({"http://www.example.com/rdf/2008/model#teamLead",
"http://www.example.com/rdf/2008/model#members"})
@inverseOf({"http://www.example.com/rdf/2008/model#leaderOf"})
public Employee getTeamLead();
public void setTeamLead(Employee value);
@rdf({"http://www.example.com/rdf/2008/model#salesRep",
"http://www.example.com/rdf/2008/model#members"})
public SalesRep getSalesRep();
public void setSalesRep(SalesRep value);
@rdf({"http://www.example.com/rdf/2008/model#engineer",
"http://www.example.com/rdf/2008/model#members"})
public Engineer getEngineer();
public void setEngineer(Engineer value);
} |
In the example above (with inferencing enabled) the members property will be updated as the properties teamLead, salesRep, and engineer are modified. The property leaderOf of Employee will also be updated with their Team as the teamLead value is changed.
The annotation oneOf can be placed on the setter method to assert the URI (value) or label values are correct. This check is only performed when assertion is enabled, which can be enabled with the vm argument "-ea".
@rdf("http://www.example.com/rdf/2008/model#gender")
public String getGender();
@oneOf(label={"M", "F"})
public void setGender(String value); |
Role annotations are placed on concepts or behaviours to statically associate them with the subject types or individuals that they should be composed with.
The @rdf annotation, in addition to being used on properties, is also used on the class/interface to associate it with a primary subject type. On class/interface declarations, alias subject types can be indicated with additional annotation values. The role will be included for any subject of any type in @rdf.
@rdf("http://purl.org/rss/1.0/channel")
public interface Channel {
@rdf("http://purl.org/rss/1.0/link")
public Set<String> getLink();
public void setLink(Set<String> link); |
Above we have defined a Channel concept that should be used with every resource of the type "http://purl.org/rss/1.0/channel". It has one property named link which maps to the RDF predicate "http://purl.org/rss/1.0/link".
To indicate restrictions of what types this role cannot be created with, the disjointWith annotation can be used. This is only checked when assertions are enabled and is only read from concepts passed on the create method of the ElmoManager.
@rdf({"http://www.example.com/rdf/2008/model#Customer",
"http://www.example.com/rdf/2008/model#Client"})
@disjointWith({Employee.class})
public interface Customer {
...
} |
These annotations are an alternative to directly mapping roles to an RDF subject type. They provide a way to assign roles based on other roles of the entity or based on their qualified-name. In this way role specific behaviours can be defined based on a combination of roles, the absence of a role, or on a set of individuals.
The intersectionOf annotation takes a list of roles, that if a entity implements all of the given roles, it will also implements the declared role. The complementOf annotation takes a single role as argument; if any entity does not implement the given role, it will implement the declared role. The oneOf takes a list of qualified-names; if any entity is created with one of the qualified-names it will implement the declared role.
By registering a public interface or class with a subject type, Elmo will dynamically include this role with all resources of the defined type. Registering concepts is required in order for them to be used with the designate method. The composition of roles can facilitate decentralized design and development for an application or a suite of applications.
Roles are statically loaded from "META-INF/org.openrdf.elmo.roles" files. These files may be included in any number of jars or paths in the Java class-path. The format of these files is the full class name optionally assigned to one or more URI subject type. If the @rdf class annotation is present its value will also be included. If no mapping is provided, the role will be applied to the role annotations of its interfaces. Here is a sample of the contents of the file.
org.openrdf.concepts.foaf.Person org.openrdf.concepts.foaf.Agent org.openrdf.concepts.dc.DcResource |
If this file does not contain any classes, but does exist in a directory or jar in the class-path, the entire directory or jar will be scanned for classes with a role annotation or classes that extend or implement a annotated class and automatically register them. Each role will be logged at the DEBUG level when being registered. Roles can also be registered at run-time using the addRole methods of the ElmoModule.
ElmoModule = new ElmoModule();
module.addRole(Engineer.class,
"http://www.example.com/rdf/2008/model#Engineer");
// URI type of SalesRep is retrieved from the @rdf annotation
module.addRole(SalesRep.class);
factory = new SesameManagerFactory(module);
manager = factory.createElmoManager(); |
A behaviour, also called a mixin, is a concrete or abstract class that implements one or more methods for an Entity. Behaviours provide functionality other then RDF property mapping. They simplify code by capturing design patterns into reusable classes. Behaviours are created with a factory, or a constructor with an interface implemented by the entity. Behaviours are registered in the same way that concepts are registered, which is described in the Section called Registering Concepts & Behaviours.
Behaviour factories are also registered in the ElmoModule. Factories use the same role mapping as concepts and behaviours. They are used to create a behaviour for used in an entity. An example of using a behaviour factory can be found in the Section called Dependency Injection.
Behaviours without a corresponding factory must have a default constructor or a constructor with a single parameter. This parameter can be any interface that the entity will implement, including the Entity interface itself. Behaviours have access to all properties and other behaviours within the entity through abstract methods or the constructor argument.
Behaviours can be used to implement methods for the entity's interfaces. Below is the EmployeeSalarySupport behaviour. Here the class implements the paySalary method for an Employee. It splits the salary amount into two bank accounts based on the properties of the employee.
public abstract class EmployeeSalarySupport implements Employee {
public void paySalary(double amount) {
double invest = getInvestmentPercent() * amount;
BankAccount fund = getInvestmentAccount();
BankAccount checking = getSalaryDepositAccount();
if (invest >= amount) {
fund.deposit(amount);
} else if (invest > 0) {
fund.deposit(invest);
checking.deposit(amount - invest);
} else {
checking.deposit(amount);
}
}
} |
Interceptors are behaviours that have the @intercepts annotation on one or more of their methods. They are used to maintain a 1:1 mapping between design concept and implementation for cross cutting concerns. The annotation @intercepts must be placed on methods that take the same parameters as the method(s) intercepted or an InvocationContext as the parameter.
The @intercepts annotation uses pattern matching to filter which methods of the entity should be intercepted. By default every method of the entity with the same method name will be intercepted. The annotation provides six inclusive ways to select which Methods to intercept.
is an regex match on the entire Method name, if not provided the method name must have the same name as the method being interepted.
checks the number of parameters.
ensures the parameterTypes of the method are equivalent to the classes given.
checks for equivalent or superclass of the Method's return type.
checks if this class has this method.
is the name of a static method in the interceptor class that takes a Method argument and returns a boolean indicating if this method should be intercepted.
Below is an example of using an interceptor.
@rdf("http://www.example.com/rdf/2008/Message")
public class EmailValidator {
@intercept(method="set.*Email.*", parameters={String.class}, returns=Void.class)
public Object setEmail(InvocationContext ctx) throws Exception {
String email = (String) ctx.getParameters()[0];
if (email.endsWith("@example.com"))
return ctx.proceed();
throw new IllegalArgumentException("Only internal emails are supported");
}
}
ElmoModule module = new ElmoModule();
// if no URI and no @rdf, applies to all entities
module.addRole(EmailValidator.class);
module.addRole(Message.class);
factory = new SesameManagerFactory(module);
manager = factory.createElmoManager();
Message message = manager.designate(Message.class);
message.setFromEmailAddress("john@example.com"); // okay
message.setToEmailAddress("jonny@invalid-example.com"); // throws exc |
Factory classes are classes that create behaviour instances. They are registered using the method addFactory in the ElmoModule or in the file "META-INF/org.openrdf.elmo.factories", using the same format as for roles. All factory classes must use the @factory annotation on their class and each of their factory methods. The factory class must have either a static getInstance method or a public default constructor.
The factory classes are mapped in the org.openrdf.elmo.factories file or mapped with role annotations. The role annotations may be placed on the factory class or on the return type of the factory methods or on an interface implemented by the return type. The return type of each factory method must also be registered for, or be an interface of, the entity being constructed. If the factory method has a parameter it must be assignable from the entity being constructed.
See the Section called Dependency Injection for an example.
Any mapped @rdf property value, that is not an entity, is either a java.util.Set or a literal object (java.util.List is an entity). A datatype (literal Class) must be Serializable, have a String constructor, or a static valueOf method, if not directly supported by the LiteralManager. Elmo ships supporting most common Java literal Objects. Elmo also includes mapping to primitive XML-Schema data-types that correspond to a standard Java Class.
Elmo, like Java5 collections, uses run-time type checking on object retrieval when casting. Elmo uses the data-type of literals exclusively to look up the Java Object that should be instantiated and does not convert the Object to conform to a property type. If the data-type of the literal in the repository does not correspond to the properly type of the concept a ClassCastException will occur at run-time when the literal is retrieved. Any data that is imported into the repository must contain the correct data-types for every literal, according to the org.openrdf.elmo.datatypes files. Note that no data-type is used for java.lang.String class.
Elmo does has some flexibility when retrieving literals from the repository. For example the xsd:positiveInteger and xsd:integer, both resolve to java.math.BigInteger. However, in the repository, literals of data-type xsd:positiveInteger do not equal literals of xsd:integer, therefore if a collection contains "1"^^xsd:positiveInteger it may not contain "1"^^xsd:integer. This will cause the Set#contains(Object) to fail when searching for a match of java.math.BigInteger("1") as it will be searching for "1"^^xsd:integer.
This data-type mapping can be extended by including a file "META-INF/org.openrdf.elmo.datatypes". This file may be included in any number of jars or paths in the Java class-path. The format of the file is the full class name of the type assigned to the URI of the data-type that should be used when saving it to the repository.
javax.xml.datatype.XMLGregorianCalendar = http://www.w3.org/2001/XMLSchema#dateTime java.util.Locale = http://www.w3.org/2001/XMLSchema#language |
By default Elmo uses XMLGregorianCalendar as the Java Object for all XML-Schema time or date related data-types. When adding an XMLGregorianCalendar to the repository it will use the xml schema type of the object when storing. Other date objects are supported, but by default will use the data-type "java:" + full class name of the Object.
Elmo properties can use the collection type java.util.Set and java.util.List for property values. Properties with this type can be assigned instances from the Java collections framework, that implement one of these two interfaces. When removing entities from the repository, any property with a type of java.util.List will need to remove the list explicitly before removing the entity itself. This is not the case for java.util.Set, as it is removed along with the entity.
Through the adaptor SesameContainer, entities of type rdfs:Container, including rdf:Seq, rdf:Alt, and rdf:Bag will implement java.util.List. When other java.util.List objects are merged into the repository, through a setter method or explicitly with the merge method, they will have the rdf:type rdfs:Container and when loaded again they will use this adaptor.
The adaptor SesameList implements java.util.List for resources of type rdf:List. Often RDF file formats provide a short hand format for rdf:List resources. These short hands may not include the required rdf:type statement for the list and therefore may not be imported properly into the repository for Elmo to read. In addition, rdf:Lists are implemented as a chain of rdf:Lists, so when removing them from the repository each must be removed explicitly, or cleared before being removed. Because of these three limitations for rdf:List resources, it is recommended to use one of the above rdfs:Containers as an alternative to rdf:List.
Another RDF collection is a set of statements that share a common subject and predicate. These statements are contained in the class ElmoProperty, which implements java.util.Set for the statement's objects. This collection is used for every @rdf property that returns a java.util.Set. Therefore collection properties can exist for java.util.Set, java.util.List, or one of the previously listed RDF concepts (rdf:Seq, rdf:Bag, rdf:Alt).
An alternative to creating static concepts is the DynaClass/DynaBean interface from commons-beanutils. The DynaBeanSupport behaviour provides the DynaBean interface for any resource. It uses the RDFS / OWL ontology in the repository to resolve none-prefixed property names. When using none-prefixed property name, they will either return a single value or a set of values depending on the ontology specification of the property. When using prefixed properties, however, the ontology is not used and all properties return a set. Property can be prefixed with a namespace prefix from the repository or the entire predicate URI. This behaviour is not active by default. Here is an example of how this behaviour can be activated and used.
ElmoModule module = new ElmoModule();
module.addRole(DynaClassSupport.class);
module.addRole(DynaBeanSupport.class);
factory = new SesameManagerFactory(module);
manager = factory.createElmoManager();
QName id = new QName(NS, "E340076");
QName type = new QName("http://www.example.com/rdf/2008/model#","User");
DynaClass userClass = (DynaClass) manager.find(type);
DynaBean entity = manager.rename(userClass.newInstance(), id);
entity.set("userName", "john");
assertEquals("john", entity.get("userName")); |
The included elmo-codegen.jar can be used from the command line to create an RDF ontology file from existing JavaBeans or generate Elmo roles from an RDF ontology file. The command below will search the given jar (example-entities.jar) for classes in the package com.example.entities and output an OWL DL ontology in example-ontology.owl using the given ontology URI and the same namespace.
java -jar elmo-codegen.jar \
-b "com.example.entities=http://www.example.com/rdf/2008/model#" \
-r example-ontology.owl \
example-entities.jar |
In the example below, the ontology example-ontology.owl will be imported, and Elmo concepts that are defined by the given ontology URI will be created and compiled in example-concepts.jar. This jar will then be ready to be used for development and deployment of an Elmo application.
java -jar elmo-codegen.jar \
-b "com.example.concepts=http://www.example.com/rdf/2008/model#" \
-j example-concepts.jar \
example-ontology.owl |
The elmo-codegen.jar will import the ontologies and concepts from elmo-rdfs.jar and elmo-owl.jar, so they don't need to be specified on the command line. However, other dependent concepts jar files must be included at the end of the command.
The elmo-codegen.jar will create concept interfaces from OWL classes and included getters and setters for each of the declared properties within the concept interface. Method declarations will be included in the interface if a special class hierarchy is be used. Any class that extends elmo:Message, where elmo is mapped to the namespace "http://www.openrdf.org/rdf/2008/08/elmo#", will be used to create a method declaration in a corresponding interface. There are three property restrictions that are used to determine the declaration. All values from restrictions on elmo:target determines the concept that should contain this method. Restrictions on elmo:objectResponse determine the return type of the method, use owl:Nothing for void return types. Restrictions on elmo:literalResponse determine the return type of methods with a literal response. Methods that return a primitive type should include a owl:cardinality restriction of 1 on elmo:literalResponse. Both response properties can be restricted with owl:maxCardinality of 1 to indicate they return a single result and not a Set of results. Any other properties declared for classes that extend elmo:Message will be included as parameters in the method declaration ordered by their URI.
The following RDF/XML fragment will produce an interface called "HelloWorld" with a method "hello" that returns a single String.
<owl:Class rdf:ID="HelloWorld"/>
<owl:Class rdf:ID="hello">
<rdfs:subClassOf="&elmo;Message"/>
<rdfs:subClassOf>
<owl:Restriction>
<owl:onProperty rdf:resource="&elmo;target"/>
<owl:allValuesFrom rdf:resource="#HelloWorld"/>
</owl:Restriction>
</rdfs:subClassOf>
<rdfs:subClassOf>
<owl:Restriction>
<owl:onProperty rdf:resource="&elmo;literalResponse"/>
<owl:allValuesFrom rdf:resource="&xsd;string"/>
</owl:Restriction>
</rdfs:subClassOf>
<rdfs:subClassOf>
<owl:Restriction>
<owl:onProperty rdf:resource="&elmo;literalResponse"/>
<owl:maxCardinality>1</owl:maxCardinality>
</owl:Restriction>
</rdfs:subClassOf>
</owl:Class> |
Classes that extends elmo:Message will also generate their own concept interfaces. These concepts will have properties that correspond to the parameters of the method. They will also extend the interface org.openrdf.elmo.Message and have behaviour that implements elmoInvoke() that correctly maps the concept properties to the method parameters invoking the method on the elmoTarget property value.
The elmo-codegen.jar tool can compile valid Java or Groovy behaviour methods while generating concepts and package them both into the generated jar. Any owl:ObjectProperties that extend elmo:method with the annotation elmo:java or elmo:groovy will be used to create a behaviour method for a corresponding concept method. The rdfs:domain indicates the concept that should implement this method and rdfs:range indicates the method declaration that is implemented by the elmo:java or elmo:groovy annotation.
If the method includes parameters they will be available as read only variables in the method body. The name will be a combination of the compiled namespace prefix of the property joined with the local name of the property in the same way they are joined to create Java properties on concepts.
The following RDF/XML fragment will create a behaviour method that implements the hello method, returning the string "World" for resources that implement the HelloWorld concept.
<owl:ObjectProperty rdf:ID="world"> <rdfs:subPropertyOf rdf:resource="&elmo;method"/> <rdfs:domain rdf:resource="#HelloWorld"/> <rdfs:range rdf:resource="#hello"/> <elmo:java>return "World";</elmo:java> </owl:ObjectProperty> |
Methods with multiple parameters will also include an bridge method that accepts a java.util.Map argument. Although this bridge method has no interface it is available through reflection in the proxy entity and therefore available to interpreted languages, for example, in Groovy's named parameter syntax.
The Elmo scutter is a generic RDF crawler that follows rdfs:seeAlso links in RDF documents, which typically point to other relevant RDF sources on the web. The Elmo scutter is based on original code by Matt Biddulph for Jena.
RDF(S) seeAlso is also the mechanism used to connect FOAF profiles and thus (given a starting location) the scutter allows to collect FOAF profiles from the Web. Several advanced features are provided to support this scenario:
Blacklisting: sites that produce FOAF profiles in large quantities are automatically placed on a blacklist. This is to avoid collecting large amounts of uninteresting FOAF data produced by social networking and blogging services or other dynamic sources.
White listing: the crawler can be limited to a domain (defined by a URL pattern).
Metadata: the crawler can optionally store metadata about the collected statements.
Filtering: incoming statements can be filtered individually. This is useful to remove unnecessary information, such as statements from unknown name-spaces.
Persistence: when the scutter is stopped, it saves its state to the disk. This allows to continue scuttering from the point where it left off. Also, when starting the scutter it tries to load back the list of visited URLs from the repository (this requires the saving of metadata to be turned on).
Logging: The Scutter uses slf4j to provide a detailed logging of the crawler.
The data collected by the scutter is stored in a Sesame repository. We recommend using a Native RDF repository for scuttering, because it provides the best performance for uploads.
The Scutter is available as a Java class as well as a Java servlet. The servlet provides access to all of the above features, except for filtering (which requires programming) and it can be deployed by placing the Elmo.war file in the web application directory of a Servlet/JSP container.
The servlet initialization parameters to be specified in the web.xml descriptor file are listed below. An example web.xml file is provided in the war file.
| Parameter | Description | Optional/Default |
| server | URL of the Sesame server to store the collected data | Required |
| repository | Name of the repository on the server | Required |
| username | Username for access to the Sesame repository | Optional |
| password | Password for access to the Sesame repository | Optional |
| queue | Location of the file used to save the queue when the scutter is stopped | Required |
| start | URL(s) used to start scuttering. URLs should be separated by white space. | Optional |
| domain | Limits crawling to URLs that match the provided regular expression. | Optional |
| metadata | Produce reified statements containing information about the provenance of the statements and the time they were collected. Possible values: true/false | Optional, defaults to false. |
| autoblacklist | Enable/disable automatic blacklisting. Possible values: true/false | Optional, defaults to true (enabled). |
| vocab | Restrict crawling to FOAF specific vocabularies (statements with predicates from the RDF, RDFS, FOAF or WGS_84 namespaces) | Optional, only possible value is 'foaf' |
| focused | Collect data about a specific set of target persons. The target persons are given as foaf:Person instances in the repository. | Optional, actual value is ignored |
| maxThreads | Maximum number of threads allowed to be running. Must be a positive integer. | Optional, defaults to 20. |
The request parameters to the server are listed in the table below. For convenience, there is an html file provided in the distribution for calling various operations on the servlet.
| Parameter | Description | Optional/Default |
| start | Try to load the set of visited URLs and start the scutter | Parameter value ignored. |
| stop | Stop the scutter, save the queue to disk | Parameter value ignored. |
| preloadQueue | Preload the queue from the saved file | Parameter value ignored. |
| clear | Clear the queue and the set of visited URLs | Parameter value ignored. |
A custom filtering of statements can be implemented by setting an instance of the StatementFilter interface using the setStatementFilter method of the Scutter class. See the JavaDoc for more details.
The task of the Elmo smusher is to find equivalent instances in large sets of data. This is a very common problem when processing collections of FOAF profiles as several sources on the Web may describe a the same individual using different identifiers or blank nodes (which are always assumed to be different). While the servlet provided is specific to smushing foaf:Person instances, the underlying mechanism is generic
The smusher uses instances of ResourceComparator for comparing instances. Implementations of ResourceComparator are given for foaf:Person and swrc:Publication.
The smusher reports the results (matching instances) by calling methods on registered listeners. Listeners implement the SmusherListener interface. Two implementations of SmusherListener are provided: one writes out results in text, while the other represents matches using the owl:sameAs relationship and uploads such statements to a Sesame repository. While Sesame does not directly support OWL semantics, the semantics of this relationship (the equivalence of property values) can be easily axiomatized using Sesame's custom rule language.
The concept interface can appear limited and may not fit well into existing Java interfaces. This can be resolved by creating a behaviour adaptor. Entities will implement all interfaces on each of its behaviours. By creating annotated abstract methods on the behaviour to store the property values, it can adapt these values to an established Java interface.
An example of this are the RDF collection behaviours, implementing java.util.List, described in the Section called RDF Collections.
In AOP, cross-cutting-concerns are separated into aspects that contain a small amount of behaviour used throughout a domain model. These aspects are then weaved into the primary model at matching point-cuts. Elmo includes support for aspects as a single behaviour or interceptor class, which declare their own point-cuts in the role annotations, method declaration, and @intercepts annotation.
Behaviour methods are chained together if they have the same signature. The chain is broken when a method returns a non-null value, true, or a non-zero primitive. Chained methods with a void return type cannot break the chain until all behaviours in the chain are evaluated.
public class SupportAgentEmailReader {
public boolean readEmail(Message message) {
if (message.getToEmailAddress().equals("help@support.example.com")) {
// process email here
return true;
} else {
return false;
}
}
}
public abstract class PersonalEmailReader implements EmailUser {
public boolean readEmail(Message message) {
String un = getUserName();
if (message.getToEmailAddress().equals(un + "@example.com")) {
// process email here
return true;
} else {
return false;
}
}
}
String agentType = "http://www.example.com/rdf/2008/model#SupportAgent";
String userType = "http://www.example.com/rdf/2008/model#User";
ElmoModule module = new ElmoModule();
module.addRole(SupportAgent.class, agentType);
module.addRole(SupportAgentEmailReader.class, agentType);
module.addRole(PersonalEmailReader.class, userType);
module.addRole(EmailUser.class, userType);
factory = new SesameManagerFactory(module);
manager = factory.createElmoManager();
QName id = new QName(NS, "E340076");
manager.designate(User.class, id);
manager.designate(SupportAgent.class, id);
EmailUser user = (EmailUser) manager.find(id);
user.setUserName("john");
Message message = manager.designate(Message.class);
message.setToEmailAddress("john@example.com");
if (user.readEmail(message)) {
// email has been read
} |
Typical OO design tends to implement communicational cohesion - that is, the methods are grouped together into a class because they operate on the same data. With Elmo the higher level of functional cohesion is achieved by separating the class even further into functional behaviours. Test driven development becomes much easier at this level of cohesion, making each unit test small and easily understood. This allows the design to meet the single responsibility principle, giving each behaviour class one, and only one, reason to change.
Elmo also supports context specific entity managers. We have already demonstrated how a entity can implement both a SalesRep and an Engineer, but what if someone is a SalesRep only within a specific context and not otherwise?
Entity managers can be created with multiple inclusive contexts. The ElmoModule class is optionally associated with a context, described as a QName. When these modules include other modules they also inherit a read-only view of the included contexts. New factories created against a module with a context will only read property values from that context and any of the included contexts. When a property is changed to a new value, the old value is removed only if it is in the primary context. When using contexts, each triple statement is stored as a 4-tuple with the primary context as the fourth value.
In the example below we can see that the employee E340076 is a SalesRep in the first period and an Engineer in the second with a different salary. Any manager with the common context will have E340076 as a employee named John, but only in period1 will John be a SalesRep with a salary of 90, and only in period2 will John be an Engineer with a salary of 100. In this way contexts can also be used to setup security domains around sensitive data.
QName c = new QName(NS, "Period-common");
QName p1 = new QName(NS, "Period-1");
QName p2 = new QName(NS, "Period-2");
ElmoModule module = new ElmoModule();
module.addRole(Employee.class);
module.addRole(SalesRep.class);
module.addRole(Engineer.class);
module.addRole(SalesRepBonusBehaviour.class);
module.addRole(EngineerBonusBehaviour.class);
module.setContext(c);
ElmoModule m1 = new ElmoModule().setContext(p1).includeModule(module);
ElmoModule m2 = new ElmoModule().setContext(p2).includeModule(module);
repository = new SailRepository(new MemoryStore());
repository.initialize();
factory = new SesameManagerFactory(module, repository);
factory1 = new SesameManagerFactory(m1, repository);
factory2 = new SesameManagerFactory(m2, repository);
common = factory.createElmoManager();
period1 = factory1.createElmoManager();
period2 = factory2.createElmoManager();
Employee emp;
Object obj;
QName id = new QName(NS, "E340076");
emp = common.designate(Employee.class, id);
emp.setName("John");
SalesRep slm = period1.designate(SalesRep.class, id);
slm.setTargetUnits(10);
slm.setUnitsSold(15);
slm.setSalary(90);
Engineer eng = period2.designate(Engineer.class, id);
eng.setBonusTargetMet(true);
eng.setSalary(100);
obj = common.find(id);
assertTrue(obj instanceof Employee);
assertFalse(obj instanceof SalesRep);
assertFalse(obj instanceof Engineer);
emp = (Employee) obj;
assertEquals("John", emp.getName());
assertEquals(0.0, emp.getSalary());
obj = period1.find(id);
assertTrue(obj instanceof Employee);
assertTrue(obj instanceof SalesRep);
assertFalse(obj instanceof Engineer);
emp = (Employee) obj;
assertEquals("John", emp.getName());
assertEquals(90.0, emp.getSalary());
assertEquals(6.75, emp.calculateExpectedBonus(0.05), 0);
obj = period2.find(id);
assertTrue(obj instanceof Employee);
assertFalse(obj instanceof SalesRep);
assertTrue(obj instanceof Engineer);
emp = (Employee) obj;
assertEquals("John", emp.getName());
assertEquals(100.0, emp.getSalary());
assertEquals(5, emp.calculateExpectedBonus(0.05), 0); |
Elmo supports seamless data localization for String values. This is activated on concept properties with the @localized annotation. By setting the locale of the ElmoManager, the property values will be retrieved and stored for the closest language that matches the manager's locale.
factory = new SesameManagerFactory(new ElmoModule());
english = factory.createElmoManager(Locale.ENGLISH);
french = factory.createElmoManager(Locale.FRENCH);
QName id = new QName(NS, "D0264967");
document = (DcResource) english.find(id);
document.setTitle("Elmo User Guide");
document = (DcResource) french.find(id);
assert "Elmo User Guide".equals(document.getTitle());
document.setTitle("Elmo Guide de l'Utilisateur");
assert "Elmo Guide de l'Utilisateur".equals(document.getTitle());
english = factory.createElmoManager(Locale.ENGLISH);
document = (DcResource) english.find(id);
assert "Elmo User Guide".equals(document.getTitle()); |
Hyperslices are used to capture multi-dimensional separation-of-concerns. They can be thought of as aspects or program-slices managed at a higher level of concern. A hyperslice is a complete object behaviour hierarchy intended to encapsulate concerns in dimensions other than the dominant one. The behaviour classes within a hyperslice are weaved using the annotations defined in the Section called Role Annotations.
Above we can see examples of three dimensional hyperslices. The top hyperslice shows an object hierarchy defining the salary range and adequate resources behaviours. These behaviours use polymorphism to extend or override the super-classes behaviour. The bottom left captures the complex concern of calculating bonuses. In this hyperslice, each department maintains its own calculation for assigning bonuses, which can differ within a department and by job. The hyperslice on the right shows how a third dimension could also be captured within this domain model.
Without hyperslicing, classes would have to be created for each combination of the dimensions (experience level, department, and job). For example SeniorAccountingManager, JuniorShippingEngineer, ..etc. The behaviours described above might potentially be scattered throughout the hierarchy. However, by using hyperslicing in Elmo, these individual concerns are separated into distinct manageable packages, simplified to only the relevant dimension, and weaved together as needed.
Not all behaviours are self sufficient; some need additional support from other objects or components. In the example below we can see the behaviour BankAccountSupport delegating the method calls to the injected BankAccountService.
@rdf("http://www.example.com/rdf/2008/model#BankAccount")
public interface BankAccount extends BankAccountBehaviour {
@rdf("http://www.example.com/rdf/2008/model#accountNumber")
public long getAccountNumber();
public void setAccountNumber(long number);
}
public interface BankAccountBehaviour {
public double getBalance();
public void withdraw(double amount);
public void deposit(double amount);
}
public class BankAccountSupport implements BankAccountBehaviour {
private AccountReference account;
private BankAccountService service;
public void setAccountReference(AccountReference account) {
this.account = account;
}
public void setBankAccountService(BankAccountService service) {
this.service = service;
}
public double getBalance() {
return service.getBalanceOfAccount(account);
}
public void withdraw(double amount) {
service.withdrawFormAccount(account, amount);
}
public void deposit(double amount) {
service.depositToAccount(account, amount);
}
}
@factory
@rdf("http://www.example.com/rdf/2008/model#BankAccount")
public class BankAccountFactory {
@factory
public BankAccountBehaviour createBankAccountBehaviour(BankAccount account) {
BankAccountSupport behaviour = new BankAccountSupport();
long number = account.getAccountNumber();
assert number > 0;
BankAccountService service = BankAccountService.getInstance();
behaviour.setAccountReference(service.getAccountReference(number));
behaviour.setService(service);
return behaviour;
}
}
ElmoModule module = new ElmoModule();
module.addRole(BankAccount.class);
module.addFactory(BankAccountFactory.class);
factory = new SesameManagerFactory(module);
manager = factory.createElmoManager();
long number = 10000001;
QName qname = new QName(NS, Long.toString(number));
BankAccount account = manager.designate(BankAccount.class, qname);
account.setAccountNumber(number);
double balance = account.getBalance(); // will call accountService |
In the example above the BankAccount interface is the Elmo concept with the mapped concept property "accountNumber", and the BankAccountBehaviour interface is the behaviour interface of BankAccountSupport. This behaviour has the factory BankAccountFactory, which is responsible for creating the support class instances and injecting them with the BankAccountService instance.
When static events need to be triggered from methods called on a entity, such as their property values changing, an interceptor or behaviour should be created. Interceptors are described in the Section called Interceptors. For dynamic observers that are interested in property modification, the PropertyChangeNotifierSupport interceptor can be used by registering it with the subject types that need to be observed.
The PropertyChangeNotifierSupport interceptor implements the PropertyChangeNotifier interface, tracking PropertyChangeListeners (observers) and notifying them when a setter property method has been called on the entity or a Set has been modified. If the notifier repository is used and the manager is not in auto-flush mode when the property is modified, the entity will not inform its listeners until the connection's state has changed (rollback or commit), otherwise the listeners will be informed before the setter method returns. Only when the event is fired from within the setter method is the listener informed of the property and new value of the change. This behaviour is not active by default, and can be activated at run-time (shown below) or placed into a resource file "META-INF/org.openrdf.elmo.roles" as stated in the Section called Registering Concepts & Behaviours.
ElmoModule module = new ElmoModule();
module.addRole(PropertyChangeNotifierSupport.class,
"http://www.example.com/rdf/2008/model#Employee");
repository = new SailRepository(new MemoryStore());
repository = new NotifyingRepositoryWrapper(repository, true);
repository.initialize();
factory = new SesameManagerFactory(module, repository);
manager = factory.createElmoManager();
QName id = new QName(NS, "E340076");
Employee employee = manager.designate(Employee.class, id);
TestListener listener = new TestListener();
((PropertyChangeNotifier) employee).addPropertyChangeListener(listener);
manager.setAutoFlush(false);
employee.setName("john");
assertFalse(listener.isUpdated());
manager.flush();
assertTrue(listener.isUpdated()); |
There are two ways to implement rules in Elmo. One way is to used goal driven, or backward chaining, approach. A virtual read-only property is created on an interface that is implemented by a behaviour rule. The rule is executed whenever the property is read and the state of the property is calculated and returned. By using Elmo's build-in method chaining, these rules can be executed in turn until the result is complete. A good example of this are validation rules. Take for example the following example, a rule that states a team must have a team lead before it can be given a project.
public abstract class TeamHasNoTeamLeadRule implements Team {
public boolean isRejectingProjects() {
return getTeamLead() == null;
}
public void collectReasonsForRejectingProjects(List<String> reasons) {
if (getTeamLead() == null) {
reasons.add("Team does not have a team lead.");
}
}
}
public abstract class TeamAlreadyHasProjectRule implements Team {
public boolean isRejectingProjects() {
return getProject() != null;
}
public void collectReasonsForRejectingProjects(List<String> reasons) {
if (getProject() != null) {
reasons.add("Team already has project.");
}
}
} |
Another technique that is used is called data driven, or forward chaining. These types of rules have the advantage of persisting their state and can also be used in a filter of a query. Below is a behaviour that is triggered when the expenses change on a project, if the expenses are over budget the overbudget state is modified. This triggers the overbudget rules, which will alert the manager using the injected messaging service.
@rdf("http://www.example.com/rdf/2008/model#Project")
public interface Project {
@rdf("http://www.example.com/rdf/2008/model#manager");
public Manager getManager();
public void setManager(Manager manager);
@rdf("http://www.example.com/rdf/2008/model#expenses");
public double getExpenses();
public void setExpenses(double expenses);
@rdf("http://www.example.com/rdf/2008/model#budget");
public double getBudget();
public void setBudget(double budget);
@rdf("http://www.example.com/rdf/2008/model#overBudget");
public boolean isOverBudget();
public void setOverBudget(boolean over);
}
public abstract class ExpensesOverBudgetRule implements Project {
public void setExpenses(double expenses) {
if (expenses > getBudget()) {
setOverBudget(true);
} else if (isOverBudget()) {
setOverBudget(false);
}
}
public void setBudget(double budget) {
if (budget < getExpenses()) {
setOverBudget(true);
} else if (isOverBudget()) {
setOverBudget(false);
}
}
}
public class AlertManagerRule implements Project {
public void setOverBudget(boolean over) {
MessagingService messaging = MessagingService.getInstance();
if (over) {
messaging.alert(getManager(), "Project is over budget");
} else {
messaging.inform(getManager(), "Project is within budget");
}
}
} |
Entities are not serializable. They store all their properties in the repository and are just a facade to the underlying connection. To store and retrieve an Entity, the method getQName will return a QName (or null if an anonymous entity), which is serializable. This can later be used to access the entity with find(QName) method. By serializing the QName of the entity instead of the entity itself, the entity can be restored by another manager on another connection.
Below is an example of how to serialize and deserialize the entire repository. For more information about how to selectively export or to export in other formats like XML, please see the Sesame User Guide.
File file = new File("repository.rdf");
Writer writer = new FilteWriter(file);
manager.getConnection().exportStatements(new RDFXMLWriter(writer));
writer.close();
File file = new File("repository.rdf");
manager.getConnection().add(file, "", RDFFormat.RDFXML); |
Elmo dynamically includes appropriate behaviours for entities based on subject type mapping. In the example below, the calculateExpectedBonus method on Employee has two implementations, one for SalesRep and one for Engineer. Elmo will include the appropriate strategy (or behaviour) for the given subject.
public abstract class SalesRepBonusSupport implements SalesRep {
public double calculateExpectedBonus(double percent) {
int units = getUnitsSold();
int target = getTargetUnits();
if (units > target) {
return percent * getSalary() * units / target;
} else {
return 0;
}
}
}
public abstract class EngineerBonusSupport implements Engineer {
public double calculateExpectedBonus(double percent) {
boolean target = isBonusTargetMet();
if (target) {
return percent * getSalery();
} else {
return 0;
}
}
}
ElmoModule module = new ElmoModule();
module.addRole(SalesRep.class);
module.addRole(Engineer.class);
module.addRole(SalesRepBonusSupport.class);
module.addRole(EngineerBonusSupport.class);
factory = new SesameManagerFactory(module);
manager = factory.createElmoManager();
QName id = new QName(NS, "E340076");
Engineer eng = manager.designate(Engineer.class, id);
eng.setBonusTargetMet(true);
eng.setSalery(100);
Employee employee = (Employee) manager.find(id);
double bonus = employee.calculateExpectedBonus(0.05);
assertEquals(5.0, bonus); |
The ElmoManager is in auto-commit mode by default with no active transaction. To activate a transaction call the begin method on the transaction object returned from getTransaction - manager.getTransaction().begin(). The transaction is closed by calling manager.getTransaction().commit() or manager.getTransaction().rollback(). It is recommended to use only one active transaction per manager.
ElmoManager implements the memento design pattern. Because Elmo is transaction based, only operations that were performed by the entity pool the memento was created in, will be undone. When using multiple entity pools, or using the ThreadProxyRepository with multiple connections, multiple memento objects must be created for each. Memento support is only available when the repository stack contains a notifying repository. Below is an example of how to use the undo feature in Elmo.
ElmoModule module = new ElmoModule();
module.addRole(Employee.class);
repository = new SailRepository(new MemoryStore());
repository = new NotifyingRepositoryWrapper(repository, true);
repository.initialize();
factory = new SesameManagerFactory(module, repository);
manager = factory.createElmoManager();
Employee emp = manager.designate(Employee.class);
Memento memento = manager.createMemento();
emp.setName("John");
assertEquals("John", emp.getName());
manager.undoMemento(memento);
assertNull(emp.getName()); |
The memento object does not lock any modified resources. If the same resources have been modified outside of the memento object and cause a conflict when the memento is undone, unexpected results can appear in the repository, so use with caution.
Datatypes must be able to store their state in character format, but may have repository conversion optimizations as MemoryStore does. When using non-String literal property values, overhead of conversion can occur particularly if no String constructor is present and no static valueOf method exists. The default LiteralManager ships with common Java conversions and also supports valueOf method, String constructors, and Java serialization. Java serialization is provided as a catch-all solution and often under-performs compared to object-specific converters. When using non-standard literal objects, ensure that they have a static valueOf method or String constructor (if this is not possible, consider using a custom LiteralManager).
When using non-local repositories, overhead costs exist for each repository hit. If concept properties are retrieved in nested loops, the overhead cost can add up quickly. In this case the repository client can be wrapped in an AugurRepository or ReadAheadRepository. The AugurRepository can reduce the number of hits to the delegate connection from o(m^n) to o(n) by tracking requests and anticipating related information. The ReadAheadRepository reduces the number of hits to the repository for the same subject.
Augur is best used when the complete results are expected to fit into memory and not all the properties will be read. ReadAhead is best used when the complete results may not fit into memory and most of the concept properties will be retrieved. Below is an example of using the AugurRepository or ReadAheadRepository in a SesameManagerFactory:
repository = new HttpRepository("http://server.example.com/openrdf-sesame/", "default");
repository = new AugurRepository(repository); // or new ReadAheadRepository(repository);
repository.initialize();
ElmoModule module = new ElmoModule();
Module.addRole(Employee.class);
factory = new SesameManagerFactory(module, repository);
manager = factory.createElmoManager();
// By using Augur all information is gathered in 6 hits.
// By using ReadAhead: 1 + number of employees.
// Otherwise it would hit: 1 + 5 * number of employees.
for (Employee emp : manager.findAll(Employee.class)) {
String name = emp.getName();
String address = emp.getAddress();
String phone = emp.getPhoneNumber();
String email = emp.getEmailAddress();
String details = name + address + phone + email;
System.out.print(details);
} |
Above, five properties are retrieved for each employee. By using just the AugurRepository the repository will be hit for:
a list of employees,
the type of each employee,
the name of each employee,
the address of each employee,
the phone number of each employee, and finally
the email address of each employee.
With just the ReadAheadRepository every property will be retrieved at once for each entity. Without anything the repository will be hit for every method called.
There is extra overhead for tracking what properties need to be and have been retrieved. When using a local repository, such as MemoryStore or NativeStore directly, the extra overhead does not outweigh the benefits.
The subject types of every entity are stored in the repository using the predicate "http://www.w3.org/1999/02/22-rdf-syntax-ns#type". The designate method adds the types to the repository and are read during create and find methods. RDF data by design is semi-structured, making it necessary to read the type of each entity. However, in some domains, reading the type of the entity may not be necessary. By overriding the default resource manager, domainspecific optimizations can be performed.
Elmo creates new entity proxy classes dynamically at run-time to account for the different combinations of roles and to implement the RDF property mapping. These classes are only created once for each combination, and are reused for subsequent entities. When the necessary role combinations are known in advance and compiled classes are included in the Java class-path, no classes will be created at run-time. When the system property "elmobeans.target" is set, every dynamic class created will be saved to the folder value of the property. These class files can be packaged into a jar and deployed with an Elmo application to avoid new classes being created at run-time.
There are three types of classes created: mappers, entities, and behaviours. They are created in the package elmobeans.mappers, elmobeans.proxies, and elmobeans.behaviours respectively. The class names are hash combinations of the names of the roles they implement. The mappers are created by ElmoMapperClassFactory implementing ElmoMapperResolver, the proxies and factories are created by ElmoEntityCompositor implementing ElmoEntityResolver. Both implementations attempt to load existing classes using ClassFactory before creating a new copy. Elmo uses Javassist to generate the Java byte-code. If all role combinations are compiled in advance, these implementations can be substituted.
When using the SesameManagerFactory, all entities created will implement the interface Entity and SesameEntity. The Entity has access to the qualified-name of the resource as a QName and the ElmoManager. The SesameEntity interface provides access to the the Sesame resource for this entity and the SesameManager instance used to create it. The SesameManager provides access to the Sesame repository connection used by this manager.
The entity's non-functional properties (type of java.util.Set) stream the results from the repositories and should be closed after every iteration to clean up temporary resources. They will close themselves automatically once all the results have been read. However, the manager also has a close(Iterator) method to close opened iterators. This can be used when not all results have been read.
Elmo provides a subset of the JPA implementation for easier integration into existing frameworks. EntityManager is an Enterprise JavaBean persistence interface for relational mapping of Entity Objects. Elmo ships supporting a subset of this interface. This interface provides a standard way for applications to interact with the Bean pool. Because entities are dependent on the repository connection they can only be used within a transaction.
To use Elmo through JPA, use the provider org.openrdf.elmo.sesame.SesamePersistenceProvider with the property repositoryId, one of dataDir or applicationId, with optional property resources, which are described below.
the Sesame data directory, stores multiple repository configurations and persistence data.
when no dataDir is provided use this unique id to identify a system specific dataDir location.
the remote Sesame server URL, an alternative to dataDir and applicationId.
the repository configuration id stored in the dataDir or serverUrl, if none exists a persisting MemoryStore repository configuration will be created with this id.
load RDF datasets from resource files - file can either be a supported RDF file or a property file listing RDF resources to load, optionally assigned to a context.
See the FOAF example for how to setup Elmo's JPA implementation in Spring.
Elmo does not support detached entities (unlike other JPA implementations). Therefore entities can only be used within a JPA transaction. Elmo supports foreign entities being merged into the pool if they implement an registered interface, however, they are only copied and cannot be attached to the session. In Elmo the merge and persist method do the same thing. Once an entity has been persisted any further changes to it will not be reflected in the repository.
Elmo includes a set of configuration and classes that can be used to allow HiveMind to manage the ElmoManager as a singleton service. It also includes some configuration points that can be used to provide additional configuration inside a hivemind.xml file.
See the GEDCOM example for how to setup Tapestry and Elmo.
Working examples of the above code samples can be found under the src folder in the file UserGuideTest.java. The elmo-examples.war includes three small applications to help better understand how the different interfaces can be used.
User can input parameters, through channel.jsp or on the command line, which are then used to create a RSS feed. This example demonstrates the use of an application managing ElmoManager itself.
Using the local name of a FOAF entry, the servlet prints a listing of their friends and provides navigation along the social graph. In this example the application is using the EntityManager interface and the Spring container manages the entity pool.
With a search input the application lists all royal people with the given substring in their name from the repository. The application provides a browse and an edit screen. Here the entity pool is configured and managed by HiveMind. It uses DynaBean interface to set and retrieve concept properties.
The two inferencing annotations, when enabled, simply duplicate every add and remove call to the other predicates. There is no truth maintenance on removed statements as a reasoner would do. When two properties have the same equivalent (or inverseOf) predicate and the same value, but only one is removed, its equivalent predicate is also removed. If truth maintenance is necessary, it is recommend to add a reasoner to the repositorysail stack instead.
The disjointWith annotation is only read on the concept that is passed on the create method. This is not a guarantee that the concepts will be disjointed, but only serves as a assertive check within the create method. This check is only performed when assertions are enabled (-ea JVM flag).
The HTTP repository does not include uncommitted values when evaluating queries. The memory and native stores do however.
Elmo can use any Sesame repository that may or may not need a database. The two repositories used in this user guide, NativeStore and MemoryStore, do not use a database. Other repositories that use a backing database may scale better under higher volume or load.
The generator module can facilitate creating your own concepts, but Elmo also includes support for DynaBeans that allow accessing predicates by their local name, without using a concept interface.
Use the method removeDesignation on the manager to remove a concept from an entity.
Entitys keep their state in the repository and cannot be serialized in isolation. However, their QName can be serialized, and the entire repository can also be serialized.
Elmo concepts must first be created, see the Section called Compiling and Decompiling and Ontologies. Since Elmo concepts are also Java interfaces, a existing domain model could also implement the concepts, this would allow the same model to work in both Elmo and other entity pools.
When two behaviours implement the same method and are being integrated into the same class, Elmo chains the methods together calling one after another in no particular order until a value is returned or all methods have been called. See the Section called Chain of Responsibility for more information.
If the property types are the same, both setter methods will be called when the property is set and only one will be retrieved when the property is read. If the property types are not the same an exception is thrown when the concepts are combined.
Elmo supports interceptors that can be matched to some or all methods implemented by any role. Through this, cross cutting concerns like logging and security can be implemented in an Elmo entity pool.
By using interceptors to wrap a method invocation, events can be triggered before and after a method call. Elmo also provides the PropertyChangeListener stated in the Section called Observer.
On servlet initialization, the repository and manager factory should be initialized. For each request a new ElmoManager should be created and its Entitys can only be used within the request they were created with. The manager should be closed after the request is complete. The manager factory should be closed when the servlet is destroyed.
Create an interceptor to log the operations of every type and every method.
Elmo supports data contexts. In the Section called Context Specific Data it was shown how property values can be stored within a context. This can be used to separate security domains.
adapts one interface into another that a client expects. An adaptor allows classes to work together that normally could not because of incompatible interfaces by wrapping its own interface around that of an already existing class or interface.
is a part of a program that cross-cuts its core concerns, therefore violating its separation of concerns.
(Aspect Oriented Programming) attempts to aid programmers in the separation of concerns, specifically cross-cutting concerns, as an advance in modularization.
is a reusable block of computer code or script that, when applied to an object causes it to respond to input in meaningful patterns. In Elmo a behaviour is a class associated to a subject type.
is a series of processing objects. Each processing object contains a set of logic that describes the types of command objects that it can handle, and how to pass off those that it cannot to the next processing object in the chain. Behaviours with the same signature are chained together until a value is returned.
is an ordinal type of measurement. Modules with high cohesion tend to be preferable because high cohesion is associated with several desirable traits of software including robustness, reliability, re-usability, and understandability whereas low cohesion is associated with undesirable traits such as being difficult to maintain, difficult to test, difficult to reuse, and even difficult to understand.
is analogous to logical negation: the class extension consists of those individuals that are NOT members of the class extension of the complement class.
is a field of study within software engineering. It claims that software components, like the idea of hardware components, used for example in telecommunications, can ultimately be made interchangeable and reliable.
is a way and practise to combine simple objects or data types into more complex ones.
is a description of supported operations on a type, including syntax and semantics. In Elmo the semantics specify the subject type and property predicates of a Java interface.
allows client software to talk to repository server software, whether these exist on the same machine or not. A connection is required to send commands and receive answers, usually in the form of a result set. All JavaBean properties are stored and retrieved over a repository connection.
are aspects of a program which affect (crosscut) other concerns. These concerns often cannot be cleanly decomposed from the rest of the system in both the design and implementation, a