Note: This tutorial builds on the skills learned in Tutorial 1. Please do that first before proceeding.
Welcome to this enlightening tutorial where we will explore the versatile capabilities of the Sirius framework and how it simplifies the process of authoring OML models within the Rosetta workbench. Throughout this tutorial, we will embark on a journey to create an OML project, where we harness the power of built-in Sirius-based editors and viewers to not only author but also visually depict OML models. But that's not all—our expedition continues as we venture into the creation of a Sirius viewpoint project, where we will craft user-friendly, domain-specific editors tailored for the seamless authoring of description models. This approach is a game-changer, significantly reducing the learning curve for OML users who may find working with a graphical user interface more intuitive than a traditional textual editor. Finally, we will get another glimpse of the powerful analysis capabilities of OML.
Upon completing this tutorial, you will gain the following essential skills:
- Proficiency in utilizing the built-in OML diagram editors as a compelling alternative to the conventional OML text editor for authoring and visualizing OML models.
- The ability to craft tailor-made editors and viewers that cater to specific domains, ensuring an exceptionally user-friendly experience when authoring OML description models.
- Empowerment to conduct analyses seamlessly within the authoring views and as part of comprehensive analysis scripts.
Prepare to embark on a transformative journey, as this tutorial equips you with the tools and knowledge to harness the full potential of OML with the assistance of the Sirius framework. By the end, you'll be well-equipped to navigate the world of OML modeling and analysis with confidence and finesse. Let's get started!
Note: The source files created in this tutorial are available for reference in this repository, but we encourage the reader to recreate them by following the instructions below.
In this step, we will create a new OML project called basicfamily-model in the Rosetta workbench. This process should already be familiar from Tutorial 1.
-
Right click in the [=Model Explorer view=] and select New -> OML Project.
-
Enter the project name as
basicfamily-model. Press Next. -
Enter the project details as shown below. Press Finish.
-
The
basicfamily-modelproject should now be visible in the [=Model Explorer view=]. -
Expand the
basicfamily-modelproject node in the [=Model Explorer view=] as shown in the image below.
In the step, we will create a vocabulary called basicfamily, which is the OML equivalent of an Ecore metamodel that is published as an example with the Sirius framework. Unlike in previous tutorials, we will use the built-in Sirius-based OML vocabulary diagram editor.
-
Right click on the
src/oml/example.comsubfolder in the [=Model Explorer view=] and select New -> OML Model. -
Enter the details of the
basicfamilyvocabulary as shown below. Press Finish.
-
The
basicfamilyvocabulary will be created and its OML editor opens.
Before we can use any Sirius-based viewpoints, we need to convert the project to what Sirius calls a
Modeling Project.
-
Right-click on the
basicfamily-modelproject in the [=Model Explorer view=] and select "Configure -> Convert to Modeling Project".
Before we can use the built-in Vocabulary viewpoint, which provides the vocabulary diagram editor, we need to enable the viewpoint on the project.
-
Right-click on the on the
basicfamily-modelproject in the [=Model Explorer view=] and select "Viewpoints Selection".
-
Check the "Vocabularies" box and click OK. This activates the ability to create vocabulary diagrams in the project.
Now, we are ready to create a vocabulary diagram for
basicfamilyvocabulary to use it as an alternative editor to the OML text editor.
-
Navigate to the
basicfamily.omlfile in the [=Model Explorer view=] and expand it to show the root vocabulary element. Right-click on the element and select "New Representation -> Basicfamily Editor".
-
The New Vocabulary Editor dialog opens up to allow you to customize the diagram name. In this case, we will keep the default name and click OK.
-
In order to observe how the graphical syntax is synchronized with the textual syntax, drag the diagram editor using its title bar and dock it to the right of the text editor.
-
We will now use the Vocabulary diagram editor to author the vocabulary. Follow the steps in the video below and save the editor regularly to see the corresponding textual syntax.
- If you have done the visual authoring all correct, you should end up with the following OML text in
basicfamily.oml.
Note: that the order of the statements below may be slightly different for you if you created the diagram elements in different order from the video.
vocabulary <http://example.com/vocabulary/basicfamily#> as basicfamily {
extends <http://www.w3.org/2001/XMLSchema#> as xsd
aspect Named
scalar property name [
domain Named
range xsd:string
functional
]
concept Family < Named
concept Person < Named [
restricts parents to max 2
]
relation members [
from Family
to Person
]
concept Man < Person
concept Woman < Person
relation parents [
from Person
to Person
reverse children
]
relation mother [
from Person
to Woman
functional
]
relation father [
from Person
to Man
functional
]
}Notice that the vocabulary diagram editor does not yet have feature parity with the textual editor in terms of its ability to author the full OML syntax. However, we intend to make incremental progress towards this goal with every release (if you want us to prioritize this, let us know). On the other hand, the editor's visualization capabilities are more complete. Since the textual and diagram editors are always in sync, a modeler could easily switch back and forth between them to edit the vocabulary.
- In the OML textual editor for
basicfamily.oml, add specialization from relationsmotherandfatherto relationparents.
relation mother [
from Person
to Woman
functional
] < parents
relation father [
from Person
to Man
functional
] < parentsFinally, we want to create an OML vocabulary bundle that includes the
basicfamilyvocabulary.
-
The
basicfamilyvocabulary diagram now looks like this (notice the{subsets parents}on relationmotherandfather).
-
Right-click on the
src/oml/example.com/vocabularyfolder in [=Model Explorer view=] and select New -> OML Model. Fill in the diagram like in the picture. Click Finish.
-
In the open editor for
bundle.oml, add an include statement forbasicfamilylike this:
vocabulary bundle <http://example.com/vocabulary/bundle#> as ^bundle {
includes <http://example.com/vocabulary/basicfamily#>
}Note: Recall that a vocabulary bundle automatically asserts disjointness between included concepts with no common subtypes.
In this step, we will create an OML description model using the OML textual editor, then visualize it with the built-on OML description diagram.
Compared with the built-in vocabulary diagram which has authoring capabilities, th built-in OML description diagram only has visualization abilities (so far).
-
Right click on the
src/oml/example.com/descriptionsubfolder in the [=Model Explorer view=] and select New -> OML Model. -
Enter the details of the
family1description as shown below. Press Finish.
-
The
family1description will be created and its OML editor opens.
We will now use the text editor to create an OML description model defining
family1, its men and women members, and their interrelationships.
- Copy the following OML text and paste it as the new content of the open editor.
description <http://example.com/description/family1#> as family1 {
uses <http://example.com/vocabulary/basicfamily#> as basicfamily
instance family1 : basicfamily:Family [
basicfamily:members Paul
basicfamily:members Isa
basicfamily:members Elias
basicfamily:members Lea
basicfamily:members Dave
basicfamily:members Alain
basicfamily:members Bryan
basicfamily:members Fiona
basicfamily:members Katell
basicfamily:members Clara
basicfamily:members Albert
basicfamily:members Jane
basicfamily:members Peter
]
instance Paul : basicfamily:Man
instance Isa : basicfamily:Woman
instance Elias : basicfamily:Man [
basicfamily:father Paul
basicfamily:mother Isa
]
instance Lea : basicfamily:Woman [
basicfamily:father Paul
basicfamily:mother Isa
]
instance Dave : basicfamily:Man [
basicfamily:father Elias
]
instance Alain : basicfamily:Man [
basicfamily:father Dave
basicfamily:mother Katell
]
instance Bryan : basicfamily:Man [
basicfamily:father Elias
]
instance Fiona : basicfamily:Woman [
basicfamily:father Elias
]
instance Katell : basicfamily:Woman
instance Clara : basicfamily:Woman [
basicfamily:father Elias
]
instance Albert : basicfamily:Man
instance Jane : basicfamily:Woman [
basicfamily:mother Fiona
]
instance Peter : basicfamily:Man [
basicfamily:mother Clara
]
}We will now visualize the
family1.omlmodel using the built-in Sirius-based description diagram.
-
Right-click on the
basicfamily-modelproject in the [=Model Explorer view=] and select "Viewpoints Selection". In the dialog, check the "Descriptions" box and click OK.
Now, we are ready to create a description diagram for
family1.oml.
-
Navigate to
family1.omlin the [=Model Explorer view=] and expand it to reveal the root description. Right-click on the description and select "New Representation -> Family1 Viewer". In the opened dialog, leave the default name and click OK.
The
Family1 Viewerdiagram opens with visualization of the model content. You will notice that the diagram is a bit busy since thefamily1instance, which is related to all family members using themembersrelation is visualized. Deleting this node from the diagram and rearranging the remaining nodes improves the layout.
-
Follow the few steps in the video below to improve the layout of the diagram.
-
The final diagram should look like this now.
Notice that although the diagram looks reasonable is still hard to read. Also, while you can make notational/stylistic changes to the diagram, you cannot use it to edit the description model. In the next section, we will develop a custom diagram to improve both aspects.
Finally, we want to add the
family1description to the description bundle and make the latter use thebasicfamilyvocabulary.
-
Navigate in [=Model Explorer view=] to the
src/oml/example.com/description/bundle.omlfile and double click it to open its OML textual editor. -
In the open editor for
bundle.oml, replace the contents with the following. Save the editor.
description bundle <http://example.com/description/bundle#> as ^bundle {
uses <http://example.com/vocabulary/basicfamily#>
includes <http://example.com/description/family1#>
}Note: Recall that a description bundle represents a closed dataset that we want to reason on.
In this step, we will use Sirius to develop a custom diagram for the basicfamily vocabulary that a) improves the notation/style, and b) allows for editing the description model. In order to do this in Sirius, we need to define what Sirius calls a Viewpoint Specification Project.
-
In the [=Model Explorer view=], right click on an empty area and choose New -> Project -> Sirius -> Viewpoint Specification Project.
-
Give the project the name
basicfamily-viewpointand click Next. -
In the Viewpoint Specification Model page, rename the model to simply
basicfamily.odesignthen click Finish.
The project shows up in the [=Model Explorer view=] and its editor opens up.
-
Drag and dock it to the right of the working area. Your screen should now look like this.
-
In the odesign editor, expand the tree to reveal a viewpoint node called
MyViewpoint. Select it and then in the Property Sheet view, rename it topersons. Also set the "Model File Extension" field tooml.
-
Under the
personsnode, you see another node for a Java service. Select it and edit it its text todefaultpackage.Services.
This step is only needed since we chose the name of the project to be
basicfamily-viewpoint, which contains an invalid Java character-. Eclipse could not use that name for the root package and instead named itdefaultpackage.
<img src="assets/tutorial5/Rename-Default-Services.png" width="100%" style="border:1px groove black;">
- Double click the
defaultpackage.Servicesnode in the tree. It should open a Java editor for the Services class. This is how you know you have configured this correctly. Close the Java editor.
We will add a few more Java services from the openCAESAR public API.
-
Navigate in the [=Model Explorer view=] to the file
basicfamily-viewpoint/META-INF/MANIFEST.MFand double click on it. In the open editor, switch to theDependenciestab. Click the Add button. In the Plug-in Selection dialog, search foromland select theio.opencaesar.omlitem and click Add. -
Click the Add button again in the
Dependenciestab and in the dialog search forrosettaand select theio.opencaesar.rosetta.sirius.viewpointitem and click Add. Save theMANIFEST.MFeditor and close it.
Note: The versions of the new dependencies you added will correspond to the version of Rosetta you installed (i.e., they do not have to match the picture).
<img src="assets/tutorial5/Add-Manifest-Dependencies.png" width="100%" style="border:1px groove black;">
- Back in the odesign editor, right click on the
defaultpackage.Servicesnode and select Copy. Then, right click on thepersonsnode, and select Paste. Repeat the paste a total of 4 times. Now, click on each of the pasted services and edit them in the Property Sheet view to the following class names. Save the editor after.
-
io.opencaesar.oml.util.OmlRead
-
io.opencaesar.oml.util.OmlSearch
-
io.opencaesar.oml.util.OmlDelete
-
io.opencaesar.rosetta.sirius.viewpoint.OmlServices
We need to copy some icons to the
basicfamily-viewpointproject, so we use them in the definition of the viewpoint.
-
Download icons.zip and unzip it. Move the
iconsfolder to the root of thebasicfamily-viewpointproject.
Finally, we need to activate the new
personsviewpoint on thebasicfamily-modelproject.
-
Right click on the
basicfamily-modelproject in the [=Model Explorer view=] and select Viewpoint Selection. Check thepersonsbox in the dialog and click OK.
Now, all the editors we will define in the
personsviewpoint can be used in thebasicfamily-modelproject.
In this step, we will define a custom diagram editor for OML description models that use the basicfamily OML vocabulary.
-
In the
basicfamily.odesigneditor, right click on thepersonsviewpoint and select New Representation -> Diagram Description.
-
Select the newly created diagram node in the editor then click on the Property Sheet view to edit its properties. Switch first on the Metamodels tab, click on
Add from registrybutton (on the right), typeomlin the edit box, and select thehttp://opencaesar.io/omlitem and click OK.
Now, we will specify that this diagram can be created for a
oml.ConceptInstancethat is typed by thebasicfamily:Familyconcept from the vocabulary.
-
In the Property Sheet view, switch to the General tab and make the following modifications:
General: - Id: Persons diagram - Domain Class: oml.ConceptInstance - Precondition Expression: aql:self.findIsKindOf('basicfamily:Family')
We will now create two kinds of nodes within the diagram, one for a Man and one for a Woman (the two kind of Person from the vocabulary).
-
In the
basicfamily.odesigneditor, expand thePersons diagramnode to reveal theDefaultnode. Right click on that node and select New Diagram Element -> Node. In the Property Sheet, enter the following information for the node:General: - Id: ManNode - Domain Class: oml.ConceptInstance - Semantic Candidates Expression: aql:self.findTargetInstances('basicfamily:members') Advanced: - Precondition Expression: aql:self.findIsKindOf('basicfamily:Man') -
In the
basicfamily.odesigneditor, right click on theManNodenode and select New Style -> Workspace Image. In the Property Sheet, change the following:General: - Image Path: /basicfamily-viewpoint/icons/man.svg Label: - Show Icon: false - Label Position: border Corner: - Arc Height: 1 - Arc Width: 1 Advanced: - Size Computation Expression: 4
-
In the
basicfamily.odesigneditor, right click on theManNodenode and select Copy. Select theDefaultnode, right click and select Paste. In the Property Sheet, change the following information only:General: - Id: WomanNode Advanced - Precondition Expression: aql:self.findIsKindOf('basicfamily:Woman')
We will now create two kinds of edges within the diagram, one for the
basicfamily:fatherrelation and one for abasicfamily:motherrelation (from the vocabulary).
-
In the
basicfamily.odesigneditor, right click on theDefaultnode and select New Diagram Element -> Relation Based Edge. In the Property Sheet, change the following:General: - Id: FatherEdge - Source Mapping: ManNode, WomanNode - Target Mapping: ManNode - Target Finder Expression: aql:self.findTargetInstances('basicfamily:father') -
In the
basicfamily.odesigneditor, expand thefatherEdgenode and select the nested Edge Style node. In the Property Sheet, change the following:Color: - Stroke Color: blue
-
In the
basicfamily.odesigneditor, right click on thefatherEdgenode and select Copy. Select theDefaultnode, right click and select Paste. In the Property Sheet, change the following information only:General: - Id: MotherEdge - Target Mapping: WomanNode - Target Finder Expression: aql:self.findTargetInstances('basicfamily:mother') -
In the
basicfamily.odesigneditor, expand themotherEdgenode and select the nested Edge Style node. In the Property Sheet, change the following:Color: - Stroke Color: purple
Save the
basicfamily.odesigneditor. Now, we will create an instance of this diagram editor in the description model.
-
In the [=Model Explorer view=], navigate to the file
basicfamily-model/src/oml/example.com/description/family1.oml. Expand the file's node to reveal the rootdescriptionnode. Right click the nestedfamily1node and select New Representation -> new Persons diagram. Rename the diagram toPersons diagram. Click OK.
-
The new diagram editor opens. Drag it and dock it to the right editor stack. It should look like this:
When compared to the built-in description diagram, the new custom diagram is much more concise and readable. So far, this is a viewer only, now we will turn it into an editor. We will start by adding tools to create Man and Woman nodes.
-
In the
basicfamily.odesigneditor, right click on theDefaultnode and select New Tool -> Section. In the property sheet, change the following:General: - Id: Tools
-
Right click on the
Toolsnode and select New Element Creation -> Node Creation. In the property sheet, change the following:General: - Id: createMan - Label: Man - Node Mapping: ManNode Advanced: - Icon Path: /basicfamily-viewpoint/icons/man.gif
-
Expand the
Node Creation Mannode and right click on the nestedcontainernode and select New Variable -> Expression Variable. In the property sheet, change the following:General: - Name: context - Label: aql:container.getDescription()
-
Copy the
contextvariable and paste it under the samecontainervariable. In the property sheet, change the following:General: - Name: containerInstance - Label: aql:container.oclAsType(oml::ConceptInstance)
-
Right click on the nested
Beginnode, select New Operation -> Change Context. In the property sheet, change the following:General: - Browse Expression: aql:context.createConceptInstance('basicfamily:Man', containerInstance, 'basicfamily:members') -
Right click on the new
Change Context...node, select New Operation -> Set. In the property sheet, change the following:General: - Feature Name: name - Value Expression: aql:context.getNewMemberName('man') -
Copy the
Node Creation Mannode and paste it in theSection Toolsnode. In the property sheet, change the following:General: - Id: createWoman - Label: Woman - Node Mapping: WomanNode Advanced: - Icon Path: /basicfamily-viewpoint/icons/woman.gif
-
Expand the
Node Creation Womannode and select the nestedChange Context...node. In the property sheet, change the following:General: - Browse Expression: aql:context.createConceptInstance('basicfamily:Woman', containerInstance, 'basicfamily:members') -
Select the nested
Setnode. In the property sheet, change the following:General: - Value Expression: aql:context.getNewMemberName('woman')
Now, we will add tools to create Father and Mother edges.
-
Right click on the
Toolsnode and select New Element Creation -> Edge Creation. In the property sheet, change the following:General: - Id: createFather - Label: Father - Edge Mapping: FatherEdge Advanced: - Icon Path: /basicfamily-viewpoint/icons/father.png
-
Expand the
Edge Creation Fathernode and right click on the nestedsourcenode and select New Variable -> Expression Variable. In the property sheet, change the following:General: - Name: context - Label: aql:source.getDescription()
-
Copy the
contextvariable and paste it under thesourcevariable. In the property sheet, change the following:General: - Name: sourceInstance - Label: aql:source.oclAsType(oml::ConceptInstance)
-
Copy the
sourceInstancevariable and paste it under thetargetvariable. In the property sheet, change the following:General: - Name: targetInstance - Label: aql:target.oclAsType(oml::ConceptInstance)
-
Right click on the
Beginnode of the tool and select New Operation -> Change Context. In the property sheet, change the following:General: - Browse Expression: aql:context.addPropertyValue(sourceInstance, 'basicfamily:father', targetInstance)
-
Copy the
Edge Creation Fatehrnode and paste it under theToolsnode. In the property sheet, change the following:General: - Id: createMother - Label: Mother - Edge Mapping: MotherEdge Advanced: - Icon Path: /basicfamily-viewpoint/icons/mother.png
-
Navigate to the nested
Begin->Change Context...node. In the property sheet, change the following:General: - Browse Expression: aql:context.addPropertyValue(sourceInstance, 'basicfamily:mother', targetInstance)
Now that we created four create tools, we will create a couple of delete (from model) tools.
The first is
Recursivedelete, which when run on an instance, it deletes the instance and recursively its links (e.g., deleting a Man instance deletes its incoming and outgoing father and mother links).
-
Right click on the
Section Toolsnode and select New Element Edition -> Delete Element. In the property sheet, change the following:General: - Id: Recursive - Mappings: ManNode, WomanNode, FatherEdge, MotherEdge
-
Expand the
Delete Element Recursivenode, right click on theBeginnode and select New Operation -> Change Context. In the property sheet, change the following:General: - Browse Expression: aql:self.recursiveDelete()
The second is
Cascadedelete, which when run on an instance, it deletes the instance, its links, and its related instances based on cascade delete rules. For example, when deleting aManorWomaninstance, we can configure it to cascade the delete to their children recursively.
-
Right click on the
Section Toolsnode and select New Menu -> Operation Action. In the property sheet, change the following:General: - Id: Cascade Delete - Precondition: ManNode, WomanNode, FatherEdge, MotherEdge
-
Expand the
Operation Action Cascade Deletenode, right click on theBeginnode and select New Operation -> Change Context. In the property sheet, change the following:General: - Browse Expression: aql:self.cascadeDelete()
The
recursiveDeletefunction used in the expression is not provided by the openCAEASR API. Instead, we need to implement it as a service in the viewpoint project itself.
- In the
basicfamily.odesigneditor, double click on thedefaultpackage.Servicesnode. This opens a Java editor for the Services class. Replace the content by the following. Save the editor after.
package defaultpackage;
import java.util.ArrayList;
import java.util.HashSet;
import io.opencaesar.oml.NamedInstance;
import io.opencaesar.oml.Relation;
import io.opencaesar.oml.util.OmlDelete;
import io.opencaesar.oml.util.OmlDelete.CascadeDirection;
import io.opencaesar.oml.util.OmlDelete.CascadeRule;
import io.opencaesar.oml.util.OmlRead;
import io.opencaesar.rosetta.sirius.viewpoint.OmlServices;
public class Services {
public void cascadeDelete(NamedInstance instance) {
var cascadeRules = new ArrayList<CascadeRule>();
Relation parents = (Relation) OmlRead.getMemberByAbbreviatedIri(instance.getOntology(), "basicfamily:parents");
cascadeRules.add(new CascadeRule(CascadeDirection.TARGET_TO_SOURCE, null, parents, null, "delete children"));
var result = OmlDelete.cascadeDelete(instance, cascadeRules);
OmlDelete.recursiveDelete(result);
}
}Now that we created the tools, we can use them on the
family1diagram.
- In the
family1diagram, click the palette arrow on the right-hand-side to reveal the node/edge creation tools. Then use the tools to create amanand awomanthat are father and mother of anotherman.
- Save the diagram and inspect the
family1.omlfile with the OML text editor. It should have the following at the end:
instance man : basicfamily:Man
instance woman : basicfamily:Woman
instance man1 : basicfamily:Man [
basicfamily:father man
basicfamily:mother woman
]- Select the family1 diagram editor again, and from the main menu run Undo a few times until the new changes are all undone. Save the editor. Look at the
family1.omltext editor and observe how the new statements got deleted.
Now, we will try the delete tools.
- In the
family1diagram, selectPauland from the diagram's toolbar select the Delete from Model button. This invokes the Recursive delete tool. After this, undo the delete.
- Select
Paulagain and this time right click on it and select Cascade Delete action in the menu. This invokes the Cascade delete tool where it deletes Paul and his offspring. After this, undo the delete. Save the editor.
In this step, we will define a custom table editor for OML description models that use the basicfamily OML vocabulary.
-
In the
basicfamily.odesigneditor, right click on thepersonsviewpoint and select New Representation -> Edition Table Description. In the Property Sheet, change the following information:General: - Id: Persons table - Domain Class: oml.ConceptInstance Metamodels: - Click `Add from registry` and select `http://opencaesar.io/oml`. Advanced: - Precondition Expression: aql:self.findIsKindOf('basicfamily:Family')
Similar to the custom diagram, this enables the custom table to be createable on concept instances of type
basicfamily:Family. Next, we creat a Line (Row) in the table.
-
Right click on the
Persons tablenode and select New Table Element -> Line. In the Property Sheet, change the following:General: - Id: PersonLine - Domain Class: oml.ConceptInstance - Semantic Candidates Expression: aql:self.findTargetInstances('basicfamily:members') Label: - Header Label Expression: feature:name -
Right click on the
PersonLinenode and select New Style -> Foreground. In the property sheet, change the following:Label: - Label Size: 11
-
Right click on the
Persons tablenode and select New Table Element -> Feature Column. In the property sheet, change the following:General: - Id: FatherColumn - Feature Name: name Label: - Header Label Expression: Father Advanced: - Feature Parent Expression: aql:self.findTargetInstance('basicfamily:father') -
Right click on the
Persons tablenode and select New Table Element -> Feature Column. In the property sheet, change the following:General: - Id: MotherColumn - Feature Name: name Label: - Header Label Expression: Mother Advanced: - Feature Parent Expression: aql:self.findTargetInstance('basicfamily:mother') -
Right click on the
Persons tablenode and select New Table Element -> Feature Column. In the property sheet, change the following:General: - Id: ChildrenColumn - Feature Name: * Label: - Header Label Expression: Children - Label Expression: aql:self.findSourceInstances('basicfamily:parents')->size() -
Right click on the
Persons tablenode and select New Table Element -> Feature Column. In the property sheet, change the following:General: - Id: SiblingsColumn - Feature Name: * Label: - Header Label Expression: Siblings - Label Expression: aql:self.calculateSiblingsCount()
This last label expression is not a standard API from openCAESAR, but rather a custom Java function in
basicfamily-viewpointproject.
- In
basicfamily.odesigneditor, double click ondefaultpackage.Servicesnode. This opens a Java editor forServicesclass. Add the following function to the class:
public int calculateSiblingsCount(NamedInstance self) {
var siblings = new HashSet<>();
var parents = OmlServices.findTargetInstances(self, "basicfamily:parents");
for (var parent : parents) {
siblings.addAll(OmlServices.findSourceInstances(parent, "basicfamily:parents"));
}
siblings.remove(self);
return siblings.size();
}Now, we are now ready to create an instance of the table for the
family1instance.
-
In the [=Model Explorer view=], navigate to the file
basicfamily-model/src/oml/example.com/description/family1.oml. Expand the file's node to reveal the rootdescriptionnode. Right click the nestedfamily1node and select New Representation -> new Persons table. Rename the diagram toPersons table. Click OK. -
The new table editor opens. Drag it and dock it to the right editor stack while docking the diagram to the left stack. Compare the info presented in both the table and the diagram to verify they are in sync.
So far, this table is a viewer only, now we will turn it into an editor. We will start by adding tools to create Man and Woman nodes.
-
In the
basicfamily.odesigneditor, right click on thePersons tablenode and select New Tool -> Create Line Tool. In the property sheet, change the following:General: - Id: createManLine - Label: New Man - Mapping: PersonLine
Expand the
New Mannode to reveal the variable nodes.
-
Right click on the
rootvariable and select New Variable -> Expression Variable. In the property sheet, change the following:General: - Name: context - Computation Expression: aql:root.getDescription()
-
Right click on the
rootvariable and select New Variable -> Expression Variable. In the property sheet, change the following:General: - Name: containerInstance - Computation Expression: aql:root.oclAsType(oml::ConceptInstance)
-
Right click on the
New Mannode and select New Operation -> Change Context. In the property sheet, change the following:General: - Browse Expression: aql:context.createConceptInstance('basicfamily:Man', containerInstance, 'basicfamily:members') -
Right click on the last
Change Contextnode and select New Operation -> Set. In the property sheet, change the following:General: - Feature Name: name - Value Expression: aql:context.getNewMemberName('man') -
Copy the
New Mannode and paste it under thePersons tablenode. In the property sheet, change the following:General: - Id: createWomanLine - Label: New Woman - Mapping: PersonLine
-
Expand the
New Womannode and navigate to the nestedChange Contextnode. In the property sheet, change the following:General: - Value Expression: aql:context.createConceptInstance('basicfamily:Woman', containerInstance, 'basicfamily:members') -
Navigate to the nested
Setnode. In the property sheet, change the following:General: - Value Expression: aql:context.getNewMemberName('woman')
We will now add a Delete tool
-
Right click on the
PersonsLinenode and select New Tool -> Delete Line Tool. In the property sheet, change the following:General: - Id: Delete
-
Right click on the
Deletenode and select New Operation -> Change Context. In the property sheet, change the following:General: - Browse Expression: aql:self.recursiveDelete()
Now, let us use these tools to add and delete persons from the table and observe the diagram in the same time.
- In the
Persons table, right click on any row and select from the popup menu New Man. Notice that a new row gets added to the end of the table and on the diagram in the same time. Repeat the step creating a New Woman this time. Finally select both new rows, right click and choose Delete Line.
- Similarly, in the
Persons diagram, use the pallette tools to create a man and a woman. Observe how the table stays in sync and shows new rows. Then, use the delete tool on the diagram to delete them and observe them being deleted from the table. Save the editor after.
In this step, we will add a couple of SPARQL queries to the basicfamily-model project to explore how OML has powerful query and reasoning capabilities. One of the queries is meant to replicate the sibling relation in the table above. The other queries for a cousin relation in a couple of ways.
-
In the [=Model Explorer view=], navigate to
basicfamily-model/srcfolder, right click and choose New -> Folder, and name itsparql. -
Right click on the
sparqlfolder and choose New -> File, and name itsiblings.sparql. Click Finish. A text editor opens up. -
In the
siblings.sparqleditor, copy/paste the following SPARQL code. Save the editor.
PREFIX basicfamily: <http://example.com/vocabulary/basicfamily#>
SELECT DISTINCT ?person (COUNT(?sibling) as ?siblings)
WHERE {
?person a basicfamily:Person .
OPTIONAL {
?person basicfamily:parents ?parent .
?parent basicfamily:children ?sibling .
FILTER (?person != ?sibling)
}
}
GROUP BY ?person ?parent
ORDER BY ?person-
Click in the [=Gradle Tasks view=] and double click on the task
basicfamily-model/oml/owlQuery. Execution runs in the [=Gradle Executions view=]. -
Inspect the results by navigating in the [=Model Explorer view=] to the
basicfamily-model/buildfolder. Right click on it and select Refresh. Navigate to thebasicfamily-model/build/results/silblings.jsonfile. Double-click to open it. You should see the following result:
{ "head": {
"vars": [ "person" , "siblings" ]
} ,
"results": {
"bindings": [
{
"person": { "type": "uri" , "value": "http://example.com/description/family1#Alain" } ,
"siblings": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
} ,
{
"person": { "type": "uri" , "value": "http://example.com/description/family1#Albert" } ,
"siblings": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
} ,
{
"person": { "type": "uri" , "value": "http://example.com/description/family1#Bryan" } ,
"siblings": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "3" }
} ,
{
"person": { "type": "uri" , "value": "http://example.com/description/family1#Clara" } ,
"siblings": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "3" }
} ,
{
"person": { "type": "uri" , "value": "http://example.com/description/family1#Dave" } ,
"siblings": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "3" }
} ,
{
"person": { "type": "uri" , "value": "http://example.com/description/family1#Elias" } ,
"siblings": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "1" }
} ,
{
"person": { "type": "uri" , "value": "http://example.com/description/family1#Fiona" } ,
"siblings": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "3" }
} ,
{
"person": { "type": "uri" , "value": "http://example.com/description/family1#Isa" } ,
"siblings": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
} ,
{
"person": { "type": "uri" , "value": "http://example.com/description/family1#Jane" } ,
"siblings": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
} ,
{
"person": { "type": "uri" , "value": "http://example.com/description/family1#Katell" } ,
"siblings": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
} ,
{
"person": { "type": "uri" , "value": "http://example.com/description/family1#Lea" } ,
"siblings": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "1" }
} ,
{
"person": { "type": "uri" , "value": "http://example.com/description/family1#Paul" } ,
"siblings": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
} ,
{
"person": { "type": "uri" , "value": "http://example.com/description/family1#Peter" } ,
"siblings": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
}
]
}
}Compare this result to the one we got previously in the
Persons table. It should be the same.
-
In the [=Model Explorer view=], right click on the
sparqlfolder and choose New -> File, and name itcousins.sparql. Click Finish. A text editor opens up. -
In the
cousins.sparqleditor, copy/paste the following SPARQL code. Save the editor.
PREFIX basicfamily: <http://example.com/vocabulary/basicfamily#>
SELECT DISTINCT ?person (COUNT(?cousin) as ?cousins)
WHERE {
?person a basicfamily:Person .
OPTIONAL {
?person basicfamily:parents ?parent .
?parent basicfamily:parents ?grandparent .
?grandparent basicfamily:children ?uncleOrAunt .
?uncleOrAunt basicfamily:children ?cousin .
FILTER(?uncleOrAunt != ?parent)
}
}
GROUP BY ?person
ORDER BY ?person-
Click in the [=Gradle Tasks view=] and double click on the task
basicfamily-model/oml/owlQuery. Execution runs in the [=Gradle Executions view=]. -
Inspect the results by navigating in the [=Model Explorer view=] to the
basicfamily-model/buildfolder. Right click on it and select Refresh. Navigate to thebasicfamily-model/build/results/cousins.jsonfile. Double-click to open it. You should see the following result:
{ "head": {
"vars": [ "person" , "cousins" ]
} ,
"results": {
"bindings": [
{
"person": { "type": "uri" , "value": "http://example.com/description/family1#Alain" } ,
"cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "2" }
} ,
{
"person": { "type": "uri" , "value": "http://example.com/description/family1#Albert" } ,
"cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
} ,
{
"person": { "type": "uri" , "value": "http://example.com/description/family1#Bryan" } ,
"cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
} ,
{
"person": { "type": "uri" , "value": "http://example.com/description/family1#Clara" } ,
"cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
} ,
{
"person": { "type": "uri" , "value": "http://example.com/description/family1#Dave" } ,
"cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
} ,
{
"person": { "type": "uri" , "value": "http://example.com/description/family1#Elias" } ,
"cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
} ,
{
"person": { "type": "uri" , "value": "http://example.com/description/family1#Fiona" } ,
"cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
} ,
{
"person": { "type": "uri" , "value": "http://example.com/description/family1#Isa" } ,
"cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
} ,
{
"person": { "type": "uri" , "value": "http://example.com/description/family1#Jane" } ,
"cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "2" }
} ,
{
"person": { "type": "uri" , "value": "http://example.com/description/family1#Katell" } ,
"cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
} ,
{
"person": { "type": "uri" , "value": "http://example.com/description/family1#Lea" } ,
"cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
} ,
{
"person": { "type": "uri" , "value": "http://example.com/description/family1#Paul" } ,
"cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
} ,
{
"person": { "type": "uri" , "value": "http://example.com/description/family1#Peter" } ,
"cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "2" }
}
]
}
}Notice that the
cousinrelation is not asserted in the model but is derived by query. This suggests that every query that needs to match cousins would need to incorporate the same derivation logic, which could potentially make them both complex to write and costly to run. Besides, if this relation matters for logical reasoning (e.g., if you want to consider cousins marrying to be inconsistent), then you may want to add it to the vocabulary explicitly but still derive it by a rule.
-
In the [=Model Explorer view=], navigate to
basicfamily-model/src/oml/example.com/vocabulary/basicfamily.omland double click to open it. -
Add the following OML fragment to the end of the
basicfamilyvocabulary (before the closing braces):
relation cousin [
from Person
to Person
]
rule infer_couson [
parents(x, y) &
parents(y, z) &
children(z, c) &
differentFrom(c, y) &
children(c, m) ->
cousin(x, m)
]This basically adds the relation
cousinto the vocabulary and provides a rule (equivalent to the query we saw previously) to infer it when the DL reasoner generate entailments. This makes thecousinaxioms available directly as part of the dataset, which makes querying for them easier (as we will see) and usable by the reasoner when checking consistency. Of course, this comes at the expense of inflating the size of the dataset, which may or may not be a concern.
- In the [=Model Explorer view=], navigate to
basicfamily-model/src/sparql/cousins.sparqlfile and double click to open it. Replace the contents by:
PREFIX basicfamily: <http://example.com/vocabulary/basicfamily#>
SELECT DISTINCT ?person (COUNT(?cousin) as ?cousins)
WHERE {
?person a basicfamily:Person .
OPTIONAL {
?person basicfamily:cousin ?cousin
}
}
GROUP BY ?person
ORDER BY ?personNotice, how the pattern for matching
cousinis now direct (one hop).
-
Click in the [=Gradle Tasks view=] and double click on the task
basicfamily-model/oml/owlQuery. Execution runs in the [=Gradle Executions view=]. -
Inspect the results in the
basicfamily-model/build/results/cousins.jsonfile. You should see the following result:
{ "head": {
"vars": [ "person" , "cousins" ]
} ,
"results": {
"bindings": [
{
"person": { "type": "uri" , "value": "http://example.com/description/family1#Alain" } ,
"cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
} ,
{
"person": { "type": "uri" , "value": "http://example.com/description/family1#Albert" } ,
"cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
} ,
{
"person": { "type": "uri" , "value": "http://example.com/description/family1#Bryan" } ,
"cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
} ,
{
"person": { "type": "uri" , "value": "http://example.com/description/family1#Clara" } ,
"cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
} ,
{
"person": { "type": "uri" , "value": "http://example.com/description/family1#Dave" } ,
"cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
} ,
{
"person": { "type": "uri" , "value": "http://example.com/description/family1#Elias" } ,
"cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
} ,
{
"person": { "type": "uri" , "value": "http://example.com/description/family1#Fiona" } ,
"cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
} ,
{
"person": { "type": "uri" , "value": "http://example.com/description/family1#Isa" } ,
"cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
} ,
{
"person": { "type": "uri" , "value": "http://example.com/description/family1#Jane" } ,
"cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
} ,
{
"person": { "type": "uri" , "value": "http://example.com/description/family1#Katell" } ,
"cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
} ,
{
"person": { "type": "uri" , "value": "http://example.com/description/family1#Lea" } ,
"cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
} ,
{
"person": { "type": "uri" , "value": "http://example.com/description/family1#Paul" } ,
"cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
} ,
{
"person": { "type": "uri" , "value": "http://example.com/description/family1#Peter" } ,
"cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
}
]
}
}Opps, this result is unexpected. Why were no cousins found? If you look more closely at the
infer_cousinrule (above) again, you will see that it uses the standarddifferentFrompredicate to ensure that a parent is not matched again as an uncle or an aunt (children of a grandparent). So where is the problem? The problem can be explained by the fact that the vocabulary, as defined, lacks a way to make a person different from another person. Having different names is not by itself sufficient, since a DL reasoner may infer that they are just aliases to the same individual. So how do we fix this? We need to either add akeyaxiom to thePersonaspect to say that one (or more) of its properties should be considered as a unique key. Alternatively, we can configure the DL reason to use theUnique Name Assumptionby turning on theuniqueNamesoption on theowlReasontask in thebuild.gradlescript. Since we demonstrated the use of keys in Tutorial 1 already, we will use the second method here.
- In the [=Model Explorer view=], navigate to
basicfamily-model/build.gradlefile and double click to open it. Find theowlReasontask and set theuniqueNamesflag totrueas follows:
task owlReason(type:io.opencaesar.owl.reason.OwlReasonTask, group:"oml", dependsOn: omlToOwl) {
...
// use unique name assumption
uniqueNames = true
}- Rerun the task
basicfamily-model/oml/owlQueryand inspect the results in thebasicfamily-model/build/results/cousins.jsonfile. You should see the following correct result now:
{ "head": {
"vars": [ "person" , "cousins" ]
} ,
"results": {
"bindings": [
{
"person": { "type": "uri" , "value": "http://example.com/description/family1#Alain" } ,
"cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "2" }
} ,
{
"person": { "type": "uri" , "value": "http://example.com/description/family1#Albert" } ,
"cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
} ,
{
"person": { "type": "uri" , "value": "http://example.com/description/family1#Bryan" } ,
"cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
} ,
{
"person": { "type": "uri" , "value": "http://example.com/description/family1#Clara" } ,
"cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
} ,
{
"person": { "type": "uri" , "value": "http://example.com/description/family1#Dave" } ,
"cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
} ,
{
"person": { "type": "uri" , "value": "http://example.com/description/family1#Elias" } ,
"cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
} ,
{
"person": { "type": "uri" , "value": "http://example.com/description/family1#Fiona" } ,
"cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
} ,
{
"person": { "type": "uri" , "value": "http://example.com/description/family1#Isa" } ,
"cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
} ,
{
"person": { "type": "uri" , "value": "http://example.com/description/family1#Jane" } ,
"cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "2" }
} ,
{
"person": { "type": "uri" , "value": "http://example.com/description/family1#Katell" } ,
"cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
} ,
{
"person": { "type": "uri" , "value": "http://example.com/description/family1#Lea" } ,
"cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
} ,
{
"person": { "type": "uri" , "value": "http://example.com/description/family1#Paul" } ,
"cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "0" }
} ,
{
"person": { "type": "uri" , "value": "http://example.com/description/family1#Peter" } ,
"cousins": { "type": "literal" , "datatype": "http://www.w3.org/2001/XMLSchema#integer" , "value": "2" }
}
]
}
}In this comprehensive tutorial, we've delved into some captivating facets of OML. Firstly, while OML's textual syntax offers a highly convenient means of working with the language, it's important to note that it's not the sole method available to OML modelers. OML Rosetta takes the experience a step further by furnishing ready-made graphical views (diagrams) for both authoring and viewing OML models. What's truly remarkable is that these graphical and textual views remain in perfect harmony, allowing modelers to seamlessly switch between them without missing a beat.
Furthermore, OML Rosetta empowers users with the ability to craft custom views, leveraging the robust Sirius framework. These custom views can encompass diagrams, tables, and more, all designed to enhance the user experience and make it more accessible. Imagine the potential here – not only can this feature be used to create specialized, methodology-specific views that guide users through systematically describing their systems, step by step (akin to the approach we outlined in Tutorial 2), but these views also stay impeccably synchronized since they stem from the same semantic models.
Lastly, we revisited the remarkable analytical capabilities that OML brings to the table. While certain analyses can be effortlessly integrated into the authoring views – as exemplified by our demonstration of computing sibling counts within the table view using the OML Java API – OML's true power lies in its capacity to infer axioms using OML rules and subsequently query these axioms with SPARQL queries. This not only grants methodologists unparalleled control over vocabulary expressiveness, dataset size, and analysis performance but also opens the door to more intricate and potent analyses. With OML, the potential for robust, insightful modeling and analysis knows no bounds.
