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:
maximale Länge des Tabellennamens: 26 Zeichen
möglichst sprechender Name (z.B. SHARE_FEED)
Einzahl
Englisch
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:
maximale Länge des Spaltennamens 30 Zeichen
möglichst sprechender Name
Präfix mit Typkürzel des Datenfeldtyps, gefolgt von Unterstrich (z.B. STR_NAME, B_IS_VISIBLE, etc.)
Englisch
Großschreibung
Hier eine Übersicht über Präfixe:
String: STR
Boolean: B
Float und Currency: FLT
Longtext: TXT
Datum, Uhrzeit, Datum und Uhrzeit: DT
Datei: FILE
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:
Sicher gegen das Erraten der Datensatz-ID - Integer-Werte können durch Hochzählen erraten werden.
Ermittlung der neuen ID bei Anlage eines neuen Datensatzes nicht erforderlich.
Lückenlose ID - beim Löschen von Datensätzen entstehen bei Ganzzahlwerten Lücken.
Beim Zusammenführen oder Kopieren von Daten mit GUIDs müssen keine IDs ermittelt werden.
Der Aufwand und die Fehleranfälligkeit sind bei GUIDs geringer als bei Integer-Werten. Der notwendige Code ist schlanker.
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:
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:
Englisch
korrekte Grammatik und Rechtschreibung
einheitliche Syntax
korrekte CamelCase
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:
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:
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:
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.
Über entsprechende Validatoren werden die übermittelten Werte bereits vorab geprüft (z.B. Whitelist).
Wird keine Übereinstimmung gefunden, erfolgt keine serverseitige Verarbeitung.
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).
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.
Anhand der Feld-GUIDs werden die zugehörigen Spaltennamen in der
Datenbank ermittelt und ein Prepared-Statement aufgebaut.
Die aktuellen Werte werden an das Prepared-Statement übergeben.
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.