# DevelopmentS

BGERP is quite a large and complex project. It was created by a team consisting of ordinary humans, who may be imperfect and make mistakes but try to learn continuously. This is a trial and error process, fortunately or not.

This article provides some recommendations collected during the learning process - these are accompanied by a relative small amount of code for exactly the same reason. Said that, you have to follow those recommendations using The Boy Scout Rule.

| Always leave the campground cleaner than you found it.

While changing a block of code, it is important to try to decrease the usage of outdated approaches, methods and classes. Write a new code and keep it compliant with the latest recommendations, but at the same time try to get neighboring/adjacent code improved. During changes testing, code improvements will be tested as well. All in all, gradually, we will improve overall code quality. We plan to introduce a set of formal code quality metrics: number of compiler warnings, format checks, etc.

## Languages

The project currently undergoes a migration process from Russian language to an international one (English). All the software documentation, including this article, and all log messages will be converted to English. Information shown to the end-user via interface has to be localized.

## Project

### Environment

The program can be developed on any Java-supported platform.

#### Windows

Minimal required set of software:

- OpenJDK 21;

- Docker for Windows;

- GIT.

##### Shell

System of build and publish is written on Bash scripts and uses GNU utilities, you can use the following when using Windows:

- WSL 2 - embedded in Windows virtual Linux machine;

- Cygwin - Windows ports of GNU utils, not tested.

The best console emulator on Windows with embedded FAR Manager - ConEmu.

#### Linux

Install using a package manager:

- OpenJDK 21;

- Docker;

- GIT;

- GIT-LFS.

#### Gradle

Gradle is a build tool, used for the project. It is running using `gradlew` wrapper script, provided in the sources.

To update a Gradle version in the wrapper, execute for example for 8.4:

    ./gradlew wrapper --gradle-version=8.4

### Checkout

Checkout the project using GIT to an wanted directory, e.g.: `BGERP`.

#### Contributors

For project’s contributors, internal repo with branches:

    git clone -c core.fileMode=false -c core.longpaths=true -c core.autocrlf=input -c core.ignorecase=true https://git.bgerp.org/bgerp/bgerp.git BGERP

Configure GIT inside the directory. Use your name and email instead.

    git config user.name "Shamil Vakhitov"
    git config user.email shamil@bgerp.org
    git config pull.rebase true
    git config --global core.fileMode false
    git config --global core.longpaths true
    git config --global core.autocrlf input
    git config --global core.ignorecase true

#### Read-Only Access

For only readers, including Custom developers.

    git clone -c core.fileMode=false -c core.longpaths=true -c core.autocrlf=input -c core.ignorecase=true https://github.com/Pingvin235/bgerp BGERP

### Running

#### DB Container

Use the Docker image for running developer DB instance. Run the following command inside of the project’s directory.

| We expose non-standard port 3388 to avoid possible conflicts with a running locally MariaDB server.

    docker pull bgerp/devd && docker run -d --name bgerp-devd --restart unless-stopped -p 3388:3306 bgerp/devd && docker logs --follow bgerp-devd

Make sure, that the container is running, wait the message:

    BGERP Dev DB is running

| For accessing the DB instance with console SQL client use the command:
    docker exec -it bgerp-devd /opt/bgerp/mysql.sh
For removing the created container:
    docker rm -f bgerp-devd

Get configuration and data files from the container:

    docker exec bgerp-devd cat /opt/bgerp/bgerp.properties | bash -c "sed 's#127.0.0.1/#127.0.0.1:3388/#'" | bash -c "sed 's/^#title/title/'" > bgerp.properties &&
    docker cp bgerp-devd:/opt/bgerp/lic.data lic.data &&
    docker cp bgerp-devd:/opt/bgerp/log4j.properties log4j.properties &&
    docker cp bgerp-devd:/opt/bgerp/filestorage filestorage

| On *NIX system check permissions of `filestorage` directory, it has to be available for both read and write

Run Integration Tests with the DB to make sure it works properly:

    ./gradlew itest

#### Command Line

Execute command:

    ./gradlew startServer

| The Gradle task finishes on 75%

After correct start of the server, Web interface has to be available at URL: http://localhost:9088/user Use admin - admin credentials.

#### Inside IDE

Configuration for running inside IDE:

- Main class: org.bgerp.Server

- Program arguments: start

- VM arguments: -Dbgerp.setup.data=bgerp

- Classpath: click on User entries - Advanced - Add folders and add project’s current folder.

### Code Format

#### Indents

IDE or editor must be configured for displaying whitespace symbols.

Set trim trailing whitespaces on saving, the option is provided for VS Code Settings Template.

The projects mostly follow recommended formatting rules for each of programming languages they use. Indentions:

- 4 whitespaces - Java, Gradle, Bash, YML;

- tabs - SQL, XML, HTML, JS, JSP, Dockerfile.

#### Java

Jave code formatting should be Java Conventions-compliant, with the following additional changes (Eclipse formatter settings shown below)

Use settings in Windows - Preferences - Java - Code style - Formatter. Open a standard formatter and save it under a different name after changing the following parameters:

- Indentation - Tab policy - Spaces only

- Indentation - Tab size - 4

- Line Wrapping - Maximum line width - 150

Window - Preferences - Java - Code style - Organize Imports - put 99 and 1 in the respective fields.

Example/resulting Eclipse formatter file: formatter.xml

#### SQL

All the SQL code has to be written with high case letters, except of names for tables, columns, variables.

    SELECT id, title FROM user WHERE comment LIKE 'Admin';

### IDE

#### Eclipse

Currently one of the most handy platforms. Download Eclipse for Java EE Developers, as this build already includes Gradle, JSP and XML editors. Install the following plugins:

- AsciiDoctor - documentation editor;

- MoreUnit - convenient plugin to work with unit-tests.

Import the project directory as a Gradle Project.

| Due to a bug in the Eclipse plugin define exact 6.9.1 version of Gradle before the import.

Window - Preferences - General - Editors - Text Editors - set a Show whitespace characters checkbox

Window - Preferences - Team - Git - History remove Relative history checkbox

Import Java formatter via Window - Preferences - Java - Codestyle - Formatter.

#### VS Code

A much faster than Eclipse, has better JavaScript and Gradle support.

Drawbacks:

- JSP support is nearly absent;

- external GIT client is required.

Install VS Code itself and the following extensions:

- Java Extension Pack

- GitLens

- Git Graph

- Git History

- AsciiDoc

- Eclipse Keymap - optionally

Visit open and navigate to `.vscode` catalog in project’s directory in order to get some configuration examples. Java formatter and space symbols indicators have been already enabled there.

#### Logging

A taken from DB container `log4j.properties` file doesn’t produce output to STDOUT IDE console. In order to change that you have to add out appender there.

    log4j.logger.ru.bgcrm=ALL, file, session, out
    log4j.logger.ru.bgerp=ALL, file, session, out
    log4j.logger.org.bgerp=ALL, file, session, out

### File’s structure

Project’s folder listing along with description.

- bin - IDE-compiled Java classes;

- build - files related to build;

- docpattern - Document plugin templates;

- filestorage - file storage when launched from IDE;

- lib - Java libraries, which are not linked via Gradle;

- plugin - plugin declarations;

- src - Java source code;

- srcx - documentation, unit-tests, integration tests, build utilities sources;

- webapps - Web application’s root directory;

- work - folder created by Tomcat for JSP file compilation.

### Libraries

#### Java

When running a program, a number of JAR files are linked from the following folders:

- lib/app - application’s and Custom classes;

- lib/ext - external libraries.

External libraries are supplied with a separate update package. Artifacts and versions are indicated in the following file: build.gradle (for bgerp configuration)

| This configuration does NOT use transitive dependencies - all required libraries and versions have to be explicitly defined. We would suggest to gradually add a small set of libraries until getting a "workable" application - this will allow to decrease build’s size and simplify further support and troubleshooting.

In order to speed up project initialization, our default Eclipse IDE configuration does not load source code and documentation.

    // PzdcDoc snippet of: 'build.gradle', lines: 40 - 46

    eclipse {
        classpath {
            // speed up of initial start, change if needed
            downloadJavadoc = false
            downloadSources = false
        }
    }

After adding new libraries in `build.gradle` create an empty `build/changes.lib.txt` file using `./gradlew touchChangesLib` command. Existence of the file causes build library update package during release build.

#### JS

JS libraries are located in the following folders:

- `webapps/js` - application’s JS files: kernel’s and plugins';

- `webapps/lib` - external libraries.

Non-minified libraries are used in order to simplify debugging.

Some of the external libraries have been patched and all changes are accompanied by relevant comments, for example:

    // PzdcDoc snippet of: 'webapps/lib/jquery-ui-1.12.1/jquery-ui.js', lines: 8896 - 8901

    controls = (!inst.inline ?
                    // PATCH ADDITION BGERP,  added X button, changed closeProcess
                    '<button type="button" class="ui-datepicker-close btn-white btn-small icon btn-close" onclick="$.datepicker._clearAndHide(\'#' + inst.id + '\');"><i class="ti-eraser"></i></button>' +
                    '<button type="button" class="ui-datepicker-close btn-white btn-small" onclick="$.datepicker._setNowIfEmptyAndHide(\'#' + inst.id + '\');">OK</button>'
                    // END PATCH
                    : '');

### Remote development methodology

BGERP launches within IDE, gets connected to the database and, optionally, billing. At the end of the development/work session remote client database will have all the actual information, so it is only required to update the product itself.

- Use SSH to connect to the client, utilize port-forwarding in order to connect to the database and, optionally, billing. Example: ssh user@X.X.X.X -L3307:127.0.0.1:3306 -L8081:Y.Y.Y.Y:8080

- Create your own bgerp_customer.properties file - you can use it to keep your configuration and access parameters. This file does not get pushed to GIT. You can also use this file to alter billing connection URL - for example, to a localhost.

- Reconfigure BGERP port so you could save passwords in your browser for specific client(s).

- Create IDE launch configuration using this properties file

- Optionally, alter .gitignore in order to save custom client’s files in case you are using fork.

## Naming Conventions

### Configuration variables, JS functions, HTTP StyleId

- All plugin configuration variable names have to start with <plugin>:

- All plugin’s JS function names have to start with <plugin>- prefix. "-" cannot be used within a function name in other places.

- All DOM element identifiers (style id) for a plugin have to start with <plugin>- "-" cannot be used within a DOM element identifier name in other places.

### Java and JSP variables, HTTP request parameters

Have to use camelCase notation: processId, paramId

### SQL

Database table and column names have to be in lower case, underscore separated: user_group, process_id, param_id.

### Date and time fields

Date only, day precision.

Language | Type | Naming for single date field per entity | Naming for many date fields per entity | Naming for period
--- | --- | --- | --- | ---
Java | java.util.Date | date | createDate, closeDate, paymentDate, statusDate | dateFrom, dateTo
SQL | DATE | date | create_date, close_date, payment_date, status_date | date_from, date_to

Date plus time, second precision.

Language | Type | Naming for single datetime field per entity | Naming for many datetime fields per entity | Naming for period
--- | --- | --- | --- | ---
Java | java.util.Date | time | createTime, closeTime, paymentTime, statusTime | timeFrom, timeTo
SQL | DATETIME | dt | create_dt, close_dt, payment_dt, status_dt | dt_from, dt_to

NULL means undefined datetime or plus/minus infinite value in period.

## Architecture

BGERP is a standalone Java application with a dynamic web-interface. When developing, you can launch it directly from IDE.

Figure below illustrates a request processing pipeline:

Request processing stages:

1. JS is used to form a request via HTML form , which is then sent to a Java Action class method.

2. Upon a change request system sends only a confirmation - JSON response with OK status

3. Any exception on Java Action side triggers a JSON with an ERROR status to be sent towards a client side.

4. Any successful data read request returns an HTML Fragment (Table with a list of usernames as an example), which can then be embedded into resulting HTML on the client side.

Below is a summary of a technology stack used, in descending order of importance:

1. Java - all application logic is implemented as Java code, as Java provides best options in terms of easy of development, reliability and performance.

   1. MariaDB + JDBC - data processing.

2. HTML + CSS - browser’s markup language and style tables.

3. JSP + JSTL - HTML page rendering templates, presentation layer;

4. JS + JQuery - client-side scripting language for dynamic content, only used as predefined framework calls.

| Pay attention to avoid mixing the layers, e.g. creating application logic outside of Java code, defining presentation layer without JSP, performing data manipulation without SQL.

### Plugin

Any logically detached functionality has to be moved into a plugin. Isolation is a primary trait of a plugin. Each plugin works with a kernel and kernel has no knowledge of the inner works of a given plugin. Plugins do not have to 'know' how other plugins work, either. 'Knowing' here means a necessity to rely on certain APIs or specific method calls.

#### Kernel

The special org.bgerp.plugin.kernel.Plugin plugin is responsible for keeping the kernel functionality of the system.

This plugin is a specific one, because:

- always enabled and required for normal work of the program

- because that do not need endpoints

- Java classes spread outside a single PLUGIN_PACKAGE

- JSP files are also in many directories

Using those assumptions the system may be presented as set of plugins.

#### Plugin’s Java class

Application detects plugin upon start by a mandatory Java class, extended from org.bgerp.plugin.pln.blow.Plugin Class example; org.bgerp.plugin.pln.blow.Plugin

Each plugin has a corresponding and unique:

- PLUGIN_ID - a single and unique for a plugin English word, all single case (no upper/lower mix) and without special symbols.

- Java PLUGIN_PACKAGE which includes this specific class.

##### Init

Plugin class init method is called for all enabled plugins during the server start.

    // PzdcDoc snippet of: 'org.bgerp.plugin.msg.email.Plugin', lines: 36 - 45

    @Override
    public void init(Connection con) throws Exception {
        super.init(con);

        new ProcessNotificationListener();

        EventProcessor.subscribe((e, conSet) -> {
            new ExpressionObject().toContext(e.getContext());
        }, ContextInitEvent.class);
    }

In the particular case shown above:

- super init has been called;

- a process notification listener is created to track process changes for the plugin;

- plugin’s JEXL object gets added into context.

First init is called for Kernel Plugin.

#### Database

If plugin uses DB, then plugin package can contain a script which creates or modifies tables. When `db.sql` script presented in PLUGIN_PACKAGE it gets invoked during standard initialization routine.

Below is an example of a script for a FullText plugin

    // PzdcDoc snippet of: 'src/ru/bgcrm/plugin/fulltext/db.sql', lines: 1 - 11

    CREATE TABLE IF NOT EXISTS fulltext_data (
            object_type VARCHAR(100) NOT NULL,
            object_id INT NOT NULL,
            scheduled_dt DATETIME,
            data TEXT NOT NULL,
            FULLTEXT (data) /*!50700 WITH PARSER ngram */,
            KEY scheduled_dt (scheduled_dt),
            UNIQUE KEY type_id (object_type, object_id)
    );

    -- ! after must be only a new line !;

Plugin DB table names have to start from PLUGIN_ID.

DB Structure is documented in the common process, simultaneously with making changes.

Use PRIMARY key only for auto incremented INT columns, otherwise prefer named UNIQUE key.

    // PzdcDoc snippet of: 'src/org/bgerp/plugin/kernel/db.sql', lines: 305 - 305

    UNIQUE KEY id_param_id_n (id,param_id,n),

##### Database Queries Cache

To speed-up the application startup initialization all the already executed calls are cached in db_update_log table. The cache might be reset using installer console util.

To force re-execution a query, add a whitespace before ending semicolon on the query line.

    // PzdcDoc snippet of: 'src/org/bgerp/plugin/kernel/db.sql', lines: 253 - 253

    CALL drop_key_if_exists('param_address', 'PRIMARY');

##### Cache

Caching is used within internal Map and List in order to significantly (100s of times) expedite directory data retrieval when building UI or when running JEXL scripts Map allows to get specific value based on they kay (from a 'key-value' pair), list - an alphabetically sorted list. Users, User Groups, Process types, configurations are examples of such cached data.

| Use caches as much as possible for getting reference values and avoid querying DB extensively.

Directory data is edited directly in DB and cache is cleared after an edit.

    // PzdcDoc snippet of: 'ru.bgcrm.struts.action.admin.UserAction', lines: 89 - 95

        public ActionForward permsetDelete(DynActionForm form, Connection con) throws Exception {
            new UserPermsetDAO(con).deletePermset(form.getId());

            UserCache.flush(con);

            return json(con, form);
        }

#### Endpoints

| Earlier plugin endpoints were described in `plugin.xml` file located within PLUGIN_PACKAGE

Endpoints used for connecting non-Java plugin parts:

- JSP includes;

- JS files.

##### JavaScript

Extension points definition within Plugin declaration:

    // PzdcDoc snippet of: 'org.bgerp.plugin.pln.blow.Plugin', lines: 22 - 22

    Endpoint.JS, List.of(Endpoint.getPathPluginJS(ID)),

File containing script: webapps/js/pl.blow.js

##### Menu items

Menu item in user interface.

Extension points definition within Plugin class:

    // PzdcDoc snippet of: 'org.bgerp.plugin.pln.blow.Plugin', lines: 24 - 24

    Endpoint.USER_PROCESS_MENU_ITEMS, List.of(PATH_JSP_USER + "/menu_items.jsp"),

Adding a menu item using JSP tag: webapps/WEB-INF/jspf/user/plugin/blow/menu_items.jsp

#### Action

Plugin actions classes, if used, have to be declared in PLUGIN_PACKAGE.action package.

`action.xml` with permission tree for user interface actions has to be located in PLUGIN_PACKAGE.

JSP templates:

- user interface in `webapps/WEB-INF/jspf/user/plugin/<PLUGIN_ID>` or `webapps/WEB-INF/jspf/admin/plugin/<PLUGIN_ID>`

- open interface in `webapps/WEB-INF/jspf/open/plugin/<PLUGIN_ID>`

#### Localization

Plugin’s localization file (`i10n.xml`) has to be located in PLUGIN_PACKAGE. It is only used for localizing actions and action’s JSP templates

### MVC

The project is using Apache Struts framework in a very own and customized way:

- requests are sent using AJAX and responses update HTML partially

- from Struts JSP tags used only <html:form with <html:param inside

- action methods have different signature as standard

- the form object has always the same class

#### Interfaces

There are three user interfaces exist in the program.

The following table shows rules for path and packages of actions and JSP templates. Shortcuts there mean:

- ppp - plugin ID;

- aaa - action.

| JSP paths are defined starting from `webapps/WEB-INF` directory.

Interface | Action path | Action class | JSP path
--- | --- | --- | ---
user | /admin/aaa /admin/plugin/ppp/aaa | …​action.admin.AaaAction …​plugin.ppp.action.admin.AaaAction | jspf/admin/…​/aaa.jsp jspf/admin/…​/plugin/ppp/…​/aaa.jsp
/user/aaa /user/plugin/ppp/aaa | …​action.AaaAction …​plugin.ppp.action.AaaAction | jspf/user/../aaa.jsp jspf/user/../plugin/ppp/…​/aaa.jsp
usermob | /usermob/aaa | …​action.usermob.AaaAction | jspf/usermob/../aaa.jsp
open | /open/aaa /open/plugin/ppp/aaa | …​action.open.AaaAction …​plugin.ppp.action.open.AaaAction | jspf/open/../aaa.jsp jspf/open/../plugin/ppp/…​/aaa.jsp

##### Demo Zones

For testing MVC framework available special demo zones, providing the actual set of UI components, same as examples of CRUD operations, permission check, etc. These zones can be used as playgrounds for developers, allowing to learn the system.

Interface | Action path, mapping JSP | Action class | JSP path
--- | --- | --- | ---
user | /user/demo jspf/user/menu.jsp | org.bgerp.action.DemoAction | jspf/user/demo.jsp
open | /open/demo jspf/open/demo/url.jsp | org.bgerp.action.open.DemoAction | jspf/open/demo.jsp

#### Java Action

Action classes have to extend org.bgerp.action.base.BaseAction class marked by Action annotation.

    // PzdcDoc snippet of: 'org.bgerp.action.admin.RunAction', lines: 25 - 27

    @Action(path = "/admin/run", pathId = true)
    public class RunAction extends BaseAction {
        private static final String PATH_JSP = PATH_JSP_ADMIN + "/run";

The sample above means that HTTP requests with URL /admin/run.do mapped to the class. A method HTTP request’s parameter is used to indicate method’s name. If this HTTP parameter is not defined, then unspecified method gets invoked.

Action methods have to return by invoking html or json.

First method gets processed by JSP forward page and sends HTML back to the client.

    // PzdcDoc snippet of: 'org.bgerp.action.admin.RunAction', lines: 32 - 36

        @Override
        public ActionForward unspecified(DynActionForm form, ConnectionSet conSet) throws Exception {
            form.setRequestAttribute("runnableClasses", runnableClasses());
            return html(conSet, form, PATH_JSP + "/run.jsp");
        }

Second one is used to perform changes and only returns JSON-based change execution result.

    // PzdcDoc snippet of: 'org.bgerp.action.admin.RunAction', lines: 59 - 85

        public ActionForward runClass(DynActionForm form, ConnectionSet conSet) throws Exception {
            // 'class' is passed when value is chosen from drop-down, 'data' - entered directly
            String className = form.getParam("class");
            if (Utils.isBlankString(className))
                className = form.getParam("data");

            String ifaceType = form.getParam("iface", "event");
            // running interface EventListener
            if ("event".equals(ifaceType))
                ((EventListener<Event>) Bean.newInstance(className)).notify(new RunClassRequestEvent(form), conSet);
            // running interface Runnable
            else {
                Class<?> clazz = Bean.getClass(className);

                if (java.lang.Runnable.class.isAssignableFrom(clazz)) {
                    boolean sync = form.getParamBoolean("sync");
                    if (sync)
                        ((java.lang.Runnable) clazz.getDeclaredConstructor().newInstance()).run();
                    else
                        new Thread((java.lang.Runnable) clazz.getDeclaredConstructor().newInstance()).start();
                } else {
                    throw new BGMessageException("The class does not implement java.lang.Runnable: {}", className);
                }
            }

            return json(conSet, form);
        }

##### Form Object

Each action class method call gets a form object ru.bgcrm.struts.form.DynActionForm as a parameter. This object contains a context of request’s execution;

- User;

- Request parameters and supplementary methods for parsing those parameters

| Do not use a legacy action format with HttpClientRequest and HttpClientResponse parameters (note that they are still present in the form)

A snippet of an action method:

    // PzdcDoc snippet of: 'org.bgerp.plugin.pln.blow.action.BoardAction', lines: 42 - 62

    public ActionForward show(DynActionForm form, Connection con) throws Exception {
        BoardConfig boardConf = setup.getConfig(BoardsConfig.class).getBoard(form.getId());
        if (boardConf != null) {
            // первичные процессы
            List<Pair<Process, Map<String, Object>>> processes = new BoardDAO(con, form).getProcessList(boardConf);

            Set<Integer> processIds = processes.stream().map(Pair::getFirst).map(p -> p.getId()).collect(Collectors.toSet());

            // связи между процессами, пока используем только родительское отношение
            Collection<CommonObjectLink> links = new ProcessLinkDAO(con, form).getLinksOver(processIds);

            Board board = new Board(boardConf, processes, links);

            form.setResponseData("board", board);
            form.setResponseData("processIds", processIds);

            updatePersonalization(form, con, map -> map.put("blowBoardLastSelected", String.valueOf(form.getId())));
        }

        return html(con, form, PATH_JSP + "/show.jsp");
    }

Result gets redirected to a JSP page: webapps/WEB-INF/jspf/user/plugin/blow/board/show.jsp.

Use the same form to pass data for JSP rendering, excluding some auxiliary directories. You can use form’s response field for this. When set to responseType=json, all data in the response gets serialized into JSON - that’s why it is important to put directories into HttpResponse.

    // PzdcDoc snippet of: 'ru.bgcrm.plugin.dispatch.action.DispatchAction', lines: 48 - 54

    public ActionForward messageList(DynActionForm form, ConnectionSet conSet) throws Exception {
        form.setRequestAttribute("dispatchList", new DispatchDAO(conSet.getSlaveConnection()).dispatchList(null));

        new DispatchDAO(conSet.getConnection()).messageSearch(new Pageable<>(form), form.getParamBoolean("sent", null));

        return html(conSet, form, PATH_JSP + "/message/list.jsp");
    }

##### Naming Convention

Plugin’s action classes have to be placed into `PLUGIN_PACKAGE.action` package, class name should end with Action. Previously Action classes have been located within `struts.action` packages - this approach is considered obsolete.

user interface actions are divided into /user and /admin This separation will be used in future for distinguishing administrative calls.

usermob и open interfaces have their own actions - in this case package names and URL have to contain usermob и open, respectively. org.bgerp.action.usermob.ProcessAction provides an example of such action.

We recommend to use the following naming convention for methods:

1. [optional] Name of the object being handled in case a given class works with several objects.

2. Verb which defines method’s operation. For example, for CRUD these are: list, get, update, delete.

ru.bgcrm.struts.action.admin.UserAction has some example method names:

- permsetList;

- permsetGet;

- permsetUpdate.

Another set of examples from org.bgerp.action.admin.AppAction:

- status - provides application status;

- update - triggers update installation;

- userLoggedList - provides a list of logged in users.

It is recommended to use identical names for both method and forward JSP files.

##### Permissions

All action methods for user interface have to be defined within `<PLUGIN_PACKAGE>/action.xml` files. Here are some of those:

- org/bgerp/plugin/kernel/action.xml - for Kernel plugin

- org/bgerp/plugin/pln/blow/action.xml - for Blow plugin

Definitions from those files are forming a tree which is used for access control.

Each action is identified by ID (path or class, depending on annotation) AND method, separated by semicolon.

First primary action can be followed by additional ones, separated by a comma - for example, when renaming classes or methods. Enabling each action enables all the item. This allows to provide backwards compatibility with permissions already present in the DB. The first identifier is used upon the next save operation of the permission set.

In the following example an action class was renamed two times and action method changed first to unspecified, defined in the configuration by null and later to status. And the action ID there was changed from class (`org.bgerp.action.admin.AppAction`) to path (`/admin/app`).

    <!-- PzdcDoc snippet of: 'org/bgerp/plugin/kernel/action.xml', lines: 313 - 314 -->

    <item title="App">
            <item title="Status" action="/admin/app:status, org.bgerp.action.admin.AppAction:status, ru.bgcrm.struts.action.admin.StateAction:null, ru.bgcrm.struts.action.admin.AppAction:status"/>

The same principe may be used for grouping many methods to a single logical action, for example typical get and update calls.

    <!-- PzdcDoc snippet of: 'org/bgerp/plugin/bil/invoice/action.xml', lines: 6 - 6 -->

    <item title="Update" action="org.bgerp.plugin.bil.invoice.action.InvoiceAction:get, org.bgerp.plugin.bil.invoice.action.InvoiceAction:update, org.bgerp.plugin.bil.billing.invoice.action.InvoiceAction:get, org.bgerp.plugin.bil.billing.invoice.action.InvoiceAction:update"/>

Actions may be hierarchically organized when some parent action is required for accessing children.

    <!-- PzdcDoc snippet of: 'org/bgerp/plugin/svc/backup/action.xml', lines: 2 - 7 -->

    <item title="Backup" action="org.bgerp.plugin.svc.backup.action.admin.BackupAction:null">
            <item title="Create" action="org.bgerp.plugin.svc.backup.action.admin.BackupAction:backup"/>
            <item title="Restore To" action="org.bgerp.plugin.svc.backup.action.admin.BackupAction:restore"/>
            <item title="Download" action="org.bgerp.plugin.svc.backup.action.admin.BackupAction:downloadFileBackup"/>
            <item title="Delete" action="org.bgerp.plugin.svc.backup.action.admin.BackupAction:deleteFileBackup"/>
    </item>

Auxillary actions, which have to be permanently allowed, have to be marked with allowAll="1" attribute.

###### Check

Action’s allowance can be checked in Java code:

    // PzdcDoc snippet of: 'org.bgerp.plugin.svc.dba.action.admin.DatabaseAction', lines: 23 - 23

    private static final String ACTION_DROP = ActionServlet.pathId(DatabaseAction.class, "drop");

    // PzdcDoc snippet of: 'org.bgerp.plugin.svc.dba.action.admin.DatabaseAction', lines: 41 - 41

    if (form.getUser().checkPerm(ACTION_DROP)) {

But more often its checked in JSP.

##### Exception handling

Exception interrupts actions execution, also rolls back DB transaction - response will be always sent back as JSON. All Exception handling is defined centrally in org.bgerp.action.base.BaseAction, DAO methods or script handlers just need to raise a given exception. Said that, action methods declarations and DAO just need to use throws java.lang.Exception.

ru.bgcrm.model.BGException class is typically used for system-generated exceptions and is rarely used elsewhere.

org.bgerp.app.exception.BGMessageException, is used to sent a localized message to the user, without writing this message in the log. For example:

    // PzdcDoc snippet of: 'ru.bgcrm.event.listener.ProcessClosingListener', lines: 40 - 42

    for (Process link : result)
        if (link.getCloseTime() == null)
            throw new BGMessageException("Есть незакрытый процесс: {}, привязанный к данному с типом: {}", link.getId(), linkType);

##### DB Operations

DB-related operations are performed via separate Java DAO (Data Access Objects) classes which are then used within actions. Actions are using transactional request processing: transaction starts before method gets invoked, then a) gets committed (COMMIT) if there are no errors when returning results or b) gets rolled back (ROLLBACK) if exception is thrown.

DB connection details have to be specified via action methods' con parameters. Some methods use conSet (ru.bgcrm.util.sql.ConnectionSet) parameter, which invoke DB connection via a separate call. The latter is more suitable for methods which do not demand a DB connection, or, on the contrary, for the ones demanding several connection types: to replica or "trash" DB for non-critical data (currently not supported)

Table names have to be specified via constants in order to improve code cohesion (see ru.bgcrm.dao.Tables as example). If a given table is only used in a single DAO class, then this constant has to be defined as private (ru.bgcrm.plugin.fulltext.dao.SearchDAO as example)

Use java.sql.PreparedStatement class for building queries or. alternatively, use its wrapper - ru.bgcrm.util.sql.PreparedDelay[]. The latter allows to 'glue' both queries and parameters and does not require to specify parameters' positions.

#### JSP View

HTML gets rendered on server-side using JSP templates after receiving data from Action. UI uses a set of unified components - thanks to that in most cases there is no need to install additional styles or create additional JS handlers. JSP UI code examples.

The principal schema of JSP work looks like:

##### SetRequestParamsFilter

The filter is executing after action and sets in request object Java objects, those methods may be called. The following objects prefixes are available:

- u. ru.bgcrm.util.Utils

- tu. ru.bgcrm.util.TimeUtils

- su org.apache.commons.lang.StringUtils

- сu org.apache.commons.collections.CollectionUtils

As an example see date and time format.

Beside of the mentioned static functions, the filter sets also caches. All the request parameters are set in class ru.bgcrm.servlet.filter.SetRequestParamsFilter

##### Functions

JSP functions are defined in the directory `webapps/WEB-INF/tld`. Same as for tags IDEs support autocompletion for them.

| Most of the functions there are deprecated because of possibility to call Java analogs.

Using semicolon-separated JSP functions makes sense only for JSP specific things, like in the following cases.

###### Element IDs

HTML DOM model assumes all element IDs are globally defined using `class` and `id` attributes. Due to that it is quite tricky to track their usage and ensure their uniqueness. Moreover, developer has to create a single-page application, which prevents to keep state for hidden elements.

In order to overcome this limitation, project attaches JS handlers via HTML onClick (and the like) attributes using context variable 'this'. Another method used for that is using `u:uiid()` function to generate a unique identifier, which is then used in auto-generated JS call.

In webapps/WEB-INF/jspf/user/log/log.jsp might be seen how to completely avoid absolute element IDs.

##### Tags

Components are defined within JSP tags and are declared in `webapps/WEB-INF/tags` IDE Eclipse supports autocomplete when using them in the code.

UI Demo Zones provide several examples of using JSP tags with user control elements.

For kernel, webapps/WEB-INF/jspf/user/menu.jsp file defines menu items which are used to match URL and Java Action. For plugins this is done via extension points.

###### u:sc

JSP page stores all variables ones. Combined with includes and long templates, this can cause certain inconvenience. `<u:sc>` context recovery tag is used to overcome this problem - all variables defined with this tag are cleared upon exit.

###### p:check

The tag enables code inside it only when mentioned action is allowed for the current user.

    <%-- PzdcDoc snippet of: 'webapps/WEB-INF/jspf/user/plugin/blow/board/show.jsp', lines: 11 - 14 --%>

                    <p:check action="org.bgerp.plugin.pln.blow.action.BoardAction:search">
                            <form action="${form.requestURI}">
                                    <input type="hidden" name="method" value="search"/>
                                    <c:forEach var="id" items="${frd.processIds}">

For combining permission check with other checks use function ctxUser.checkPerm, pointing to ru.bgcrm.model.user.User.

    <%-- PzdcDoc snippet of: 'webapps/WEB-INF/jspf/user/message/process/list/list.jsp', lines: 249 - 249 --%>

                                                            <c:if test="${messageType.readable and message.read and ctxUser.checkPerm('/user/message:messageUpdateRead')}">

###### shell:title and shell:state

Set the left area and the right areas of top line in user interface. Tag shell:state may create a documentation help link.

    <%-- PzdcDoc snippet of: 'webapps/WEB-INF/jspf/admin/config/list.jsp', lines: 38 - 39 --%>

    <shell:title text="${l.l('Configuration')}"/>
    <shell:state help="kernel/setup.html#config"/>

##### Includes

###### <%@ include

The include directive inserts a page to another one like a part.

    <%-- PzdcDoc snippet of: 'webapps/WEB-INF/jspf/user/parameter/edit.jsp', lines: 153 - 155 --%>

    <c:when test="${parameter.type eq 'email'}">
            <%@ include file="edit/email/editor.jsp"%>
    </c:when>

Despite this is the fastest way of including and widely used in the project, it has large disadvantages. First of them caused by using the same variable context in both parent and child pages, so passed variables can be hardly recognized. Like a workaround is recommended to describe those at the beginning of included files using Incoming variables comment.

    <%-- PzdcDoc snippet of: 'webapps/WEB-INF/jspf/user/parameter/edit/email/edit.jsp', lines: 4 - 7 --%>

    <%--
    Incoming variables:
            frd.values - list of ParameterEmailValue
    --%>

###### <jsp:include>

Another drawback of previously described include directive is impossiblity to make recursive includes. That can be solved with tag jsp:include.

    <%-- PzdcDoc snippet of: 'webapps/WEB-INF/jspf/admin/user/perm_check_tree_item.jsp', lines: 39 - 46 --%>

    <c:if test="${not empty node.children}">
            <ul>
                    <c:forEach var="child" items="${node.children}">
                            <c:set var="node" value="${child}" scope="request"/>
                            <jsp:include page="perm_check_tree_item.jsp" />
                    </c:forEach>
            </ul>
    </c:if>

As for this include type an included page becomes the saparated page context, variables can be passed only via request context. String parameters should be passed to included templates in a clear and safe way with using <jsp:param> tags.

    <%-- PzdcDoc snippet of: 'webapps/WEB-INF/jspf/user/process/process/process.jsp', lines: 84 - 87 --%>

    <jsp:include page="/WEB-INF/jspf/process_create_wizard_end.jsp">
            <jsp:param name="returnBreakCommand" value="${returnBreakCommand}"/>
            <jsp:param name="returnOkCommand" value="${returnOkCommand}"/>
    </jsp:include>

How one of them is used after, in this case for generation JS commands.

    <%-- PzdcDoc snippet of: 'webapps/WEB-INF/jspf/process_create_wizard_end.jsp', lines: 8 - 10 --%>

    $$.ajax.post(command).done(() => {
            ${param.returnBreakCommand}
    })

###### <c:import>

This way of including performs internal action’s call. The execution result HTML has been placed into the place of the tag usage.

    <%-- PzdcDoc snippet of: 'webapps/WEB-INF/jspf/admin/user/user/update.jsp', lines: 154 - 160 --%>

    <c:url var="url" value="/user/parameter.do">
            <c:param name="method" value="parameterList" />
            <c:param name="id" value="${form.id}" />
            <c:param name="objectType" value="user" />
            <c:param name="header" value="${l.l('Parameters')}"/>
    </c:url>
    <c:import url="${url}" />

| To avoid attributes collision in nested ServletRequest you might be needed add <c:remove var="form"/> prior <c:import> tag usage.

#### Java Script

JS is used to enabled dynamic content on the client side. Scripts and associated libraries are located within this path. plugins can have their own scripts.

In certain cases JS gets dynamically generated via JSP templates on a server-side. This method is generally discouraged as it significantly complicates broweser-side debugging.

System functions are organized as object hierarchy built from the $$ root object. For example: $$.ajax, $$.ui A typical scheme for attaching JS code to HTML is to setup event handler with a function call - AJAX example

Deprecated JS functions are marked using the following method:

    // PzdcDoc snippet of: 'webapps/js/kernel.ajax.js', lines: 589 - 593

    function formUrl(forms, excludeParams) {
            console.warn($$.deprecated);

            return $$.ajax.formUrl(forms, excludeParams);
    }

When such a function gets invoked, browser console gets a "clickable" "Deprecated" message which allows to find a this deprecated function call It is forbidden to use those functions in the new code, usage has to be decreased in the existing code over time.

### Customization

One of the main solution’s prioritized features are extensibility and flexibility. Said that, standard functions can be extended by using several methods.

However, it is important to understand that the most effective way to efficiently develop and maintain functionality longer-term is to either use built-in plugin system or include as part of the kernel and at the same time keep configurable parameters to a minimum. JEXL scripts or dynamic code can be used for a quick prototyping or in order to implement certain application logic which is extremely specific to a given installation.

Our experience shows that all such customizations over time crystallize successful solutions suitable for a much larger group of users. Such customizations have to be moved into a main code to make them part of the system and to then allow further community development and improvement.

The following diagram illustrates above-mentioned transformation dynamics for a code of different types. Size of a given rectangle correlates with a code size for a specific type. Code size is a cumulative value for all Customers - larger size from scattered customizations gets translated into a much smaller universal code within a single product.

### Localization

All log messages are produced in English only.

Localization gets applied to a user interface and messages addresses to a system user. System language is defined globally within configuration.

#### Files

XML localization file `l10n.xml` are located in plugin’s packages. New localization phrases should be added to the end of the list in the file.

Custom localization may overwrite all of them and read out `custom/l10n.xml` file in case of existing that.

The key of the localizing phrase is the first entry in any language, for example English:

    <p><en>Re-login is required</en><ru>Требуется повторный вход</ru></p>

#### SYS language

It is possible to use short abbreviated keys, representing them as records in a special system language, for example:

    <!-- PzdcDoc snippet of: 'src/org/bgerp/plugin/msg/email/l10n.xml', lines: 3 - 9 -->

            <p>
                    <sys>email.sign.standard</sys>
                    <ru>Сообщение подготовлено системой BGERP (https://bgerp.ru).
    Не изменяйте, пожалуйста, тему и не цитируйте данное сообщение в ответе!</ru>
                    <en>The message was prepared by BGERP system (https://bgerp.org).
    Please, do not change response subject and to not citate the message there!</en>
            </p>

In the example also might be seen how to use multiline phrases.

#### Own language

To change the labels on the buttons in the interface, add a separate localization (you cannot change the Russian localization, since it is used as a key), for example, change the button in the creation wizard, through the my language:

    <p><ru>Завершить</ru><en>Finish</en><my>Создать заявку</my></p>

#### Code

Localization can be done in JSP templates and Java actions. In JS code, localization is available only if it is generated by JSP. In the JSP template code, the localization call from the example above looks like this:

    $('#loginForm').dialog({
            modal: true,
            draggable: false,
            resizable: false,
            title: "${l.l('Re-login is required')}",
            position: { my: "center top", at: "center top+100px", of: window }
    });

By doing Java Action in object l the localization context is passed, containing phrases for the core and the plugin being called. Localized key string may contain placeholders for some values, marked as {}.

    // PzdcDoc snippet of: 'org.bgerp.action.MessageAction', lines: 242 - 242

    message.setText(l.l("Перенесено из процесса #{}", process.getId()) + "\n\n" + message.getText());

BGMessageException can contain localized message, translated when shown to a user. In the following snippet might be seen, how it can be thrown in a plugin’s logic.

    // PzdcDoc snippet of: 'org.bgerp.plugin.bil.subscription.dao.SubscriptionDAO', lines: 87 - 88

    if (discount.compareTo(result) > 0)
        throw new BGMessageException("Discount can't be more than product's cost: {}", discount.toPlainString());

As there is no way to make localization on the Frontend, phrases may be sent there from the Backend. Here are two samples for such cases.

    // PzdcDoc snippet of: 'org.bgerp.app.dist.lic.License', lines: 154 - 157

    form.getResponse().addEvent(
        new LicenseEvent(new Message(form.l.l("License Will Expire Soon"),
            form.l.l("Your license will expire at {}", TimeUtils.format(dateTo, TimeUtils.FORMAT_TYPE_YMD))),
            true));

    // PzdcDoc snippet of: 'org.bgerp.cache.UserNewsCache', lines: 82 - 86

    final var l = Localization.getLocalizer();

    result = new NewsInfoEvent(searchResult.getList().size(), currentUnprocessedMessages, popupNews, blinkNews, blinkMessages);
    result.message(l, "News");
    result.message(l, "Unprocessed messages");

#### Development

All the new code has to be written using localized strings. For legacy code recommended during other changes replace static strings to localized.

For missing keys the following messages appear in log output:

    01-02/00:44:01  WARN [http-nio-9088-exec-2] Localizer - Missing translation for pattern: 'Иниациализировать плагины'

After adding missing patterns, application server has to be re-started.

### Logging

#### Java

ru.bgerp.util.Log logger class is used, which is based on Log4j framework. When launching from IDE, you can copy build/bgerp/files/log4j.properties file from distribution kit into project’s root folder and adapt, as needed.

Use `log` protected variable when logging within actions

Create static final class variable within Java classes:

    // PzdcDoc snippet of: 'org.bgerp.exec.MessageExchange', lines: 18 - 19

    public class MessageExchange extends Task {
        private static final Log log = Log.getLog();

Log message with substitutions:

    // PzdcDoc snippet of: 'org.bgerp.Server', lines: 139 - 139

    log.debug("Scan type: {}, name: {} => {}", type, name, result);

Exception message output:

    // PzdcDoc snippet of: 'org.bgerp.exec.MessageExchange', lines: 46 - 50

    try {
        type.process();
    } catch (Exception e) {
        log.error(e);
    }

#### JS

Show debug data instead of console.log:

    // PzdcDoc snippet of: 'webapps/js/kernel.shell.js', lines: 5 - 12

            const debug = $$.debug("shell");

            const pushHistoryState = function (href) {
                    if (!history.state || history.state.href != href) {
                            debug("pushHistoryState: ", href, "; current: ", history.state);
                            history.pushState({href: href}, null, href);
                    }
            }

Enabling debug:

    // PzdcDoc snippet of: 'webapps/js/kernel.js', lines: 12 - 18

    $$.debugAreas = {
            openUrl: 0,
            ajax: 0,
            shell: 0,
            'shell.login': 0,
            'shell.message': 0,
            buffer: 0,