3 When implementing an editor in Simantics platform, it is very common that the graph representation cannot be directly used, but the editor needs to create an intermediate model of the subgraph it edits. Some reasons for this are:
4 * Accessing the database directly is not fast enough.
5 * An editor using the database directly holds frequently long read locks and cannot operate during write transactions.
6 * The editor needs to store auxiliary objects attached to the model.
7 * Editor modifies the intermediate model speculatively before the modification is committed to the database or canceled.
8 * The modifications in database cannot be applied in the editor immediately (for example during rendering).
9 * Third-party component requires certain classes to be used.
10 * Editor needs to be backend-agnostic and cannot directly use database API.
12 There are two different approaches for implementing the intermediate model:
15 The editor modifies the database directly and the changes in the database are eventually propagated to the intermediate model. The editor doesn't change the intermediate model directly.
17 ![triangleModel](triangleModel.png)
19 2. Bidirectional model
21 The editor operates only using the intermediate model and modifications are updated from intermediate model to the database and vice versa.
23 ![bidirectionalModel](bidirectionalModel.png)
25 By experience, the triangle model is easier to implement correctly, in particular when resources are linked to each other in complex fashion. The `org.simantics.objmap`-plugin tries to make the implementation of the bidirectional model easier by providing a framework for declaratively defining the update procedure between database and intermediate model. It can also be used with triangle model only for one direction or with hybrid model where some operations are implemented using the intermediate model and others modifying the database directly.
30 * For every operation from database to Java objects there is a corresponding operation from Java objects to database. This makes the framework easier to learn and undestand.
32 * The Java objects used with the framework do not need to implement any specific interface or follow some specific naming convention. The mapping schema can be defined using annotations or separately from the class definition.
33 * Support for different use scenarios
34 * bidirectional / unidirectional
35 * one shot / continuous
36 * automatic listening / manual updating
38 * For every resource there is a single corresponding Java object and vise versa. This makes the framework easier to understand. It is not a transformation framework.
42 *Mapping* consists of a set of resources called a *domain*, a set of Java objects called a *range* and a collection of *links*. Each link is attached to exactly one domain and range element and each domain and range element has at most one link attached to it. Additionally the link has a *link type* that contains requirements for the domain and range elements in the same link.
44 ![objectMappingTerminology](objectMappingTerminology.png)
46 A mapping is *up-to-date* if every domain and range element has a link and all links satisfy the requirements of their link types. The links of up-to-date mapping form a bijection from domain to range.
48 A *mapping schema* associates all domain and range elements with a link type. It is used to add new domain and range elements to the mapping.
52 The plug-in represents a mapping with interface `org.simantics.objmap.IMapping`. The interface is symmetric in the sense that every operation on the domain of the mapping has also a counterpart that operates on the range. Typically, if one of the operations requires a read graph, its counterpart requires a write graph. We will describe only the methods operating on the domain of the mapping:
54 Set<Resource> getDomain();
56 Returns the domain of the mapping. All set operations are supported. Adding a new domain element does not automatically create a link to it. Removal of a domain element removes also a link and the target element, but does not remove the element from the database.
58 Collection<Resource> updateDomain(WriteGraph g) throws MappingException;
60 Updates all domain elements whose counterpart is modified and creates new domain elements for previously added range elements. Returns the collection of domain elements that were modified or created in the update process.
62 Object get(Resource domainElement);
64 Returns the counterpart of a domain element or null if the element does not belong to the domain or does not have a link.
66 Object map(ReadGraph g, Resource domainElement) throws MappingException;
68 A convenience method that adds a domain element to the mapping and immediately updates the mapping and returns the corresponding range element.
70 void domainModified(Resource domainElement);
72 Tells the mapping that the domain element has been modified.
74 boolean isDomainModified();
76 Tells if some domain elements have been modified or added.
78 Collection<Resource> getConflictingDomainElements();
80 Returns a collection of domain elements which have been modified and also their counterparts in the mapping are modified. These elements are in conflict in the sense that the updating domain and range in different orders may produce different results.
82 void addMappingListener(IMappingListener listener);
83 void removeMappingListener(IMappingListener listener);
85 Adds or removes a listener for domain and range modifications.
87 # Defining a mapping schema
89 The primary way for defining a mapping schema is to use Java annotations. The current annotation support is still lacking. Only the following annotations are supported:
91 * Specifies the domain type that the class corresponds to.
92 * **RelatedValue(uri)**
93 * Specifies a correspondence between a field and functional property.
94 * **RelatedElement(uri)**
95 * Specifies a correspondence between a field and functional relation
96 * **RelatedElements(uri)**
97 * Specifies a correspondence between a field and a relation. The type of the field has to be a collection.
101 Suppose we have the following annotated classes:
104 @GraphType("http://www.simantics.org/Sysdyn#Configuration")
105 static class Configuration {
106 @RelatedElements("http://www.vtt.fi/Simantics/Layer0/1.0/Relations#ConsistsOf")
107 Collection<Component> components;
110 static abstract class Component {
113 @GraphType("http://www.simantics.org/Sysdyn#Dependency")
114 static class Dependency extends Component {
115 @RelatedElement("http://www.simantics.org/Sysdyn#HasTail")
117 @RelatedElement("http://www.simantics.org/Sysdyn#HasHead")
121 static abstract class Variable extends Component {
122 @RelatedValue("http://www.vtt.fi/Simantics/Layer0/1.0/Relations#HasName")
126 @GraphType("http://www.simantics.org/Sysdyn#Auxiliary")
127 static class Auxiliary extends Variable {
131 Them the schema can be defined as follows:
134 SimpleSchema schema = new SimpleSchema();
135 schema.addLinkType(MappingSchemas.fromAnnotations(g, Configuration.class));
136 schema.addLinkType(MappingSchemas.fromAnnotations(g, Dependency.class));
137 schema.addLinkType(MappingSchemas.fromAnnotations(g, Auxiliary.class));
140 # Using the mapping interface
142 Assume that a mapping scheme `scheme` has already been defined and `modelRoot` is the root resource of the model that the editor edits. Then the model is created as follows:
145 IMapping mapping = Mappings.create(scheme);
146 in read transaction {
147 MyModel model = (MyModel)mapping.map(graph, modelRoot);
151 There are different ways how the mapping can be updated. The following code forces update for all domain elements.
154 in read transaction {
155 for(Resource r : mapping.getDomain())
156 mapping.domainModified(r);
157 mapping.updateRange(graph);
161 If the range elements have some kind of "dirty" flags, the update can be optimized:
164 in write transaction {
165 for(Object obj : mapping.getRange())
166 if(obj implements MyObject && ((MyObject)obj).isDirty())
167 mapping.rangeModified(obj);
168 mapping.updateDomain(graph);
172 Often the editor has to update some auxiliary structures when the mapping modifies the range. This can be implemented for example as:
175 for(Object obj : mapping.updateRange(graph))
176 if(obj implements MyObject)
177 ((MyObject)obj).updateAuxiliary();
180 The most convenient way for updating the target would be to add graph request listeners for each domain element in the mapping. This is not yet implemented although the current interface should support this without modifications. Currently the only way to listen the database changes is to listen the request that is used to call the updateRange-method.