How to create build validations and module scripts

From InfiniteERP Wiki
Revision as of 09:46, 11 July 2021 by Wikiadmin (talk | contribs) (Created page with "== Objective == In this howto article, we will create both a Build Validation and a Module Script. They are both very similar concepts, and are also implemented in a very simi...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

Objective

In this howto article, we will create both a Build Validation and a Module Script. They are both very similar concepts, and are also implemented in a very similar way, so we are going to describe them in the same document.

Build Validations

A build validation in Openbravo is a Java class which is executed at the beginning of the update.database task, and which returns a list of errors. If there is one or more errors, the build is stopped, and the errors are shown to the user.

Because of the fact that the Build Validation can stop the build process, it is very important that it follows these rules:


  • The errors created by the validation will be shown as a message in the Rebuild window. In this message it is very important to state very clearly the cause of the failure and if needed, an alert rule must be created to help users identify the problematic records if finally the build fails.
Validationerror.jpg
  • Every developer must ensure and check that the message is understandable once he creates the validation.

Please see this image as an example:

BuildValidation.jpg
  • You can find a clear and simple example of how to develop a validation containing alerts in:
    • src-util/buildvalidation/src/org/openbravo/buildvalidation/Cbpvendoracct_data.xsql
    • src-util/buildvalidation/src/org/openbravo/buildvalidation/Cbpvendoracct.java

The build validations main use is to stop the build because the instance is in such a state that rebuilding the system cannot be safely done. In modules, validations can be used to stop the build in case the module cannot be installed safely in the system for some reason.

So, for example, a validation could be used to check if the user has done a necessary manual setup step that cannot be automated. Or a validation could also be used to check if the user has filled some data which is needed for the module to be installed correctly.

Build validations are a different piece of code in Openbravo, in that they are distributed as binaries (compiled classes), which are executed directly without them being compiled on the run. The packaging task for modules will automatically include the binaries inside the .obx file, the developer should only make sure that the classes were compiled before packaging the module.

Bulbgraph.png   Note: Build validations shouldn't be a common case. Normally, they should only be needed when there was a mistake in a previous version of the module, or Core. The ultimate result for a validation is that, if it detects what it was meant to detect, the user will need to do manual actions to fix the problem, and this should be avoided if possible by all developers

Introduction to the implementation of build validations

The main steps to create a build validation are:

  1. Write it (you need to create a Java class for each validation you want to do).
  2. Compile it (Build validations are compiled separately from the rest of the Openbravo code, using a specific ant compile task).
  3. Test it (you should carefully test the validation before including it in your module, or in Core).
  4. Add it to the module, or Core (Don't forget to add the binary classes too, as they are needed, in addition to the Java source files. ant package.module will automatically add the files to the .obx file, but you need to manually add the binary files in addition to the source files to the Mercurial repository if you are using one for your module, or if you want to add the validation to Core).


To create a build validation, you need to create a class which extends the org.openbravo.buildvalidation.BuildValidation class. This is an abstract class which has one abstract method: List<String> execute()

This method needs to be implemented. The class will be loaded at the beginning of the update.database task, and this method will be called. As you can see, a List of Strings must be returned. If the list is empty, the build will continue. If the list contains at least one string, every string will be shown to the user, and the build will stop. Every string is supposed to be a meaningful error message, which shows the user what does he need to fix in his system for the validation to pass.

Writing the build validation

As was just explained, a build validation is nothing more than a class which extends the org.openbravo.buildvalidation.BuildValidation class, and implements the abstract execute method. Here you can see a small example:


<source lang="java">

public class ValidationExample extends BuildValidation {

 public List<String> execute() {
   try {
     ConnectionProvider cp = getConnectionProvider();
     PreparedStatement ps = cp
         .getPreparedStatement("SELECT COUNT(*) FROM C_Bpartner WHERE name IS NULL");
     ps.execute();
     ResultSet rs = ps.getResultSet();
     rs.next();
     ArrayList<String> errors = new ArrayList<String>();
     if (rs.getInt(1) > 0) {
       errors
           .add("There are business partners which don't have a defined name. Please fill the name of every business partner before installing the module MyModule.");
     }
     return errors;
   } catch (Exception e) {
     return handleError(e);
   }
 }

} </source>

In this example, you can see the main points of any build validation:

  • The class implements the BuildValidation class, and its corresponding execute method.
  • In the method, you can do the validation (i.e. check if the Openbravo instance complies with some specific rule). There is a convenient getConnectionProvider() method, provided by the abstract superclass, which allows you to do direct queries to the database. It is very important to remark that validations should only do queries to the database, they should never change the contents of the database. Module Scripts can be used to change the database if needed.
  • Finally, the method returns a List of error Strings, which can be empty if the validation went well, or can contain one or more errors if the system didn't comply with the validation.

You can also use SqlC if you want to do the database operations.

<source lang="java"> public class ValidationExample2 extends BuildValidation {

 public List<String> execute() {
   try {
     ConnectionProvider cp = getConnectionProvider();
     ArrayList<String> errors = new ArrayList<String>();
     int numBpartners=Integer.parseInt(ValidationExample2Data.queryBPartners(cp));
     if (numBpartners > 0) {
       errors.add("There are business partners which don't have a defined name. Please fill the name of every business partner before installing the module MyModule.");
     }
     return errors;
   } catch (Exception e) {
     return handleError(e);
   }
 }

} </source>

This needs the following xsql file: <source lang="xml"> <?xml version="1.0" encoding="UTF-8" ?>

<SqlClass name="ValidationExample2Data" package="org.openbravo.buildvalidation">

 <SqlMethod name="queryBPartners" type="preparedStatement" return="string">
     <SqlMethodComment></SqlMethodComment>
     <Sql><![CDATA[
        SELECT COUNT(*) FROM C_Bpartner WHERE name IS NULL
         ]]>
     </Sql>
 </SqlMethod>

</SqlClass> </source>

The class source files themselves should be inside the module folder, in the folder: src-util/buildvalidation/src

And they should follow the standard Java package rules. The folder will not exist if it's your first validation, so you will need to create it.

Compiling the build validation

Warn-icon.png   Build Validations should be (must be in case they are going to be included in Openbravo 3 distribution or in Retail pack) compiled using, at most, latest supported JDK version.

To compile the build validation, you use the following command: <source lang="java"> ant compile.buildvalidation -Dmodule=javapackageofmodule </source> If you want to compile the validations of Openbravo Core, the module property needs to be set to "org.openbravo"

This task will compile the Java classes, and copy them to the correct "build" folder in the module, or in the src-util/buildvalidation of Core.

Executing the build validation

The build validations will be automatically executed in the update.database task, or in update.database.mod if the module is being applied. Validations will not be compiled in this task, they need to have been compiled previously to be executed.

Another important point to remark is that the build validations will be executed in every version of the module, or Core, in which they are present (this means, for example, that if a validation exists in Openbravo Core MP15 and later, it will be executed when updating to MP15 or later, and will be executed again when updating to a later version). The developer needs to take this into account (it is advisable not to design validations which are version-dependant, they should always be designed to be generic).

The package.module task will automatically include the compiled binaries in the .obx file, provided that you compiled the validations before packaging the module.

If you are adding a validation to Openbravo Core, remember that you need to include the binary classes (contained in src-util/buildvalidation/build/classes/) to the repository like you would add any source files. If you don't, they won't be executed (because the classes are not compiled by default in the build process, they are executed only if the binary files are there).

Defining the build validation execution limits

Bulbgraph.png   This feature is supported since 3.0PR17Q1

The build validations provide by default a couple of mechanisms that allow to control when they should be executed:

Build Validation version limits

It is possible to define a dependency with two versions of a particular module to identify when the build validation should be executed:

  • First Execution Version: defines the first version since the build validation should be executed. Before an upgrade, if the dependent module has a version lower or equal than this version, the build validation will NOT be executed. By setting this limit we are ensuring that this version and previous ones do not require the build validation execution.
  • Last Execution Version: defines the last version of the dependent module for which the build validation should be executed. Before an upgrade, if the dependent module has a version higher or equal than this version, the build validation will NOT be executed.

This way, the build validation can be executed only when it is really needed, avoiding extra time calculations when updating database.

The dependent module and its limit versions can be set by overriding the getBuildValidationLimits() method in our BuildValidation subclass:

<source lang="java">

 @Override
 protected ExecutionLimits getBuildValidationLimits() {
   return new ExecutionLimits("0", new OpenbravoVersion(3, 0, 28207), new OpenbravoVersion(3, 0, 29495));
 }

</source>

Following this code example, we are setting that our build validation should only be executed when upgrading core module (id = "0") from a version between 3.0.28207 (first execution version) and 3.0.29495 (last execution version). This way, we are avoiding the execution of our build validation in those cases which it does not apply.

Executing just once

In case we need to execute the build validation just once, this can be covered by setting just the Last Execution Version.

For example, if we add a check in version 1.7.0 of a module, we want to execute the build validation when upgrading from versions previous this version. So we can define its limits as follows:

<source lang="java">

 @Override
 protected ExecutionLimits getBuildValidationLimits() {
   return new ExecutionLimits(ad_module_id, null, new OpenbravoVersion(1, 7, 0));
 }

</source>

Where ad_module_id is the UUID of the module.

This way the build validation will be executed when upgrading the module from any version prior to 1.7.0 and it will not be executed anymore.

Execute on install

It is also possible to configure whether the build validation should be executed when installing the dependent module defined with the getBuildValidationLimits() method. By default, the build validation will be executed when installing the dependent module.

But in case we do not want to execute it in this case, the executeOnInstall() method should be overridden as follows:

<source lang="java">

 @Override
 protected boolean executeOnInstall() {
   return false;
 }

</source>

Module Scripts

A module script is a task that is executed when a module is being applied in the database. This task is supposed to do operations that need to be done in the database, and cannot be done in a different way.

Conceptually, they are extremely similar to Build Validations, and the main steps involved to create a Module Script are virtually the same, so it's advisable that you read the previous section before this one. The main points of module scripts are the following ones:

  • Whereas build validations are executed at the beginning of update.database, module scripts are executed in the middle of the process, when the foreign keys and triggers of the database have been disabled.
  • As with build validations, module scripts will be executed every time an update.database or update.database.mod for that particular module is called. This means that the script must be built in such a way that can be repeatedly executed without problems, and this is a very important consideration that the developer needs to take into account.
  • The module script should never fail. If it fails, the build will be stopped, but as it has already been started, the system will be in an inconsistent state (for example, all the foreign keys and triggers will be down). The developer must avoid this at all costs if possible.

Introduction to the implementation of module scripts

As with build validations, a module script is a Java class which extends the abstract class org.openbravo.modulescript.ModuleScript, and implements the execute() method. This execute method will be called by the update.database task. This method will include the logic of the module script, here all the operations will be done.

Writing the module script

Here goes a very simple Module script, which just sets the value of some column to a default in case the column value is null:


<source lang="java">

public class ModuleScriptExample extends ModuleScript {

 public void execute() {
   try {
     ConnectionProvider cp = getConnectionProvider();
     PreparedStatement ps = cp
         .getPreparedStatement("UPDATE mytable SET mycolumn=0 WHERE mycolumn IS NULL");
     ps.executeUpdate();
   } catch (Exception e) {
     handleError(e);
   }
 }

} </source>

The main two important points that you need to take into account when building the script are:

  • The script should never fail. Failure will leave the user in a very unfriendly situation (with the build stopped in the middle), and should be avoided at all costs. In this case, this script will not fail unless the table doesn't exist.
  • The script needs to be designed so that it can be executed an indefinite number of times. This example script, for example, can be executed several times without problems (the first time will set all rows which have a null value, and after that, only the new rows which have been inserted before will be modified, but the previously modified rows will not be modified again unless they have the supposedly wrong null value again).


The class source files themselves should be inside the module folder, in the folder: src-util/modulescript/src

And they should follow the standard Java package rules. The folder will not exist if it's your first script, so you will need to create it.

Compiling the module script

Warn-icon.png   Module Scripts should be (must be in case they are going to be included in Openbravo 3 distribution or in Retail pack) compiled using, at most, latest supported JDK version.

To compile the module script, you use the following command: <source lang="java"> ant compile.modulescript -Dmodule=javapackageofmodule </source> If you want to compile the scripts of Openbravo Core, the module property needs to be set to "org.openbravo"

This task will compile the Java classes, and copy them to the correct "build" folder in the module, or in the src-util/modulescript of Core.

Executing the module script

The module scripts will be automatically executed in the update.database task, or in update.database.mod if the module is being applied. Module scripts will not be compiled in this task, they need to have been compiled previously to be executed.

Another important point to remark is that the module scripts work as build validations in regards to execution criteria; that is, they will be executed in every version of the module, or Core, in which they are present (this means, for example, that if a script exists in Openbravo Core MP15 and later, it will be executed when updating to MP15 or later, and will be executed again when updating to a later version). The developer needs to take this into account (it is advisable not to design scripts which are version-dependant, they should always be designed to be generic).

And, as it was explained above, scripts should also be designed to produce the same result if executed multiple times, because they will be executed every time the system is rebuilt, or the module is updated.

The package.module task will automatically include the compiled binaries in the .obx file, provided that you compiled the module scripts before packaging the module.

If you are adding a module script to Openbravo Core, remember that you need to include the binary classes (contained in src-util/modulescript/build/classes/) to the repository like you would add any source files. If you don't, they won't be executed (because the classes are not compiled by default in the build process, they are executed only if the binary files are there).

Defining the module script execution limits

Bulbgraph.png   This feature is supported since 3.0PR15Q4

The module scripts provide by default a couple of mechanisms that allow to control when they should be executed:

Module version limits

It is possible to define a dependency with two versions of a particular module to identify when the module script should be executed:

  • First Execution Version: defines the first version since the module script should be executed. Before an upgrade, if the dependent module has a version lower or equal than this version, the module script will NOT be executed. By setting this limit we are ensuring that this version and previous ones do not require the module script execution.
  • Last Execution Version: defines the last version of the dependent module for which the module script should be executed. Before an upgrade, if the dependent module has a version higher or equal than this version, the module script will NOT be executed.

This way, the module script can be executed only when it is really needed (see image below), avoiding extra time calculations when updating database.


ModulescriptApplicationScheme.png

The dependent module and its limit versions can be set by overriding the getModuleScriptExecutionLimits() method in our ModuleScript subclass:

<source lang="java">

 @Override
 protected ModuleScriptExecutionLimits getModuleScriptExecutionLimits() {
   return new ModuleScriptExecutionLimits("0", new OpenbravoVersion(3,0,27029), 
       new OpenbravoVersion(3,0,27050));
 }

</source>

Following this code example, we are setting that our modulescript should only be executed when upgrading core module (id = "0") from a version between 3.0.27029 (first execution version) and 3.0.27050 (last execution version). This way, we are avoiding the execution of our modulescript in those cases which it does not apply.

Executing just once

One typical case of modulescripts usage, is to populate the values of a newly added column. So we need to execute the modulescript once. This can be covered by setting just the Last Execution Version.

For example, if we add a new column in version 1.5.0 of a module, we want to execute the modulescript when upgrading from versions previous this version. So we can define its limits as follows:

<source lang="java">

 @Override
 protected ModuleScriptExecutionLimits getModuleScriptExecutionLimits() {
   return new ModuleScriptExecutionLimits(ad_module_id, null, 
       new OpenbravoVersion(1,5,0));
 }

</source>

Where ad_module_id is the UUID of the module.

This way the modulescript will be executed when upgrading the module from any version prior to 1.5.0 and it will not be executed anymore.

Execute on install

It is also possible to configure whether the module script should be executed when installing the dependent module defined with the getModuleScriptExecutionLimits() method or during the install.source ant task. By default, the module script will be executed in the following cases:

  • When installing the dependent module
  • On install.source task

But in case we do not want to execute it in these cases, the executeOnInstall() method should be overridden as follows:

<source lang="java">

 @Override
 protected boolean executeOnInstall() {
   return false;
 }

</source>

Executing just on install.source

In case we want to configure a modulescript to be executed just on every install.source, we do not override the executeOnInstall() method and we define the execution limits as follows:

<source lang="java">

 @Override
 protected ModuleScriptExecutionLimits getModuleScriptExecutionLimits() {
   return new ModuleScriptExecutionLimits(ad_module_id, null, 
       new OpenbravoVersion(0,0,0));
 }

</source>