Previous Up

Appendix F  CBserver Plug-Ins

Users with Prolog experience may find it interesting to use the LPI ("logic plug-in") feature of ConceptBase server. An LPI plug-in is essentially a small Prolog program that is inserted into the ConceptBase server code at run-time. It extends the functionality of the CBserver, for example for user-defined builtin queries.

The implementation of pre-defined builtin queries (see appendix E) is part of the CBserver.

You can create a file like MyPlugin.swi.lpi to provide the implementation for your self-defined builtin queries or for action calls in active rules (see section 4). You can use the full range of functions provided by the underlying Prolog system (here: SWI-Prolog, link) and the functions of the CBserver to realize your implementation. You may consult the the CB-Forum for some examples at link. You find there examples for sending emails from active rules, and for extending the set of builtin queries and functions.

F.1  Defining the plug-in

Once you have coded your file MyPlugin.swi.lpi, there are two ways to plug it into the CBserver. The first way is to copy the file into an existing database directory created by the CBserver.

  CBserver -d MYDB 
  [load Telos definitions; then stop the CBserver]
  cp MyPlugin.swi.lpi MYDB

This option makes the definitions only visible to a CBserver that loads the database MYDB. The second option is to instruct ConceptBase to load your LPI code to any database created by the CBserver. To do so, you just have to copy the LPI file into the directory with the system definitions:

  cp MyPlugin.swi.lpi <CB_HOME>/lib/system

where CB_HOME is the directory into which you installed ConceptBase. The number of LPI files is not limited. You may code zero, one or any number of plug-in files.

A couple of useful LPI plug-ins are published via the CB-Forum, see link. Note that these plugins are copyrighted typically come with their own license conditions that may be different to the license conditions of ConceptBase. If you plan to use the plugins for commercial purposes, you may have to acquire appropriate licenses from the plugin’s authors.

F.2  Calling the plug-in

There are two ways to trigger the call of a procedure implemented by an LPI plugin.

  1. By explicitely calling a builtin query class (or function) whose code has been implemented by the LPI plugin. The LPI code would then look similar to the code in SYSTEM.SWI.builtin and you must have defined an instance of BuiltinQueryClass that matches the signature of the LPI code. The call to the builtin query class may be enacted from the user interface, or it may be included as an ASKquery call in an instance of AnswerFormat. Refer for more information to section 3.2.5 and to the directory Examples/BuiltinQueries in your ConceptBase installation directory.
  2. By calling the implemented function as a CALL action of an active rule. See section 4.2.2 for an example. In that case, there does not need to be a definition of a builtin query class (or function) to declare the signature of the procedure.

If the code of an LPI plugin realizes a function, e.g. selecting the first instance of a class, then you can use that function whereever functions are allowed. As an example, consider the definition of the LPI plugin selectfirst.swi.lpi from link:

  compute_selectfirst(_res,_class,_c1) :-
        nonvar(_class),                              
        cbserver:ask([In(_res,_class)]),             
        !.

  tell: 'selectfirst in Function isA Proposition with
  parameter class: Proposition end'.

The first clause is the Prolog code. The predicate must start with the prefix compute_ followed by the name of the function. The first argument is for the result of the function. Subsequently, each input parameter is represented by two arguments, one for the input parameter itself and a second as a placeholder of the type of the input parameter. The second clause tells the new function as Telos object so that it can be used like any other Telos function. For technical reasons, the ’tell’ clause may not span over more than 5 lines. Use long lines if the object to be defined is large.

If you just want to invoke the procedure defined in an LPI plugin via the CALL clause of an active rule, you do not need to include a ’tell’ clause. Consider for example the SENDMAIL plugin from link.

In all cases you need to be very careful with testing your code. Only use this feature for functions that cannot be realized by a regular query class or by active rules. LPI code has the full expressiveness of a programming language. Program errors may lead to crashes of the CBserver or to infinite loops or to harmful behavior such as deletion of files. Query classes, deductive rules and integrity constraints can never loop infinitely and can (to the best of our knowledge) only produce answer, not changes to your file system or interact with the operating system. Active rules could loop infinitely but also shall not change your file system and shall not interact with the operating system unless you call such code explicitely in the active rule.

You can disable the loading of LPI plugins with the CBserver option -g nolpi. The CBserver will then not load LPI plugins upon startup. This option might be useful for debugging or for disabling loading LPI plugins that are configured in the lib/system sub-directory of your ConceptBase installation.

F.3  Programming interface for the plug-ins

The CBserver plug-ins need to interface to the functionalities of the CBserver, the Prolog runtime, and possibly the operating system. To simplify the programming of the CBserver plug-ins, we document here the interface of the module cbserver. We assume that the code for the plug-ins is written in SWI-Prolog and the user is familiar with the SWI-Prolog system.

cbserver:ask(Q,Params,A)
asks the query Q with parameters Params to the CBserver. The answer is returned as a Prolog atom in A. The atom holds the answer represented in the answer format of Q, see also chapter 3.
Example: cbserver:ask(find_classes,[bill/objname],A)
cbserver:ask(Preds)
evaluates the predicates in list Preds. The predicate can backtrack and will bind in case of success the free variables in Preds. We currently support only the following predicates: In, A, AL, and Isa.
Example: cbserver:ask([In(X,Employee),A(X,salary,1000)])
cbserver:askAll(X,Preds,Set)
finds all objects X that satisfy the condition in Preds and puts the result into the list Set. Supported predicates in Preds are: In, A, AL, and Isa.
Example: cbserver:askAll(X,[In(X,Employee),A(X,salary,1000],S)
cbserver:tellFrames(F)
tells all frames contained in the atom F. The call will fail if there is any error in F.
Example: cbserver:tellFrames(’bill in Employee end’)
cbserver:makeName(Id,A)
converts an object identifier to a readable object name.
cbserver:makeId(A,Id)
converts an object name (Prolog atom) into an object identifier used by the CBserver to identify Telos objects. If A is already an object identifier, it is returned as well in Id.
cbserver:arg2val(E,V)
transforms an argument (either an object identifier or a functional expression) to a value (a number or a string). The value can then be used in Prolog style computations such as arithmetic expressions.
cbserver:val2arg(V,I)
transforms a Prolog value (number, string) to an object identifier, possibly by creating a new object for the value.
cbserver:concat(X,Y)
concats the strings (Prolog atoms) contained in the list X. The result is returned in Y.

Note that ConceptBase internally manages concepts by their object identifier. The programming interface instead addresses concepts (and objects) by their name, i.e. the label of the object or the Prolog value corresponding to the label. You may have to use the procedure makeName and makeId to switch between the two representations. The two procedures arg2val and val2arg are useful for defining new builtin functions on the basis of Prolog’s arithmetic functions. Assume for example, that the object identifier id123 has been created to correspond to the real number 1.5. Then, the following relations hold: makeName(id123,’1.5’), arg2val(id123,1.5). Hence, makeName returns the label ’1.5’ whereas arg2val returns the number 1.5.

The interface shall be extended in the future to provide more functionality. Be sure that you only use this feature if user-defined query classes cannot realize your requirements!


Previous Up