Components
Several interfaces are typically used together in the scope of a software sub-system where some of them are exposed to clients and the sub-system itself is a client for other sub-systems via their interfaces. In such a scope, interfaces may have interdependencies manifested as constraints on the allowed order of their events. As a trivial example consider a car that first has to be started (by pressing the 'start' button) and only after that the driver can operate the car entertainment touch screen. The actions of starting the car and starting the MP3 player may logically belong to different interfaces still obeying some cross-interface rules. The rules for using several interfaces together need to be explicitly given similarly to the rules for a single interface.
In the CommaSuite framework, this need is addressed in component models. A component is an abstraction where several interfaces are used together and specifies how these interfaces interact with each other. Furthermore, a component may impose restrictions on the interaction among possibly multiple clients of the same interface. The name 'component' may clash with the same name used to denote a unit in a software/system logical or physical architecture. In this respect, a CommaSuite component does not necessarily correspond to an architectural or implementation construct that might also be named 'component'.
Furthermore, apart from using several interfaces in a single context, a component may have an internal structure that consists of other components usually connected with each other. This is a feature that many software and architecture description languages support. It is provided by the CommaSuite component language as well.
Syntax of Component Models
Component models are stored in files with extension 'component'. A component has number of ports, each port associated to a CommaSuite interface. Ports are connection points of the component and are classified as provided or required. A provided port is used by the clients of the component to communicate with it following the port’s interface protocol. A required port is used by the component to communicate with its environment (in the role of a client), again following the corresponding interface protocol. All ports are named. It is possible to have more than one port associated to the same interface. In the general case, we assume that multiple clients can connect to a provided port.
A component may have a number of parts: named instances of existing component types. The ports of the parts and the ports of the containing component can be connected via connections. A connection indicates a channel for transmitting messages between the connected ports. Connections are subject of constraints explained in details later.
The allowed orders of messages observed at the component ports and its parts are defined in component functional constraints. Furthermore, components may have time and data constraints expressed in the same syntax as the ones for interfaces. The general syntax of component models is given below:
import "interface1.interface" import "interface2.interface" //... or alternatively use namespace imports // import a.b.c.interface1 // import x.y.z.interface2 component componentName provided port IInterface1 port1 //port interface and port name required port IInterface2 port2 //... other ports //optional parts parts ComponentType1 part1 ComponentType2 part2 //... other parts //optional connections between ports connections port1 <-> part1::p //connection between boundary port port1 and part port part1::p part1::q <-> part2.p //connection between two ports belonging to component parts
functional constraints
Optional functional constraints
timing constraints
Optional timing constraints
data constraints
Optional data constraints
Connections
In the context of a component model we distinguish ports defined by the component, called boundary ports and ports that belong to a part, named part ports (defined in the type of the part).
The following restrictions are imposed on connections between ports:
-
the ports in a connection have the same interface
-
there is at most one connection for any pair of ports
-
a provided boundary port may only be connected to at most one provided part port. The part port then cannot participate in other connections
-
a required boundary port may only be connected to at most one required part port. The part port then cannot participate in other connections
-
a required part port may be connected to at most one provided part port
-
part ports in a connection belong to different parts
When a message is received at a boundary port, this port acts as a gateway that allows the message to travel further to the port connected to the boundary port (please note that it is not mandatory to connect every boundary port). There is no new message created at the boundary port: the port simply allows the message to reach its destination via the connection. From that perspective, ports do not perform any message processing logic.
Observable Messages
The purpose of a component model is to define the allowed order among all the messages observable in the scope of this model. For a given model, these observable messages are:
-
all messages observed at the component boundary ports (originating or received at the port)
-
all messages observed at ports belonging to the parts (part ports)
Functional, time, and data constraints need to refer to the observable messages. Generally, this is done by using the message port and the message name. If the port is a part port then the part’s name is also used. For example:
p::start //command start observed at boundary port with name p part1::q::ready //notification observed at port q of part1
This syntax for reffering to messages is used in the remaining part of this page.
Example
The important new construct introduced in component models is functional constraint. It will be explained on the basis of a simple example.
The example uses a hypothetical component that is capturing micro images of materials in a controlled environment with required temperature and presence of vacuum. Each of the three aspects of image capturing, setting the temperature and the vacuum is controlled via a separate interface: IImaging, ITemperature and IVacuum respectively.
IImaging defines commands for preparing and creating images. IVacuum defines commands for enabling/disabling vacuum (vacuum On and Off). ITemperature defines commands for setting the temperature at certain level and resetting the temperature level. All interfaces send notifications as an indication of successful execution.
The following table gives the state machines of these interfaces:
ITemperature | IVacuum | IImaging |
---|---|---|
The component satisfies the following requirements regarding the interaction among the three interfaces:
-
Creating an image is possible only if the vacuum is present and the requested temperature is reached
-
Setting the temperature is possible only if the vacuum is present
These two requirements will be expressed as component functional constraints. The goal of a functional constraint is to capture only an aspect of the total behavior of the component. Component models are not expected to define the complete component behavior in terms of reactions to all possible commands in different states. In other words, component models are not design specifications that are used to derive an implementation. An implementation of a component is expected to satisfy all the functional constraints defined in a component model along with the time and data constraints.
import "IImaging.interface" import "ITemperature.interface" import "IVacuum.interface" component Imaging provided port IImaging iImagPort provided port ITemperature iTempPort provided port IVacuum iVacPort functional constraints ...
One example functional constraint is explained in the next section.
Functional Constraints
We distinguish between state-based and predicate functional constraints. The first kind is a state-based specification of behavior using a subset of the union of all events that can be observed on all the component ports (boundary and part ports). The second kind is a Boolean expression that has to be true every time a message is observed.
Every sent or received message is associated to a component port which in turn is associated to an interface. This means that when the message is observed the current state of the interface at the given port is known. This allows component functional constraints to refer to the port states. Examples are shown shortly.
State-based Functional Constraints
The example below is a state-based functional constraint that implements the first requirement: imaging is possible only if the vacuum is present and a target temperature is set. We also temporarily assume that the ports have a single client.
State-based constraints have syntax similar to the syntax of interface state machines:
ImagingConstraint { use events command iImagPort::CreateImage iImagPort::reply to command CreateImage initial state Status { transition trigger: iImagPort::CreateImage guard: iVacPort in Vacuum and iTempPort in TemperatureSet do: iImagPort::reply(Result::Ok) to command CreateImage next state: Status transition trigger: iImagPort::CreateImage guard: not iVacPort in Vacuum or not iTempPort in TemperatureSet do: iImagPort::reply(Result::NotAvailable) to command CreateImage next state: Status } }
The constraint named 'ImagingConstraint' first declares which events will be used (clause 'use events'). In the example we are interested in two events only: command CreateImage and its reply. As can be seen all events are prefixed with the ports at which they are observed. This is needed for name disambiguation because an interface can be associated to more than one port. Events that are not declared in this clause cannot be used in the constraint.
The allowed order among the declared used events is given in a state machine. In the example we have only one state with two transitions. The intention behind the specification is that if the command CreateImage is received under the correct conditions the reply gives the value OK (the first transition). Otherwise the reply returns value NotAvailable (second transition).
The guards in the transitions show how functional constraints use information about the state of the interface at a given port. The general syntax is portName in stateName. State Vacuum indicates that the vacuum is properly set, state TemperatureSet indicates the desired temperature is established. This access to interface state information is extremely handy. Without it, the functional constraint needs to replicate the sequence of the events in the other two interfaces that lead to the indicated states, an information already present in the interface specs. This way, code duplication is avoided and the size of the constraint is reduced.
Similarly to interface specifications, state-based constraints may use events with variables and optional condition on their values.
The allowed transition triggers are:
-
commands and signals on provided ports
-
replies and notifications on required ports
Conversely, the allowed actions in transition bodies are:
-
replies and notifications on provided ports
-
commands and signals on required ports
Informally, a state-based functional constraint determines a set of traces that conform to it. A trace in this set is such that (i) for every port, the projection of the trace on this port (i.e. the trace obtained by keeping only the messages on this port) conforms to the port interface; (ii) the trace obtained by eliminating all the events not listed in the 'use events' declaration conforms to the constraint state machine. Here 'conforms' means that starting from the initial state and the first event in the trace, there is at least one transition traversal path that accepts the trace.
Each transition may accept a fragment of the trace by executing the actions in its body. It is possible that more than one transition can be taken for an event in a given state. In this case, all these transitions will be used thus giving the possibility to have more than one traversal path that accepts the trace. The trace is accepted if there is at least one path with transitions that match the events in the trace.
The full details on the syntax of use events clause, and the allowed actions in the transition bodies are given in the subsequent appendix.
Predicate Functional Constraints
As an example of predicate functional constraint we will give a different interpretation and implementation of the same requirement as before. We will require that if port iImagPort is in state Imaging then iTempPort is in state TemperatureSet and iVacPort is in state Vacuum.
ImagingConstraint_Predicate always not iImagPort in Imaging or (iTempPort in TemperatureSet and iVacPort in Vacuum)
The meaning of this functional constraint is that whenever an event is observed at any component port, the boolean expression after 'always' must evaluate to true.
Please note that the two example constraints are not equivalent in terms of the set of traces that conform to them. The first constraint strictly requires reply(OK) to command CreateImage if the temperature and vacuum ports are in the required states whereas the second constraint allows reply(NotAvailable) to be sent under the same conditions. If however, iImagPort enters state Imaging (which is only possible if reply(OK) is observed) then the temperature and vacuum ports must be in the required states.
Another important subtlety is how the state of a port is determined. In this example we assumed a single client per port meaning that a single interface instance will be created per port thus the port states are uniquely determined. However, the general case is multiple clients per port leading to possibly different states per client on the same port. Next section explains the language constructs that give fine grained access to port states.
Identification and Quantification over Port States
As already mentioned, we generally assume that multiple communication parties can be connected to a port. For a provided port these are the clients of the component. For a required port these are the server used by the component. A given event observed at a given port is always associated to a communication party. It is possible to capture the identity of this party in a variable and to use it in expressions. An instance of the port’s interface is created for every client communicating via this port. This gives the possibility to obtain the current interface state at a given port for a given communication party. Based on this information, the CommaSuite component language provides a number of expressions that quantify over the interface states at a given port stating, for example, that all or some connections are in a given interface state. We will illustrate these expressions on the basis of an example.
As an example, consider a component that provides a hypothetical resource to its clients via an interface IResource. Multiple clients can request control over the resource. The component is responsible for implementing a policy for sharing the resource among multiple clients. In our example, the component will allow at most one client at a time to be in control of the resource. The following is the definition of the IResource interface:
interface IResource machine Main { initial state Idle { transition trigger: getControl do: reply(true) next state: InControl OR do: reply(false) next state: Idle } state InControl { //... do something with the resource transition do: controlLost next state: Idle } }
Here is a snippet of the component definition with two functional constraints that use constructs for identification and quantification of clients connected to the component.
component ResourceControl provided port IResource resPort //other ports //... functional constraints FC1 { use events command resPort::getControl resPort::reply to command getControl notification resPort::controlLost variables id clientInControl initial state ResourceFree { transition trigger: resPort::getControl do: resPort::reply<id c>(bool r) to command getControl where r == true clientInControl = c next state: ResourceTaken transition trigger: resPort::getControl do: resPort::reply(false) to command getControl next state: ResourceFree } state ResourceTaken { transition do: resPort::controlLost<id c> where c == clientInControl next state: ResourceFree transition trigger: resPort::getControl do: resPort::reply(false) to command getControl next state: ResourceTaken } } FC2 always [0-1] connections at portRes in InControl
Functional constraint FC1 is state-based. If in the initial state ResourceFree a reply to command getControl with argument true is observed then the identifier of the client who receives the reply is bound to the variable c. Note the type of the variable: id. This is a predefined primitive type used only in component definitions. It allows only identity comparison operations.
If a client receives a positive reply to the getControl request the state ResourceTaken becomes the current state of the FC1 machine. In this state no more positive replies to control requests are allowed. The control over the resource can be released only if the component decides to sent notification controlLost to the client which currently has the control. Observe the usage of the variable c that captures the identity of the receiver of the notification. It is used in the condition that ensures that the client which is currently in control receives the notification.
The logic in FC1 can be expressed more compactly as a predicate constraint. The requirement is that at most one client is in the interface state InControl. Constraint FC2 shows the corresponding expression. It uses a quantifier over the port connections (zero or one connection satisfies a condition). The syntax of the possible quantifiers is given in the appendix.
It should be noted that this example shows how component functional constraints can restrict the order of events over the connections of a single port. In contrast, the first example (about imaging, vacuum and temperature) involves multiple ports and interfaces.
More examples of functional constraints can be found in the Imaging Component Example (File > New > Example > Imaging Component Example).
Time and Data Constraints
Similarly to interface specifications, component models may have time and data constraints. The main difference is that events belonging to different interfaces or observed at different ports can be used. For example, a time constraint may specify that receiving a request at a provided port is followed within a certain interval by an outgoing request at a required port.
The syntax of the constraints remains the same as in interface specifications with two exceptions:
-
all events must be prefixed with their ports. If the port belongs to a component part then the prefix includes the part identifier
-
events cannot be annotated with states in which they are expected to occur. Note: in case of interface specification, states refer to interface states. In component models, there are no component states. The possibility to refer to the port states is under investigation and may be included in a future version of the CommaSuite framework.
Appendix: Functional Constraints Syntax
The syntax of state-based functional constraints has the following general structure:
constraintName { use events one or more event patterns variables optionally, declaration of variables init optionally, initialization of variables initial state S{ //... transitions } //other states (optional) state P{ //... transitions }
The syntax of predicate functional constraint is:
constraintName always boolean_expression
Use Events Patterns
List of event patterns. Every pattern denotes a non-empty set of events. Patterns are one of:
-
Single event: (command | signal | notification) (partName::)? portName::eventName
-
Reply on a given port. Can denote replies to multiple commands: (partName::)? portName::reply
-
Reply to a concrete command: (partName::)? portName::reply to command commandName
-
Wildcard for events per kind: any (partName::)? portName::(command | signal | notification)
-
Wildcard that denotes all events: any (partName::)? portName::event
if a command pattern is used, the 'use events' clause has to contain a matching reply pattern. Similarly, if a reply pattern is used it has to be matched by a command pattern. For example:
use events command port1::GetName //the command is matched by the next reply pattern port1::reply to command GetName any port2::command //the next reply pattern is the proper matching pattern port2::reply
Actions in State-based Functional Constraints
All actions allowed in interface state machines can be used in state-based functional constraints except any order.
In addition, the syntax of event actions is extended to denote the port at which the action is observed and to allow capturing the communication party:
-
(partName::)? portName::eventName(<var of type id>)? (expr1, expr2, …). The number and type of expressions must match the parameters in the event definition given in the interface signature. '*' is allowed as a value like in interface specifications;
-
(partName::)? portName::reply(<var of type id>)?(expr1, expr2, …) to command commandName;
-
(partName::)? portName::eventName(<id varName>)? (varDecl1, varDecl2, …) (where cond)?. The number and type of variables must match the parameters in the event definition given in the interface signature. The variable that identifies the communication party must be of type id;
-
(partName::)? portName::reply(<id varName>)?(varDecl1, varDecl2, …) to command commandName (where cond)?;
The check if an observed event matches the pattern is similar to the matching in interface specs.
any order construct is not allowed in functional constraints.
Quantifiers over Port Connections
In the syntax bellow, port uniquely identifies a component port, that is, if the port is a part port, its name is prefixed with the part name (e.g. partA::portP).
-
var at port in (state | {states, …}). Evaluates to true if the value of var denotes an existing communication party that is connected to the given port and this connection is in the given state (or in one of the given states). Remark: if the port is a provided port of singleton interface then the result is the same for all existing communication parties since all connections share the same instance of the interface (thus all are in the same state).
-
all at port in (state | {states, …}). Evaluates to true if all the connections at the given port are in one of the given states. Note that if no connections exist at the time of evaluation the value is vacuously true.
-
some at port in (state | {states, …}). Evaluates to true if at least one connection at the given port is in one of the given states.
-
one at port in (state | {states, …}). Evaluates to true if exactly one connection at the given port is in one of the given states.
-
[m-n] connections at port in (state | {states, …}). Evaluates to true if between m and n connections at the given port are in one of the given states.
-
port in state. Evaluates as follows:
-
if there is already at least one connection established over the port, the expression is an abbreviation of all at port in state
-
if no connections are present yet on the port, the expression evaluates to true if the state is an initial state of a machine in the port’s interface
-