Back-end Development Guide

Version: 1.0 | Release Date: 30/6/2018

Logical Modules

Module's CURD

If you are not familiar with the M18 Module, you can refer to Module.

M18 provides basic and common interfaces for Module records to create, update, read and delete easily.

Use of Web Service

Create Entity

Create a blank entity for desired module.

WsParameter param = WsLib.createWsParam("entity/create/" + getModule(), WsType.post);
param.addQueryParam("menuCode", getMenuCode());

HttpResponse response = WsLib.callWs(param);

if (WsLib.isResponseOK(response)) {
    String entryJson = WsLib.resolveResponse(response);
  	if (!FacesUtil.isEmpty(entryJson)) {
   		SqlEntity  result = JSON.parseObject(entryJson, SqlEntity.class);
  	}
} else {
  	WebUtil.showMessageInfo(response, null, "createFail");
}

HTTP Request

POST http://[server]/jsf/rfws/entity/create/[module]?menuCode=[menuCode]


Parameters

Name Type Description
module String (Path) Required. Module name
menuCode String (Query) Required. Menu code
param json String (Query) Used when module's checker needs parameters for special treatment

Response

Note: If response's result is success, a blank SqlEntity will be found.

Note: If response's result is fail, CheckMsg in JSON format can be found in the response's header using the key error_info.

Type Location Description
success Body Module's blank SqlEntity
fail Header(error_info) CheckMsg in JSON format

Read Entity

Read module's record in SqlEntity format.

WsParameter param = WsLib.createWsParam("entity/read/" + getModule(), WsType.get);
param.addQueryParam("menuCode", getMenuCode());
param.addQueryParam("id", action.getData(ActionParam.id).toString());
param.addQueryParam("iRev", action.getDataOrDefault(ActionParam.iRev, 0).toString());

HttpResponse response = WsLib.callWs(param);

if (WsLib.isResponseOK(response)) {
  	String entryJson = WsLib.resolveResponse(response);
    if (!FacesUtil.isEmpty(entryJson)) {
    	entity = JSON.parseObject(entryJson, SqlEntity.class);
    }
} else {
	WebUtil.showMessageInfo(false, null, null, "readFail");
}

HTTP Request

GET http://[server]/jsf/rfws/entity/create/[module]?menuCode=[menuCode]&id=[id]


Parameter

Name Type Description
module String (Path) Required. Module name
menuCode String (Query) Required. Menu code
id long (Query) Required. Entity's id
iRev int (Query) Version of the record. 0 or leave it blank to read the latest record
param json String (Query) Used when module's checker needs parameters for special treatment

Response

Note: If response's result is success, the record in SqlEntity format will be found.

Note: If response's result is fail, CheckMsg in JSON format can be found in the response's header using the key error_info.

Type Location Description
Success Body Record in SqlEntity format
Fail Header(error_info) CheckMsg in JSON format

Save Entity

Save the module's record in SqlEntity format.

WsParameter param = WsLib.createWsParam("entity/save/" + getModule(), WsType.put);

param.addQueryParam("menuCode", getMenuCode());
param.addQueryParam("entity", JSON.toJSONString(getEntity()));

HttpResponse response = WsLib.callWs(param);

if (WsLib.isResponseOK(response)) {
	long recordId = Long.parseLong(WsLib.resolveResponse(response));
 	action.setData(ActionParam.id, recordId);
}

WebUtil.showMessageInfo(response, "saveSuccess", "saveFail");

HTTP Request

PUT http://[server]/jsf/rfws/entity/save/[module]?menuCode=[menuCode]&entity=[SqlEntity json String]


Parameters

Name Type Description
module String (Path) Required. Module name
menuCode String (Query) Required. Menu code
entity json String (Query) Required. SqlEntity in JSON format
param json String (Query) Used when module's checker needs parameters for special treatment

Response

Note: If response's result is success, id of the saved SqlEntity will be returned. Note: If response's result is fail, CheckMsg in JSON format can be found in the response's header using the key error_info.

Type Location Description
Success Body id of SqlEntity
Fail Header(error_info) CheckMsg in JSON format

Delete Entity

Delete module's record using SqlEntity format.

WsParameter param = WsLib.createWsParam("entity/delete/" + getModule(), WsType.delete);

param.addQueryParam("menuCode", getMenuCode());
param.addQueryParam("id", action.getData(ActionParam.id).toString());
HttpResponse response = WsLib.callWs(param);

WebUtil.showMessageInfo(response, "deleteSuccess", "deleteFail");

HTTP Request

DELETE http://[server]/jsf/rfws/entity/delete/[module]?menuCode=[menuCode]&id=[id]


Parameters

Name Type Description
module String (Path) Required. Module name
menuCode String (Query) Required. Menu code
id long (Query) Required. id of the ready-to-delete record.
param json String (Query) Used when module's checker needs parameters for special treatment

Response

If response's result is fail, CheckMsg in JSON format can be found in the response's header using the key error_info.

Type Location Description
Success Status Status = 200 means success
Fail Header(error_info) CheckMsg in JSON format

Use of EJB

Create Entity

Different from web service, cache of the record will not be used when using EJB.

@EJB
CawEntityCurdAction curdEJB;

public void create(String module,string menuCode){
	SeCreateParam param = new SeCreateParam(module);
	param.setMenuCode(menuCode);

	EntityResult result = curdEJB.createEntity(param);
}

SeCreateParam

Input: Module name and menu code.

If module's checker needs parameters for special treatment, user can put those variables into SeCreateParam's jsonParam.


Return Type

Note: If SqlEntity in EntityResult is not null, a blank SqlEntity can be retrieved.

Note: If SqlEntity in EntityResult is null, error messages can be found under List<CheckMsg>.


Read Entity

Different from web service, cache of the record will not be used when using EJB.

@EJB
CawEntityCurdAction curdEJB;

public void read(String module, String menuCode, long id, int iRev){
	SeReadParam param = new SeReadParam(module);
	param.setMenuCode(menuCode);
	param.setEntityId(id);
	param.setIrev(iRev);

	EntityResult result = curdEJB.loadEntity(param);
}

SeReadParam

Input: Module name, menu code, id of the record. Optional: iRev to read specific version.

If module's checker needs parameters for special treatment, user can put those variables into SeReadParam's jsonParam.


Return Type

Note: If SqlEntity in EntityResult is not null, record of the specific version can be retrieved.

Note: If SqlEntity in EntityResult is null, error messages can be found under List<CheckMsg>.


Save Entity

@EJB
CawEntityCurdAction curdEJB;

public void save(String module, String menuCode, SqlEntity entity){
	SeSaveParam param = new SeSaveParam(module);
	param.setMenuCode(menuCode);
	param.setSqlEntity(entity);
	param.setInsertMir(true);

	CheckResult result = curdEJB.updateEntity(param);
}

SeSaveParam

Input: Module name, menu code, record's in SqlEntity format. Optional: Set insertMir to true if historical record needs to be kept.

If module's checker needs parameters for special treatment, user can put those variables into SeSaveParam's jsonParam.


Return Type

Note: If pass in CheckResult is true, the record has been saved successfully, id can be found using the method getEntityId().

Note: If pass in CheckResult is false, error messages can be found under List<CheckMsg>.


Delete Entity

@EJB
CawEntityCurdAction curdEJB;

public void delete(String module, String menuCode, long id){
	SeDeleteParam param = new SeDeleteParam(module);
	param.setMenuCode(menuCode);
  	param.setEntityId(id);
  	param.setInsertMir(true);

	CheckResult result = curdEJB.deleteEntity(param);
}

SeDeleteParam

Input: Module name, menu code, id of the record. Optional: Set insertMir to true if historical record needs to be kept.

If module's checker needs parameters for special treatment, user can put those variables into SeDeleteParam's jsonParam.


Return Type

Note: If pass in CheckResult is true, the record has been deleted successfully.

Note: If pass in CheckResult is false, error messages can be found under List<CheckMsg>.

Checker and Transaction

During the process of module CURD, checker, defined in module.xml, can be used to handle some checking or process special treatments of the records.

Checker's method setting

Define checker in module.xml. The method with @EntityCheck annotation in checker files will be used during CURD process. Parameter: SeCurdParam

public class EmployeeChecker {

	@EntityCheck(range = CheckRange.BEFORE, type = CheckType.SAVE)
	public CheckMsg checkMothed(SeCurdParam param) {
    	return null}

@EntityCheck's Parameters'

Name Required Type Description
type true CheckType Options: SAVE(1), DELETE(2), CREATE(3), READ(4)
range true CheckRange Options: BEFORE(1), AFTER(2), BOTH(3), SAVING(4), ROLLBACK(5), DELETEING(6). SAVING can only be used when CheckType = SAVE. DELETEING can only be used when CheckType = DELETE
checkOrder false int Default: 0. The smaller the number, the earlier it will process
actionBreaker false boolean Default: false. If true and checker's method return errors, the process will stop immediately and return the error message(s)
overrideMethod false String Use Class.method to override methods in order checker

Save Process

  1. curdEJB invokes save method.

  2. Checker Save-Before: Invoke the method with CheckType = SAVE, CheckRange = BEFORE or BOTH. If any method returns CheckMsg with pass = false, the save process will be stopped and return error message. The purpose of these methods are validating data. It does not involve any data change in database as it is still not in the save transaction.

  3. Sql Before: If no error occurs during the Checker Save-Before stage, it will create a save transaction and run the SQL retrieved from SeSaveParam.getSqlBeforeDtos(). If error occurs during this stage, the save transaction will be stopped and rolled back. Methods with CheckType = SAVE, CheckRange = ROLLBACK will be invoked. Error message will be returned.

  4. Update To Database: If no error occurs during the Sql Before stage, entity will be saved into the database. If error occurs during this stage, the save transaction will be stopped and rolled back. Methods with CheckType = SAVE, CheckRange = ROLLBACK will be invoked. Error message will be returned.

  5. Checker Saving: After saving the entity into the database, methods with CheckType = SAVE, CheckRange = SAVING will be invoked. If error occurs during this stage, the save transaction will be stopped and rolled back. Methods with CheckType = SAVE,CheckRange = ROLLBACK will be invoked. Error message will be returned.

  6. Sql After: If no errors return during the Checker Saving stage, it will run the SQL retrieved from SeSaveParam.getSqlAfterDtos(). If error occurs during this stage, the save transaction will be stopped and rolled back. Methods with CheckType = SAVE, CheckRange = ROLLBACK will be invoked. Error message will be returned.

  7. If no error occurs during the process, the save transaction will be committed.

  8. Checker Save-After: Methods with CheckType = SAVE, CheckRange = AFTER or CheckRange = BOTH will be invoked. If error returns during the process, the save transaction will NOT be rolled back. It is not recommended to do any error checking during this process.

Please refer to below diagram for more details.

mssst


Delete Process

Almost same as Save Process, the only difference is the method with CheckType = DELETEING will be invoked before the Entity is deleted.

Create Process

There are no Sql before, Saving, Sql After stages in Create Process when compared with Save Process.

Read Process There are no Sql before, Saving, Sql After stages in Read Process when compared with Save Process.


Search Module Setup a stSearch with code same as the module name in stSearch.xml.

Non-Module's Operations

M18 has provided SqlTableCurdEAO to handle the operation of non-module's tables. Note: If the target table belongs to any module, it is not recommended to use this method.

Read Record

Use read(StReadParam param) in SqlTableCurdEAO to read the record in database.

@EJB
protected SqlTableCurdEAO curdEAO;

public void read(long id){
	StReadParam param = new StReadParam();
	param.setTableName("draft");	
	param.setId(id);

	SqlTable data = curdEAO.read(param);
}

Create blank table and save data

Use genEmptyTable(String tableName) to retrieve a blank SqlTable with correct format.

Use save(StUpdateParam param) in SqlTableCurdEAO to save the table's data.

tableName and SqlTable must be put into StUpdateParam.

@EJB
protected SqlTableCurdEAO curdEAO;

public long save(DraftDto draft){
    SqlTable sqlTable = emptyTable.genEmptyTable("draft");
    sqlTable.addRow();

    sqlTable.setBoolean(1, "autoSave", draft.isAutoSave());
    sqlTable.setString(1, "draftCode", draft.getDraftCode());
    sqlTable.setObject(1, "lastModifyDate", new Date());
    ***//do some coding

    StUpdateParam param = new StUpdateParam();
    param.setTableName("draft");
    param.setData(sqlTable);

    try {
      	long insertId  = curdEAO.save(param);

      	return insertId;
    } catch (SQLException e) {
     	CawLog.logException(e);
    }
    reutrn 0;
}

Delete record

Use delete(StDeleteParam param) in SqlTableCurdEAO to delete the record in database.

@EJB
protected SqlTableCurdEAO curdEAO;

public void delete(){
	StDeleteParam param = new StDeleteParam();
    param.setTableName("draft");	
    param.setId(1);
    try {
    	curdEAO.delete(param);
    } catch (SQLException e) {
      	CawLog.logException(e);
    }
}

Core XML Configuration

With reference to Logical Modules, M18 uses XML structure to handle common operations and functions. Besides logical modules, many features and functions use XML to control and handle.

The XML's default location is under share project. The path is like /main/resources/META-INF/*.xml

Keywords in Table

As almost all tables are defined in XML format, basic requirement and limitation in XML should be considered.

Although XML is not mainly used in M18 server, XML affects most of the key operation when starting JBoss. It is suggested it should be modified cautiously.

It is recommended to define the code in small letter. Users must not use any special character.

It is also suggested to use app name as the prefix of table and module name. A good naming rule usually prevents conflicts among different parties, escpecially under M18 App concept.

datadict.xml

datadict.xml is used to describe the SqlTable, view and its column's information in MySQL database. It also defines the table constrains like index, foreign key, unique key, etc. M18 transfers the information in datadict.xml and creates those tables and constrains in the database. It is suggested to avoid using any keywords in MySQL.

Note: When M18 is started, M18 will compare the current database table structures and constrains with xml and will only add/change those that are different.

Note: Synchronization of view, stored procedure and function will be explained later


Table's attributes

Name Required Type Default Value Description
name Y String Define the name of the table, unique in M18, small letter only and must not use any special characters
sys N Boolean false Indicate if this table belongs to system. System tables will not check access right and cannot be imported not exported
mess Y String Define the mess code of the table
pk N String id Define the primary key of this table. Must be id
virtual N Boolean false Indicate if the current table's information, including columns or constrains, will not affect the MySQL database
supportUdf N Boolean true Indicate if UDF column will be supported
templet N Boolean false Indicate if the current table is a template. A template will not affect MySQL database and generate cache. Other table can use <Inherit> to inherit the information under this template
extend N Boolean false Indicate if the current table is the extension of any existing records
onlyInMain N Boolean false Indicate if xxx_v table will be created

Inherit's attributes

Name Type Description
name String Define the name of the table in which template = true.

Note: If column's information which belongs to template needs to be modified in the current table, add that column in the table and change the attribute accordingly. However, this method is not recommended as this will be mis-leading.


Column's attributes

Name Required Type Default Value Description
name Y String Define the column name, unique key under the same table. Lower camel case naming rule is recommended. MUST NOT USE SPECIAL CHARACTERS e.g. *, _, - etc.
type Y String Define the type of the column
mess Y String Define the column's messCode
length N Integer Define the data length. Used when type = char/number
decimal N Integer Define the decimal length. Used when type = number
defValue N String Define the default value of the column
defPattern N String Define the pattern, defined in Pattern
required N Boolean false Indicate if the column is required during saving
identity N Boolean false Indicate if auto increment should be enabled for this column. Usually for id column
i18nField N Boolean false Indicate if multiple language is supported for this field. Used only when type = char
i18nSrc N String Define the column name (Table.Column) that i18n field will be enabled together. Ignore when i18nField = false
allowNull N Boolean false Indicate if the column allows null value in database. Note: Some type of columns in Database require null value
buildIn N Boolean false Indicate if the column is build-in. Some functions like fields in search will group these build-in fields into a folder
dataImport N Boolean true Indicate if the column can be imported
dataExport N Boolean true Indicate if the column can be exported
skipLookup N Boolean false Indicate if the column will not be shown when lookup
skipAccess N Boolean false Indicate if the column needs to be considered when access right is enabled
checkLookupVal N Boolean false Indicate if the column needs to search again for access right when being imported
genRuleDate N Boolean true Indicate if the column can be used in generating date field in CodeFormat setup
batchUpdate N Boolean true Indicate if the column can be batch-updated. Note: It is not recommended to batch-update the fields which need calculation
udfUpdate N Boolean Indicate if the column can be used in UDF Update
skipFLR N Boolean false Indicate if the column is controlled by field right
dataDupCheck N Boolean false Indicate if the column can be used in Duplicated Data Check.If the value is not set, the column can be used in Duplicated Data Check when the column type is string or date , or the pattern is lookup.
dataEasy N Boolean dataImport Indicate if the column can be used in Data Easy. If the value is not set, the value default use dataImport value.
lookupCurrentModule N Boolean false Indicate if the column is lookup current module.

Column type's description

Name Description
int_unsigned Java: long. Note: All id should use int_unsigned
bigint Java: long. Not recommended to use.
bit Java: boolean.
datetime Java: date. Date with time.
date Java: date.
nvarchar/varchar Java: String. length required.
text/longtext Java: String. Note: text's length is limited. Use longtext if text's length is not enough.
numeric Java: double. length and decimal required.
Note: File type please refer to File Stream Handling

Index's attributes'

Name Type Description
name String Define the index's name. Unique in the same table.
columns String Define the column(s) used in index. ; separated.
unique Boolean Indicate if this is a unique key.

fk's attributes'

Name Type Description
name String Define the name of Foreign Key.
columns String Define the column name in the current table.
refTable String Define the target table.
refColumns String Define the target column in target table. Default: id.

Note: M18 will create or update the tables/columns/constrains information from datadict.xml to MySQL Database. M18 will not delete any columns from the database, it is recommended to handle it manually.

It is recommended to create another field if the attribute of any field needs modification because there may be unexpected problem when datadict.xml starts synchronizing the database.

Note: M18 allows modification of attribute in columns. However, there will not be any recovery nor warning message if error occurred.

Please pay attention when the field's length is shortened as database may cut the data based on the new length and it is unrecoverable.

Example

<?xml version="1.0"?>
<dd xmlns="http://www.multiable.com/datadict">

    <!-- test info -->
    <table name="test_base" mess="test_base" templet="true">
        <column name="id" type="int_unsigned" mess="core.id" identity="true"/>
       	<column name="iRev" type="int" mess="core.iRev" defValue="0" defPattern="iRev" dataImport="false"/>
		<column name="createDate" type="datetime" mess="core.createDate" defValue="NOW()" defPattern="datetime" dataImport="false" dataExport="false" buildin="true"/>
    </table>

    <table name="test" mess="test" pk="id">
        <inherit name="test_base"/>
        <column name="code" type="varchar" mess="code" length="100" defPattern="code" required="true"/>
        <column name="desc" type="varchar" mess="desc" length="200" defPattern="desc" i18nField="true"/>

        <index name="code" columns="code" unique="true"/>
    </table>

</dd>

Database Synchronization

When M18 is started, besides datadict.xml, it will also update other information like views, stored procedures, functions, etc.

How M18 update these information:

It is required to create three folders with name view, proc and func under the path ejb/src/main/resources/sql/. Every views, stored procedures, functions, etc should have its own file and the content should start with deleting the information and be followed by creating it again.

Example:Stored Procedure's file.

drop procedure if exists test_dm;

DELIMITER $$

CREATE PROCEDURE test_dm(IN dbname varchar(100))  
BEGIN
    select * from test A where A.db = dbname;
END$$

DELIMITER ;

Update process is like below:

mssst

pattern.xml

M18 uses pattern to describe how a field looks like in database and in user interface.

record's attributes

Name Required Type Default Value Description
code true string Define the name. Unique key
type true string Define the pattern's type. Options: text, number, date, options, boolean, lookup, color
info false string Define the description

text's attributes. If pattern's type = text, text tag will be used in the next level to denote it is of text type.

Name Required Type Default Value Description
length true int Define the length of the text
trim false boolean false Indicate if the space in front of or behind the word will be removed
upperCase false boolean false Indicate if the words should be changed to capital letter
mask false string Define the format
regex false string Define the regular expression
html false boolean false Indicate if the user interface should use html component

number's attributes. If pattern's type = number, number tag will be used in the next level to denote it is of numeric type.

Name Required Type Default Value Description
length true int Define the total length (integer + decimal) of the number
decimal false int 0 Define the length of the decimal part
max false double false Define the maximum value of the number
min false double Define the minimum value of the number
noSep false boolean false Indicate if the number should be separated by thousand separator (,) in user interface
negativeSymbol false boolean false Indicate if blanket will be used if the value is negative in user interface

lookup's attributes. If pattern's type = lookup, lookup tag will be used in the next level to denote it is of lookup type.

Name Required Type Default Value Description
searchType true string Define the stSearch used for this lookup

options's attributes. If pattern's type = options, options tag will be used to denote it is a drop down list.

Name Required Type Default Value Description
valueClass true string string Define the type of the option's value. Options: String, Integer, Double, Combobox
emptyValue false string Define the value if no option has been chosen

option's attributes

Name Required Type Default Value Description
value true string Define the value when the option is selected
label true string Define the value displayed in the user interface

option's icon attributes

Name Required Type Default Value Description
name false String Icon name in the library.
library false String Library url in system.
url false String The icon url.
style false String Icon css style.
styleClass false String Icon css style class.

Special Patterns Followings are some special patterns used by M18. It is not suggested to modify it.

	<!-- this is for file.info the id of filedata -->
	<record code="fileId" type="number"><number length="12" /></record>

	<!-- this is for image, use code for safe! -->
	<record code="imgCode" type="text"><text length="60" /></record>
	
	<!-- date with time -->
	<record code="datetime" type="date"></record>
	
	<!-- time in format HH:MM-->
	<record code="time" type="text"><text length="10" /></record>

	<!-- date only-->
	<record code="date" type="date"></record>

	<!-- color component-->
	<record code="color" type="color"></record>

	<!-- can only save with length less than 2^32. Use longtext if not 				enought. -->
	<record code="charMax" type="text"><text length="-1" /></record>

	<!-- json string-->
	<record code="json" type="text"><text length="-1" /></record>

	<!-- unlimited length. datadict.xml should use type = longtext for this 		pattern-->
	<record code="longtext" type="text"><text length="-1" /></record>

	<!-- html component-->
	<record code="html" type="text"><text length="-1" html="true" /></record>

M18 uses navmenu.xml to define the system's Menu displayed in user interface. User can assign access right through this menu.

menuType description. menuType defines the type of the menu with the use of icons and images.

Name Child Element Description
name Define a menu type.
desc Define the mess code of menu type.
icon name Define the icon name. (icon used in menu)
icon library Define the location of the icon. System will find the specific image through the library and name
image name Define the image name. (image used in menu)
image library Define the location of the image

M18 has some default menu type as follows:

  • FM is used to represent File Master Module

  • TRAN is used to represent Transaction Module

  • SETTING is used to represent Setting Module

  • EBI is used to represent EBI Module

  • OTHER/ blank is used to represent Other Module

    mssst

Example

<menuType name="FM" desc="fm/master">
	<icon name="cicon-fm-master-data" library="font/cawIcon"></icon>
	<image name="fmIcon.png" library="img/icon"></image>
</menuType>

function's attributes. function defines the available function/ right of a menu. It is also the basic unit of menu's access right.

Name Required Description
name Y Define the function name. Recommended to use alphabet and underscore
group N Define the group shown in [Role Right Setup]
messCode Y Define the messCode used in user interface
tooltip Y Define the tooltip displayed in [Role Right Setup]

Please refer to [Role Right Setup] for the list of current function.


menuHelper's attributes. Mainly used to create menuParam.

Name Description
code Define the menu code that uses the function under the class
mType Define the menu type that uses the function under the class
class Define the class name that implements MenuHelper. Can use isFor to define specific menu

folder's attributes

Name Description
code Define the folder name
messCode Define the folder mess code
apDebug Boolean. Only visible in user interface when debug mode is on
flag Define the flag in which this flag can be used in java bean
order Define the order of the folder in the tree

menu's attributes

Name Child Element Type Description
code String Define the menu code
messCode String Define the menu mess code
src String Define the link when this menu is clicked
module String Define the module used by this menu
mType String Define the menu type for this menu
templet Boolean Indicate if the current menu is a template. Template will not shown in user interface but can be inherited by other menu
apDebug Boolean Indicate if it can only be visible in user interface when debug mode is on
flag String Define the flag in which this flag can be used in java bean
inherit name String Define the template used
inherit include String Define the function name only inherit from the template. ; separated
inherit except String Define the function name exclude from the function list in the template. ; separated
function name String Define the function name. ; separated
controller NA String Define the controller class name used for this menu
listener NA String Define the listener class name used for this menu
param key String Define the key used in key-value pair for the purpose of adding additional parameters to the editor
param value String Define the value used in key-value pair for the purpose of adding additional parameters to the editor
menuParam name String Parameter's name
menuParam description String Parameter's mess code
menuParam pattern String Parameter's pattern
menuParam folder String Parameter's grouping
order int Define the order of the menu in the folder

Example

<?xml version="1.0"?>
<nm xmlns="http://www.multiable.com/navmenu" app="caw">

	<function name="New" group="Editor" messCode="core.saveNew" tooltip="right.tt.new" />
	<function name="Save" group="Editor" messCode="core.save" tooltip="right.tt.save" />
	
	<folder code="sys" messCode="core.sys">
		<menu code="test" messCode="test" src="view/module/test" module="test" mType="">        
			<function name="New;Save" />
		</menu>
	</folder>
</nm>

How to determine if a user has defined access right?

UserRightInfo info = UserLib.getRight(uid);
FrameRight right = info.getFrameRight(menu,beId);

Note: Function added by other parties should use otherRight in right to determine.

module.xml

Module represent a group of editor (or function)'s tables. In M18, module must have a main table and other tables must be related to the primary key of the main table.

module's attributes'

Name Type Description
name String Define the module name
mess String Define the module mess code
extend Boolean Indicate if the current element is the extension of any existing element.
mainTable String Define the main table used in this module. Note: The main table must exist in the child tag table
useBe Boolean Indicate if the current module should use business entity
fmshare String Define the level of data separation. blank means it is not a file master's editor. Y means it is a file master's editor and the data is based on business entity. N means it is a file master's editor and the data is shared among business entity
useAccess Boolean Indicate if the current module should be controlled by Data Guard
useAttach Boolean Indicate if the current module should enable attachment function
useApv Boolean Indicate if the current module should enable approval function
useChangenote Boolean Indicate if the current module should enable change note function
useCache Boolean Indicate if cache should be used. Default true. Note: If the module is manually cached or cannot be cached, it is recommended to turn this flag to false. To update the cache, please increase the iRev in the table
useAutoGenCode Boolean Indicate if the module can use code format setup. New functions is needed to enable this feature
genCode_Field String Define the field that the output from code format setup will be assigned to. Default code
genCode_Date String Define the date field that is used in code format setup. Default createDate
fieldDataGuard Integer Indicate if the current module should enable Field Data Guard. -1 means disabled. 1 means enabled. 0 means enabled when New function is enabled
udfLogicSkip Boolean Indicate if UDF Logic should be skipped
supportUdf Boolean Indicate if current module should support managing UDF fields
checkCodeExist Boolean Indicate if M18 should automatically check if code already exists in the database and warn the user when code is input in the user interface
dataSynch String Define Data Easy should be enabled for this module. New function should be enabled. Default is enabled
dataImportConveter String Define the class name used for special handling in importation. Please refer to Data Import and Export for more details
excelGener String Define the class name used for special handling in exportation. Please refer to Data Import and Export for more details
template Boolean Mark if current configuration is UDF template or not, which is used in UDF Editor
tableOrders String Define the order of the table for most of the process. ; separated. Note: Main Table defined in mainTable must appear first
codeDupLevel String Define the code's duplicated level. If BE, beId + code will be the unique key in the table. Note: M18 will create unique key constraint in the database when the system is started, please ensure the uniqueness of the code before the constraint is created
skipExpired Boolean Indicate the current module does not enable expired function
skipSysBC Boolean Indicate the current module will not create the code unique index. And will not check the duplicated code between BEs.
importAllowUpdate Boolean Indicate the current module can be update in Data Import. Default is true.
importThreadMode Boolean Indicate the current module can be using multithreading in Data Import. Default is true.

table's attributes

Name Type Description
name String Define the table name. The table name must be already defined in datadict.xml
tpl String Used by UDF Editor. Mark the actual datadict table used by this UDF editor
c Boolean Indicate if this table supports creation of record. If false, new record will not be created in this table
r Boolean Indicate if this table supports reading of record. If false, system will ignore this table when reading record in the module
u Boolean Indicate if this table supports saving of record. If false, system will ignore this table when saving record in the module
d Boolean Indicate if this table supports deletion of record. If false, system will ignore this table when deleting record in the module
initRow Integer Define the number of row added for this table when the module is initialized
forceInit Boolean Indicate if the number of row should be added if the row of the table after reading from database is less than initRow defined
hpk String Define the fields that used to link with the primary key in main table. Note: No need to input when the current table is main table. Do not create any foreign key for this field
fkey String Define the fields in the footer which can be used to determine the uniqueness of the record
hfname String Mark the upper level footer's info
hfkey String The key field of upper level footer
order String Define a field that is used to order the table. Only affect the read action
cpnType String Define the type of this table. if table, the relationship between main table and this table will be 1:N. Note: If table, the table will support UDF Field
discriminatorColumn String Define a field that is used to discriminate the module used when this table is used by many module
resetDiscriminatorColumn Boolean Indicate if discriminator column should be reset
dataImport Boolean Indicate if importation is supported. Note: If the main table does not support importation, the current module will not support importation too
dataExport Boolean Indicate if exportation is supported. Note: If the main table does not support exportation, the current module will not support exportation too
columnOrders String Define the column order for importation. ; separated
fieldRightSetting Boolean Indicate if the current table should enable Field Data Guard
compareKey String Define the field that is used to determine the difference between the historical data and current data. ; separated
sfKey String The key field to join the current footer and upper level footer

checker's attributes

Name Type Description
class String Define the class used for this checker
exclude String Define the method excluded from the class. Excluded method will not be triggered. ; separated
include String Define the method included from the class. Only included method will be triggered. ; separated
skipSuper Boolean Indicate if methods in super class should be ignored
apployTo String Define the modules that this checker will be applied to. * means apply to all modules

param's attributes

Name Description
key key of the param. use Module.getParam() to get all the parameters in this module.
value value of the param.

dataImportExtend's attributes

Name Description
extendSrc Indicate the extend page path which use in Date Import.
dtoClass The data class which use in extend page.

dataExportExtend's attributes

Name Description
extendSrc Indicate the extend page path which use in Date Export.
dtoClass The data class which use in extend page.

Example:

<?xml version="1.0"?>
<md xmlns="http://www.multiable.com/module" app="caw">

	<module name="user" mess="user" mainTable="user" useAccess="true" useAttach="true">
		<table name="user" key="code" initRow="1"/>
		<table name="useroption" key="id" initRow="1" forceInit="true"/>
		<table name="userrole" key="beId" hpk="hId" cpnType="table"/>
        <table name="usercontrol" key="id" initRow="1" hpk="hId" forceInit="true"/>
        <table name="userdefbe" key="id" initRow="0" hpk="hId" fKey="beId" order="priority" cpnType="table"/>
        
        <!-- this checker is for all module!  -->
		<checker class="com.multiable.core.ejb.checker.ModuleChecker" applyTo="*"/>
		<checker class="com.multiable.core.ejb.checker.ExtDataChecker" applyTo="*"/>
		
		<checker class="com.multiable.core.ejb.checker.UserChecker"/>
  	</module>
</md>

i18n Implementation

M18 supports multiple language display. It is required to display the content at least in English, Simplified Chinese and Traditional Chinese. User can add additional language in M18.

Related document is located in /main/resources/META-INF/lang/Message_*.properties

Developer can use Eclipse to edit the above documents.

mssst

Note: For any new mess code, English translation must exist.

To ensure the uniqueness of mess code, it is suggested to use App's name for the prefix of mess code.

Developers can retrieve the translation using the mess code like the following example:

String text = CawGlobal.getMess("EBI");

In addition, if the mess code needs to be used in JavaScript, please add a record in webKey.properties.

var mText = myView.getMess('EBI');

Note: Please do not use the keyword in HTML if the mess code can be used in JavaScript.


webKey.properties Example

// web mess for core
CloseAllTabs
EBI
all

User defined language and mess code. Please refer to M18 editors: UDF Language, Messcode

M18 supports data saving in multiple language. The simplest way is to set i18nField of the desired column to true in datadict.xml. These data will be stored in database in JSON format.

mssstmssst

For simplicity, M18 will translate the i18n's fields to the current language. The following is an example showing how desc, an i18n's field, can be retrieved from SqlTable.

String desc= employee.getString(1,"desc_en");
employee.setString(1,"desc_zh_CN",desc);
employee.setString(1,"desc_en","");

Note: The multiple language information is stored in the column i18nField, If you want to modify the i18n's field in database directly, please modify i18nField as well.

Search Implementation

Table/ View can be searched by setting up stInfo.xml and stSearch.xml. Lookup component in user interface also searches data using the above two XMLs.

stInfo.xml

stInfo is to define the table used in searching and to avoid exposing the table directly to the user. If a stInfo is used in stSearch and the corresponding information is not defined in stInfo.xml, the system will generate the corresponding stInfo information according to datadict by default.

stInfo's attributes

Name Required Type Description
name true string stInfo's name, normally same name as corresponding table
mess true string stInfo's mess code
table true string Target table's name (in datadict)
inCols false string Define which fields in the table are available in this stInfo. If this parameter is empty, all fields representing the corresponding table are available in this stInfo. If not empty, the two fields need to be separated by a semicolon ;
exCols false string Define which fields in the table are NOT available in the current stInfo. If this parameter is empty, all fields representing the corresponding table are available in this stInfo. If not empty, the two fields need to be separated by a semicolon ;
autoRelation false boolean Indicate auto create the relation of lookup field
skipBuildin false boolean Indicate build-in column can used in lookup condition.

relation's attributes. The relation tag defines how the table of the current stInfo associates with the table of another stInfo.

Name Required Type Description
col false string The field to join with other's stInfo
tarSt true string The target stInfo's name
tarCol false string The target stInfo's column
tarNullAble false boolean The default is false; if true; the left outer join is used; otherwise, the inner join is used
joinCond false string If not empty, this joining condition string will be used, otherwise use tarSt and tarCol

col's attributes. The col tag is used to define a custom field to be returned by the search result. This tag is not required.

Name Required Type Default Value Description
name true string Field's name
mess true string Field's mess code
pattern false string Field's pattern
sql false string If the value of this field is to be queried from the database, you need to input the SQL query string
cond false boolean true If the value is true, this field can be used as a query condition.
sort false boolean true If the value is true, this field can be used for sorting
show false boolean true If the value is true, this field can be displayed on the M18's UI.

handler's attributes. The handler tag is used to configure stlnfo's handler, which can further control the initialization of stInfo. This tag is optional.

Name Required Type Description
stInfoName true string stInfo's name
className true string handler's class name

stSearch.xml

stSearch's attributes

Name Required Type Default Value Description
name true string stSearch's name
mess true string stSearch's mess code
srcSt false string Use the stInfo in stInfo.xml
maxLevel false int 3 Indicate the maximum number of levels that can be related to stInfo
defCond false string Default query condition
sort false string Default sorting order
supportMultiSort false boolean false If false, only support single field sorting
asyncGetCount false boolean false (defaults to true if ejb is not set or slave is not set) If set to true, the count of lookup queries will be queried separately from the query data
mustJoinSt false string Used to define the stInfo that must be joined in this query. StInfo must be in the relation hierarchy of srcSt; Two or more stlnfos need to be separated by semicolons ;
ejb false string The EJB to do search, if not provided the default EJB will be used. If you need to provide an EJB, it must extend GenSearchSqlAdapter
udfUse false boolean true If set to false, it will not appear in the UDF modules
accessModule false string The module corresponding to the current stSearch
accessField false string “id” Specify which field of srcSt is associated with the id field of the footer table
nameCardSupport false boolean true Support nameCard or not
hideFormat false boolean false If set to true, lookup will hide the format, and then user can not set the format
hideFilter false boolean false If set to false, users can not add a filter condition on the lookup
supportSearch false boolean true If set to false, users can not use mask search on the lookup
supportDeletedData false boolean false If true, search can query the records that have been deleted
slaveEjb false string slave's class name
slaveApplyTo false string The current slave can be used in other stSearch; fill in the stSearch name here; if you need to fill in more than two stSearch, please use the semicolon ; to separate them.
skipAccess false boolean false If set to true, lookup will skip access right

slaveCol's attributes. The slaveCol tag is used to define a custom field that is returned as a result of the search. This tag is optional.

Name Required Type Default Value Description
col true string Field's name
mess true string Field's mess code
pattern false string Field's pattern
sql false string If the value of this field is to be queried from the database, you need to input a string of SQL to this
cond false boolean true If the value is true, this field can be used as a query condition
sort false boolean true If the value is true, this field can be used for sorting order
show false boolean true If the value is true, this field can be displayed in the M18's UI
followField false string Fill a field under the current stInfo or associated table. Used to add the currently added slaveField behind the field in the left column of the format setting interface.

bindCond's attributes. The compulsory condition for this stSearch

Name Required Type Default Value Description
andOr false string If not set, SQL will use AND to construct
leftBrackets false string If needed, please fill "("
leftFieldMode false string "column" Indicates the type of leftField
leftField false string If leftFieldMode is "column", then we will fill in the column name. If leftFieldMode is "value", then fill in the data
operator fasle string "=" Fill in the operator here; the default is "="
rightFieldMode false string "value" Indicates the type of rightField
rightField fasle string If rightFieldMode is "column", then we will fill in the column name. If rightFieldMode is "value", then fill in the data
rightBrackets false string If needed, please fill ")"
condString false string Fill in a SQL query here; If you set this, all the above parameters will lose their effect.

format's attributes. format is used to configure the query condition

Name Required Type Default Value Description
id true int -1 Can not be greater than 0. If greater than 0 will be changed to -
mess true string format's mess code

format's cond's attributes. The default query condition

Name Required Type Default Value Description
andOr false string If not set, will use "AND" to construct SQL
leftBrackets false string If needed, please fill "("
leftFieldMode false string "column" Indicates the type of leftField
leftField false string If leftFieldMode is "column"; then we will fill in the column name; if leftFieldMode is "value", then fill in the data
operator fasle string "=" Fill in the operator here; the default is "="
rightFieldMode false string "value" Indicates the type of rightField
rightField fasle string If rightFieldMode is "column"; then we will fill in the column name; if rightFieldMode is "value", then fill in the data
rightBrackets false string If needed, please fill ")"
condString false string Fill in a SQL query here; If you set this, all the above parameters will lose their effect.

format's col's attributes. To configure default display fields.

Name Required Type Default Value Description
col true string Field's name
mess false string Mess code
width false string The width of the field in browse dialog table
sortable false boolean true Indicate the current column can be sorted.

format's sort's attributes. To set the default sorting order.

Name Required Type Default Value Description
col true string Field's name
ascending false boolean false If set to true, it will be sorted using descending order

namecard's attributes. Please read Name Card.


handler's attributes. The tag is used to configure the init method of the lookup parameter StParameter. The init method runs before the search data is loaded into the lookup field, and the page lookup field runs before the query.

Name Required Type Default Value Description
name true string The class name; the method inside the class must have @InitStSearchParam annotation

stSearchInitHandler's attributes. The stSearchInitHandler tag is used to configure the class that initializes stSearch.

Name Required Type Default Value Description
stSearchName true string stSearch's name
className true string Fill a class that extends StSearchInitHandlerAdapter

Note: By setting stInfo and stSearch, you can use "search" to query data

Configure XML: stInfo

<stInfo name="simpleUser" mess="share.user" table="user">
</stInfo>
<stInfo name="employee" mess="share.employee" table="employee">
	<relation col="createUid" tarSt="simpleUser" tarCol="id"/>
	<relation col="lastModifyUid" tarSt="simpleUser" tarCol="id"/>
</stInfo>

Configure XML: stSearch

<stSearch name="employee" mess="employee" srcSt="employee" maxLevel="3" sort="#employee#.code" accessModule="employee" asyncGetCount="true">
	<bindCond leftField="id" operator="!=" rightField="0"/>

	<format mess="core.defFormat">
		<col col="code"/>
		<col col="desc"/>
	</format>

	<namecard imgCode="photoCode" width="290"></namecard>
</stSearch>

Search Process Customization

Note: In the search process, developers can configure slave or handler to modify the search process.

  1. Slave configuration: It needs to inherit the SearchSlaveAdapter. Then through the methods beforeDatalookup() and afterDatalookup() it overrides the search process. In beforeDatalookup(), developers can add conditions that can affect the query results. In afterDatalookup(), developers can modify the query results before it return to users.
  2. handler tag configured in stSearch.xml: It is the processing method to run before the lookup checking of data import, and before lookup result is returned in the page.

As you can see in the below example, the processing method in the handler needs to be annotated with @InitStSearchParam. The method must be static, and the parameters are also fixed (StParameter param, SqlEntity entity, String tableName, String colName, int row).

<handler name="com.multiable.core.share.modulestsearch.InitStParam"></handler>
public class InitStParam {

	@InitStSearchParam(moduleName = "dept", stSearch = "dept")
	public static void initDeptSupDeptParam(StParameter param, SqlEntity entity, String tableName, String colName, int row) {

		if (!("dept".equals(tableName) && "supDept".equals(colName))) {
			return;
		}
	}
}

  1. initHandler Class: Adding tag stSearchInitHandler in stSearch.xml can make 3PD further customize stSearch initialization.

handler class needs to inherit StSearchInitHandlerAdapter.

<stSearchInitHandler stSearchName ="employee" className ="com.multiable.core.share.modulestsearch.EmpStSearchHandler"/>
public class EmpStSearchHandler extends StSearchInitHandlerAdapter {

	@Override
	public void initStSearch(StSearchInfo stSearchInfo) {
		CawLog.info(stSearchInfo.getName());
	}
}

  1. handler tag configured in stInfo.xml: It can let 3PD further customize stInfo's initialization.

handler class needs to inherit StInfoHandlerAdapter.

<handler stInfoName ="employee" className ="com.multiable.core.share.modulestsearch.EmpStInfoHandler"/>
public class EmpStInfoHandler extends StInfoHandlerAdapter {
	@Override
	public void initStInfo(StInfo stInfo) {
		CawLog.info(stInfo.getName());
	}
}

Note: If the system default constructed search SQL does not fulfill the needs, you can re-define the SearchEJB

Restriction: Need to extend GenSearchSqlAdapter

Example: Enter the name of ejb "GenEmptySQLEJB" in stSearch tag, then extends class GenSearchSqlAdapter

Example

@Stateless
@Local(GenSearchSql.class)
public class GenEmptySqlEJB extends GenSearchSqlAdapter {

	@Override
	public SearchSqlInfo getLookupSql(StSearchInfo myInfo, StParameter param) {
		SearchSqlInfo emptyData = new SearchSqlInfo();
		emptyData.setSearchSql("");
		emptyData.setCountSql("");
		return emptyData;
	}
}

Solution 2: Re-define EJB of Search and Slave

Note: If an stSearch data does not need to be queried from the database, you can use the redefined SearchEJB + use slave to generate data.

Example: Enter the name of ejb "GenEmptySQLEJB" and slaveEjb = "PatternInfoSlave" in stSearch.xml tag, ejb needs to extend GenSearchSqlAdapter, slave needs to extend SearchSlaveAdapter

Java Code

GenEmptySqlEJB: Please refer to the above GenEmptySqlEJB example

PatternInfoSlave: To generate data in slave, only needs to override afterDatalookup

Example

@Stateless
@Local(SearchSlave.class)
public class PatternInfoSlave extends SearchSlaveAdapter {

	@SuppressWarnings("unchecked")
	@Override
	public void afterDatalookup(SearchResult result, StParameter param) {
		SqlTable sqlTable = StLookupLib.createEmptyLookupTable();
		sqlTable.addField(new SqlTableField("type", String.class));

		List<String> keys = new ArrayList<>();
		*** //do some search coding

		int id = 1;
		int startRow = param.getStartRow() < 1 ? 1 : param.getStartRow();
		int endRow = param.getEndRow() > keys.size() ? keys.size() : param.getEndRow();
		for (int i = startRow; i <= endRow; i++) {
			CawPattern pattern = CawGlobal.getPattern(keys.get(i - 1));
			int rec = sqlTable.addRow();
			sqlTable.setLong(rec, "st_id", id);
			sqlTable.setString(rec, "st_code", pattern.getCode());
			sqlTable.setString(rec, "st_desc", pattern.getType());
			sqlTable.setLong(rec, "id", id);
			sqlTable.setString(rec, "code", pattern.getCode());
			sqlTable.setString(rec, "type", pattern.getType());
			id++;
		}

		result.setResultTable(sqlTable);
		result.setTotalRows(keys.size());
	}
}


Solution 3: Implement New Lookup Component

Note: If existing lookup component can not meet the needs of users, then you can write your own components, and no longer use stSearch/ stInfo configuration.

Restrictions: Everything on the page and in the query needs to be handled.

Login and Access Control

Login Customization

loginHandler

During M18 system login, developers can set loginHandler tag in app.xml to intercept the process. Overriding the method checkLoginInfoBeforeLogin() can handle before login event, while overriding afterLogin() method can handle after login process.

loginHandler's attributes

Name Description
className class name of the handler, needs to extend LoginHandlerAdapter

wsSkipAccess

For most web service requests to M18, access control will be checked with the userKey and authorization token. Using wsSkipAccess tag in app.xml can make a web service request skip such checking.

wsSkipAccess's attributes

Name Description
pathNames path of web service to skip the access checking, multiple paths can be separated by semicolons ;. Example: "/cawLogin/checkLoginUser;/image/getImage“

webSkipAccess

M18 will check session login status for non-web service requests as well, use webSkipAccess tag can make a web request to M18 to skip such checking.

webSkipAccess's attributes

Name Description
pathNames path of web request to skip session checking, multiple paths can be separated by semicolons ;. Example: "/CaptchaServlet;/wfAction”

Module Rights

M18 uses XML of module and navigation menu to configure available rights for a module. For details please refer to navmenu.xml.

Data Guard

If a module has data guard enabled, when doing lookup a record in the module, data guard will be checked. For how to enable data guard in a module, please refer to module.xml.

The SQL related to data guard is put in the corresponding StSearchInfo.java

SQL of Data Guard

Name Description
accessStr_noBe SQL used when module is not BE specific
accessStr_oneBe SQL used when module is BE specific, and only one BE's records are queried
accessStr_manyBe SQL used when module is BE specific, and multiple BEs' records are queried
accessStr_mir SQL used when deleted records are queried

Generation of data guard SQL

Upon stSearch initialization, system will run StSearchInfo initAccess method to generate these query SQLs. It will construct the white list and black list SQL based on the data guard configuration. Then the data guard of all related lookup field's of the module will be considered. If user wants a lookup field to be skipped in data guard SQL, please mark skipAccess="true" in datadict.xml.

How to Customize Data Guarded Results

Data Guard module has auto-calculate data guard function. During module save, it will auto-calculate related access condition data.

Note: Developer can implement entityHandler's autoCalcAccess method to intercept the data guard logic

entityHandler's attributes

Name Type Description
name string Module name to be affected. If empty, it will affect all modules.
className string class qualified name. class needs to extend EntityHandlerAdapter

How to Replace Search's Data Guard

Developer can refer to Search Process Customization, point 3, to modify the data guard of search.

Field Level Access Rights

All fields in system tables and the fields: code, desc, beId in all tables cannot have field level access rights. Fields that are marked with buildin = true cannot have field level access rights.

Developers can disable field level access rights for all fields in a table by setting fieldRightSetting = false for a table in module.xml.

Note: Can use methods in RightLib to get field rights.

public static List<FieldRightDto> getFieldRight(long beId, Set<String> tables)// Get field rights for current user, input is beId and a set of table names 
public static List<FieldRightDto> getFieldRight(long beId, Set<String> tables, long uid)// Get field rights for a specific user (need to pass in uid)

Common Functions

About JBoss Configuration File

cawserver.properties

cawserver.properties is a very important configuration file put under wildfly9/ config folder. It is used to configure startup parameters of M18 and JBoss.

cawserver.properties core parameters introduction

#jboss server's ID, if jboss cluster is setup, each node need to have diff. jboss id
caw.jboss.id=cl

#jboss server belongs to which cluster
caw.jboss.cluster=abc

#some task will only run in main server, in a cluster environment, there should be only one main server
caw.main.server=true

#path for dbdoc and appdoc
caw.fileData.dbDoc.path=/home/centos/xxxshare
caw.fileData.appDoc.path=/home/centos/xxxshare

#temp path for temp files
caw.file.app.path=home/centos/xxxshare/temp

#External URL for M18
caw.web.url=http://www.abc.com/jsf/

#Server internal URL
caw.internal.url=@web_url

#M18 DB IP
caw.database.ip=192.168.10.117

#M18 DB Port
caw.database.port=3306

#M18 DB Name
caw.database.dbname=ce01

#M18 DB User
caw.database.user=ce01

#M18 DB Pwd
caw.database.password=ce01abcdef

#Decide if current jboss can run scheduled task, 1 means can.
caw.sch.quartz = 1

reset.caw

This file can be put under wildfly9/ config folder, which is useful for development environment. Developer can set any parameters of cawserver.properties here, when eclipse's JBoss is running, will use parameters in reset.caw to run.

#Set to 1 if want AP debug mode during jboss run
caw.ap.debug = 1
#during jboss startup, skip sync data structure or not
caw.ap.skip.sycnDB=1

#Below control if detail log message created for specifc module CRUD actions
caw.ap.debug.checker.create=0
caw.ap.debug.checker.save=0
caw.ap.debug.checker.read=0
caw.ap.debug.checker.delete=0
caw.ap.debug.checker.module=

#for lock and debug scheduled job, can enter multiple, separated by semicolons.
caw.ap.sch.debugJobName=
#for loc and debug scheduled job group, can enter multiple, separated by semicolons.
caw.ap.sch.debugJobGroup=
#lock job for how many hours
caw.ap.sch.debugTime=

#If set to 1, then report jasper report format update will be skipped
caw.report.skipUploadFormat = 0

Table Structure Information

For M18's developers, understanding the data structure of existing modules/functions is essential for their development. In this part we will introduce how to find important table structure information and how to use API to lookup table structure information of a module.

In M18, developers can use [Data Dictionary] to find table/ field structure of any editor in the system:

mssst

In Java code, to get datadict info with known table name, developer can use this:

DdTable table = CawGlobal.getDdTable("employee");

Loop the DdTable object:

Ehcache cache = CawCache.getCache(CawCache.ddCache);
		
@SuppressWarnings("rawtypes")
List keys = cache.getKeys();

for (Object key : keys) {
    DdTable table = (DdTable) cache.get(key).getObjectValue();
    // add you code here
}

Data Import and Export

M18 has data import/export functions for most modules, which is useful for data initialization, temporary backup and data migration to/ from other systems.

Data Export If developers need to control the data in the exported excel, one can set excelGener in module.xml for corresponding module, with excelGener = "class qualified name". The class needs to implement IExcelGener interface. Developers can use the method exportData and appendMoreData to manipulate exported data in excel.

Note: The default implementation class for IExcelGener is ExcelGener class.

public interface IExcelGener {
	// Method to generate excel importation template excel
	public Workbook createTemplate(String moduleName, long beId, String dataExportConfigJson) throws Exception;
	// Convert SqlEntity data to excel
	public Workbook exportData(DataExportConfig config, SqlEntity entity) throws Exception;
}

Data Import If developers want to control excel importation, one can set excelGener and dataImportConverter in module.xml for corresponding module, with excelGener = "class qualified name". The class needs to implement IExcelGener interface. dataImportConverter needs to extend ImportConverterAdapter class.

One can use IExcelGener createTemplate method to create an excel template for importing data. One can use the dataImportConverter convert method to convert excel data to the M18 system's SqlEntity structure; One can use the getImportResultWriter method to get the ImportResultWriter for writing error messages to an excel file.

Note: The M18 system uses the EntityImportConverter class to convert the imported excel data by default. The error information is written to the excel file by using the ExcelImportResultWriter class;

Note: If you want to intercept the import process without the need to redefine IExcelGener and dataImportConveter, there is one more way: define tag entityHandler in app.xml:

entityHandler's attributes

Name Type Description
name string Module name, if empty will affect all modules
className string class qualified name, class need to extend EntityHandlerAdapter

Below methods in entityHandler are related to data import:

public Map<String, Set<String>> getImportSkipColumn(String moduleName, long beId);

public Map<String, List<DdColumn>> getImportExtraColumn(String moduleName, long beId);

public List<String> handleExtraBeforeAllColumn(String table);

public void updateExtraColumnValue(SqlEntity entity, int sqlTableRow, String moduleName, String tableName, DdColumn ddColumn, Object cellValue, long beId);

public Set<String> getImportSkipTable(String moduleName, long beId);

public default String modifyColumnsRemark(String remark, String tableName, DdColumn col, DataExportConfig config) {
	return remark;
}

entityHandler's methods related to data import:

Name Description
getImportSkipColumn Return Map <String, Set <String >>; Map's key is the table name, set is the fields that will skip import
getImportExtraColumn Return Map <String, Set <String >>; Map's key is the table name, list is the DdColumn List; Used to indicate which table in the current module needs more fields to import. These fields will be displayed in the import module and import excel template
handleExtraBeforeAllColumn Return a list, indicates which extra fields need to be handled before all fields
updateExtraColumnValue Used to process extra field values
getImportSkipTable Returns a set indicating which tables in the current module will not be able to use the import function
modifyColumnsRemark Used to modify the column remarks in excel importation template

Document Printing

M18 system supports document printing using ireport. It converts data and report format to PDF using JasperReports API. User can then print the PDF directly.

Before handle report format, please make sure you have basic understanding of ireport.

Please install the latest version of ireport on your computer and replace com.jaspersoft.studio.data_6.2.0.final.jar in M18 with the installation directory. Note: ireport editing software is an Eclipse.

Please add caw share jar to JaspersoftStudio project, so that the system can give the correct information/messages when dealing with M18 functions.

docReport.xml

report's attributes

Name Type Description
code string Used to uniquely identify a report. Recommended the code starts with @
module string Module code related to this printing. Support multiple input, each separated by semicolons ;
providerCode string Print provider class name
reportDto string Dto's class name, which provides the report parameters
reportDtoSettingSrc string Dto's setting source
providerMess string Print provider messCode
providerOrder int Valid when there is more than one print provider in a module. Decide the print order of the provider
apDebug boolean When true, only visible in debug mode

format's attributes

Name Description
code format code
description format's description
remark format's remarks
packageName jrxml location

jrxml's attributes

Name Description
name jrxml's name, should end with .jrxml
main Mark the main jrxml in a format

resetData's attributes

Name Description
prCode provider's class name
resetClass class name, implements CawJrPrintRestData

reportHandler's attributes

Name Description
reportCode report code
handlerClass class name, needs to implement ReportHandler. Allow developer to modify the jrxml during initialization, normally used to hide some functions in the report

reportShowHandler's attributes

Name Description
prCode provider's class name
handlerClass class name, needs to implement ReportShowHandler. Can control if a pdf should be downloaded directly or do other operations

Example:

<docReport>
	<report module="employee" code="@bpm_emp"  
			reportDto="com.multiable.core.share.data.ireport.emp.EmpJrDto" reportDtoSettingSrc="/view/dialog/empPrintSetting.xhtml"
	        providerCode="com.multiable.ireport.provider.emp.EmployeeProvider" providerOrder="1" providerMess="jr.emp_provider" apDebug ="true">
	        
		<format code="@bpm_emp" description="Print" remark="Print Employee" packageName="com.multiable.core.share.data.ireport.resource.emp">
			<jrxml name="emp.jrxml" main="true"></jrxml>
	        <jrxml name="empPic.jrxml"></jrxml>    
	        <jrxml name="empUser.jrxml"></jrxml>    
		</format>
	</report>
</docReport>

Add Provider Class and SQL

Dto class

public class EmpJrDto extends ModuleReportDto {
	private boolean prtPic = true;

	@Override
	public String getDefaultTitle() {
		return "Testing Ireport!";
	}
	public boolean isPrtPic() {
		return prtPic;
	}
	public void setPrtPic(boolean prtPic) {
		this.prtPic = prtPic;
	}
}

This is a simple POJO class, this dto must extend ModuleReportDto


Provider

public class EmployeeProvider extends ModuleProvider {

	@Override
	public void initReportStru(CawReportDataSet reData) {
		// set table alias name
		reData.setAlias(0, "EMPLOYEE", "employee");

		reData.setAlias(1, "DEPT", "dept");
		reData.setAlias(2, "EMPLOYEEPIC", "employeepic");
		reData.setAlias(3, "PICUSER", "user");

		reData.setQuery("EMPLOYEE");
	}

	@Override
	public void adjustData(CawReportDataSet reData) {

		reData.setRelationTo("EMPLOYEE", "dept", "id", "DEPT");
		reData.setRelationTo("EMPLOYEEPIC", "userId", "id", "PICUSER");
		reData.assignSubReport("EMPLOYEE", "EMPLOYEEPIC", null, "id", "hId");
	}

	@Override
	public CawReportDataSet genIdsData() {
		String sql = "{Call prtEmployee('" + getReportDto().getMainIdString() + "')}";

		MacQuery query = new MacQuery();
		query.setQuery(sql);

		CawReportDataSet ds = fillAndAdjustData(query);

		return ds;
	}
}

SQL

drop procedure if exists prtEmployee;

DELIMITER $$

CREATE PROCEDURE prtEmployee(in idStr varchar(2000))
  
BEGIN
	
	create temporary table if not exists prtemptempTable 
    select a.*  
	from employee a
	where find_in_set(a.id, idStr);
  
   	#create index
	create index I_Employee_dept ON prtemptempTable(dept); 

    #main query 0
    SELECT * from prtemptempTable;
    
    #dept 1
    select * from dept a 
    where exists(select NULL from prtemptempTable b where b.dept=a.id);
    
    drop table prtemptempTable;
END$$

DELIMITER ;

Note: SQL should mostly use "select *" to get all fields in a table, such that when UDF fields are added in the future the SQL does not need to modify again.

Start JBoss (Use Jaspersoft Studio data source), then write JrXml

mssst

mssst


Note: URL above is http://127.0.0.1:8080/jsf/rfws/cawProvider/fields/com.multiable.ireport.provider.emp.EmployeeProvider?accessToken=iz&lang=zh_CN

accessToken please use the caw.ws.glAccessToken in cawserver.properties

The field returned by lang specifies the language of mess (when not transmitting, it uses the lang of GlobalOption)

If skipLang is true, the returned field will not add mess description (optional, the default is false)


Note: Not limited to use the web service "cawProvider / fields", if necessary, developers can design their own process by referring to CawJrProviderEJB

mssst

UDF Editor's Template

Developers can implement the UDF template that will be used in UDF editor created by end users, like the trading or finance editors template created by trading and finance App in M18 ERP.

module's attributes

Name Required Type Description
code true string Template's Code
description true string Template's mess code
module true string Corresponding module of the template, the module needs to have template="true" in module.xml
helper true string CawUeHelper Helper class name, needs to extends CawUeHelper
<page> true string The initial page of the UDF editor generated by this template
<setting> true string The setting specific to this UDF template

Example: helper of CawUeHelper

public class FmTemplateHelper extends CawUeHelper {

	private static final long serialVersionUID = 5554948686647491057L;

	@Override
	public void initModuleInfo(SqlTable ue, int row, Module module) {

		String jsonStr = ue.getString(row, "backEndInfo");


		if (!StringLib.isEmpty(jsonStr)) {
			FmtOption option = JSON.parseObject(jsonStr, FmtOption.class);
			module.setUseAttach(option.isSupportAttachment());
		} else {
			module.setUseAttach(false);
		}

		module.setFmShare("A");

	}

}

Initial Data

M18 system database information is automatically generated by JBoss through java codes. Therefore, developers can not keep any preset data in the database.

To create initial/default data to database, developers can set entityDataInit in app.xml:

Name Description
name entityDataInit's name
initDataCreator class name, extendsInitDataCreator, create data in the init() method

Note: This method runs every time Wildfly is started. Please check if the data that needs to be initialized already existed in database.

Example

public class TestDriverCreator extends BaseDataCreator {

	@Override
	public void init() {
		if (hasEntityData()) {
			return;
		}

		// Init test
		SqlEntity entity = create();
      
		JdbcDriverEntity accseeDriver = EntityLib.toObject(entity, JdbcDriverEntity.class);
		accseeDriver.setCode("test");
		accseeDriver.setDesc("test");
		accseeDriver.setJdbcType(JdbcType.Driver);
		accseeDriver.setClassName("net.test.jdbc.TestDriver");
		accseeDriver.setGrammarType(GrammarType.UDF);
		accseeDriver.setIndepPw(true);
		accseeDriver.setWithDbName(false);
		accseeDriver.setConnStrTplt("jdbc:test://${[@D_FilePath]};memory=false");
		EntityLib.mergeToEntity(accseeDriver, entity);
      
		update(entity);
	}
}

Data Fix

During M18 program update, data fix may need to be carried out to handle data fix. Developers can define dataFix in app.xml.

Name Description
className class name, needs to extend DataFixAdapter , handles the data fix in run() method. Note: This is also an unique key
ap Mark the programmer name who writes this data fix
desc Description of the data fix
order Indicate the order of the data fix.

Example

<dataFix className="com.multiable.core.ejb.bean.cache.datafix.TestToFix" ap="Test" desc="fix test"></dataFix>
public class TestToFix extends DataFixAdapter {

	@Override
	public int run() {
		
		String fixSql = "update testinfo set myIdType = 'text'; ";

		try {
			CawDs.runUpdate(fixSql);
		} catch (SQLException e) {
			CawLog.logException(e);
		}
		return 0;
	}
}

Note: Please write the data fix carefully, and ensure all error cases are handled.

Note: The data fix will only run one and only one time, success or not.

How to Add JAR

To make sure that the corresponding third-party jar package is available while Wildfly is running, you need to ensure that the jar has been added to the Wildfly runtime.

M18 recommends using module.xml (Wildfly) way to deal with it. M18 also recommends modifying both MANIFEST.MF and jboss-deployment-structure.xml.

Note: No compilation errors during development does not mean your code can run in JBoss runtime successfully.

Note: Wildfly has its own way of classloader management.

Note: At the time of project launch, Wildfly will automatically copy the lib under the ear (or the lib below the war). If the project is deployed multiple times, it will take a lot of hard disk space.

For detail please refer to Wildfly Docs.

Identify the Source of JBoss Startup Errors

Due to configuration/ jar update/ code/ system environment and so on, M18 startup sometimes fails. To debug, please always check and fix the first error you found during startup, and then try to restart JBoss and see if other error happens, and fix it and restart again, and so on.

  • Do not look for mistakes from bottom to top of error messages
  • Do not try to solve all the errors in the log prompts, always fix the first error first and restart
  • Do not assume that the problem must be related to java codes
  • If you find that there is not enough information, it is recommended to modify your codes and print more log out
  • If in Eclipse, it is recommended to delete all breakpoints and try restart

Schedule Tasks

Create Schedule Task

Create a Job Class

Job class need to extend CawQuartzJob, and implement the doExecute method:

public class SyncJob extends CawQuartzJob {
  
	public static String JOBGROUP = "caw.sync";
  
	@Override
	protected CheckMsg doExecute(JobExecutionContext context, CawJobDto dto) throws Exception {
         //do something	

		return msg;
	}

}

Publish a Job

First build a CawJobDto object, use the method CawJobLib.scheduleJob (dto) to release. CawJobDto has a three-parameter-constructor, the first is jobGroup, the second is the current entity id, the third is job class. jobGroup + id must be a unique jobName. There is need to provide TimeScheduleDto. It is the dto component of the time, can be generated by the configuration page or you can create an instance of it using new. There is a jsonData in CawJobDto, you can fill some job parameters, which can be obtained in dto in doExecute.

CawJobDto dto = new CawJobDto(SyncJob.JOBGROUP, id, SyncJob.class);

dto.getJsonData().put("id", id);
dto.setTimeSchedule(timeDto);
dto.setJobMess("syncJob");   

CawJobLib.scheduleJob(dto);

Delete a Job

Use the deleteJob method in CawJobLib class.

CawJobDto dto = new CawJobDto(UnlockUserJob.JOBGROUP, param.getEntityId(), UnlockUserJob.class);

CawJobLib.deleteJob(dto);

Job in a JBoss Cluster

After a job is published, the information will be saved to the cawjob table; if the job expires, there will be scheduled tasks to remove these expired jobs.

Only when the server cawserver.properties is configured caw.sch.quartz = 1, the server can run M18 jobs.

Note: This server will not run jobs when the server's caw.sch.quartz = 0. However, the jobs posted from this server can be assigned to a target server via the caw.sch.targetUrl and caw.sch.targetAccessToken configurations. To post job to a target server, the target server must be able to run the job.

cawserver.properties

//this server can run schedule job
caw.sch.quartz = 1

//the target server to run a job
caw.sch.targetUrl = http://127.0.0.1:8080/jsf/rfws/
//access token for the target server
caw.sch.targetAccessToken=iz

How to Test Scheduled Tasks

If you want to debug a job during development, you need to ensure the job will run at your local JBoss, which means other JBoss servers will not run the job ahead of your own server, the following parameters need to be set in reset.caw:

reset.caw

#lock the job to run locally, support multiple input, separated by semicolons
caw.ap.sch.debugJobName=caw.sync.1
#lock the job group to run locally, support multiple input, separated by semicolons
caw.ap.sch.debugJobGroup=caw.sync
#how many hours for the job to lock in this server
caw.ap.sch.debugTime=5

SQL Tables related to scheduled tasks

Table Name Description
cawjob Store published schedule task details
cawjoblog Scheduled task log
cawjoblock Scheduled task lock info

File Stream Handling

All files in M18 are stored in fileData table:

  • One file corresponds to one record in fileData, system uses the MD5 + size to identify the uniqueness of the file
  • Files saved on filedata do not provide the delete function
  • In order to avoid user reading the picture traversal (picture does not need to verify the login link), the system uses imgcode

File Operation

FileCurdEao fileEao = JNDILocator.getInstance().lookupEJB(FileCurdEao.class);

byte[] byteArray = **
//Save file in memory to db
long fileId = fileEao.saveFile("pdf", byteArray, "work.pdf");

String filePath =**
//Save File object to db
fileId = fileEao.saveFile("pdf", filePath, "work.pdf");

//Read file from M18
String filePath_read = fileEao.getFilePath(fileId);

Image Operation

ImageLocal imageEJB = JNDILocator.getInstance().lookupEJB("ImageEJB", ImageLocal.class);
InputStream inputStream = ***

// Upload image, and get imgCode
String imgcode = imageEJB.uploadImage("imgName", inputStream);

// Get image from imgCode
File img = imageEJB.getImage(imgcode, false); 	
// if 2nd para is true, thumbnail will be returned

M18 JBoss Cache

Data that need to be frequently queried but not always modified, can be stored in CawCache, which is a application server cache in JBoss. Such data include but are not limited to datadict, mess code and module configuration, etc.

CawCache Usage

Developers who want to add a cache, can use the CawCache method to add. See CawCache addSelfPopulatingCache method

Then use the CawCache putObject or remove method to manipulate the data in the cache.

Through the getObject method of CawCache, you can get the data of the cache.

//The first parameter is the name of the cache, the second parameter refers to the max record count of the cache, the third parameter refers to the cache expiry time (in seconds); if the 4th parameter is true, When reading this cache to copy an object out to return
CawCache.addSelfPopulatingCache(navMenu, CawLib.getBaseCacheSize() * 2, 86400, false, new NavmenuEntryFactory());

//The first parameter is the name of the registered cache; the second parameter is the key to be cached in this cache, which needs to be a serializable key; the third parameter is the content of the cache to be placed in this cache
CawCache.putObject(cawcheName, key, object);

//The first parameter is registered cache name; The second parameter is the cache inside the cache key
MenuItemConfig menuCfg = (MenuItemConfig) CawCache.getObject(CawCache.menuCache, menukey);

CawCache.remove(CawCache.userSettingCache, uid);

Cache in JBoss Cluster

In the case of clustering, due to data changes, the cache may appear out of synchronization in different JBoss servers;

Note 1: When using a cache, you have to think about how to synchronize your data or you will have unpredictable problems (not just for CawCache)

Note 2: Not all caches need to be synchronized

Two ways to synchronize cache within a JBoss Cluster.

The default synchronization cache method

Recommended usage: The contents of the cache is simple, just do a simple put or delete CawCache.

Please use the method callCawCacheClusterEvent of CawClusterLib

Add cluster cache

CawClusterCacheDto dto = new CawClusterCacheDto();
dto.setName(UseInfoCache.CACHE_KEY);
dto.getCacheMap().put(menuCode, uic);
dto.setCacheClass(UseInfoCache.class);
CawClusterLib.callCawCacheClusterEvent(dto);

Remove cluster cache

CawClusterCacheDto dto = new CawClusterCacheDto();
dto.setName(UdfLogic.CACHE_KEY);
dto.getDeleteKeys().add(code);
CawClusterLib.callCawCacheClusterEvent(dto);

CawClusterCacheDto

Name Type Description
name String Cache name that need to sync
cacheMap Map<Serializable, Object> The cached values in this map will be added to other machines in the cluster, and the cached map is key-value structure
cacheClass Class<?> Class of the value in cacheMap
deleteKeys Set<Serializable> The set of keys that to be deleted in other servers in the cluster

Self defined cache synchronization

Need to synchronize CawCache or some static Map, Collection and other caches, some caches may not simply add or delete, or need to operate several different caches, then you need to customize the class to deal with;

Configure clusterListerner in app.xml:

clusterListerner's attributes

Name Type Description
eventType string Cache event type
className string class name, class need to extend CawClusterListenerAdapter

The callClusterEvent method of CawClusterLib is called when the cluster cache needs to be synchronized. Then the machines of other clusters will run the corresponding listener's method:

JSONObject jo = new JSONObject();
jo.put("cacheName", JsfCache.theme);
jo.put("cacheKey", (long) action.getData(ActionParam.id));
	
//The first parameter is xml eventType configuration; the second parameter is a String, usually pass a json, the cluster server to get this parameter will be passed to the clusterListerner run method;
CawClusterLib.callClusterEvent(CawClusterEvent.JSFCACHE, jo.toJSONString());

Object Sharing

M18 system supports data sharing between different M18 sites via create/install objects. To use this function one needs to set dataObject in app.xml.

dataObject's attributes

Name Required Type Default Description
className true string Full class name. class needs to extend ModuleObjectHandler

dataObject example

<dataObject  className="com.multiable.core.ejb.bean.dataobject.handler.ComboObjectHandler" ></dataObject>

DataObjectHandler example

public class ComboObjectHandler extends ModuleObjectHandler {

	@Override
	public String getName() {
		return "comboData";
	}

	@Override
	public String getMenuCode() {
		return "comboData";
	}

	@Override
	public String getModuleName() {
		return "comboData";
	}

	@Override
	public String getMess() {
		return "comboData";
	}

	@Override
	public List<String> getInfoMessList() {
		List<String> messList = new ArrayList<>();

		messList.add("dataObject.comboDataInfo1");

		return messList;
	}

	@Override
	public Set<String> getDepends() {
		Set<String> depends = super.getDepends();
		depends.add("udfMess");
		return depends;
	}
}

ModuleObjectHandler implements DataObjectHandler, some methods stated here:

public interface DataObjectHandler {
	// Import object upload file will run this method, you can override this method to intervene in the generation of entity
	public DataObjectBaseDto restoreDsDto(DsEntryObject entryObj);

  	// Export object, you can override this method, some of the picture file type information written to the derived Object inside;
	public DsEntryObject genDsEntry(DataObjectBaseDto dto);

  	// Export object, you can override this method to interfere with the export entity
	public void fillupDto(DataObjectBaseDto dto, DataShareObjectDto dsDto);

	// Import object to install, you can override this method to interfere with the installation of object
	public Map<Long, CheckMsg> install(long beId, DataObjectBaseDto dsDto, List<DsInstallDto> insList, Map<String, Map<Long, Long>> depMapping, Object detail);
}

Common Utility Classes

Basic Operation

Class Description
ArrayLib Tools to deal with the data; Worth noting 1. Check whether the array contains a string, 2. Merge the array
ClassLib Tools to deal with classes: 1. Get the class name, 2. Get the class Field 3. Get the class method 4. Get the type of class
DateLib Tools to deal with dates
DateFormatLib Tools to deal with date formats
ListLib Tools to deal with list, and provide method to compare two lists
MathLib Tools to deal with common math functions like rounding, max, min, etc.
StringLib Tools to deal with string, and provide method to convert between string and html

Special Handling

Class Description
SqlTableLib Tools to deal with SqlTable, and provide methods to do POJO and SqlTable conversion, and JSON and SqlTable conversion.
CheckMsgLib Tools to deal with CheckMsg, which is a common response object in entity CRUD actions/web services.
ConvertLib Tools to deal with conversion of different data types
CawLib Tools to deal with CAW configuration, like getting parameters in cawserver.properties

Send E-mail Example

@EJB(beanName = "MacEmailSenderEJB")
private MacEmailSenderLocal emailEJB;
***
MacEmail email = new MacEmail();
for (MacEmailAttachment atta : getAttachments()) {
	email.addAttachment(atta);
}

email.setTo("test@mac.com");
email.setCc("test1@mac.com");
email.setSubject("sunject - test");
email.setContent("conent");

CheckResult result = emailEJB.sendEmailWithId(email, emailSettingId);
***

Note: emailSettingId is the SMTP record ID in M18 system.

Audit Trail's Example

List<AuditTrailData> datas = new ArrayList<>();
AuditTrailData data = new AuditTrailData();
data.setCode(code);
data.setDesc(desc);
data.setEntityId(entityId);
data.setExeTime(System.currentTimeMillis() - start);
data.setiRev(iRev);
data.setMenuCode("user");
data.setModuleName("user");
data.setOperation("test");
data.setOperationDesc("failed");

datas.add(data);

AuditTrailLib.addAuditTrail(datas);