Developer Guide

General

When building applications, the Developer Guide is intended to create a uniform technical basis for developing applications.

Namespace for applications

The namespace for the application should be used for naming data groups, request parameters and session parameters. The namespace can be a company name or a combination of the company and application. This is the greatest challenge because with a large amount of partners and applications, it is very likely that names will overlap.

The application manual should at least list the request parameters used and their possible values if the application is to be secured in the portal with request value validators.

Data groups

Intrexx data groups are automatically given names when they are created, depending on their type, in order to avoid duplicate naming. So that they can easily be recognized in SQL statements (e.g. in Groovy or Velocity) all table names should be adjusted accordingly. In principle, the following rules should be kept when naming data groups:

  1. maximum length of the table name: 26 characters

  2. as descriptive as possible (e.g. SHARE_FEED)

  3. in the singular form

  4. in English

  5. in block capitals

Intrexx data groups

Intrexx data groups are automatically given the name "XDATAGROUP" when they are created, followed by a random key to avoid duplicate naming.

File data groups

When a data field with the data type "File" is created, a subordinate data group is also created automatically. This data group is given the name "XFILEDATAGROUP", followed by a random key. Name these groups with the same logic as the standard data groups.

System data groups

The naming of system data groups should be analogous to the Intrexx data groups. The name should always end with "SETTING".

External data groups

For external data groups with a connection to Intrexx standard data groups (e.g. DSUSER, DSOBJECT, etc.) or to the Intrexx data groups in the application, no renaming is required.

Please ensure that there are no database-specific prefixes in the data group definition (e.g. dbo.<data group name>), only the pure data group name in capital letters.

Data fields

The appropriate naming of data fields is also important if applications are later modified or expanded and accessed via SQL statements in Velocity or Groovy. The legibility and also the internal use of the application play a role here.

Field names

The names of Intrexx data fields consist of a type key followed by the title that was entered when the data field or the respective element was created (e.g. "STR_TITEL_6A585D30"). Umlauts are converted and the field name is shortened to 30 characters if necessary. Field names should be defined as follows to avoid using SQL keywords and to achieve an optimal legibility:

  1. maximum length of the column name should be 30 characters

  2. as descriptive a name as possible

  3. must begin with the type key of the data field type (e.g. STR_NAME, B_IS_VISIBLE, …)

  4. in English

  5. in block capitals

Here is an overview of the prefixes:

  • String: STR

  • Boolean: B

  • Float and currency: FLT

  • Longtext: TXT

  • Date, time, date and time: DT

  • File: FILE

Primary key

The data type of the primary key can either be an integer value or a GUID. Preferably, this should be used because the GUID has multiple advantages over an integer:

  • A GUID protects against the data record ID being guessed. Integer values can be guessed using incrementing.

  • Creating a new data record does not require the new ID to be identified.

  • There are no gaps in the IDs. When deleting data records with integer values, gaps are created.

  • When merging or copying data with GUIDs, IDs do not need to be identified. The effort required and errors are reduced when using GUIDs, in comparison to integer values - and the code is leaner.

References

The names of reference data fields in Intrexx automatically have the prefix "REF_" followed by a random key. The name can be adjusted via the properties of the reference data field on the Expert tab before the application is published for the first time. To do this, show the data fields via the main menu Edit / Show data fields if the data group is selected in the application structure.

Index

To optimize performance, indexes should be included for data fields that are searched particularly frequently or used for sorting. The index name must not exceed a length of 18 characters and is structured according to the following naming scheme: IX_<DG abbreviation>_<COUNTER>, e.g. IX_SHAREFEED_1.

Naming elements

To keep names in the Application Designer as uniform as possible, certain rules should be considered here. Principally, the following applies: give as many elements as possible a corresponding name and do not keep the default presets.

View pages

The titles of pages are used in the browser e.g. as tooltip titles or in path information on the page. For that reason, the titles for development cannot be separated from the displayed titles. However, certain view pages used for specific cases are an exception to this:

Groupings

As groupings are often also shown and hidden via JavaScript, they should be given descriptive names according to the following scheme: _grpGroupName. The group name should be specified in English.

Naming conventions

The INTREXX GmbH quality assurance department carries out automated tests of applications. Here, the application structure, programming code (JavaScript, Groovy, Velocity) and layout will be checked as to whether they meet certain requirements. The following names are part of this:

Test type

Test

Comments

Names

Email

Naming with hyphen, such as e-mail, is not allowed. This refers to labels – not the data fields themselves.

Table length

The compliance to the maximum length for the table name

Table column

Size

The compliance to the maximum length for the column name

Column name

LID

Data type may only be integer

Column name

FKLID

Data type may only be integer

Column name

STRID

Data type may only be string

Column name

FKSTRID

Data type may only be string

Permissions

The rights required for the application must be defined exclusively with groups. The groups have to be in English and according to the following pattern:

Application.<application name>.<role>

The following foundational permission objects appear in many applications:

User group

Description

Application.<application name>.user

Normal user of the application

Application.<application name>.administrator

Administrator of the application who manages settings and master data

Application.<application name>.manager

Users who, for example, are responsible for editing tasks in an application

Application.<application name>.Approver

Checker or approver if the application has a corresponding process

Application.<application name>.reviewer

Auditors and/or people who provide feedback regarding a procedure

Application.<application name>.Responsible

Responsible person, such as department leaders, who are given access to the data from their department

The user group "User" should not be used so that the rights control remains flexible. In the portal, the role should be mapped to the user role in the application if required. Special rights objects can be defined depending on the application. The implemented permission objects and their function must be documented in the application's handbook as depicted in the table above. A filter is available for context-related restriction of the selection of users, which can be assigned the defined rights groups. The selection of users via the UserId is restricted with "Is contained in" of the system value "Set and contained sets" and the specification of the rights group. It is important with this filter variant that inheritances are also taken into account.

Script

The program code delivered with the application should be as uniform as possible. This means that the methods, function names, variables and comments should be created

  • in English

  • with correct grammar and spelling

  • with uniform syntax

  • in correct CamelCase

JavaScript coding conventions

JavaScript should be avoided as far as possible. The conditional display of groupings and buttons with server-side velocity code is more secure and also supports accessibility requirements.

Error handling

In some error handling cases, the Intrexx notifier function is needed to provide the user with feedback – this helps maintain a uniform standard.

            Notifier.status.notify("No date was specified.", "Note");
Notifier.status.error("The entered cost center does not exist.", "Error");

        

Velocity coding conventions

Security

Among other things, entire HTML constructions, can be created with Velocity. In the event of errors, these can prevent an entire page and even the entire portal from loading. Additionally, information can be provided using Velocity templates for which the user does not have permission, according to the Intrexx framework. For that reason, Velocity templates must be checked well and errors intercepted.

If your own Velocity files are used in applications that, for example, perform their own SQL queries and thus bypass the normal authorization structure of the Intrexx business logic, the authorized use of the Velocity code should be checked. If this check is not carried out, the file can simply be called up via the browser and the content displayed without an authorization check. This problem is similar to that of an SQL injection. User entries are applied without verification and forwarded to API functions; this should be prevented. Your code should be enclosed by the following construct to guarantee the server-side verification:

            #if($AccessController.hasPagePermission("APP_GUID", "PAGE_GUID", "access"))
 ## Your Code ##
#end

        

The GUID of the target application is entered as the first parameter, the GUID of the target page as the second. The key "access" is entered as the third parameter. The parameters should be specifically entered into the call. The transfer in the form of request values makes the verification more dynamic but makes you more susceptible to attack. If access to a data group is to be checked in Velocity (before a database access is carried out), the following construct can be placed around the actual function:

            #if($AccessController.hasDatagroupPermission($ProcessingContext,"APP_GUID", "DATAGROUP_GUID", "read"))
 ## Your Code ##
#end

        

The processing context is transferred using the first parameter. The GUID of the target application is specified as the second parameter, the GUID of the data group as the third. As the fourth parameter, one of the following keys should be entered:

Key

Function

create

Create

delete

Delete

delete-own

Delete own data

read

Read

read-own

Read own data

write

Change

write-own

Edit own data

The parameters should be specifically entered into the call. The transfer in the form of request values makes the verification more dynamic but makes you more susceptible to attack.

Velocity includes

Velocity files should always be included in the application package to ensure easy import. If there are overarching Velocity templates, these must be stored in the portal directory internal/system/vm/html/include and in a separate subdirectory there. All other directories are reserved for Intrexx.

Groovy coding conventions

Documenting codes

The codes in JavaScript, Velocity or Groovy should be documented in detail. Primarily, this means that meaningful comments should be implemented into the code to make its function apparent. You can also use the option to insert JSDOC comments.

Prepared query

The correct use of PreparedQuery for performing SQL statements in Groovy and Velocity prevents an attack via SQL injection. Knowledge of the effect and influence of parameters that are included in the SQL statement is an important prerequisite. Intrexx provides an abundance of functions for using PreparedQuery. That being said, using it incorrectly can create holes, which should be avoided. In Groovy, a correct PreparedQuery is shown below. It is important that the values in the WHERE clause are defined using the placeholders ? and then set using set methods. The respective set methods guarantee that the expected format is inserted. This means that if a character string is inserted instead of an integer value, it is not processed and causes an error. The detailed error message must be suppressed in the portal so that the full effect unfolds.

            
def conn = g_dbConnections.systemConnection 
def stmtUpdate 
def stmt = g_dbQuery.prepare(conn, "SELECT LID FROM DSUSER") 
def rs = stmt.executeQuery() 
while (rs.next()) 
{ 
	stmtUpdate = g_dbQuery.prepare(conn, "UPDATE DSUSER SET ... WHERE LID = ?'") 
	stmtUpdate.setInt(1, rs.getIntValue(1)) 
	stmtUpdate.executeUpdate() 
	stmtUpdate.close() 
} 
rs.close() 
stmt.close()

        

The following construct is incorrectly defined and carries a high risk.

            def conn = g_dbConnections.systemConnection

def l_UserId = g_record["GUID"].value

def stmt = g_dbQuery.prepare(conn, "SELECT DTBIRTH FROM DSUSER WHERE LID = '${l_UserId}'")
def rs = stmt.executeQuery()

while (rs.next())
{
  rs.getDateValue(1)
}

rs.close()
stmt.close()

        

The value from the data field can be manipulated by a hacker and instead of the expected LID, a character string with the expansion of the ""WHERE"" request can be smuggled in.

Expected:

            WHERE LID = 10
        

Manipulated:

            WHERE LID = 10 OR LID > 0
        

With this change, the hacker receives all database entries in the user database. If the request values are additionally checked using validators, for example, character strings via request values that are to provide integer values do not get so far that they are inserted into the statement. Another very critical construction is shown below. The SQL statement is formed by merging a character string and a variable. In this case as well, the variable, which is additionally transferred via a request value, can be abused to expand the ""WHERE"" clause.

            def l_request = g_request.get("rq_myparameter")

def l_sql = "SELECT DTBIRTH FROM DSUSER WHERE LID = "l_sql += l_request

def stmt = g_dbQuery.prepare(conn, l_sql)
def rs = stmt.executeQuery()

...

        

The same conditions apply in the Velocity environment. The PreparedQuery must also be used correctly here:

            #set($UserId = $DC.getValueHolder("GUID").getValue())

#set($stmt = $PreparedQuery.prepare($DbConnection, 
"SELECT DTBIRTH FROM DSUSER WHERE LID = ?"))
$stmt.setInt(1, $UserId)
#set($rs = $stmt.executeQuery())

#foreach($element in $rs)
  $element.getDateValue(1)
#end

$rs.close()
$statement.close()

        

An equally critical construction is the definition of a query which is used to enter the data field name dynamically. Especially if the field names are determined using data from the request or other external sources. This applies to Groovy and Velocity.

            def ergebnis = g_dbQuery.executeAndGetScalarIntValue(conn, 
"SELECT count(*) FROM DATAGROUP WHERE LID = ? AND $FIELDNAME1 IS NOT NULL AND $FIELDNAME <> ''",0)

        

A lot of security levels need to envisaged for such a solution. The ideal solution is to avoid such constructions altogether. The following security policies would be advised:

  1. The client sends random unique key values for the individual fields to the server. The key values can, for example, be the GUIDs of data field names or query string parameters.

  2. Using corresponding validators, the transferred values will be checked in advance (e.g. whitelist). If no match is found, no server-side processing takes place.

  3. The server checks whether the logged in user has the necessary permissions to perform the following code (usually read access to the data group).

  4. The server searches in a list of allowed field GUIDs. If it encounters an unassigned parameter, it throws an exception or ignores the parameter - depending on the requirement.

  5. Based on the field GUIDs, the corresponding column names in the database will be identified and a prepared statement will be constructed.

  6. The current values are transferred to the prepared statement.

  7. The database request is performed

Multilingual applications

All information on this topic can be found here.

Clonable applications

In order for an application to be duplicated by copy, the groovy and velocity script must be defined correctly. This particularly applies to the data groups and SQL statements. When cloning/duplicating an application, Intrexx reassigns all of the GUIDs and with that, adjusts the GUIDs in the scripts (Groovy, Velocity, JavaScript) accordingly, so that everything continues to work together. For this reason, the data group definitions must always be made using the data group's GUID:

            def l_strIsoLanguage = it
def l_intLanguageDetect = g_dbQuery.executeAndGetScalarValue(conn, 
"SELECT COUNT(*) FROM DATAGROUP('98C0EC3CC539925C8B7644F4AB726BE2F38038F1')
WHERE LANG = ?", 0) {
  setString(1, l_strIsoLanguage)
}

#set($stmtFloors = $PreparedQuery.prepare($DbConnection, 
"SELECT T0.STRID, T1.STRNAME, T1.STRSHORTNAME FROM
DATAGROUP('B1156946E0CF3215576D4595A757A3FD0BB31C22') T0 LEFT OUTER JOIN
DATAGROUP('1037FA883B005B35FE5D4B8A10645B9FB2B04933') T1 ON ((T0.REF_PROPERTY
= T1.PROPID) AND (T1.LANG = ?)) WHERE T0.STRPARENTID = ? AND T0.REF_CLASS = ?
ORDER BY T1.LSORT"))
$stmtFloors.setString(1, $lang)
$stmtFloors.setString(2, $Builiding)
$stmtFloors.setString(3, "LEVEL")
#set($rsFloors = $stmtFloors.executeQuery())

        

GUIDs should also be used when accessing data fields on application pages in the Velocity context. To do this, the key "page.requiredDataFields.mode" must be set to "all" in the settings of the respective page. (the quickest method to provide all of the data group's data fields via GUIDs). This is also the basis for the access in the Velocity templates from the previous section.

In the Velocity context, the saved data can be accessed with the following method:

            $DC.getValueHolder("GUID_DATA_FIELD").getValue()

## When page is used in a free table
$drRecord.getValueHolder("GUID_DATA_FIELD").getValue()

        

The advantage of this method is that the information does not need to be physically positioned on the page as a view field (or where applicable, hidden).

Portable applications (databases)

Because various databases can be implemented for use with Intrexx, the SQL statements should be defined as neutrally as possible. This means that database-specific functions should not be used. The ANSI SQL standard is to be upheld here. If there is no neutral alternative for an SQL statement, a database switch must be defined for the SQL statement, which uses the appropriate statement for each database.

Switching point for Groovy:

            def conn = g_dbConnections.systemConnection

switch (conn.descriptor.databaseType)
{
	case "Db2":
		// DB2
		break

	case "Derby":
		// Derby/Java DB
		break

	case "Firebird":
		// Firebird
		break

	case "HSQLDB":
		// HSQLDB
		break

	case "Ingres":
		// Ingres
		break

	case "Oracle8":
		// Oracle 8
		break

	case "Oracle9":
		// Oracle 9
		break

	case "Oracle10":
		// Oracle 10
		break

	case "Oracle11":
		// Oracle 11
		break

	case "Oracle12":
		// Oracle 12
		break

	case "PostgreSQL":
		// PostgreSQL
		break

	case "MaxDB":
		// MaxDB
		break

	case "MsSqlServer":
		// Microsoft SQL Server
		break

	case "Standard":
		// unspecified
		break

	default:
		assert false : "Unexpected database type."
		break
}

        

Switch for velocity:

            #set($DbName =
$DbUtil.getConnection("IxSysDb").getDescriptor().getDatabaseType())

#if($DbName == "MsSqlServer")
  #set($sql = " SELECT TOP 10 * FROM MyTable ORDER BY LID DESC")
#elseif($DbName == "PostgreSQL")
  #set($sql = "SELECT * FROM MyTable ORDER BY LID DESC LIMIT 10")
#elseif($DbName == " Oracle9" || $DbName == " Oracle8")
  #set($sql = " SELECT * FROM (SELECT * FROM MyTable ORDER BY LID DESC) 
  WHERE rownum <= 10")
#else
  $Debug.info("Unexpected database type")
#end

        

Processes

The following should be noted for processes: Timer actions on data groups should, if possible, limit the number of hits via a filter in order to keep the number of database operations low. By default, the setting "Only log warnings and errors" should be set in the process properties. G_log.info() inserted for debugging can remain in the code. this can be kept in the code and is ignored during regular use, and thus the log files are kept small.