Using Macro-Based BRC
The macro-based BRCs (Macros) are usable in multiple circumstances. They can be used to:
- Do complex calculations
- Query on Business Data Tables or external database tables lookup using SQL statements
- Java-based function calls
- Dynamically determining the output CPE (which was unknown beforehand)
This section focuses on some specific issues concerning macros.
Understanding macros
Macros are written using the CPQ Macro Language, which can be compared to a script language. CPQ Macro Language is an interpreted language, meaning that macros are not compiled (once they are written) but interpreted by the CPQ Engine.
The CPQ Macro Language itself is Java-based. This has as a consequence that it can easily be extended with custom functions that fulfill one or another role in the context of a Configuration Process. More information about extending the CPQ Macro Language will be given in a subsequent paragraph.
In the context of CPQ, the CPQ Macro Language is used to express BRC, meaning that macros are a way to express constraints. This is extremely important, as macros are in essence procedural. As these macros are procedural, you can theoretically do a lot of things with them, but you will have to keep in mind that they are triggered by a constraint engine, and that “a lot of things” can have a negative impact on performances.
Understanding the impact on performances
As the CPQ Macro Language is a scripting language, a lot of things can be done. However you should keep in mind that the execution of a macro is not controlled by you, but by the CPQ Engine.
- The existence rule for an input alias issues “TRUE” (by the execution of another constraint)
- The domain for an input alias is calculated (by the execution of another constraint)
- The domain for an input alias is filtered (by the execution of another constraint)
- The form property corresponding to the input alias becomes answered (by the execution of another constraint, by the user, by an event, …)
- The answer to the form property corresponding to the input alias is changed (by the execution of another constraint, by the user, by an event, …)
- The answer to the form property corresponding to the input alias becomes invalid (by the execution of another constraint)
- The answer to the form property corresponding to the input alias is reset (by the user, by an event, …)
- The domain for an input alias is reset (by the execution of another constraint)
- The existence rule for an input alias issues “FALSE” (by the execution of another constraint)
As the macro will frequently re-execute during its lifetime, and as you control the content of a macro, you should watch out when you use:
- SQL connections: on top of license issues (DBMS-related licenses), you can have performance issues because an SQL connection “takes time”. To optimize these connections, make sure that they have been defined in a macro-language data source in the cameleon-ds.xml file.
- SQL statements: poorly written SQL statements can obviously have a negative impact on performances (for instance when using multiple joins).
- Business Data Tables: SQL statements on Business Data Tables can slow down when the tables are not optimized. The use of indexes is recommended in order to avoid table scans.
- External databases: external databases can slow down the system because of multiple issues.
- Connection times, distant data coming across the network, not-optimized data…everything comes into play here. Moreover, from a functional point of view, external database cannot be a part of the versioning mechanism.
- Complex calculations: watch out for (infinite) loops, and for recursive processes. These can slow down the process.
- Traces: macros enable the use of trace files. Tracing will write to physical files, and can degrade performances.
- File management: macros can create, read and write physical files. Physical file manipulation can degrade performances.
- OS calls: operating system calls (e.g. to executables) should be avoided whenever possible. These calls can degrade performances or can give concurrency problems (problems related to pieces of code which are not meant to be executed by multiple users simultaneously).
The basic idea of all of these remarks is the following:
Using data tables
In some cases, it can be necessary to execute SQL statements in order to retrieve data from tables. When using SQL Statements, please respect the following recommendations:
Tip:
- Prefer the usage of Business Data Tables over externally managed tables.
- Use indexes in order to optimize the queries on the Business Data Tables.
- Do not query physical tables in the CPQ database with some exceptions for the Business Data Table when you need it absolutely.
Moreover, it is important to remember that Business Data Tables are versioned, so you cannot immediately access them. In order to access a Business Data Table using an SQL statement, the following syntax has to be used:
BRC DictionaryListDiscounts()
/* Look up the physical name of the BDT */
LOCAL bdtTableName “”
LET bdtTableName confML.getBdtTableName("bdtName")
/* Construct the SQL statement */
LOCAL lQuery “”
LET lQuery “SELECT COL1 FROM “ + bdtTableName + “ WHERE …”
/* Execute the SQL statement */
SQL_QUERY lQuery IN “outputTable”
/* Format the resulting array by mapping the aliases and by explicitly
defining the number of columns (the number of rows is calculated by the query
itself) */
LET outputTable[0,0] “aOutput”
LET outputTable[“NC”] 1
/* Return the table to the constraint engine */
DISPLAY “outputTable”
END_DEFINE
Tip:
- Use the getBdtTableName(“bdtName”) function to retrieve a Business Data Table in the current workspace.
- Use the getBdtTableName("bdtWorkspace","bdtName") function to retrieve a Business Data Table in a different workspace.
Dynamical BRC
In some cases, it may be difficult or impossible to know which output CPE will have to be issued by a BRC. In this particular case, a “dynamical BRC” may provide a solution.
Definition: A dynamical BRC is a BRC for which at least one output alias is undefined at design time.
A dynamical BRC will use a macro in order to determine, at run-time, the correct CPE to output. We will refer to these CPE as dynamical CPEs.
A dynamical BRC will be interpreted differently than a “normal” BRC. In a general case, a BRC can have N+M aliases, N “input” aliases and M “output” aliases. Whereas a Matrix-based BRC would create a constraint with “N+M” columns, a Macro-based BRC can create a constraint with a number of columns varying between “M” and “N+M”.
Let’s suppose the following example, which is designed using a regular (as in: not dynamic) Macro-based BRC. The aliases are defined as follows:
| NAME | CPE | MUSTEXIST | MUSTBEANSWERED |
|---|---|---|---|
| aEngine | CPE.currentForm.FP/fpEngine.value | TRUE | TRUE |
| aPack | CPE.currentForm.FP/fpPack.value | TRUE | FALSE |
| aWheel | CPE.currentForm.FP/fpWheel.value | TRUE | FALSE |
The macro itself can be written as follows:
DEFINE regularMacro()
LET outputTable[0,0] “aPack”
LET outputTable[0,1] “aWheel”
IF aEngine = “DIESEL”
/* content for DIESEL */
ELSE_IF aEngine = “GASOLINE”
/* valid combination 1 */
LET outputTable[1,0] “STANDARD”
LET outputTable[1,1] “15 INCH”
/* valid combination 2 */
LET outputTable[2,0] “STANDARD”
LET outputTable[2,1] “16 INCH”
/* valid combination 3 */
LET outputTable[3,0] “COMFORT”
LET outputTable[3,1] “17 INCH”
/* valid combination 4 */
LET outputTable[4,0] “SPORT”
LET outputTable[4,1] “18 INCH”
/* formatting output table */
LET outputTable[“NR”] 4
LET outputTable[“NC”] 2
END_IF
DISPLAY “outputTable”
END_DEFINE
In this example, “aEngine” is clearly an “input” alias (it needs to be answered before determining the values for the pack and the wheel). The resulting constraint for a “Gasoline” engine can be represented as follows:
| APACK | AWHEEL |
|---|---|
| STANDARD | 15 INCH |
| APACK | AWHEEL |
|---|---|
| STANDARD | 16 INCH |
| COMFORT | 17 INCH |
| SPORT | 18 INCH |
The CPQ Engine will read this constraint “by line”, meaning that each “line” is a valid combination. If the user would choose a “Sport” pack, the only possible wheel size would be “18 inch”.
Suppose now that this BRC is a dynamical BRC. The alias table would not know the “output” aliases and can be represented as follows:
| NAME | CPE | MUSTEXIST | MUSTBEANSWERED |
|---|---|---|---|
| aEngine | CPE.currentForm.FP/fpEngine.value | TRUE | TRUE |
The macro would have to determine itself which CPE to output:
DEFINE dynamicMacro()
LET outputTable[0,0] “CPE.currentForm.FP/fpPack.value”
LET outputTable[0,1] “CPE.currentForm.FP/fpWheel.value”
IF aEngine = “DIESEL”
/* content for DIESEL */
ELSE_IF aEngine = “GASOLINE”
/* first column */
LET outputTable[1,0] “STANDARD”
LET outputTable[2,0] “COMFORT”
LET outputTable[3,0] “SPORT”
LET outputTable[“NR”,0] 3
/* second column */
LET outputTable[1,1] “15 INCH”
LET outputTable[2,1] “16 INCH”
LET outputTable[3,1] “17 INCH”
LET outputTable[4,1] “18 INCH”
LET outputTable[“NR”,1] 4
/* formatting output table */
LET outputTable[“NR”] 4
LET outputTable[“NC”] 2
END_IF
DISPLAY “outputTable”
END_DEFINE
The resulting matrix would look as follows for the “Gasoline” answer:
| CPE.CURRENTFORM.FP/FPPACK.VALUE | CPE.CURRENTFORM.FP/FPWHEEL.VALUE |
|---|---|
| STANDARD | 15 INCH |
| STANDARD | 16 INCH |
| COMFORT | 17 INCH |
| SPORT | 18 INCH |
In this case, you can clearly see that the header of the resulting constraint has been dynamically constructed by the macro. Also, the number of results in each column is not necessarily the same.
Most importantly, this constraint is not applied in the same way. Indeed, whereas the “normal BRC” would issue “valid combinations between all output aliases”, the “dynamical BRC” only issues “possible combinations per output alias”.
In this example, if the user would choose a “Sport” pack, he would still be able to choose between 4 wheel sizes: “16 inch”, “17 inch” or “18 inch”.
Managing your macros
It is well known that any coding can quickly become spaghetti. In order to avoid drowning in macro code, do not hesitate to use child macros. Here’s an example of a parent macro that calls a child macro:
DEFINE parentMacro()
/* Do something */
/* Initialize variables to be passed as parameters to the child macro */
LOCAL lInput1 “something”
LOCAL lInput2 “something else”
/* Initialize variable to receive the result from the child macro */
LOCAL lChildOutput “”
LET lChildOutput childMacro(lInput1, lInput2)
/* Do something else and display end-result */
END_DEFINE
Here’s the child macro that issues a result:
DEFINE childMacro(parameter1, parameter2)
/* Do something with parameter1 and parameter2 */
/* Send back result to parent macro */
LET childMacro “the result”
END_DEFINE
Java code inclusion
As mentioned before, it is possible to extend the standard functions available in the macro language by custom Java code inclusion. In order to that, the ml.javalib parameter in the cameleon.properties file will have reference the necessary class names. For instance:
ml.javalib = java.lang.Integer
Moreover, you will need to deploy the ear or jar file in the jboss target in which the Configurator has been installed.
Example of a Macro-based BRC with Java code inclusion:
DEFINE javaCodeInclusion()
/* Initialization of a numeric variable in macro language */
LET counter 1
/* Creation of the associated Java Integer object */
LET intCounter Integer(STR counter)
/* Initialization of a string variable in macro language */
LET zipCode “32600”
/* Usage of the associated Java Integer object static method “parseInt” */
LET intZipCode Integer.parseInt(zipCode)
/* rest of the macro */
END_DEFINE
