Developer Guide

General Information

The Developer Guide serves as the foundation for ensuring the most consistent technical foundation possible when building applications.

Namespace for Applications

An application's namespace should be used when naming data groups, request parameters, and session parameters. The namespace can also be, for example, a company name or a combination of a company and an application. This is where the biggest challenge lies, because with so many partners and applications, there is a real risk of naming conflicts.

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

Data Groups

When created, Intrexx data groups are automatically assigned names based on their type to avoid duplicate names. To make them easy to identify in SQL statements (e.g., in Groovy or Velocity), all table names should be adjusted accordingly. The following rules should generally be followed when naming data groups:

  1. Maximum length of the table name: 26 characters

  2. A name that is as descriptive as possible (e.g., SHARE_FEED)

  3. Singular

  4. English

  5. Capitalization

Intrexx Data Groups

When created, Intrexx data groups are automatically named "XDATAGROUP" followed by a random key to prevent duplicate names.

File Data Groups

When you create a data field with the "File" data type, a child data group is also created automatically. This data group is named "XFILEDATAGROUP," followed by a random key. Name the file data groups using the same logic you use to name the regular data groups.

System Data Groups

System data groups should be named in the same way as Intrexx data groups. The name should always end with "SETTING."

External Data Groups

For external data groups linked to standard Intrexx data groups (e.g., DSUSER, DSOBJECT, etc.) or to Intrexx data groups within the application, renaming is not necessary.

Please make sure that the data group definition does not contain any database-specific prefixes (e.g., dbo.<data group name>), but only the data group name itself in uppercase letters.

Data Fields

It is also important to name data fields appropriately in case applications are modified or expanded later and accessed via SQL statements in Velocity or Groovy. The app's usability and its international applicability are important factors here.

Field Names

The names of Intrexx data fields consist of a type code followed by the title that was entered when the data field or the respective element was created (e.g., "STR_TITEL_6A585D30"). In this process, umlauts are converted, and the field name is truncated to 30 characters if necessary. To avoid using SQL keywords and ensure optimal readability, field names should be formulated as follows:

  1. Maximum column name length: 30 characters

  2. a name that is as descriptive as possible

  3. Prefix consisting of the data field type code, followed by an underscore (e.g., STR_NAME, B_IS_VISIBLE, etc.)

  4. English

  5. Capitalization

Here is an overview of prefixes:

  • String: STR

  • Boolean: B

  • Float and Currency: FLT

  • Long text: TXT

  • Date, Time, Date and Time: DT

  • File: FILE

Primary Key

The data type of the primary key can be an integer or a GUID. A GUID has several advantages over an integer:

  • Protection Against Guessing the Record ID - Integer values can be guessed by incrementing them.

  • It is not necessary to determine the new ID when creating a new record.

  • Seamless ID — When records are deleted, gaps occur in integer values.

  • When merging or copying data that includes GUIDs, there is no need to determine IDs. GUIDs require less effort and are less prone to errors than integer values. The code required is more concise.

References

In Intrexx, the names of reference data fields are automatically prefixed with "REF_" followed by a random key. You can customize the name in the " Expert" tab using the properties of the reference data field before publishing the application for the first time. To do this, display the data fields via the main menu Edit / Show Data Fields when the data group is selected in the application structure.

Index

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

Naming Elements

To ensure that terminology is as consistent as possible throughout the portal, there are a few rules that should be followed. As a general rule, however, you should name all elements with appropriate names whenever possible, rather than using the default names.

Page Views

Page titles are used in the browser, for example, as tooltip titles or in page path information. Therefore, the names assigned to the development cannot be logically separated from the display. However, there are a few exceptions involving application-specific view pages:

Groups

Since groups are often shown and hidden using JavaScript ( ), they should be given descriptive names following this pattern: _grpGroupName. The group name should be in English.

Naming Conventions

INTREXX GmbH 's Quality Assurance department performs automated testing of applications. During this process, the application structure, program code (JavaScript, Groovy, Velocity), and layout are checked for compliance with specific rules. This also includes the following terms:

Test Type

Test

Note

Naming

Email

Names without a hyphen, such as eMail, email, or Email, are not permitted. However, this applies only to the labels—not to the data fields.

Table length

Compliance with the maximum length of table names

Table column

Length

Compliance with the maximum length of column names

Column Name Naming Convention

LID

The data type must be an integer

Column Name Naming Convention

FKLID

The data type must be an integer

Column Name Naming Convention

STRID

The data type must be a string only

Column Name Naming Convention

FKSTRID

The data type must be a string only

Rights

The permissions required for the application must be defined exclusively using groups. The groups must be assigned according to the following scheme and in English:

Application.<Application Name>.<Role>

In many applications, the following basic rights objects exist:

User Group

Description

Application.<Application Name>.User

Regular user of the application

Application.<Application Name>.Administrator

Application administrator who manages settings and master data

Application.<Application Name>.Manager

Users who, for example, perform editorial tasks in an application.

Application.<Application Name>.Approver

Reviewer or approver, if the application has a corresponding process

Application.<Application Name>.Reviewer

Reviewers or individuals who provide feedback on a case

Application.<Application Name>.Responsible

Managers, such as department heads, who are granted access to their own department's data

The "Users" user group should not be used so that access control remains flexible. In the portal, the role should be mapped to the user role in the application as needed. Specific rights objects can be defined depending on the application. The rights objects used and their functions must be documented in the application manual as shown in the table above. A filter is available to restrict the selection of users based on context; this filter can be configured with the defined permission groups. The selection of users based on their User ID is restricted using the "Is included in" option of the system value "Set and included sets" and by specifying the permission group. It is important with this filter option that inheritance is also taken into account.

Script

The program code included with the application should be written in as consistent a style as possible. Methods, function names, variables, and comments must be written as follows:

  • English

  • correct grammar and spelling

  • consistent syntax

  • proper CamelCase

JavaScript Coding Conventions

JavaScript should be avoided as much as possible. Displaying groupings and buttons conditionally using server-side Velocity code is more secure and also supports accessibility requirements.

Error Handling

When implementing your own error handling, you must use the Intrexx Notifier function to provide feedback to the user in order to maintain a consistent 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, Velocity can be used to generate complete HTML structures. If errors occur, they can prevent an entire page—or even the entire portal—from loading. In addition, Velocity templates can be used to deliver information to which the user does not have access rights under the Intrexx framework. Therefore, Velocity templates must be thoroughly tested, and any errors must be handled.

If applications use custom Velocity files —for example, to execute their own SQL queries and thereby bypass the standard authorization structure of the Intrexx business logic—the proper use of the Velocity code should be verified. If this check is not performed, the file can simply be accessed via the browser and its contents displayed without an authorization check. This issue is similar to an SQL injection. User input is accepted without validation and passed on to API functions, which must be prevented. Your code should be enclosed within the following construct to ensure that the validation is performed on the server side:

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

        

The GUID of the target application is specified as the first parameter, and the GUID of the target page as the second parameter. The "access" key is entered as the third parameter. The parameters should be hard-coded into the call. Passing data as request parameters makes the validation dynamic—but also makes it vulnerable again. If you want to validate access to a data group in Velocity (before performing a database operation), you can wrap the actual function with the following construct:

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

        

The first parameter is used to pass the processing context. The GUID of the target application is specified as the second parameter, and the GUID of the data group as the third parameter. One of the keys listed below must be entered as the fourth parameter:

Key

Function

create

Create

delete

Delete

delete-own

Deleting Your Own Data

read

Read

read-own

Reading Your Own Data

write

Change

write-own

Editing Your Own Information

The parameters should be hard-coded into the call. Passing data as request parameters makes the validation dynamic, but also makes it vulnerable to attacks.

Velocity Includes

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

Groovy Coding Conventions

Code Documentation

Code written in JavaScript, Velocity, or Groovy should be thoroughly documented. In other words, the first priority is to include sufficient, meaningful comments in the code so that its function can be understood. You can also take advantage of the option to insert JSDOC comments.

Prepared Query

The correct use of PreparedQuery to execute SQL statements in Groovy and Velocity prevents SQL injection attacks. Understanding the impact of and how to influence the parameters that are included in the SQL statement is an important prerequisite. Intrexx offers a wide range of features for using PreparedQuery. However, incorrect use can create gaps that should be avoided. In Groovy, a valid `PreparedQuery` looks like this. 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 ensure that the expected format is also inserted. In other words, if a string is passed in instead of an integer value, it will not be processed and will cause an error. The detailed error message must be suppressed in the portal for the feature to be fully effective.

            
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 poses 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()

        

A hacker could manipulate the value in the data field and inject a string containing the WHERE clause extension instead of the expected LID.

Expected:

            WHERE LID = 10
        

Manipulated:

            WHERE LID = 10 OR LID > 0
        

With this change, the hacker gains access to all entries in the user database. If the request values are also checked using validators, strings in request values that are supposed to return integer values, for example, won't even make it far enough to be inserted into the statement. Another very complex design is shown below. The SQL statement is formed by concatenating a string and a variable. Here, too, the variable—which is also passed via a request parameter—can be exploited to extend 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. Here, too, the PreparedQuery must be used correctly:

            #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()

        

Another complex construct is the definition of a query in which the data field names are inserted dynamically. This is especially true when the field names are determined based on 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 solution like this requires the implementation of many security measures. The ideal solution is to avoid such constructs. The following safety measures are recommended:

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

  2. The transmitted values are checked in advance using appropriate validators (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 execute the following code (typically, read permissions for the data group).

  4. The server looks up the allowed field GUIDs in a list. If it encounters an unassigned parameter during this process, it either throws an exception or ignores the parameter—depending on the requirements.

  5. The field GUIDs are used to determine the corresponding column names in the database and to construct a prepared statement.

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

  7. The database query is being executed

Multilingual Applications

You can find all the information on this topic here.

Clonable Applications

In order to duplicate an application by copying it, it is essential that Groovy and Velocity scripts be defined correctly. This applies primarily to the data groups and the SQL statements. When duplicating an application, Intrexx reassigns all GUIDs and also changes the GUIDs in the scripts (Groovy, Velocity, JavaScript) so that everything aligns again. For this reason, data group definitions must always be specified 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 to access data fields on application pages in the Velocity context. To do this, set the "page.requiredDataFields.mode" key to "all" in the settings for the respective page. This is the fastest way to expose all data fields in the data group using GUIDs. It also serves as the basis for accessing the Velocity templates discussed in the previous sections.

In the Velocity context, the stored data can then be accessed using the following methods:

            $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 have to be physically placed on the page as a viewport (and potentially hidden).

Portable Applications (Databases)

Since Intrexx can be used with different databases, SQL statements should be defined as neutrally as possible—that is, without any database-specific functions. The ANSI SQL standard must be followed. If there is no neutral alternative for an SQL statement, a database switch must be defined for that SQL statement that uses the appropriate statement for each database.

Switch 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
}

        

Switches 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

When working with processes, keep the following in mind: Whenever possible, timer actions on data groups should use a filter to limit the number of results in order to minimize the number of database operations. By default, the "Log only warnings and errors" setting should be selected in the process properties. g_log.info() statements added for debugging purposes can remain in the code. They are ignored during normal operation and keep the log files small.