Salome HOME
bos #26458 Versioning of sources via git commit id (sha1)
[modules/yacs.git] / doc / progdsc.rst
1 .. _progdsc:
2
3 Introduction to the DSC programming model for SALOME components
4 ======================================================================
5
6 The purpose of this introduction is to provide essential concepts about how to use the DSC (Dynamic Software Component) 
7 extension in SALOME components.  It is intended for SALOME application developers who need to integrate calculation 
8 codes in the form of SALOME components or supervisor services.
9
10 The principle of the programming model
11 ----------------------------------------
12 The DSC programming model is provided by the SALOME kernel and is available not only for services but also for 
13 SALOME components without supervision.  A SALOME application that would like to use the DSC extension always 
14 follows this principle:
15
16 1. The different programs, codes and services that make up the application declare the various available ports 
17    during the initialization phase.
18 2. The system (for example YACS or a user script) then connects the different ports to set up the different 
19    communications between the codes.
20 3. The codes are run.  When they want to use one of their DSC ports, the code asks to the system for a pointer 
21    to its port. The system then checks that the port has been properly connected and/or properly declared.
22 4. The codes use the DSC ports.
23
24 Benefits of the DSC extension
25 ---------------------------------
26 The main benefits of the DSC extension to the SALOME programming model are as follows:
27
28 #. It enables use of datastream ports in SALOME
29 #. It enables use of the Calcium coupling tool in a calculation scheme without depending on the implementation of 
30    Calcium outside SALOME.  This results in better integration of this type of port, and greater flexibility in possible data types.
31 #. It offers the possibility of adding interface type ports into services.
32
33 Interface type ports
34 +++++++++++++++++++++++++++++
35 DSC can add interface type ports to components and therefore to services.  The datastream ports are nothing more 
36 than a specialization of these interface ports.  This interface is described through a CORBA interface.  
37 Therefore it must be implemented by a CORBA object that may be either the component itself or an object that the 
38 component uses.  Properties may be attached to each interface port through a specific configuration object.
39
40 Calcium datastream ports
41 +++++++++++++++++++++++++++++
42 The SALOME kernel proposes a new implementation of Calcium ports.  These ports are interface ports, therefore they can 
43 be used like other interface ports by means through the C ++ API, or they can be used in the same way as with Calcium 
44 through a C or FORTRAN interface.  Finally, this new implementation was made with the objective of being able to port a 
45 Calcium application into SALOME very quickly.
46
47
48 Connections between DSC ports
49 ------------------------------------
50 DSC ports may be input or output ports.  Output ports are called <<uses>> because they enable the use of an interface 
51 external to the component or the service.  Input ports are called <<provides>> because they provide the implementation 
52 of the port interface.
53
54 Service ports are described in an XML file that the catalog manager reads during creation of a calculation scheme.  
55 On the other hand, ports are not described in the CORBA declaration of the service.  This is why they are created and 
56 added dynamically when the service is created and executed (in comparison with more static models like the CORBA Component Model).  
57 DSC adds methods to components to declare interface ports.
58
59 ::
60
61   void add_provides_port(...);
62   void add_uses_port(...);
63   Ports::Port get_provides_port(...);
64   uses_port get_uses_port(...);
65
66 Methods are provided to enable the connection of uses ports with provides ports of the same type (in other words 
67 with the same CORBA interface).  Furthermore, DSC notifies the user code about modifications to connections of 
68 its ports through callback methods.
69
70 ::
71
72   void connect_provides_port(...);
73   void connect_uses_port(...);
74   boolean is_connected(...);
75   void disconnect_provides_port(...);
76   void disconnect_uses_port(...);
77   void provides_port_changed(...);
78   void uses_port_changed(...);
79
80 Different combinations of connections between uses ports and provides ports are possible.  A uses port may be connected 
81 to several provides ports.  Similarly, a provides port may be connected to several uses ports.  Finally, uses ports know 
82 the provides ports that they address, while a priori provides ports do not know the uses ports that called 
83 them (obviously, application information can be added).
84
85 The two programming layers
86 ------------------------------------------------
87 The DSC component model is divided into two parts:  DSC_Basic and DSC_User.
88
89 DSC_Basic is the lower layer of DSC.  It provides the basic functions to add the concept of ports to a CORBA object.  
90 It can be used to record servants of provides ports, declare uses ports and connect them.  It is a so-called basic layer 
91 because it does not provide much user convenience.  In particular, a uses port is in the form of a sequence of references 
92 to provides ports to which the uses port has been connected.  The user is responsible for managing this sequence and 
93 manually calling the different provides ports when uses ports are being used.
94
95 The DSC_User layer give a higher programming and encapsulation level of ports.  Firstly, it enables the creation of 
96 SALOME services with datastream (or interface) ports supplied by the SALOME platform.  It provides an initialisation 
97 method that the supervisor will call before the service is executed, which makes it possible to declare service ports 
98 correctly.  Finally, it offers abstraction for uses and provides ports.  The two types of ports are implemented 
99 by classes and the user thus has a uniform view of the two port types:  2 classes (and not one sequence and 
100 one CORBA reference as in the DSC_Basic layer).  Another benefit is the automatic management of the list of provides ports 
101 connected to a uses port.  The user code has a unique reference on its uses port;  the uses port implementation is then 
102 responsible for transmitting a call to the different port connections.
103
104 DSC through examples
105 ------------------------------------------------
106 We will now discover the principles of DSC programming through different examples.
107
108 The first example sets up a component with a provides port.  The second example sets up a second component 
109 with a uses port that will be connected to the provides port in the Hello World example.  
110 The third example shows how datastream ports are used in services.  
111 The fourth example shows how the datastream ports factory system is used to add its own family in the programming model.  
112 Finally, the fifth example shows how the new Calcium implementation is used in the SALOME context.
113
114 The following examples show examples of component programming.  On the other hand, they do not contain the entire code, this 
115 document only describes the new parts.  The entire code for the examples is contained in the SALOME DSC_EXAMPLE_SRC module.
116
117 The first two examples are intended to help understand how the DSC ports are manipulated through the DSC_Basic layer.  
118 On the other hand, the third example will have to be studied to see how services can be created with DSC ports through the DSC_User layer.
119
120 Access to the subversion base of the pal project must be authorised before the examples can be retrieved.  
121 Then type the following line in a Unix shell::
122
123   svn co svn://<nom>@nepal/PAL/DSC_EXEMPLES_SRC/trunk  DSC_EXEMPLES_SRC
124
125 Hello World
126 +++++++++++++
127
128 The sources for this example are given in the src/Ex_Serveur directory.
129
130 The objective of this example is to create a component that provides a provides port.  The following figure 
131 illustrates the procedure.  The component is called Ex_Serveur and it provides a provides port named Ex_Hello.  
132 This provides port provides the HelloWorld interface.
133
134 .. image:: images/progdsc_img1.png
135      :align: center
136
137 The first step is to define interfaces of the component and the port interface:
138
139 ::
140
141  Interface HelloWorld : Ports::Port {
142     void say_hello(in string name);
143  };
144
145  Interface Ex_Serveur : Engines::DSC {};
146
147 The IDL file comprises firstly the declaration of the provides port interface that the component will provide.  
148 In this case it is the HelloWorld interface.  This interface is a classical CORBA interface.  On the other hand, this 
149 interface must inherit from Ports::Port if it is to be a DSC port.  The Ex_Serveur component is also declared 
150 as a CORBA interface that inherits from the Engines::DSC interface instead of Engines::EngineComponent.  
151 Note that the provides port does not appear in the IDL definition of the component.  The port is added and 
152 declared in implementation sources of the component.  It is added dynamically when the component is executed.
153  
154 The objective now is to implement the component and the provides port.  The provides port is implemented 
155 through a C++ class that we will call HelloWorld_impl (see in the sources).  This implementation is in no way 
156 different from the implementation of a CORBA object.  See the implementation of the say_hello method:
157
158 ::
159
160  void 
161  HelloWorld_i::say_hello(const char * name) { 
162   std::cout << "Hello " << name << " ! " << std::endl;
163  }
164
165  
166 The next step is to implement the component.  We will be interested in the declaration of the component port 
167 and the class that the component must inherit.  The implementation of a component (Ex_Serveur_i class) that wants 
168 to use DSC ports must inherit from the class named Engines_DSC_i.  Obviously, it must also inherit from POA_Ex_Serveur.  
169 See the declaration for Ex_Serveur_i class:
170
171 ::
172
173  class Ex_Serveur_i :
174   public Engines_DSC_i,
175   public POA_Ex_Serveur
176  {
177
178   public:
179     Ex_Serveur_i(CORBA::ORB_ptr orb,
180                  PortableServer::POA_ptr poa,
181                  PortableServer::ObjectId * contId, 
182                  const char * instanceName, 
183                  const char * interfaceName);
184
185     virtual ~Ex_Serveur_i();
186  ...
187  };
188
189 Two actions have to be performed before the provides port can be used:
190
191 1. Create the port 
192 2. Save the port in the component.
193
194 These two steps are implemented by adding a method called register_ports() to the Ex_Serveur_i class that is 
195 called in the component factory before the factory returns the reference of the component to the container.  
196 This method is implemented as follows:
197
198 ::
199
200  void 
201  Ex_Serveur_i::register_ports() {
202
203  // Create the provides port
204  _Ex_Hello_port = new HelloWorld_i();
205  _Ex_Hello_port_properties = new PortProperties_i();
206
207  // Save the provides port
208  add_provides_port(_Ex_Hello_port->_this(), 
209                    "Ex_Hello",
210                    _Ex_Hello_port_properties->_this());  
211  }
212
213 The method begins with creation of the provides port.  The objective is to create the servant of the CORBA 
214 interface of the port.  An object also has to be created for port properties.  The default object is used in 
215 this example (supplied by the kernel).  The port is then registered in the component through the 
216 add_provides_port method provided by DSC.
217
218 The fact of inheriting from Engines_DSC_i obliges the component to implement two methods that are called 
219 provides_port_changed() and uses_port_changed().  These two methods are callbacks that the system uses to notify 
220 the component when the connections of its ports have changed.  With the provides_port_changed() method, a warning 
221 can be obtained when someone connects or disconnects on one of its provides ports.  
222 In particular, the callback indicates how many clients use the provides port (connection_nbr argument).  
223 This information is not used in this example.  The uses_port_changed() method performs the same function as 
224 the provides_port_changed() function, but for uses ports.  The specific features will be described in the second example.
225
226 The documentation for the different Engines_DSC_i methods are provided in the Doxygen documentation of the SALOME kernel.
227
228 This example can be executed through the src/tests/test_Ex_Serveur.py test file, in the SALOME interpreter in terminal mode.  
229 This script illustrates the use of DSC ports:
230
231 ::
232
233  import LifeCycleCORBA
234  import Engines
235  import Ports
236  import HelloWorld_idl
237
238  lcc = LifeCycleCORBA.LifeCycleCORBA()
239  component = lcc.FindOrLoad_Component('FactoryServer', 'Ex_Serveur')
240  hello_port = component.get_provides_port("Ex_Hello", 0)
241  hello_port.say_hello("andre")
242
243 After the component has been created through LifeCycleCORBA , the script uses the get_provides_port method 
244 to obtain a reference on the provides port of the component.  
245 The reference obtained is then used to execute the say_hello method for the port.
246
247 Client Hello World 
248 +++++++++++++++++++++
249 The sources in this example are located in the src/Ex_Client directory and in src/Ex_Serveur.
250
251 The purpose of this example is to create a new component that will use the Ex_Hello port in the previous example, through a uses port.
252
253 The following figure represents the application:
254
255 .. image:: images/progdsc_img2.png
256      :align: center
257
258 The Ex_Client component is described in the same way as the Ex_Serveur (Ex_Server) component in the IDL file.  
259 The only difference is that a start() method is added in its interface.  Since a component does not contain a 
260 main function when it is created, a method to start execution of the component is required, which is why the 
261 start method is defined.
262
263 The following is the IDL definition of the Ex_Client component:
264
265 ::
266
267   Interface HelloWorld : Ports::Port {
268       void say_hello(in string name);
269   };
270
271   Interface Ex_Serveur : Engines::DSC {};
272
273   Interface Ex_Client : Engines::DSC {
274       void start() ;
275   } ;
276
277 The component now has to be implemented.  As for a provides port, a uses port must be recorded in a component before 
278 it can be used by the component. The uses port corresponds to a sequence of references to the provides ports to which 
279 it has been connected:  this is why it is not implemented by a class like a provides port.  On the other hand, it is 
280 always possible to add properties to the port.  
281 The code for the register_ports() method for the Ex_Client component is given below:
282
283 ::
284
285   void 
286   Ex_Client_i::register_ports() {
287
288     // Create the properties object for the uses port.
289     _U_Ex_Hello_port_properties = new PortProperties_i();
290
291    // Add the uses port into the component
292     add_uses_port("IDL:HelloWorld:1.0", 
293      "U_Ex_Hello", 
294      _U_Ex_Hello_port_properties->_this());
295
296   }
297
298 A uses port is associated with a CORBA object type.  Declaration of this type verifies if the uses port is 
299 connected to a compatible provides port.  In this example, the type of port (declared in the IDL) is HelloWorld.  
300 CORBA proposes a character string corresponding to this type for each IDL type.  
301 In this example, it is IDL:HelloWorld:1.0.
302
303 We now need to be able to use the uses port.  To achieve this, the component asks the system to retrieve 
304 the uses port using the get_uses_port() method.  The port is in the form of a reference sequence on the different 
305 provides ports.  The references contained in this sequence are the provides ports to which the uses port has been 
306 connected at the time that the get_uses_port() was called.  The system uses the use_port_changed() method to 
307 notify the user code every time that this reference list is changed to include an addition or a removal.
308
309 The Ex_Client component start() method will retrieve the U_Ex_Hello uses port and will call the say_hello() method 
310 on the first reference.  The code for this method is given below:
311
312 ::
313
314   void 
315   Ex_Client_i::start() {
316
317    // Retrieve the uses port U_Ex_Hello
318    Engines::DSC::uses_port * uport = get_uses_port("U_Ex_Hello"); 
319
320    // Retrieve the first reference in the sequence
321    _Ex_Hello_provides_port =  HelloWorld::_narrow((* uport)[0]);
322
323    // Call the method on the port
324    _Ex_Hello_provides_port->say_hello(_instanceName.c_str());
325   }
326
327 Note that the _narrow method has to be used to transform the references contained in the uses port 
328 into the provides port type, before the provides port can be used.
329
330 Datastream ports and services
331 ++++++++++++++++++++++++++++++
332 The sources in this example are located in the src/Ex_ProducteurConsommateur directory.
333
334 There are two purposes to this example.  Firstly, the example shows how a service that wants to use DSC ports 
335 is implemented.  It then shows how datastream ports included in the SALOME kernel are used.
336
337 This example sets up two services that will be connected through a datastream port.  The "produit" service of the 
338 "Producteur" component will produce a dataflow, and the "consomme" service of the "Consommateur" component will display data.
339
340 The "produit" service terminates when it has sent all data that it has to produce.  The number of data to be produced 
341 is determined by the "nombre" dataflow port.  The "consommateur" service needs to know how many data it has to retrieve before 
342 it can terminate.  As for the product service, this number is determined by the "nombre" dataflow port.
343
344 The following is the IDL definition of the two components:
345
346 ::
347
348   interface Producteur : Engines::Superv_Component {
349     void produit(in long nombre);
350   };
351
352   interface Consommateur : Engines::Superv_Component {
353     void consomme(in long nombre);
354   };
355
356 In order to declare a component that will contain services using DSC ports, the component must inherit 
357 from the Engines::Superv_Component interface and no longer from the Engines::EngineComponent interface. 
358 In addition to adding the DSC interface to the component, Engines::Superv_Component adds the 
359 init_service() method that the supervisor calls before the service is executed. The purpose of this 
360 method is to enable the service designer to initialise ports for the service for connection before 
361 the service is actually started. init_service() performs the same function as register_ports() in 
362 the previous examples.
363
364 The next step is to implement these two components.  The first difference from a classical component is that it 
365 must inherit from the Superv_Component_i class. It must also implement the init_service() method.
366
367 The following is the implementation of the init_service method for the "Producteur" component:
368
369 ::
370
371   CORBA::Boolean
372   Producteur_i::init_service(const char * service_name) {
373     CORBA::Boolean rtn = false;
374     string s_name(service_name);
375     if (s_name == "produit") {
376       add_port("BASIC_short", "uses", "produit_port");
377       rtn = true;
378     }  
379     return rtn;
380   }
381
382 The DSC_User layer that implements the Superv_Component_i class provides new methods for adding uses and provides ports.  
383 They are methods of the add_port family (See the SALOME doxygen documentation).  The purpose of these methods 
384 is to enable creation and recording of a service port in a single step.  They also enable the use of 
385 datastream ports predefined in the SALOME kernel.
386
387 We chose to use the BASIC_short datastream port for the "produit" service.  When the SALOME kernel provides 
388 a datastream port, it always provides the implementation for the provides port and for the uses port.  
389 The first part of the name (BASIC) identifies the datastream port family (for example like CALCIUM or PALM).  
390 The second part of the name contains the transmitted data type, in this case a short.  This port type forms 
391 the first parameter of the add_port method.  The other two arguments are the type of DSC port (uses or provides) 
392 and the name of the port in the component.
393
394 When the objective is to use this port in the service, it will be necessary to retrieve a reference on the port, in the 
395 same way as in the previous examples.  New methods (like add_port) called get_port are available for this purpose.  
396 The following is an example of code to use the get_port method:
397
398 ::
399
400   uses_port * my_port = NULL;
401   get_port(my_port, "produit_port");
402   if (my_port != NULL) {
403    for (CORBA::Long i = 0; i < nombre; i++) {
404      data_short_port_uses * the_port = dynamic_cast<data_short_port_uses * >(my_port);
405         the_port->put(10);
406    }
407   }
408
409 The get_port method has two arguments.  The first will contain a pointer to the port and the second indicates 
410 the requested port name.  A generic pointer is obtained after calling the get_port method.  The next step 
411 is to change its type with the expected port type through a dynamic_cast.  The port can then be used.
412
413 The DSC_User layer offers several signatures for the get_port and add_port methods, to make programming 
414 more convenient.  For example, the "Consommateur" component uses the template versions of these methods 
415 to add and retrieve code.
416
417 ::
418
419   data_short_port_provides * my_port = NULL;
420   my_port = get_port<data_short_port_provides>("consomme_port");
421   if (my_port != NULL)
422   {
423     for (CORBA::Long i = 0; i < nombre; i++) {
424        cout << "Hello, I receive : " << my_port->get() << endl;
425     }
426   }
427
428 The list of the different port types supplied by the SALOME kernel is available in the 
429 Doxygen documentation for the SALOME kernel.
430
431 Add datastream ports and/or interfaces
432 +++++++++++++++++++++++++++++++++++++++++++++++++
433 The sources for this example are located in the src/Ex_ProducteurConsommateur_Adv directory.
434
435 The purpose of this example is to show mechanisms used to add your specific types of ports into 
436 the DSC_User layer.  To do this, this example explains how to replace the BASIC_short port in the previous 
437 example by its own port.  Considering that the components in this example are practically identical, we will 
438 only describe the declaration and implementation of the port in this document.
439
440 A Datastream port (or interface) family contains two different types of objects:
441
442 1.  A factory
443 2.  The implementation of ports
444
445 The DSC_User layer knows the types of Datastream ports through the factory pattern design.  
446 For each family, a factory is recorded when the component is created.  It is then used by 
447 the component in the add_port(...) methods to create and save ports.
448
449 Firstly, its port(s) must be declared in an IDL file (MesPorts.idl in the example):
450
451 ::
452
453   module Ports {
454     module Mes_Ports {
455       interface Mon_Type_De_Port : Ports::Data_Port {
456         boolean is_new_data_available();
457       };
458       interface Short_Mes_Ports : Mon_Type_De_Port {
459         void put(in short data);
460       };
461     };
462   };
463
464 In this example, a port is declared:  Short_Mes_Ports.  This is the port that is used to send a short, but 
465 that can also be queried to find out if new data have arrived.  On the other hand, the get() method has not 
466 been declared in the IDL (although it is possible) because it is intended to be used locally.
467
468 The next step is to implement the port type.  This is done by implementing the factory and the uses part 
469 and the provides part of the port type.
470
471 A port factory is an object that implements the interface of the port_factory abstract class (see the Doxygen documentation).  
472 The factory is called every time that a service adds a port in the component (uses or provides).  
473 The factory is identified by a character string that identifies it to the component.  The factories 
474 must be recorded as early as possible.  This is why factories are recorded in the component constructor.
475
476 The following figure shows how the factory is registered in the ProducteurAdv component:
477
478 ::
479
480   ProducteurAdv_i::ProducteurAdv_i(CORBA::ORB_ptr orb,
481                                    PortableServer::POA_ptr poa,
482                                    PortableServer::ObjectId * contId, 
483                                    const char * instanceName, 
484                                    const char * interfaceName) :
485     Superv_Component_i(orb, poa, contId, instanceName, interfaceName)
486   {
487     _thisObj = this;
488     _id = _poa->activate_object(_thisObj);
489
490     register_factory("MESPORTS", new mes_ports_factory());
491   }
492
493 In this example, the new port type is identified by the MESPORTS string.  Note that the <<_>> symbol 
494 may not be used in the name.  It is used as a separator between the family name and the port type in the family (Eg: MESPORTS_short).
495
496 The next step is to implement the ports.  The uses port part and the provides port part have to be implemented 
497 for each port defined in the IDL.  The implementation at the uses end must inherit from the uses_port class.  
498 At the provides end, it must inherit from the provides_port class.
499
500 The uses_port and provides_port classes are abstract classes.  They propose methods to automate registering 
501 and management of ports.  The component developer must implement these mechanisms in the DSC_Basic layer, while 
502 the ports developer is responsible for these functions in the DSC_User layer.
503
504 The following methods are used:
505
506 ::
507
508   For the uses end:
509
510   virtual const char * get_repository_id() = 0;
511
512   virtual void uses_port_changed(Engines::DSC::uses_port * new_uses_port,
513                                      const Engines::DSC::Message message) = 0;
514
515   For the provides end:
516
517   virtual Ports::Port_ptr get_port_ref() = 0;
518
519   virtual void provides_port_changed(int connection_nbr,
520                                          const Engines::DSC::Message message) {};
521
522 The first step at the uses end is to use the get_repository_id() method.  This obtains the CORBA typecode of the port.  
523 The objective is to provide the same information as the first add_uses_port argument in the Basic layer.  
524 The uses_port_changed(...) method enables the port to be notified about new connections with the provides port 
525 and to manage the list of connections.
526
527 At the provides end, get_port_ref() obtains a CORBA reference on the servant.  Finally, the provides_port_changed(...) method 
528 can be overdefined if the provides port uses the information originating from connections / disconnections.
529
530 In the example, the provides port is implemented by the data_short_mes_ports_provides class and 
531 the uses port is implemented by the data_short_mes_ports_uses class.
532