
In our opinion, the object language of restrictions (Object Constraint Language, OCL) should be known to everyone who is engaged in modeling or who is interested in model-oriented development. However, he is unfairly deprived of attention in the network in general, and, indeed, in the Russian-speaking segment of information is just a miser. What kind of language it is and why it is needed is described in this article. The article does not claim to be fundamental, complete coverage, accuracy of definitions, etc. Its task is: 1) to acquaint OCL with those who have never heard of this language with simple examples, 2) and for those who have heard of it, it is possible to discover new ways of using it.
Structural and additional restrictions
Let's start right away with an example. Suppose we are developing a program like Jira to account for projects, tasks, to distribute these tasks among employees. The data model for such a program can be very simplistic.

')
Having drawn such a diagram, we put restrictions on our subject area: we fixed that only employees, tasks and projects can exist in it, that they have such attributes that they can be connected by just such connections. For example, in our subject area, an employee must have a full name and date of birth. A task or project cannot have such attributes. Or a task can only be performed by an employee, but another task or project cannot be an executor. These are obvious things, but it is important to understand that when creating such a model, we formulate the restrictions. Such constraints are sometimes referred to as structural constraints.
However, often structural constraints in domain modeling are not enough. For example, we may need a restriction that the project manager cannot be a project participant at the same time. Now from this chart, such a restriction does not follow. Let's give other examples (with a trick ;-)) additional (not structural) restrictions:
- Trainee cannot lead projects
- A programmer can manage one project, but cannot participate in other projects.
- Lead programmer can manage no more than two projects at the same time
- A project must have only one manager.
- Full namesakes cannot participate in one project.
- A closed project cannot have open tasks (a task is considered closed if it has actual execution time)
- Before assigning an executor for a task, the planned execution time must be defined.
- Before closing, the task must be determined by the planned execution time
- An employee in one project can have only one open task and no more than 5 tasks for all projects.
- Employee must be adults
- The employee’s full name must consist of three parts (last name, first name and middle name), separated by spaces (spaces cannot be double, triple, etc.)
- Lead programmer can not call Sigmund
- The actual time of work on the task can not exceed the planned more than twice
- A task cannot be its subtask
- The planned time of the task should not be less than the time scheduled for subtasks
- ... invent yourself some limitations ...
Summary
Various modeling languages ​​allow you to describe the structural constraints imposed on the subject area:
- on types of objects: an object can only be an instance of the corresponding class (an employee cannot have project properties, a task cannot be saved to a table with employees, etc.);
- permissible properties and associations;
- on types: properties can take values ​​only of a certain type;
- on multiplicity: the values ​​of the required properties / associations must be specified, for the properties / associations with the multiplicity "1" several values ​​cannot be indicated, etc.
Additional restrictions can be described using additional languages ​​such as OCL.
Eclipse setup
If you want to try all this in practice, you will need Eclipse. If not, proceed to the next section.
- Download and unzip Eclipse , preferably Eclipse Modeling Tools. If you are already using Rational Software Architect 9.0 (which is based on Eclipse), you can use it.
- Install the necessary libraries:
- For Eclipse. Choose Help -> Install Modeling Components from the menu. In the window that appears, select: "Graphical Modeling Framework Tooling", "OCL Tools" and do not interfere with "Ecore Tools".
- For RSA 9.0. Copy the files org.eclipse.gmf.tooling.runtime _ *. Jar and org.eclipse.ocl.examples.interpreter _ *. Jar into the folder “C: \ Program Files \ IBM \ SDP \ plugins \” (depending on the specific installation folder maybe a little different).
- Install pm * .jar plugins with a test metamodel:
- For Eclipse. Copy these files to the $ ECLIPSE_HOME / dropins folder
- For RSA 9.0. Copy these files to the folder “C: \ Program Files \ IBM \ SDP \ plugins \” (depending on the installation, the folder may be slightly different). The dropins folder is not recommended because of some bugs.
- Restart Eclipse. From the menu choose File -> New -> Other ... In the search bar type “pm”. A “Pm Model” and a “Pm Diagram” should appear.
- You can either create a test model yourself, or take a ready-made
- Open the console: Window -> Show View -> Other ... Find the Console in the list.
- In the console window, open the “Open Console” drop-down list and select “Interactive OCL” in it (see figure below).
- Select any object on the diagram and write “self” at the bottom of the console, press “Enter”. If everything is set up correctly, you will see something like this:

If the plugin with the metamodel does not work for you, you can build it yourself from the
source codes . It uses a relatively old version of GMF, so that it works in RSA 9.0.
Example No. 1. The planned time for completing the task should not be less than the time planned for subtasks
The first thing you need to know about OCL is that the restriction always applies to a particular class of objects. Before writing the OCL rules, we need to select this class. Sometimes this choice is not very obvious, but now everything is simple: the rule refers to the task. We can access a specific task instance using the self variable. If it is necessary to obtain the value of some task property, then after the variable name we write a period and then the name of this property. Similarly, you can specify the name of the association. Try this in Eclipse.
Note
To avoid unnecessary problems, in the model all classes, properties, etc. Named with the use of Latin characters. You can find out exactly how this or that property is called using auto-completion (called by pressing Ctrl + SPACE).
The final control rule is shown in the figure.

Not a very trivial thing for newcomers to OCL is the difference between the characters "." And "->".
What comes after the point applies to each element in the collection of values ​​or objects.
What comes after the arrow applies to the entire collection of values ​​or objects.
The table shows some examples of point and arrow usage.
OCL expression | Interpreting OCL expressions |
---|
self.plan_time | Get the value of the Plan_time property |
self.Podzadachi | Get many subtasks |
self.Subtasks.Plan_time | For each subtask in the set, get the value of the property "Plan_time" and eventually get a set of such values |
self.Subtasks.plan_time-> sum () | Count the sum over the whole set of values |
self.plan_time-> sum () | Although the task can have a maximum of one value for the property "Plan_time", this value is implicitly converted to a set (with a single element). The operation sum () is applied to the resulting set. |
Note
See the OCL specification for a description of the collect () and oclAsSet () operations.
Example No. 2. The project manager should not be a participant in the project.
The figure shows several equivalent formulations of this rule: we obtain a list of project participants and make sure that there is no leader in it.

The self variable can be omitted; it is implicitly implied. However, it is important to note that some operations (select, exists, forAll, ...) create a new scope with additional implicit variables (iterators). In this case, it is rather difficult to understand to which implicit variable the chain of properties belongs. In such situations, it is highly desirable to explicitly specify all variables (or all but one).
Example No. 3. The lead programmer can manage no more than two projects at the same time.
Novices when writing such rules are usually asked first if there is an OCL if-then-else. Yes, it is, but in most rules, it is better to use implication instead.

If the premise of the implication is false, then we don’t even try to check the other conditions. And if true, then we get a list of projects that the employee manages, exclude from them closed projects, and if the number of such projects is no more than two, we consider that the condition is fulfilled.
Example No. 4. Full namesake cannot participate in one project.
Here, perhaps, the first non-trivial rule. You can try to formulate it from the employee as well as from the project. It is important to start with the correct class :-) If you are not aware of the existence of the isUnique () operation, then you can start building enough monstrous constructions, like me, when I first encountered this task.

Remove from the rule an explicit iterator declaration (the variable "y"), and try to interpret the rule. Personally, I do not remember exactly what will happen. Maybe there will be an error that it is impossible to unambiguously determine which name you are talking about. Or maybe the iterator takes precedence over self. Need to see the specification. In any case, it is desirable to avoid such situations, and specify the variables explicitly.
Note
Iterators can be more than one. Look at the OCL specification , experiment in the OCL console. See how the specification defines an isUnique () operation.
Example No. 5. An employee in one project can have only one open task and no more than 5 tasks for all projects.
In this rule, we first declare a variable with a list of open tasks. Then we check two conditions for them. We assume that the task is considered open if the actual execution time is not specified for it.

In your opinion, for which class is it better to define this rule? For an employee or task?
Example number 6. The task can not be its subtask
If you did not immediately understand the essence of the rules depicted in the figure, then this is quite natural. On the one hand, there is the “Task” object class, on the other hand, the task has the “Task” association, which indicates the parent task. It is desirable to name the classes and properties / associations differently, this improves the quality of the model and simplifies the understanding of OCL rules.

Let's start with the implementation of the rule "head on." Let us check if the “Task” association of a certain task indicates the task itself: “self. Task <> self”. In the figure we removed the explicit reference to the self variable. It would seem that there is nothing fatal in this, however, in OCL expressions we can refer not only to properties or associations, but also to classes. For example, using this expression, we can get a list of all tasks: “Task.allInstances ()”. In this example, the OCL interpreter is likely to decide that this is a “Task” association, and not a class. However, I repeat, it is desirable to avoid such ambiguities.
The second disadvantage of this rule is that it does not take into account that subtasks can have their own subtasks. The task can easily be a subtask of its subtasks. To make sure that this is not the case, we might need loops or recursive functions. But there are no cycles in OCL, and with functions everything is not very simple. But there is a wonderful operation closure (). This operation is defined for collections, so it is necessary to put an arrow in front of it, not a full stop. For each element of the collection, the operation closure () evaluates the expression specified as its argument, then combines the obtained values ​​into a collection, for each element of which it again calculates this expression, adds the obtained values ​​to the resulting collection, and so on:
self.->union(self..)->union(self...)->union(...)
The figure shows two variants of the rule, based on recursion on parental tasks and subtasks, respectively. Which option do you think is better (trick question)?
Note
Surely, sooner or later you will need cycles. In most cases, you can use the operations defined for collections (select, exists, ...) instead.
If you still need a cycle, you can try something like this:
Sequence{1..10}->collect(...)
Also note the iterate () operation.
Example No. 7. An employee’s full name must consist of three parts (last name, first name and middle name), separated by spaces (spaces cannot be double, triple, etc.)
Obviously, such a rule would be easiest to implement using regular expressions. However,
the OCL specification does not have operations to work with them. In the figure you can see examples of the implementation of such a rule without using regular expressions. The good news is that the standard OCL library is extensible, and in some implementations the matches () operation is still there, or you can implement it yourself.

OCL constructs
All the OCL expressions that we have examined are written in Basic OCL (Essential OCL). (By the way, how do you think, how do they differ?) We list the main constructions we used:
- The variable self - the expression is always bound to a specific class of objects.
- Appeal to properties (and associations) by name:
- self.Polnitel.Zadachi.Proekt.Manager.Zadachi ...
- Arithmetic and logical operations
- Function call:
- FIO.substring (1, 5)
- FIO.size () = string length
- Work with collections:
- FIO-> size () = 1, since the employee must have a single name
- Tasks-> forAll (task | task.plan_time> 10)
- Variable declaration:
- let s1: Integer = FIO.indexOf ('') in
- Conditional statement (if-then-else)
There are some additional constructions in Complete OCL that we will not discuss in detail, you can get to know them yourself by reading the specification:
- Messages, states
- Packages, context, expression assignment (package, context, inv, pre, post, body, init, derive)
Homework
See
the OCL specification .
Understand primitive data types, types of collections (set, sequence, etc.). Why do you think there are no primitive types in OCL for working with date and time? What if they are still needed?
Understand the differences between Basic OCL, Essential OCL, Complete OCL.
Implement other OCL rules yourself, check them in Eclipse.
All OCL-expressions that are given in the article can take only true or false value. (By the way, do you agree with this statement?) Do you think OCL-expressions can have a numeric range of values, return rows, sets of values ​​or objects? How could such non-boolean OCL expressions be used?
Do you think that the result of evaluating an OCL expression could be another OCL expression?
Bonus A bit about metamodels
All that we have done before is described the limitations of our model and tested these limitations on specific employees, tasks, projects. For example, we described the rule that a project manager cannot be a project participant at the same time. Moreover, we have described this rule for all projects and employees in general. And then checked it for specific projects and employees.
Example No. 1. Employee-Task-Group metamodel
Now let's go up a level. For example, we want to develop another application - for the taxi service. There will be a similar data model. Instead of employees, let there be drivers, instead of tasks, orders, and instead of projects, cities. Of course, this is a very conditional example, but it is needed only to demonstrate an idea. Or, let's say, we need to develop an application to make an appointment with a doctor. There will be doctors, offices and receptions.
We can look at all these models and see general patterns in them. A programmer, a driver and a doctor are essentially the same thing - just an employee. Order, reception - if you summarize, then just a task. Well, we generalize the city, department, project. Let's call them just a group. The link between the employee and the task is called execution. The link between the employee and the group is called participation.

Having drawn such a picture, we have risen to a new level of awareness of the subject area — we developed a meta model, a language in which we can describe any similar models.
Let me remind you that the model that we built in the first part of the article imposed some structural and non-structural restrictions on information about objects in our subject area.
Similarly, the metamodel imposes structural and non-structural constraints on the models, which are built in accordance with this metamodel. For example, in any model that corresponds to a given metamodel, there can only be elements that are based on metaclasses marked in red in the figure. In the model, for example, the class “Building”, “Road” or something like that which is not an employee, a task, etc. cannot appear.
Note
By the way, metamodels are built on the basis of metametamodels (for example, MOF, Ecore). If you're interested, read the OMG MOF specification . In general, there can be an arbitrary number of modeling levels, but usually 2-3 is enough.
By the way, what do you think on the basis of which metametametamodel is MOF itself built?
If you are not afraid of dislocation of the brain, then read the article by Dragan Djuric, Dragan Gaševic and Vladan Devedžic "The Tao of Modeling Spaces" (I personally have a dislocation of the brain from their surnames). By and large, when developing software, everything is a model (from a mental image in the mind of a developer to source code, documentation, test scripts, etc.), and the development process is the transformation of some models into others. We will try to cover the topic of model conversion in subsequent articles.
What do you think, is the model shown in the picture above (green rectangles, blue lines), metamodels (red rectangles)?
Obviously, in addition to structural constraints, the metamodel may contain non-structural constraints. The latter can be described again in the OCL language. Examples of such rules are shown in the figure.

Note
In fact, this picture is not very correct. Instead of a full-fledged metamodel, the UML profile is used here, which, strictly speaking, is the usual UML model built on the basis of the UML metamodel (which, in turn, is built on the basis of the MOF metametamodel). However, in fact, metamodels are often not built from scratch, but in the form of a UML profile. For example, in the ISO 20022 standard, the metamodel is implemented in two equivalent versions: 1) a full-fledged metamodel based on Ecore, and 2) a UML profile. From the point of view of the objectives of this article, these nuances are not very significant, so we will consider the UML profile a “metamodel”.
Example No. 2. Entity-Attribute-Communication metamodel
The above described meta model looks a bit artificial and useless. Let's try to build a more adequate metamodel. Suppose we need to develop a language that allows us to describe the data structure in a relational database. In this case, the difference between the participants, tasks, projects, etc. not so important. These are all entities that may have attributes, and which are connected by connections. In our example, there are two kinds of links: one-to-many, many-to-many. The metaclasses of this metamodel are described in the figure by red rectangles. What do you think, is the model shown in the picture (green rectangles, blue lines) consistent with the metamodel? Is every element of this model attributed to one of the metaclasses?

We now turn, finally, to practice. For this, I will use Rational Software Architect, you can use any sane UML editor. One could show everything with the example of free and ideologically correct Papyrus, but, unfortunately, it is not very convenient.
So, create a new empty project in the UML editor. Create a UML profile in it with the following stereotypes.

Note
Pay attention to the properties of the stereotype "Attribute". This is a list of properties that any attribute in our model can have.
Then create the following UML model, apply the newly created profile to it, apply the necessary stereotypes. If you are too lazy to create all this, you can take the
finished project .

Roughly speaking, we built a model in accordance with our “meta model”. Each element of our model is an “instance” of a metaclass from a “metamodel.”
Note
Once again, strictly speaking, this is not so. And the profile, and the stereotypes, and the model, and all the elements of the model - all that we have created - are instances of metaclasses from the UML metamodel. Those. both the profile and the model that uses it are on the same modeling level. But from a practical point of view, we can consider this profile a “metamodel” according to which our model is created. If you understand what it is about, then before understanding what a metamodel is, you need one more step - to understand what the rectangles and lines on the diagrams are, and to understand what the xmi file is from the point of view of modeling. This article will help you, which was mentioned above.
Now we will describe some additional limitations of our metamodel in the OCL language. For example, attributes must belong to entities. If we created a metamodel in the form of a full-fledged metamodel, and not a profile, then such a rule would look very simple:
owner.oclIsKindOf(Entity)
Moreover, we would not even have to describe such a rule. We would simply create an owner association between Attribute and Entity, which in principle cannot link elements of other kinds.
However, our metamodel is created as a UML profile, so the rule is necessary and, at first glance, it does not look very trivial:
base_Property.class.getAppliedStereotype('PMProfile::Entity') <> null
This rule refers to the stereotype "Attribute", which extends the UML metaclass "Property". This means that in the model we can attach an instance of the Attrbiute stereotype to a certain property (an instance of the UML metaclass “Property”). In the latter, we can specify certain values ​​of minLength, maxLength and pattern. The OCL rule we wrote above will check this instance of the Attribute stereotype.
Through the base_Property property, we move from a stereotype instance to a property. Then, through the class association, go to the class to which the property belongs. And finally, we check whether the “Entity” stereotype is applied to this class.
Note
I do not know how much this corresponds to the specification, but sometimes the association between the stereotype instance and the UML metaclass instance (in this case, base_Property) can be omitted, it will be implicitly implied in the same way as self.
Note
If you have a question, how did I know that you need to use the class association, then there are two ways: 1) autocompletion in Eclipse (Ctrl + SPACE) and 2) OMG UML specification .
The rule above uses a standard technique for Eclipse-based UML editors. However, the operation getAppliedStereotype () is a non-standard OCL extension, it may not be supported in other tools. The same rule can be written as follows:
class.extension_Entity->notEmpty()
We check if the class that owns the property has a connection (via the extension_Entity association) with an extension based on the “Entity” stereotype.
Note
The 2nd option theoretically looks more consistent with the standard. However, in older versions of Eclipse there may be problems with it, it is recommended to use the first version for them.

Note
Try to find the base_Property, extension_Entity and class associations in the profile.
The figure shows three more rules.
- The attribute must have a type.
- In a one-to-many relationship, the multiplicity at one end should be no more than 1, and at the other end - more than 1.
- All properties belonging to an entity must be either regular attributes or relationship roles.
Do you think that these rules could be replaced with normal structural constraints if we created the model not in the form of a UML profile, but in the form of a meta model based on MOF?
MOF and Ecore are very similar to each other. Can you imagine some fundamentally different metametamodel?
Examples of constraints at the metamodel level
Below are examples of the simplest and most complex rules from one real metamodel that we created. On the one hand, this is certainly an example of a terrible code, but on the other - a demonstration of some OCL constructions :-)
The maximum repeatability of the ADT component must be greater than 0:
upper > 0
The ADT component that is inherited through the “constraint” relationship must be in the same position as the corresponding component of the parent type:
datatype.generalization->exists( getAppliedStereotype('SomeProfile::restriction') <> null) implies ( let parent : DataType = datatype.general->any(true).oclAsType(DataType) in let props : OrderedSet(Property) = parent.ownedAttribute-> select(getAppliedStereotype('SomeProfile::Component') <> null) in let cur : Property = props->select(x|x.type=self.type)->any(true) in cur <> null implies ( let prevEnd : Integer = props->indexOf(cur) - 1 in prevEnd = 0 or ( let allPrev : OrderedSet(Property) = props->subOrderedSet(1, prevEnd) in let requiredPrev : OrderedSet(Property) = allPrev->select(lower > 0) in requiredPrev->isEmpty() or ( let prevStart : Integer = allPrev->indexOf(requiredPrev->last()) in let allowedPrev : OrderedSet(Property) = allPrev-> subOrderedSet(prevStart, allPrev->size()) in let index : Integer = datatype.ownedAttribute-> indexOf(self.oclAsType(Property)) in index > 1 and ( let selfAllPrev : OrderedSet(Property) = datatype.ownedAttribute-> subOrderedSet(1, index - 1)-> select(getAppliedStereotype('SomeProfile::Component') <> null) in selfAllPrev->isEmpty() or ( let prevType : Type = selfAllPrev->last().type in allowedPrev->exists(x|x.type=prevType)))))))
Why do I need OCL
Rules of control in a profile or metamodel - you already know everything about this.
Rules of control in the model - and about that too.
Specification of operations and calculated properties - you can get acquainted with this yourself by reading
the OCL specification .
Conversion of models (
QVT ,
MOF M2T ) - we will tell about this in the following articles.
Knowledge of the meaning of life - for this, meditate on the first picture. Where would you draw Jesus? And the Matrix?