JS4EMF/EMF Delegates

From Eclipsepedia

< JS4EMF
Revision as of 04:43, 9 June 2011 by Hal.idi.ntnu.no (Talk | contribs)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

Contents

Enriching Ecore models with Javascript

Ecore models are used for modelling data and among others generating Java code for representing the data. The model includes information about classes and attributes, inheritance and associations, which in effect defines and limits what structures of objects that are valid for the modelled domain. E.g. a model of the domain of libraries and books may express that all books have a title and an author and is owned by some library. A library may lend books to people (persons) and record the return date. EMF will make sure that all instance data will be correct according to these structural rules. However, since there are limits to what can be directly expressed in Ecore, you can still build invalid data, e.g. give a person a name with illegal characters. The solution is to go outside Ecore and implement data rules in Java and hook it into EMF's validation mechanism. The downside is that these rules are not part of the model and relies on code generation and compilation.

To allow for formulating rules with dynamic languages like Javascript and attaching them to the model, EMF has for some time, supported the integration of language interpreters and attaching scripts to model elements using annotations. Support for OCL (Object Constraint Language) has existed for some time, and now js4emf brings support for Javascript. In addition, js4emf provides UI that makes it easier to get all the annotations right, which can be a bit tricky.

Invariants, constraints, derived features and operation bodies

So what kind of annotations are provided and what are they used for? There are four kinds:

  • Constraints are boolean expressions that are attached to a class (EClass) and will tell if an instance is invalid. These are used for simple checks.
  • Invariants are similar to constraints, but are implemented by methods (EOperations) with a certain signature (return value and parameter list). These are used when you want more control over the validation process.
  • Derived features are features that compute their values from other features. These are used when you want to access values in the same manner, independent of what values are actually stored or computed.
  • Operation bodies are the actual implementation of procedural logic, that you would otherwise write in Java.

Let's look at an example and see how js4emf supports them.

Enriching the library example with Javascript

We'll take the following Ecore model as the starting point:

Initial library model

We'll start by considering the name attribute of the Person class. A name should be a sequence of letters and spaces, and to express this as a constraint we do the following:

  • Select the Person class and open the view EMF Delegates View
  • Select the Add constraint button in the view's toolbar
  • Give the constraint a name, e.g. nameHasLetterAndSpaceOnly

This will add the necessary constraint annotations, and select this constraint so you can edit the constraint's Javascript. The left figure below shows the EMF Delegates view with the appropriate code, while the right figure shows how the Ecore model has been augmented with the appropriate annotations.

A constraint for the name attribute of the Person class
Delegates view with name constraint.png Ecore model with name constraint.png

As can be seen in the left figure, the Person class has one constraint. In the right figure, we see the corresponding annotations for the nameHasLetterAndSpacesOnly constraint: First, the library package has an annotation that states that we use Javascript for validation. Second, the Person class mentions this constraint as its only constraint. Third, the constraint's Javascript has its own annotation.

Invariant for borrowed books

An alternative to constraints is using invariants. An invariant is similar to a constraint, but is explicitly modelled as a method with a specific signature. This gives better control over how the invariant is used in the validation process. We'll implement the rule that all the books associated with a loan must be have the same owning library as the loan object.

  • Select the Loan class (and open the EMF Delegates view).
  • Select the add invariant in the view's toolbar.
  • Provide the name of the operation implementing the invariant.

This will create the operation with the appropriate signature (right figure below) and if you may select it and enter the operation's body (left figure below). In this case we check that the library owning this loan (this.eContainer) also owns the borrowed book.

Invariant for borrowed books
Delegates view loan invariant.png Ecore model loan invariant.png

Derived features for given and family names

The next enrichment will be the derived features givenName and familyName. The givenName is supposed to be the first word of the name attribute and the familyName the last, with the familyName taking priority if the name only contains one word. To add the appropriate code, do the following:

  • Select the givenName (or familyName) feature.
  • The EMF Delegates view will notice this is a derived feature, as indicated in the model, and let you enter the appropriate Javascript.

After editing the Javascript, as shown on the left figure below, the appropriate annotations will be automatically added, as shown in the right figure below.

Derived features for given and family names.
Delegates view derived features.png

Ecore model derived features.png

Operation body for getLoansDueBefore

The final enrichment of the model is providing the implementation of the method (EOperation) getLoansDueBefore in the Library class. This method should take a Date (EDate) argument and return the loans that are due before this date. The steps are the following:

  • Select the getLoansDueBefore method and open the EMF Delegates view
  • Enter the body of the method (see left figure below)

Again, this will add the appropriate annotations, as shown in the right figure below.

getLoansDueBefore operaton body
Delegates view operation body.png Ecore model operation body.png

The javascript code first creates the result list (the newList() method return an EList and hence corresponds to a multiple return value) and then iterates through the library's loans. It then adds the loan if the loan's due date is less than the provided Date argument.

Testing the constraints, invariants, derived features and operation bodies

To test that our enrichments/annotations work, we need some instance data. This can easily be created using a standard feature of EMF. Right-click on the Library Model class and select Create Dynamic Instance... This will create a new file (by default named LibraryModel.xmi) containing a (serialized) Library Model instance. This instance will be the root container of the Library and Person objects in our model instance. The file should open in the Sample Reflective Ecore Model Editor, if not reopen it with this editor. You may now enter sample instance data by creating children of the Library Model instance. To test our annotations, we at least need a library with a couple of books, a person and some loan objects owned by the library and borrowed by the person. A minimal example is shown below. This model instance contains one library with one book by Dostojevskij that Hallvard has borrowed and must return by 17th of May (incidently, Norway's national day).

Library model instance

To try invariants and constraints we select the Validate action in the Sample Reflective Editor menu. To check the derived name features we can open the Properties view on Dostojevskij or Hallvard Person instances, e.g. right-click and select Show Properties View. Finally, to test the getLoansDueBefore(EDate) operation, open the EMF Invoke EOperation view.