Developer Guide

1. Allgemeines




Der Developer Guide ist die Basis für eine möglichst einheitliche technische Basis beim Aufbau von Applikationen. Bitte beachten Sie den Styleguide für die einheitliche optische Darstellung von Applikationen.

2. Namensraum für Applikationen

Der Namensraum einer Applikation sollte in der Benennung der Datengruppen, Request-Parameter und Session-Parameter verwendet werden. Der Namensraum kann z.B. auch ein Firmenname oder eine Kombination aus Firma und Applikation sein. Hier liegt die größte Herausforderung, denn bei einer Vielzahl von Partnern und Applikationen kann es durchaus zu Überschneidungen bei der Namensgebung kommen.
Im Handbuch zur Applikation sollten mindestens die verwendeten Request-Parameter und deren mögliche Werte aufgelistet werden, wenn die Applikation im Portal mit Requestwert-Validatoren abgesichert werden soll.

3. Datengruppen

Intrexx-Datengruppen erhalten bei der Erstellung je nach Typ automatisiert Namen um doppelte Namensgebungen zu vermeiden. Für die einfache Erkennbarkeit in SQL-Anweisungen (z.B. in Groovy oder Velocity) sollten alle Tabellennamen entsprechend angepasst werden. Folgende Regeln sollten bei der Benennung der Datengruppen grundsätzlich eingehalten werden:
  1. maximale Länge des Tabellennamens: 26 Zeichen
  2. möglichst sprechender Name (z.B. SHARE_FEED)
  3. Einzahl
  4. Englisch
  5. Großschreibung

3.1. Intrexx-Datengruppen

Intrexx-Datengruppen erhalten bei der Erstellung automatisch den Namen "XDATAGROUP" gefolgt von einem Zufallsschlüssel, um doppelte Namensgebungen zu vermeiden.

3.2. Datei-Datengruppen

Bei Erzeugung eines Datenfeldes mit dem Datentyp "Datei" wird auch automatisch eine untergeordnete Datengruppe erstellt. Diese Datengruppe wird dabei mit dem Namen "XFILEDATAGROUP" betitelt, gefolgt von einem Zufallsschlüssel. Benennen Sie die Datei-Datengruppen mit derselben Logik, mit der auch die regulären Datengruppen benannt werden.

3.3. Systemdatengruppen

Die Benennung von Systemdatengruppen sollte analog zu den Intrexx-Datengruppen erfolgen. Der Name sollte immer mit "SETTING" enden.

3.4. Fremddatengruppen

Bei Fremddatengruppen mit Verbindung zu Intrexx-Standard-Datengruppen (z.B. DSUSER, DSOBJECT, etc.) oder auch auf die Intrexx-Datengruppen in der Applikation ist keine Umbenennung erforderlich.
Bitte achten Sie darauf, dass keine datenbankspezifischen Präfixe in der Datengruppendefinition stehen (z.B. dbo.<Datengruppenname>), sondern nur der reine Datengruppenname in Großbuchstaben.

4. Datenfelder

Die passende Benennung von Datenfeldern ist ebenfalls wichtig, wenn Applikationen später modifiziert bzw. erweitert werden und per SQL-Statements in Velocity oder Groovy Zugriffe erfolgen. Die Lesbarkeit und auch die internationale Verwendung der Applikation spielen hier eine Rolle.

4.1. Feldnamen

Die Namen von Intrexx-Datenfeldern bestehen aus einem Typschlüssel gefolgt vom dem Titel, der bei der Erstellung des Datenfeldes bzw. des jeweiligen Elements erfasst wurde (z.B. "STR_TITEL_6A585D30"). Dabei werden Umlaute umgewandelt und der Feldname, falls notwendig, auf 30 Zeichen gekürzt. Um die Verwendung von SQL-Schlüsselwörtern zu vermeiden und eine optimale Lesbarkeit zu erreichen, sollten Feldnamen wie folgt formuliert werden:
  1. maximale Länge des Spaltennamens 30 Zeichen
  2. möglichst sprechender Name
  3. Präfix mit Typkürzel des Datenfeldtyps, gefolgt von Unterstrich (z.B. STR_NAME, B_IS_VISIBLE, etc.)
  4. Englisch
  5. Großschreibung
Hier eine Übersicht über Präfixe:

4.2. Primärschlüssel (Primary Key)

Der Datentyp des Primärschlüssels kann ein Integer-Wert oder eine GUID sein. Die GUID hat gegenüber einer Ganzzahl mehrere Vorteile:

4.3. Referenzen

Die Namen von Referenz-Datenfeldern haben in Intrexx automatisch das Präfix "REF_" gefolgt von einem Zufallsschlüssel. Über die Eigenschaften des Referenz-Datenfeldes kann der Name vor dem ersten Veröffentlichen der Applikation auf dem Expert-Reiter angepasst werden. Blenden Sie dazu die Datenfelder über das Hauptmenü Bearbeiten / Datenfelder anzeigen ein, wenn die Datengruppe in der Applikationsstruktur ausgewählt ist.

4.4. Index

Zur Performance-Optimierung sollten für Datenfelder, die besonders häufig durchsucht oder für die Sortierung verwendet werden, Indexe aufgenommen werden. Der Indexname darf eine Länge von 18 Zeichen nicht überschreiten und wird nach folgendem Namensschema aufgebaut: IX_<DG-Kürzel>_<ZÄHLER>, z.B. IX_SHAREFEED_1.

5. Benennung von Elementen

Um möglichst einheitliche Benennungen im Portal zu erhalten, sollten einige Regeln beachtet werden. Grundsätzlich gilt jedoch, möglichst alle Elemente mit entsprechenden Namen zu benennen und nicht die Standard-Vorgabe beizubehalten.

5.1. Ansichtsseiten

Die Titel von Seiten werden im Browser z.B. als Tooltip-Titel oder in Pfadangaben auf die Seite verwendet. Daher können die Bezeichnungen für die Entwicklung nicht logisch getrennt von der Anzeige vergeben werden. Ausnahmen bilden jedoch einige einsatzabhängige Ansichtsseiten:

5.2. Gruppierungen

Da Gruppierungen häufig auch per JavaScript ein- und ausgeblendet werden, sollten diese sprechende Namen nach folgendem Schema erhalten: _grpGruppenname. Der Gruppenname sollte in Englisch definiert sein.

5.3. Benennungsregeln

Die United Planet Qualitätssicherung führt automatisierte Tests von Applikationen durch. Dabei werden die Applikationsstruktur, Programmcode (JavaScript, Groovy, Velocity) und Layout auf Einhaltung bestimmter Regeln geprüft. Dazu gehören auch die folgenden Benennungen:

Testart Test Bemerkung
Benennung E-Mail Die Benennung ohne Trennstrich wie eMail, email, Email sind nicht zulässig. Dies betrifft jedoch nur die Beschriftungen – nicht die Datenfelder.
Tabellenlänge Die Einhaltung der maximalen Länge von Tabellennamen
Tabellenspalte Länge Die Einhaltung der maximalen Länge von Spaltennamen
Spaltenname Benennung LID Datentyp darf nur Integer sein
Spaltenname Benennung FKLID Datentyp darf nur Integer sein
Spaltenname Benennung STRID Datentyp darf nur String sein
Spaltenname Benennung FKSTRID Datentyp darf nur String sein

6. Rechte

Die für die Applikation benötigten Rechte müssen ausschließlich mit Gruppen definiert werden. Die Gruppen müssen nach folgendem Schema und in englischer Sprache vergeben werden:

Application.<Applikationsname>.<Rolle>

In vielen Applikationen gibt es die folgenden, grundlegenden Rechteobjekte:
Benutzergruppe Beschreibung
Application.<Applikationsname>.User Normaler Benutzer der Applikation
Application.<Applikationsname>.Administrator Administrator der Applikation, der Einstellungen und Stammdaten verwaltet
Application.<Applikationsname>.Manager Benutzer, die z.B. redaktionelle Aufgaben in einer Applikation übernehmen.
Application.<Applikationsname>.Approver Prüfer bzw. Freigeber wenn die Applikation einen entsprechenden Prozess besitzt
Application.<Applikationsname>.Reviewer Gutachter bzw. Personen die zu einem Vorgang ein Feedback geben
Application.<Applikationsname>.Responsible Verantwortliche wie Abteilungsleiter, die Zugriff auf Daten ihrer eigenen Abteilung erhalten

Die Benutzergruppe "Benutzer" sollte nicht verwendet werden, damit die Rechtesteuerung flexibel bleibt. Im Portal sollte die Rolle bei Bedarf auf die User-Rolle in der Applikation gemappt werden. Spezielle Rechteobjekte können je nach Applikation definiert werden. Die verwendeten Rechteobjekte und deren Funktion müssen im Handbuch zur Applikation wie in der Tabelle oben dokumentiert werden. Zur kontextbezogenen Einschränkung der Auswahl von Benutzern steht ein Filter zur Verfügung, der mit den definierten Rechtegruppen belegt werden kann. Die Auswahl der Benutzer über die BenutzerId wird mit "Ist enthalten in" des Systemwerts "Set und enthaltene Sets" und der Angabe der Rechtegruppe eingeschränkt. Wichtig bei dieser Filtervariante ist, dass auch Vererbungen berücksichtigt werden.

7. Skript

Der mit der Applikation ausgelieferte Programmcode sollte in einem möglichst einheitlichen Stil erfolgen. Methoden, Funktionsnamen, Variablen und Kommentare sind wie folgt zu erstellen:

7.1. JavaScript Codierungs-Konventionen

JavaScript sollte so weit wie möglich vermieden werden. Die Bedingte Anzeige von Gruppierungen und Schaltflächen mit serverseitig ausgeführtem Velocity-Code ist sicherer und unterstützt zudem auch barrierefreie Anforderungen.

7.1.1. Fehlerbehandlung

Bei eigener Fehlerbehandlung muss für das Feedback an den Anwender die Intrexx Notifier-Funktion verwendet werden, um einen einheitlichen Standard einzuhalten.
Notifier.status.notify("Es wurde kein Datum angegeben.", "Hinweis");
Notifier.status.error("Die eingegebene Kostenstelle ist nicht vorhanden.", "Fehler");

7.2. Velocity Codierungs-Konventionen

7.2.1. Sicherheit

Mit Velocity können u.a. komplette HTML-Konstruktionen erzeugt werden. Bei Fehlern können diese das Laden einer gesamten Seite und sogar des gesamten Portals verhindern. Zudem können über Velocity-Templates Informationen ausgeliefert werden, auf die der Benutzer gemäß dem Intrexx-Framework keine Rechte hat. Daher müssen Velocity-Templates gut geprüft und Fehlverhalten abgefangen werden.

Wenn in Applikationen eigene Velocity-Dateien eingesetzt werden, die z.B. eigene SQL-Abfragen durchführen und damit die normale Berechtigungsstruktur der Intrexx-Businesslogik umgehen, sollte der berechtigte Einsatz des Velocity-Codes geprüft werden. Wird diese Prüfung nicht durchgeführt, kann die Datei einfach über den Browser aufgerufen und der Inhalt ohne Berechtigungsprüfung angezeigt werden. Diese Problematik ähnelt einer SQL-Injection. Benutzereingaben werden ohne Prüfung übernommen und an API-Funktionen übergeben, was es zu verhindern gilt. Der eigene Code sollte mit dem folgenden Konstrukt umschlossen werden, um die Prüfung serverseitig sicherzustellen:
#if($AccessController.hasPagePermission("APP_GUID", "PAGE_GUID", "access"))
 ## Eigener Code ##
#end
Die GUID der Zielapplikation wird als erster Parameter, die GUID der Zielseite als zweiter Parameter angegeben. Als dritter Parameter wird der Schlüssel "access" eingetragen. Die Parameter sollten fest in den Aufruf eingetragen werden. Die Übergabe in Form von Requestwerten dynamisiert die Prüfung – macht Sie jedoch wieder angreifbar. Soll der Zugriff auf eine Datengruppe in Velocity geprüft werden (bevor ein Datenbankzugriff durchgeführt wird), kann folgendes Konstrukt um die eigentliche Funktion gelegt werden:
#if($AccessController.hasDatagroupPermission($ProcessingContext,"APP_GUID", "DATAGROUP_GUID", "read"))
 ## Eigener Code ##
#end
Mit dem ersten Parameter wird der Processing-Context übergeben. Die GUID der Zielapplikation wird als zweiter Parameter, die GUID der Datengruppe als dritter Parameter angegeben. Als vierter Parameter muss einer der nachfolgend aufgeführten Schlüssel eingetragen werden:

Schlüssel Funktion
create Erstellen
delete Löschen
delete-own Löschen eigener Daten
read Lesen
read-own Lesen eigener Daten
write Ändern
write-own Ändern eigener Daten

Die Parameter sollten fest in den Aufruf eingetragen werden. Die Übergabe in Form von Requestwerten dynamisiert die Prüfung, macht Sie jedoch wieder angreifbar.

7.2.2. Velocity-Includes

Velocity-Dateien sollten immer im Applikationspaket enthalten sein, um einen einfachen Import zu gewährleisten. Falls es übergreifende Velocity-Templates gibt, sind diese im Portalverzeichnis internal/system/vm/html/include und dort in einem eigenen Unterverzeichnis abzulegen. Alle anderen Verzeichnisse sind für Intrexx reserviert.

7.3. Groovy Codierungs-Konventionen

7.4. Dokumentation der Codes

Die Codes in JavaScript, Velocity oder Groovy sollten ausführlich dokumentiert werden. D.h. in erster Linie ausreichend sinnvolle Kommentare im Code einbauen, um dessen Funktion ersehen zu können. Nutzen Sie auch die Möglichkeit JSDOC-Kommentare einzufügen.

7.5. Prepared Query

Die korrekte Anwendung von PreparedQuery zur Ausführung von SQL-Statements in Groovy und Velocity behindert einen Angriff via SQL-Injection. Das Wissen über die Auswirkung und Beeinflussung von Parametern, die in das SQL-Statement gelangen, sind eine wichtige Voraussetzung. Intrexx bietet eine Fülle von Funktionen zur Anwendung von PreparedQuery. Allerdings können falsche Anwendung Lücken erzeugen, die es zu vermeiden gilt. In Groovy sieht eine korrekte PreparedQuery wie folgt aus. Wichtig ist, dass die Werte in der WHERE-Klausel über die Platzhalter ? definiert und im Anschluss über set-Methoden gesetzt werden. Die jeweiligen set-Methoden stellen sicher, dass auch das erwartete Format eingefügt wird. D.h. wird anstatt eines Ganzzahlwertes eine Zeichenkette eingeschleust, wird dieser nicht verarbeitet und verursacht einen Fehler. Die detaillierte Fehlermeldung muss im Portal unterdrückt werden, damit der Effekt volle Wirkung entfaltet.
def conn = g_dbConnections.systemConnection

def l_UserId = g_record["GUID"].value

def stmt = g_dbQuery.prepare(conn, 
"SELECT DTBIRTH FROM DSUSER WHERE LID = ?")
stmt.setInt(1, l_UserId)
def rs = stmt.executeQuery()

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

rs.close()
stmt.close()
Das nachfolgende Konstrukt ist falsch definiert und birgt ein hohes Risiko.
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()
Der Wert aus dem Datenfeld kann von einem Hacker manipuliert werden und anstatt der erwarteten LID eine Zeichenkette mit der Erweiterung der WHERE-Abfrage einschleusen.

Erwartet:
WHERE LID = 10
Manipuliert:
WHERE LID = 10 OR LID > 0
Mit dieser Veränderung bekommt der Hacker alle Datenbankeinträge in der Benutzerdatenbank. Werden die Request-Werte zusätzlich mittels Validatoren geprüft, kommen z.B. Zeichenketten über Request-Werte die Ganzzahlwerte liefern sollen schon nicht so weit, dass sie in das Statement eingesetzt werden. Ebenfalls eine sehr kritische Konstruktion ist nachfolgend gezeigt. Das SQL-Statement wird durch das Zusammenfügen einer Zeichenkette und einer Variablen gebildet. Auch hier kann durch die Variable, die zudem über einen Requestwert übergeben wird, zur Erweiterung der WHERE-Klausel missbraucht werden.
def l_request = g_request.get("rq_meinparameter")

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

...
Im Velocity-Umfeld gelten dieselben Bedingungen. Auch hier muss das PreparedQuery korrekt angewendet werden:
#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()
Ein ebenfalls kritisches Konstrukt ist die Definition einer Query, bei der die Datenfeldnamen dynamisch eingesetzt werden. Insbesondere, wenn die Feldnamen über Daten aus dem Request oder anderen äußeren Quellen bestimmt werden. Dies gilt für Groovy und Velocity.
def ergebnis = g_dbQuery.executeAndGetScalarIntValue(conn, 
"SELECT count(*) FROM DATAGROUP WHERE LID = ? AND $FIELDNAME1 IS NOT NULL AND $FIELDNAME <> ''",0)
Bei einer solchen Lösung müssen viele Sicherheitsstufen vorgesehen werden. Die ideale Lösung ist, solche Konstrukte zu vermeiden. Folgende Sicherheitsmaßnahmen wären angeraten:
  1. Der Client sendet beliebige eindeutige Schlüsselwerte für die einzelnen Felder an den Server. Die Schlüsselwerte können zum Beispiel die GUIDs von Datenfeldnamen oder Querystring-Parametern sein.
  2. Über entsprechende Validatoren werden die übermittelten Werte bereits vorab geprüft (z.B. Whitelist). Wird keine Übereinstimmung gefunden, erfolgt keine serverseitige Verarbeitung.
  3. Der Server prüft, ob der angemeldete Benutzer über die notwendigen Rechte verfügt, den nachfolgenden Code auszuführen (im Normalfall Leserechte an der Datengruppe).
  4. Der Server schlägt in einer Liste die erlaubten Feld-GUIDs nach. Trifft er dabei auf einen nicht zugeordneten Parameter, wirft er eine Exception oder ignoriert den Parameter - je nach Anforderung.
  5. Anhand der Feld-GUIDs werden die zugehörigen Spaltennamen in der Datenbank ermittelt und ein Prepared-Statement aufgebaut.
  6. Die aktuellen Werte werden an das Prepared-Statement übergeben.
  7. Die Datenbank-Abfrage wird ausgeführt

8. Mehrsprachige Applikationen

Alle Informationen zu diesem Thema finden Sie hier.

9. Klonbare Applikationen

Damit eine Applikation per Kopie dupliziert werden kann muss vor allem Groovy- und Velocity-Script korrekt definiert werden. Dies betrifft vor allem die Datengruppen und die SQL-Statements. Intrexx vergibt beim Duplizieren einer Applikation alle GUIDs neu und ändert dabei auch die GUIDs in den Scripten (Groovy, Velocity, JavaScript), damit alles wieder zusammenpasst. Aus diesem Grund müssen die Datengruppendefinitionen immer über die GUID der Datengruppe erfolgen:
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())
Für den Zugriff auf Datenfelder auf Applikationsseiten im Velocity-Kontext sollte ebenfalls mit den GUIDs gearbeitet werden. Dazu muss in den Settings der jeweiligen Seite der Schlüssel "page.requiredDataFields.mode" mit dem Wert "all" gesetzt werden. Dies ist die schnellste Methode, alle Datenfelder der Datengruppe über GUIDs bereitzustellen. Es ist auch die Grundlage für den Zugriff in den Velocity-Templates der vorherigen Abschnitte.

Im Velocity-Kontext kann dann mit den folgenden Methoden auf die gespeicherten Daten zugegriffen werden:
$DC.getValueHolder("GUID_DATENFELD").getValue()

## Wenn Seite in einer freien Tabelle verwendet wird
$drRecord.getValueHolder("GUID_DATENFELD").getValue()
Der Vorteil dieser Methode ist, dass die Informationen nicht physikalisch als Ansichtsfeld auf der Seite platziert (und ggf. versteckt) werden müssen.

10. Portierbare Applikationen (Datenbanken)

Da in Verbindung mit Intrexx unterschiedliche Datenbanken eingesetzt werden können, sollten die SQL-Statements möglichst neutral definiert werden, d.h. keine datenbankspezifischen Funktionen. Der ANSI-SQL Standard ist dabei einzuhalten. Falls es keine neutrale Alternative für ein SQL-Statement gibt, muss eine Datenbankweiche für das SQL-Statement definiert werden, die für jede Datenbank das passende Statement verwendet.

Weiche für 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 "MySQL":
		// MySQL
		break

	case "Oracle8":
		// Oracle 8
		break

	case "Oracle9":
		// Oracle 9
		break

	case "Oracle10":
		// Oracle 10
		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
}
Weiche für 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

12. Prozesse

Bei Prozessen sollte Folgendes beachtet werden: Timer-Aktionen auf Datengruppen sollten wenn möglich die Treffermenge über einen Filter einschränken, um die Anzahl der Datenbankoperationen gering zu halten. Im Auslieferungszustand sollte in den Prozess-Eigenschaften die Einstellung "Nur Warnungen und Fehler protokollieren" gesetzt sein. Zum Debugging eingefügte g_log.info() können im Code verbleiben. Sie werden im Regelbetrieb ignoriert und halten die Log-Dateien klein.