1 Themenübersicht
In diesem Referenzhandbuch werden die technischen Grundlagen sowie die Programmierung der anpassbaren Cockpit-Anwendungen beschrieben. Nach einer kurzen Einführung werden die beteiligten Entwicklungsobjekte und Programmierschnittstellen aufgelistet und teilweise mit kurzen Beispielen veranschaulicht.
Die Bedienung der Cockpit-Anwendungen ist in der Dokumentation „Cockpits“ beschrieben.
2 Zielgruppe
Anwendungsentwickler
3 Beschreibung
Die Cockpits sind Abfrage-Anwendungen, die sich besonders leicht und komfortabel anpassen und erweitern lassen. Die Technik der Cockpits kann auch in anderen anpassbaren Anwendungen verwendet werden, indem die UI-Komponente „SearchView“ beispielsweise als Editor eingebettet wird. Außerdem wird die Infrastruktur der Cockpits auch von den Rest-Services genutzt.
Allgemeine Informationen zu Cockpits finden Sie in diesen Kapiteln:
- Spaltenvorrat
- Anpassbarkeit
- Detailsuchen
- Programmiermodell
- Hintergrund-Anwendungen
- Export und RESTful Web-Services
3.1 Spaltenvorrat
Ein wesentliches Merkmal der Cockpits ist der Spaltenvorrat, aus dem sich die Benutzer ihre Ansichten frei zusammenstellen können. Dazu gehört die Verwendung der Spalten in der Ergebnisliste, als Suchfelder im Abfragebereich und als Sortiermerkmal.
Die Bereitstellung des Spaltenvorrats erfolgt mehrstufig. Ausgangspunkt ist ein OQL-Ausdruck („FROM“-Klausel), in dem die gewünschten Business Objects untereinander verknüpft werden. Danach werden von diesen Business Objects diejenigen Attribute ausgewählt, die in dem Spaltenvorrat enthalten sein sollen. Beides wird zusammen in den Entwicklungsobjekten „Suche“ und „Sucherweiterung“ gespeichert, wodurch der Auslieferungszustand für den Spaltenvorrat bereitgestellt wird.
Im Ziel-System kann der Benutzer den Spaltenvorrat für jede Datenbank individuell erweitern oder reduzieren, wenn er die erforderliche Berechtigung besitzt. Als Grundlage dienen dabei die im Entwicklungsobjekt „Suche“ und in den zugehörigen Entwicklungsobjekten des Typs „Sucherweiterung“ festgelegten Business Objects. Zur Auswahl stehen nicht nur alle Attribute dieser Business Objects, sondern auch die Attribute von weiteren Business Objects, wenn entsprechende 1:1-Beziehungen in der Anwendung „Entwicklungsobjekte“ erfasst sind. Diese Beziehungen können dabei beliebig tief verfolgt werden.
Neben den regulären Attributen können auch die „Benutzerdefinierten Felder“ (Entity Extensions) und virtuelle Attribute sowie „Managed Supplements“ in den Spaltenvorrat aufgenommen werden. Virtuelle Attribute sind solche, die nicht direkt zu einem Business Object gehören, aber aus den Attributen von Business-Objects berechnet werden können.
3.2 Anpassbarkeit
Die Bedienungsoberfläche von Cockpits lässt sich auf vielfältige Weise anpassen (siehe Dokumentation „Cockpits“). Die Anpassungen lassen sich auf drei Ebenen speichern: pro Benutzer, pro OLTP-Datenbank oder im Entwicklungsobjekt „Anwendungserweiterung“. Abfrage- und Arbeitsbereich werden dabei zusammen bearbeitet. Für den Arbeitsbereich können pro Ebene beliebig viele Ansichten gespeichert werden, für den Abfragebereich kann pro Layout jeweils nur eine Ansicht gespeichert werden.
Hinweis:
Vor der Auslieferung eines Entwicklungsobjekts des Typs „Anwendungserweiterung” sollte immer darauf geachtet werden, dass darin auch entsprechende Layouts gespeichert wurden. Ausnahmen sind Suchen für „SearchViews“, wenn diese so konfiguriert wurden, dass ihre Layouts in der Anwendung gespeichert werden.
3.3 Detailsuchen
In die Cockpits können Detailsuchen eingebunden werden, mit denen sich die Benutzer zu den angezeigten Zeilen bestimmte Detailinformationen anzeigen lassen können. Eine Detailsuche ist wie die Hauptsuche ein Entwicklungsobjekt des Typs „Suche“. Die Festlegung, welche Rolle eine Suche in einem Cockpit einnimmt, wird dadurch bestimmt, wie die Suchen untereinander verknüpft werden. Einer Hauptsuche können mehrere Detailsuchen zugeordnet werden und einer Detailsuche können wiederum andere Detailsuchen zugeordnet werden (Baumstruktur).
Hinweis:
Die Detailsuchen können derzeit nur in den Cockpits genutzt werden, jedoch nicht in den „SearchViews“ oder den „REST-Services“.
3.4 Programmiermodell
Die Entwicklung der Cockpits basiert überwiegend auf der Bereitstellung von Metadaten, wie beispielsweise dem Spaltenvorrat über das Entwicklungsobjekt „Suche“ und den zugehörigen Sucherweiterungen.
Wie für jede Anwendung werden auch für die Cockpits jeweils ein Entwicklungsobjekt des Typs „Anwendung“ und des Typs „Java-Klasse“ benötigt. Die Implementierung der Java-Klasse besteht im einfachsten Fall im Wesentlichen aus dem Ableiten von einer speziellen abstrakten Basisklasse (siehe diese Kapitel: CisCustomizableCockpit und AbstractOrderCockpit).
3.4.1 Visualisierung
Die Visualisierung der Daten in der Ergebnisliste wird von der Infrastruktur an spezielle Klassen delegiert. Welche Klassen für die Visualisierung zu verwenden sind, wird über den logischen Datentyp der jeweiligen Spalte bestimmt. Für alle Standard-Datentypen werden entsprechende Klassen bereits durch die Infrastruktur bereitgestellt. Je nach Datentyp nutzt die Infrastruktur dazu entweder „Formatter“ (java.text.Format) oder „Renderer“ (com.cisag.pgm.gui.Renderer).
Geeignete Renderer und Formatter werden über spezielle Factories erzeugt (siehe diese Kapitel: RendererFactory und FormatFactory). Durch die Implementierung und Registrierung eigener Factories kann die Visualisierung angepasst werden.
3.4.2 Filter
Für die Optimierung des Suchergebnisses können für jede Spalte des Spaltenvorrats entsprechende Filterausdrücke verwendet werden. Programmatisch werden solche Filter durch Unterklassen der Klasse „Filter Expression“ repräsentiert (siehe Kapitel „FilterExpression“). Um die Filterausdrücke im Abfragebereich des Cockpits anzeigen und bearbeiten zu können, sind spezielle Editoren erforderlich (siehe Kapitel „FilterExpressionEditor“). Diese Editoren werden über entsprechende Klassen bereitgestellt (siehe Kapitel „FilterExpressionEditorFactory“). Der logische Datentyp der jeweiligen Spalte bestimmt, welche Klassen verwendet werden müssen. Für alle Standard-Datentypen werden entsprechende Klassen bereits durch die Infrastruktur bereitgestellt. Durch die Implementierung und Registrierung von eigenen Factories kann die Darstellung und Bearbeitung der Filterausdrücke angepasst werden.
3.5 Hintergrund-Anwendungen
Das Suchergebnis eines Cockpits kann an eine Hintergrund-Anwendung übergeben werden. Für den Benutzer bestehen dabei folgende Wahlmöglichkeiten:
- An die Hintergrund-Anwendung werden die im Arbeitsbereich ausgewählten Objekte übergeben.
- An die Hintergrund-Anwendung werden die zum Zeitpunkt des Aufrufes der Aktion im Abfragebereich der Cockpit-Anwendung erfassten Suchmerkmale übergeben, mit denen eine Datenbankabfrage zum Ausführungszeitpunkt der Hintergrund-Anwendung ausgeführt wird. Das Ergebnis dieser Abfrage unterscheidet sich ggf. von dem, das bereits in der Anwendung ausgeführt wurde. Der Benutzer hat demnach nicht unbedingt das Suchergebnis zuvor gesehen und die zu verarbeitende Anzahl der Objekte kann unwissentlich einen nicht gewollten Umfang ergeben.
Hinweis:
Sind im Abfragebereich des Cockpits keine Suchmerkmale erfasst, dann wird vor Ausführung der jeweiligen Aktion eine Warnmeldung ausgegeben. Die Ausgabe dieser Warnmeldung kann mithilfe folgender Property gesteuert werden: com.cisag.pgm.services.batch.BatchJobSearch.suppressConfirmationDialog
- An die Hintergrund-Anwendung werden sowohl die im Arbeitsbereich ausgewählten Objekte als auch die Suchmerkmale aus dem Abfragebereich des Cockpits übergeben. Dabei wird zum Ausführungszeitpunkt der Hintergrund-Anwendung eine Datenbankabfrage mit den Suchmerkmalen ausgeführt, jedoch werden in diesem Fall alle Objekte übersprungen, die im Arbeitsbereich ausgewählt waren (Negativliste).
Für die Hintergrund-Anwendung ist diese Parametrisierung transparent: Sie bekommt über eine Schnittstelle ein „ResultSet“, der die Objekte entsprechend gefiltert liefert.
3.6 Export und RESTful Web-Services
Aus allen Cockpits (inkl. SearchView) können die angezeigten Ergebniszeilen z. B. in eine MS-Excel- oder PDF-Datei exportiert werden. Zudem besteht die Möglichkeit des Exports über eine Hintergrund-Anwendung. Um diese Möglichkeit in einem Cockpit nutzen zu können, muss das Cockpit mit einem (RESTful) Web-Service verbunden sein (siehe Dokumentation „Schnittstelle für Exporte aus Cockpits mit REST-konformen Web-Services“).
4 Entwicklungsobjekte
In diesem Kapitel werden die für eine Cockpit-Anwendung notwendigen Festlegungen in den relevanten Entwicklungsobjekttypen beschrieben.
4.1 Entwicklungsobjekttypen „Suche“ und „Sucherweiterung“
Mithilfe des Entwicklungsobjekts „Suche“ legen Sie die Datenbankabfrage und den Spaltenvorrat eines Cockpits oder einer Detailsuche fest. Haupt- und Detailsuchen werden nicht besonders gekennzeichnet, stattdessen wird die Beziehung zwischen zwei Suchen im zugehörigen Java-Code der Anwendung spezifiziert. Eine Suche kann daher in einem Cockpit als Hauptsuche und in einem anderen Cockpit als Detailsuche verwendet werden.
Hinweis:
Die Datenbankabfrage und der Spaltenvorrat von Suchen und Detailsuchen können durch Sucherweiterungen erweitert werden.
4.1.1 Karteireiter „From und Where“: Datenbankabfrage
Unter dem Karteireiter „From und Where“ werden die an der Suche beteiligten Business Objects und deren Beziehungen für die Datenbankabfragefestgelegt.
Beispiel:
com.cisag.app.edu.obj.Book BOOK
LEFT OUTER JOIN com.cisag.app.general.obj.Partner EMPLOYEE
ON BOOK:employee = EMPLOYEE:guid
Hinweis:
Für die verwendeten Aliasse sollte möglichst eine einheitliche Namenskonvention eingehalten werden. Empfohlen wird, den Namen der entsprechenden Beziehung (soweit vorhanden) zu verwenden.
4.1.1.1 Fremdschlüssel
Bei der Erstellung der Datenbankabfrage ist zu beachten, dass Fremdschlüsselbeziehungen explizit durch JOINs beschrieben sind, damit Attribute des Zielobjekts in den Spaltenvorrat übernommen und ggf. auch als Link verwendet werden können (siehe Kapitel „Checkbox: Business Object“).
4.1.1.2 REMOVABLE JOINs
Zur Laufzeit ergibt sich die konkrete Datenbankabfrage aus den tatsächlich verwendeten Spalten (Anzeige, Filter und Sortierung). Dabei werden nicht nur die „SELECT“-, „WHERE“- und „ORDER BY“-Klauseln entsprechend angepasst, sondern auch nicht benötigte JOINs aus der „FROM“-Klausel entfernt, wenn diese entsprechend gekennzeichnet sind. Um einen JOIN als entfernbar zu kennzeichnen, muss das zusätzliche Schlüsselwort „REMOVABLE“ vor dem „LEFT OUTER JOIN“ eingefügt werden.
Beispiel:
SELECT BOOK:code
FROM com.cisag.app.edu.obj.Book BOOK
REMOVABLE LEFT OUTER JOIN
com.cisag.app.general.obj.Partner EMPLOYEE
ON BOOK:employee = EMPLOYEE:guid
wird zu:
SELECT BOOK:code
FROM com.cisag.app.edu.obj.Book BOOK
Hinweis:
Bei einem Valueset, dessen Wertebereich über eine Data-Description reduziert wurde, wird diese Bedingung automatisch in die „WHERE“-Klausel aufgenommen, auch wenn die entsprechende Spalte im Cockpit nicht als Filter genutzt wird. Deshalb können JOINs grundsätzlich nicht entfernt werden, die solche Spalten enthalten. Bei reduziertem Valueset-Umfang sollte daher immer geprüft werden, ob in der Datenbank tatsächlich andere Werte gespeichert sein können als die reduzierten. Ist dies nicht der Fall, dann sollte das zugehörige Business-Object-Attribut als „vollständig“ gekennzeichnet werden. Dadurch wird die „WHERE“-Klausel nicht zwingend erweitert (siehe Kapitel „BusinessObjectRegistryHook“).
4.1.1.3 Automatische Optimierung
Eine weitere Optimierungsmöglichkeit besteht darin, bestimmte JOINs durch Zugriffe auf den Persistenzdienst ersetzen zu lassen. In diesem Fall werden bei der Datenbankabfrage der JOIN und alle Attribute des betroffenen Business Objects entfernt. Stattdessen wird der Primärschlüssel des Business Objects in die „SELECT“-Klausel aufgenommen. Anschließend wird der Persistenzdienst genutzt um die Business Objects (blockweise) zu laden und die Werte der gewünschten Attribute zu ermitteln.
Beispiel:
SELECT BOOK:code, EMPLOYEE:number
FROM com.cisag.app.edu.obj.Book BOOK
LEFT OUTER JOIN
com.cisag.app.general.obj.Partner EMPLOYEE
ON BOOK:employee = EMPLOYEE:guid
wird zu:
SELECT BOOK:code, BOOK:employee
FROM com.cisag.app.edu.obj.Book BOOK
plus Zugriff über Persistenzdienst
Partner partner = om.getObject(
Partner.getPrimaryKey(rs.getGuid(2));
String number = partner.getNumber();
Die Vorteile dieser Vorgehensweise liegen in der Einbeziehung des Shared-Caches (insbesondere bei Stammdaten) und einer geringeren Komplexität bei der Datenbankabfrage. Jedoch besteht die Gefahr, dass das so ermittelte Ergebnis von dem Ergebnis der nicht optimierten Abfrage abweicht. Daher wird diese Optimierung nicht automatisch aktiviert, sondern muss für jeden JOIN explizit durch die zusätzliche Angabe des Schlüsselwortes „REMOVEABLE“ aktiviert werden. In der Praxis ist die vorgenannte Gefahr nur sehr selten relevant, da auch im nicht optimierten Fall gilt, dass sich die Daten ändern können nachdem sie von der Datenbank gelesen wurden. Wegen der eindeutigen Vorteile wird daher empfohlen, bei (LEFT OUTER) JOINs zusätzlich das Schlüsselwort „REMOVEABLE“ anzugeben.
Eine Optimierung ist nur bei bestimmten Arten von JOINs möglich: Nur bei LEFT OUTER JOINs möglich und die Verknüpfung muss über den Primärschlüssel erfolgen. Diese Voraussetzungen werden beim Speichern (Prüfen) der Suche automatisch geprüft und ggf. wird eine Fehlermeldung ausgegeben. Zur Laufzeit kommt die Optimierung nur dann zum Einsatz, wenn Attribute des Business Objects ausschließlich für die Anzeige (d. h. innerhalb der SELECT-Klausel) verwendet werden.
4.1.2 Karteireiter „Attribute“: Spaltenvorrat
In der Anwendung „Entwicklungsobjekte“, sowohl für den Typ „Suche“ als auch den Typ „Sucherweiterung“, wird im Arbeitsbereich unter dem Karteireiter „Editor“ und dem darunter liegenden Karteireiter „Attribute“ der Spaltenvorrat in einer Liste festgelegt. Die im Cockpit gewünschten Spalten müssen in der Suche als Attribute dieser Liste hinzugefügt werden. Die Definition eines Attributes (Spalte) besteht aus dem Attributnamen, einem Ausdruck und weiteren Parametern. Verschiedene Arten von Spalten werden angeboten: reguläre Attribute, berechnete Spalten und virtuelle Spalten.
Hinweis:
Der Spaltenvorrat sollte bereits all jene Attribute enthalten, die üblicherweise von den Benutzern benötigt werden. Dabei sollte auch berücksichtigt werden, dass der Spaltenvorrat im Ziel-System nicht beliebig erweitert werden kann. Nur die Business Objects stehen zur Verfügung, die entweder explizit in der „FROM“-Klausel deklariert wurden oder die davon ausgehend über (1:1)-Beziehungen erreichbar sind, die in der Anwendung „Entwicklungsobjekte“ definiert werden.
4.1.2.1 Spalte „Name“
Bei der Vergabe eines Attributnamens müssen folgende Punkte beachtet werden:
- Der Name muss innerhalb der Suche eindeutig sein.
- Der Name dient als Identifikation für die Spalte und wird als solche auch außerhalb des eigenen Entwicklungsobjekts verwendet, beispielsweise in den Programmierschnittstellen und den gespeicherten Layouts und Suchmustern.
- Um einem Cockpit einen Filterausdruck als Anwendungsparameter übergeben zu können, muss als Parametername der Spaltenname verwendet werden
Hinweis:
Namen für Anwendungsparameter müssen immer mit einem Großbuchstaben beginnen, Attributnamen aber mit einem Kleinbuchstaben. Beim Vergleich wird dieser Unterschied automatisch berücksichtigt.
Warnung:
Nachdem eine Suche ausgeliefert wurde, sollten die darin festgelegten Attribute nicht mehr gelöscht oder umbenannt werden. Das Löschen bzw. Umbenennen von Attributen ist eine inkompatible Änderung der Suche und führt dazu, dass etwaige Suchanpassungen und Verarbeitungsaufträge, die auf der alten Version der Suche basieren, im Produktivsystem verworfen oder abgebrochen werden müssen.
Die Namen sollten möglichst nach einem einheitlichen Schema vergeben werden. Für reguläre Attribute wird empfohlen, den Attributnamen aus dem Business Object zu übernehmen und diesem den Alias des Business Objects als Präfix (kleingeschrieben) voranzustellen. Auf diese Weise ist die Eindeutigkeit der Namen auch dann noch einfach sicherzustellen, wenn ein Business Object mehrfach verwendet wird (Beispiele: bookCode, employeeNumber).
Hinweis:
Über den Button „Attribute auswählen“ in der Symbolleiste der Liste lassen sich aus einem Business Object (Alias) mehrere Attribute gleichzeitig in die Suche übernehmen. Dabei kann auch das gewünschte Präfix vorgegeben werden.
4.1.2.2 Spalte „Ausdruck“
In der Spalte „Ausdruck“ der Attributliste ist eine der folgenden Informationen einzutragen:
- Referenz auf ein Attribut eines Business Objects (Syntax: „ALIAS:Attributname“)
- Ein von der Datenbank zu berechnender Ausdruck (siehe Kapitel „Berechnete Spalten“)
- Referenz auf ein Business Object (siehe Kapitel „Komplexe Datentypen“)
- Leer, wenn ein virtuelles Attribut betroffen ist (siehe Kapitel „Checkbox: Virtuell“)
Berechnete Spalten
Als Ausdruck für ein Attribut können auch bestimmte Funktionen (UPPER, AVG, MAX, MIN, SUM, COUNT) und Operationen (*, /, +, -) verwendet werden.
Beispiele:
BO:a – BO:b
BO:a + BO:b + BO:c
SUM(BO:a) – SUM(BO:b)
SUM(BO:a) – BO:b // nicht verfügbar
Vorteile:
- Aggregationsfunktionen können häufig auf der Datenbank günstiger berechnet werden.
- Einfache Berechnungen kommen ohne virtuelle Spalten aus und benötigen keine speziellen Renderer.
- Spalten können auf der Datenbank gefiltert und sortiert werden (gilt nicht für Aggregationsfunktionen).
Restriktion:
- Für Special-Parts sind weiterhin virtuelle Attribute und Renderer erforderlich.
- Die Datenbank kann bei bestimmten Operationen zu anderen Ergebnissen kommen als die fachlichen Logiken in Comarch ERP Enterprise (z. B. Rundungsregeln).
- Die Länge des Ausdrucks ist auf 100 Zeichen beschränkt.
Komplexe Datentypen
Auch Parts oder ganze Business Objects können unter den folgenden Voraussetzungen in den Spaltenvorrat aufgenommen werden:
- Für die Visualisierung (Anzeigebereich) ist eine geeignete „FormatFactory“ oder „RendererFactory“ erforderlich.
- Die Filterung und Sortierung ist nur mit zusätzlichen (virtuellen) Attributen möglich (Ausnahme: „Special-Parts“ werden systemseitig angeboten).
- Parts werden im Editor der Suche wie normale Attribute ausgewählt.
- Bei Business Objects wird im Feld „Ausdruck“ nur der Alias (ohne Attributnamen) angegeben. Zusätzlich ist die Checkbox „Business Object“ zu aktivieren.
Hinweis:
Anstatt ganze Business Objects zu lesen, ist im Allgemeinen günstiger, nur die benötigten Attribute zu verwenden (-> virtuelle Spalten).
4.1.2.3 Checkbox: Virtuell
Mehrere Attribute können unter einem virtuellen Attribut zusammengefasst und damit als eine einzige Spalte angezeigt werden. Diese Möglichkeit ist nützlich, wenn auf der Bedienungsoberfläche nur eine Spalte angezeigt werden soll, für die Anzeige jedoch Daten aus mehreren Spalten erforderlich sind.
Das virtuelle Attribut muss als „Virtuell“ gekennzeichnet werden und das Feld „Ausdruck“ muss leer bleiben. Die Attribute, die zu einem virtuellen Attribut gehören, müssen mit dem Namen des virtuellen Attributes beginnen, gefolgt von einem Punkt.
Beispiel (vgl. „Status“ in „Cockpit: Vertriebsaufträge“):
Name | Ausdruck | Virtuell | Logischer Datentyp |
vstatus | × | ..SalesOrderStatus | |
vstatus.attr1 | SO:attr2 | ||
vstatus.attr2 | SO:attr2 | ||
… | … |
Damit ein virtuelles Attribut im Cockpit angezeigt werden kann, ist eine entsprechende „RendererFactory“ bereitzustellen, und um das Attribut als Filter verwenden zu können, wird eine passende „FilterExpressionEditorFactory“ benötigt (siehe die Kapitel „RendererFactory“ und „FilterExpressionEditorFactory“). Dazu ist bei dem Attribut in der Spalte „Logischer Datentyp“ ein logischer Datentyp einzutragen, der mit einer Data-Description verbunden ist, in der die Implementierungen dieser Klassen eingetragen sind (siehe dieses Kapitel: Entwicklungsobjekttyp „Data-Description“).
Für die Unterattribute kann jeweils festgelegt werden, ob sie anzeigbar und filterbar sein sollen. Diese Einstellungen entscheiden darüber, welche der Unterattribute dem Renderer oder FilterExpressionEditor übergeben werden. Wenn ein virtuelles Attribut als filterbar gekennzeichnet ist, dann muss mindestens eines der Unterattribute ebenfalls als filterbar gekennzeichnet werden. Gleiches gilt für die als anzeigbar gekennzeichneten Attribute.
Für die Sortierung von virtuellen Attributen gilt: Wenn ein virtuelles Attribut als sortierbar gekennzeichnet ist, dann muss genau ein Unterattribut als sortierbar gekennzeichnet werden.
4.1.2.4 Checkbox: Business Object
Mit der Kennzeichnung als Business Object wird festgelegt, dass eine Spalte eine ähnliche Funktionalität wie ein „EntityField“ bekommt. So gekennzeichnete Attribute erhalten damit ein Kontextmenü und sind als Link nutzbar. Das Kontextmenü sowie der Link beziehen sich dabei immer auf das Business Object, zu dem das Attribut selbst gehört (definiert über den Alias). Diese Kennzeichnung sollte jedes Attribut bekommen, wenn es den „Business Key“ des eigenen Business Objects darstellt.
Um Kontextmenüs und Links auch für Fremdschlüsselattribute bereitzustellen, muss anders vorgegangen werden. Die erste (und bevorzugte) Möglichkeit besteht darin, die Datenbankabfrage und den Spaltenvorrat um das Zielobjekt und das Zielattribut zu erweitern (explizierter JOIN). Sollte ein JOIN nicht möglich sein, kann bei dem Attribut ein spezieller „URIRenderer“ hinterlegt werden, der aus dem Fremdschlüssel einen URI für das Zielobjekt berechnen kann (siehe Kapitel „URIRenderer“).
Die Kennzeichnung als Business Object ist ebenfalls notwendig, wenn in der Spalte „Ausdruck“ kein Attribut eines Business Objects, sondern nur der Alias eines Business Objects eingetragen ist. Mit dieser Einstellung wird festgelegt, dass anstatt eines einzelnen Attributs das ganze Business Object von der Datenbank gelesen wird.
Hinweis:
Das Verwenden von ganzen Business Objects sollte aufgrund der folgenden Nachteile möglichst vermieden werden: der Speicherbedarf und die Datenbanklast werden erhöht. Solche Spalten erfordern zusätzlichen Programmieraufwand (Renderer) und für die Benutzer stehen weder Filterung noch Sortierung zur Verfügung.
4.1.2.5 Checkbox: Programmierter Filter
Eine Filterung kann nicht nur durch den Benutzer, sondern auch per Programmierung erfolgen. Mit der Kennzeichnung „programmierter Filter“ ist möglich, eine Spalte so zu konfigurieren, dass eine Filterung ausschließlich per Programmierung möglich ist. Benutzer können so gekennzeichnete Spalten nicht mehr als Suchfeld in den Abfragebereich einfügen. Die Hauptanwendungsfälle für die Festlegung als „programmierter Filter“ sind die Umsetzung von Berechtigungen, Customizing-Einstellungen sowie die Verknüpfung von Detailsuchen.
Hinweis:
Für ein Attribut eines Business Objects können in der Suche auch mehrere Suchattribute erfasst werden. Auf diese Weise lassen sich bei Bedarf auch mehrere Filter miteinander verknüpfen (AND).
4.1.2.6 Checkbox: Filterbar
In den Abfragebereich eines Cockpits können nur die Spalten aufgenommen werden, die als filterbar gekennzeichnet sind. Im Allgemeinen sollten alle anzeigbaren Spalten auch für die Filterung freigegeben werden, da dies der allgemeinen Erwartungshaltung der Benutzer entspricht.
Hinweis:
Wird ein virtuelles Attribut als filterbar gekennzeichnet, dann muss mindestens ein Attribut der Gruppe als filterbar gekennzeichnet sein. Außerdem muss in dem virtuellen Attribut ein logischer Datentyp mit einer passenden „FilterExpressionEditorFactory“ angegeben werden.
4.1.2.7 Checkbox: Sortierbar
Damit eine Spalte im Cockpit als Sortiermerkmal verwendet werden kann, muss sie als sortierbar gekennzeichnet werden. Im Allgemeinen sollten alle anzeigbaren Spalten auch für die Sortierung freigegeben werden. Da die Sortierung grundsätzlich immer auf der Datenbank erfolgt, muss jedoch im Einzelfall geprüft werden, ob das Ergebnis wirklich nützlich wäre. Die Entscheidung hängt dabei in erster Linie von dem Datentyp der Spalte ab.
Hinweis:
Wird ein virtuelles Attribut als sortierbar gekennzeichnet, dann muss genau ein Attribut der Gruppe als sortierbar gekennzeichnet sein. Nach diesem Attribut wird stellvertretend für das virtuelle Attribut sortiert.
4.1.2.8 Checkbox: Anzeigbar
Damit eine Spalte in der Ergebnisliste eines Cockpits verwendet werden kann, muss sie als anzeigbar gekennzeichnet werden.
Eine Spalte, die als anzeigbar gekennzeichnet wurde, sollte möglichst auch immer als filterbar und sortierbar gekennzeichnet werden. Mit diesen Einstellungen lassen sich Spaltenüberschriften per Drag & Drop direkt in den Abfragebereich ziehen um danach zu filtern oder durch Klick auf die Spaltenüberschrift direkt zu sortieren. Da dies für die meisten Spalten gilt, erwarten die Benutzer diese Funktionalität von allen Spalten.
Spalten, die nur aus technischen Gründen in den Spaltenvorrat aufgenommen wurden, wie beispielsweise der Rückgabeschlüssel oder programmierte Filter, sollten als nicht anzeigbar gekennzeichnet werden.
Hinweis:
Wird ein virtuelles Attribut als anzeigbar gekennzeichnet, dann muss mindestens ein Attribut der Gruppe als anzeigbar gekennzeichnet sein. Außerdem muss an dem virtuellen Attribut ein logischer Datentyp mit einer passenden „FormatFactory“ oder „RendererFactory“ hinterlegt werden.
4.1.2.9 Checkbox: Rückgabeschlüssel
Die Kennzeichnung als Rückgabeschlüssel dient in erster Linie dazu, einen Datensatz (Ergebniszeile) eindeutig zu identifizieren. In der Regel sind das eine oder mehrere GUIDs, die den Primärschlüssel des Business Entitys bilden. Während alle anderen Attribute nur dann von der Datenbank geladen werden, wenn sie in der Ansicht tatsächlich verwendet werden, werden die als Rückgabeschlüssel gekennzeichneten Attribute immer geladen. Eine Kennzeichnung als Rückgabeschlüssel ist daher auch notwendig, wenn einzelne Attributwerte unabhängig von ihrer Verwendung in der Bedienungsoberfläche verfügbar sein müssen. Beispiele dafür sind:
- die Übergabe und der Zugriff aus Hintergrundverarbeitungen
- die Attribute zum Verknüpfen von Detailsuchen
- die Attribute, die beim Auswerten von ausgewählten bzw. markierten Zeilen abgefragt werden
In den meisten Fällen ist für all diese Verwendungen aber der Primärschlüssel des Business Entitys ausreichend, sodass nur äußerst selten weitere Attribute als Rückgabeschlüssel gekennzeichnet werden müssen.
Hinweis:
Die als Rückgabeschlüssel gekennzeichneten Attribute sind meistens technische Attribute (z. B. GUIDs), die für Benutzer unsichtbar sein sollten. Solche technischen Attribute sollten daher nicht als anzeigbar, filterbar oder sortierbar gekennzeichnet werden.
4.1.2.10 Logischer Datentyp
Die Infrastruktur benötigt zu jedem Attribut bestimmte Metadaten, wie beispielsweise den Datentyp, ein Label und eventuell spezielle Factories. Die benötigten Metadaten liefern der logische Datentyp und die damit verbundene Data-Description. Bei Suchattributen, die direkt ein Attribut aus einem Business Object referenzieren, werden logischer Datentyp und Data-Description von dem Attribut des Business Objects übernommen. Für alle anderen Attributtypen, d. h. virtuelle Attribute, berechnete Attribute und Business Objects, muss ein geeigneter logischer Datentyp eingetragen werden. Mit dem logischen Datentyp muss eine Data-Description verbunden sein, die ein Label und die notwendigen Factories für das Attribut definiert (siehe dieses Kapitel: Entwicklungsobjekttyp „Data-Description“).
Hinweis:
Der aus einem Business-Object-Attribut übernommene logische Datentyp wird durch die explizite Angabe eines anderen logischen Datentyps außer Acht gelassen. Dies kann notwendig sein, wenn der für das Business-Object-Attribut festgelegte logische Datentyp nicht über das gewünschten eindeutige Label oder die gewünschten Factories verfügt. Wird ein anderer logischer Datentyp expliziert angegeben, dann muss jedoch darauf geachtet werden, dass der primitive Datentyp unverändert bleibt.
4.1.2.11 Empfehlungen für den Spaltenvorrat
Label
Damit die Benutzer die Spalten aus dem Spaltenvorrat möglichst verwechselungsfrei identifizieren und verwenden können, sollten sich die Bezeichnungen (Label) voneinander unterscheiden. Da die Bezeichnung über den logischen Datentyp festgelegt wird, kann notwendig sein, für einige Spalten alternative logische Datentypen anzugeben, um diesen Spalten eindeutige Bezeichnungen geben zu können.
Löschkennzeichen
Das Löschkennzeichen muss wie jede andere Spalte explizit dem Spaltenvorrat hinzugefügt werden. Aus dem entsprechenden Business Object ist dazu das Attribut updateInfo.deleteTime auszuwählen. Für den Namen der Spalte sollte die Konvention <alias>UpdateInfoDeleteState verwendet werden.
Als logischer Datentyp sollte com.cisag.pgm.datatype.DeleteState oder ein davon abgeleiteter Datentyp eingetragen werden. Über die zugehörige Data-Description werden damit automatisch passende Renderer- und FilterExpressionEditorFactories bereitgestellt, sodass die für die Löschkennzeichen übliche Visualisierung und Filterung verwendet wird.
4.1.3 Karteireiter „Layouts“
Layouts von Cockpits werden in Anwendungserweiterungen gespeichert. Die Layouts in den Suchen werden nur verwendet, wenn kein passendes Layout in der Anwendungserweiterung besteht.
Die Layouts von „SearchViews“ werden weiterhin in den Suchen gespeichert, wenn diese entsprechend eingestellt sind.
Unter dem Karteireiter „Layouts“ werden alle direkt in diesem Entwicklungsobjekt gespeicherten Layouts aufgelistet. Die Anzeige dient hauptsächlich der Kontrolle, die Bearbeitung erfolgt direkt in der „SearchView“. Um die gespeicherten Layouts über die „SearchView“ bearbeiten zu können, muss dieses Entwicklungsobjekt vorher durch den Entwickler in eine Entwicklungsaufgabe aufgenommen werden.
Damit eine „SearchView“ von den Benutzern sofort verwendet werden kann, sollte der Abfragebereich und mindestens eine Ansicht für den Anzeigebereich in der Suche gespeichert werden. Die Felder des Abfragebereichs sollten bei Bedarf mit Vorschlagswerten versehen werden, die sinnvoll für die Benutzer sind und eine möglichst geringe Datenbankauslastung verursachen.
4.1.4 Karteireiter „Einstellungen“
4.1.4.1 Hook
Über das Feld „Hook“ kann eine Java-Klasse registriert werden, welche die Schnittstelle „com.cisag.pgm.search.SearchHook“ implementiert. Mithilfe dieser Klasse kann die Datenbankabfrage vor ihrer Ausführung manipuliert werden (siehe Kapitel „SearchHook“).
Im Feld „Basisobjekt“ kann der Klassenname eines Business Objects erfasst werden. Beim Aufruf der „init“-Methode des Hooks wird dadurch ein entsprechendes Class-Objekt als Parameter übergeben.
Die Implementierung eines „SearchHooks“ ist eine Alternative zum Ändern der „preSearch“-Methode (siehe die Kapitel „CisCustomizableCockpit“ und „SearchView“). Das ist besonders dann sinnvoll, wenn die Suche nicht nur von einer einzelnen Implementierung von „CisCustomizableCockpit“ verwendet wird, sondern beispielsweise auch als REST-Service nutzbar sein soll.
4.1.4.2 Datenbank
Durch die Aktivierung der Checkbox „Distinct“ kann die Datenbankabfrage um das Schlüsselwort „DISTINCT“ erweitert werden. Zu der Verwendung dieses Schlüsselwortes sollten folgende Punkte beachtet werden:
- Im Allgemeinen enthält der Spaltenvorrat bereits ein oder mehrere Attribute, die zusammen eine eindeutige Identifizierung eines Objektes sicherstellen (z. B. der Primärschlüssel des Business Entitys). Diese im Spaltenvorrat als „Rückgabeschlüssel“ gekennzeichneten Attribute werden grundsätzlich immer in die SELECT-Klausel aufgenommen. Aufgrund der Eindeutigkeit des Rückgabeschlüssels können im Ergebnis keine „doppelten“ Objekte vorkommen und ein zusätzliches DISTINCT würde die Datenbank nur unnötig belasten.
- Zu-N-Beziehungen sollten möglichst in Detailsuchen ausgelagert werden.
- In die SELECT-Klausel wird nicht nur der Rückgabeschlüssel automatisch aufgenommen, sondern auch alle Sortier- und Gruppiermerkmale. Das Ergebnis hängt dadurch auch davon ab, ob und welche Spalten zur Sortierung und Gruppierung verwendet werden.
4.2 Entwicklungsobjekttyp „Sucherweiterung“
Sucherweiterungen erweitern den Spaltenvorrat und die FROM-Klausel der referenzierten Suche. Zu einer Suche können mehrere Sucherweiterungen existieren. In einem App-Entwicklungssystem kann zu einer Suche in jeder App eine Sucherweiterung erstellt werden. In einem „normalen“ Entwicklungssystem kann zu einer Suche nur eine Sucherweiterung pro Entwicklungspräfix erstellt werden. Sucherweiterungen haben nur einen Bezug zur Suche. Untereinander können keine Beziehungen aufgebaut werden.
Die Erweiterungen der FROM-Klauseln aller Sucherweiterungen werden an die FROM-Klausel der Suche mit einem „REMOVABLE LEFT OUTER JOIN“ angehängt. Die Attribute werden zum Spaltenvorrat hinzugenommen. Die Eindeutigkeit der Attribute wird durch das Voranstellen des Entwicklungspräfixes und des App-Namens erreicht.
Mit dem Zusammenführen verhält sich die Suche so, als ob alles in der Suche selbst festgelegt wurde.
4.3 Entwicklungsobjekttyp „Anwendung“
Für Cockpits (CisCustomizableCockpit) muss im Feld „Besondere Verwendung“ der Eintrag „Cockpit“ ausgewählt werden und in das Feld „Suche“ muss der vollständige Name des Entwicklungsobjekts „Suche“ erfasst werden. Das Eintragen der Suche ist wichtig, damit der Spaltenvorrat der Suche automatisch als Parameterdefinition für die Anwendung genutzt werden kann. Außerdem kann dadurch das Ändern der Methode „getDefaultCockpitSearchName()“ in der Klasse „CisCustomizableCockpit“ entfallen (siehe Kapitel „CisCustomizableCockpit“).
Hinweis:
Damit in einem Cockpit auch der erweiterte Export (siehe Kapitel „Export und RESTful Web-Services“) möglich ist, muss zusätzlich eine Anwendung des Typs „RPC-Dienst“ (besondere Verwendung: „REST-Service“) erfasst werden. Das Entwicklungsobjekt „Suche“ muss in diesem Fall sowohl in der Anwendung für das Cockpit als auch in der Anwendung für den RPC-Dienst eingetragen werden.
4.4 Entwicklungsobjekttyp „Anwendungserweiterung“
Die Layouts eines Cockpits werden in Anwendungserweiterungen gespeichert. Zu einer Anwendung können mehrere Anwendungserweiterungen existieren, wobei pro Entwicklungspräfix und in App-Entwicklungssystemen pro App nur eine Erweiterung existieren kann.
Beispiel pro Entwicklungspräfix:
Anwendung: com.cisag.app.general.ui.MyApplication
Anwendungserweiterungen:
com.cisag.app.general.ui.MyFirstApplicationExtension
com.bdv.app.general.ui.BranchApplicationExtension
____________________________________________
Beispiel für ein App-Entwicklungssystem:
Anwendung: com.sem.ext.app.abc.ui.MyApplication
Anwendungserweiterungen:
com.sem.ext.app.abc.ui.MyFirstApplicationExtension
com.sem.ext.app.def.ui.MySecondApplicationExtension
com.cdv.app.general.ui.CustomerApplicationExtension
Die Layouts werden in den Cockpits geführt.
4.5 Entwicklungsobjekttyp „Data-Description“
Die Metadaten einer Spalte ergeben sich aus dem logischen Datentyp und der dazugehörenden Data-Description (ggf. per Vererbung). Bei Spalten, die direkt ein Attribut eines Business Objects referenzieren, wird der logische Datentyp des Attributs übernommen. Alternativ kann einer Spalte auch ein spezieller logischer Datentyp zugewiesen werden. Den virtuellen Spalten, berechneten Spalten und Business Objects muss ein spezieller logischer Datentyp angegeben werden.
Die folgenden Eigenschaften der Data-Description werden durch die Cockpits ausgewertet:
- Vollständiges Label und Direkthilfe
- FormatFactory
- RendererFactory
- FilterExpressionEditorFactory
- DataDescriptionFilter
- Inhaltstyp (bei Strings)
4.5.1 Vollständiges Label und Direkthilfe
In der Data-Description wird zwischen „Label“ und „Vollständiges Label“ unterschieden. Auf diese Weise können mit nur einer Data-Description zwei unterschiedlich Bezeichnungen für ein Attribut bereitgestellt werden. Beispielsweise lautet das Label für das Attribut „description“ des Business Objects „Artikel“ nur „Beschreibung“, das vollständige Label lautet „Artikelbeschreibung“. Während die übrigen Anwendungen das Label verwenden, nutzen die Cockpits das vollständige Label. Wenn in der Data-Description kein vollständiges Label erfasst ist, dann wird stattdessen das Label verwendet.
Auch für die Direkthilfe kann in der Data-Description ein zweiter Text erfasst werden. Die „Direkthilfe für das vollständige Label“ wird in Cockpits verwendet.
4.5.2 Factories
Für alle primitiven Datentypen und die Special-Parts werden entsprechende Factories durch die Infrastruktur bereitgestellt. Für alle anderen Datentypen und insbesondere für virtuelle Spalten, berechnete Spalten und Business Objects müssen geeignete Factories implementiert und in der Data-Description angegeben werden.
Wenn eine Spalte anzeigbar ist, dann muss entweder eine „FormatFactory“ oder eine „RendererFactory“ bereitgestellt werden. Für Attribute, die filterbar sind, ist eine „FilterExpressionEditorFactory“ notwendig.
4.6 Proxy-Business-Objects
In Cockpits werden Suchen verwendet. In bestimmten Fällen kann nützlich sein, Informationen aus der Repository- oder Konfigurationsdatenbank in einer Suche zu verwenden. Beispielsweise kann mit diesen Informationen dem Benutzer mitgeteilt werden, welcher Benutzer ein Objekt erfasst oder als letzter geändert hat. Eher technisch relevant ist die Anzeige der verwendeten Version eines Entwicklungsobjekts.
Aus technischen Gründen ist kein „JOIN“ über Business Objects aus verschiedenen Datenbanken möglich. Daher stehen Proxy-Business Objects zur Verfügung. „Proxy-Business-Objects“ sind auf der OLTP-Datenbank die Stellvertreter für Business Objects aus der Repository-Datenbank und der Konfigurations-Datenbank.
Mithilfe einer Datenaktualisierung können diese Objekte auf der OLTP-Datenbank aktualisiert werden. Auf Proxy-Business-Objects kann und darf keine Logik beruhen. Sie dienen allein der Darstellung in Cockpits.
Da Proxy–Business-Objects außer für eher technische Analysen nicht verwendet werden, müssen die Daten nicht notwendigerweise gefüllt sein. Daher sollte jede Verwendung in Suchen als „REMOVABLE LEFT OUTER JOIN “ ausgeprägt sein (siehe Kapitel „REMOVABLE JOINs“).
Proxy-Business-Objects haben denselben Primärschlüssel wie ihre jeweilige Entsprechung. Weiterhin sind sie auf einige zur Anzeige relevante Attribute reduziert.
4.6.1 Konfigurations-Objekte
Namensraum: com.cisag.sys.configuration.obj
Original | Proxy |
User | ProxyUser |
UserGroup | ProxyUserGroup |
UserGroupAssignment | ProxyUserGroupAssignment |
4.6.2 Repository-Objekte
Namensraum: com.cisag.sys.repository.internal.obj
Original | Proxy |
ObjectDirEntry | ProxyObjectDirEntry |
Version | ProxyVersion |
4.6.3 Datenaktualisierung
Die Proxy-Business-Objects können mit der Anwendung „Proxy-Business-Objects aktualisieren“ (com.cisag.sys.update.log.UPDProxyBOs) aktualisiert werden. Diese Anwendung ist eine wiederholbare Datenaktualisierung.
5 Java-Programmierschnittstellen
In diesem Kapitel werden die für Cockpits relevanten Java-Klassen beschrieben. Weitere Informationen zu einzelnen Methoden finden Sie auch in der „JavaDoc“ der jeweiligen Klassen.
5.1 CisCustomizableCockpit
Alle Cockpits müssen von folgender abstrakten Basisklasse abgeleitet werden:
com.cisag.pgm.base.CisCustomizableCockpit
Diese Klasse erweitert folgende Klasse und bietet verschiedene Hook- und Template-Methoden, die von den konkreten Subklassen geändert werden können:
com.cisag.pgm.base.CisUiApplication
Hinweis:
Für die Implementierung von belegbezogenen Cockpits steht mit der Klasse „AbstractOrderCockpit“ eine spezielle Unterklasse zur Verfügung, welche die Entwicklung dieser Cockpits vereinfacht und standardisiert (siehe Kapitel „AbstractOrderCockpit“).
5.1.1 Hook-Methoden
Die folgenden Methoden werden von der Infrastruktur bei bestimmten Ereignissen oder zu bestimmten Zeitpunkten aufgerufen. Durch Ändern dieser Methoden kann ein Cockpit die Standardfunktionalität anpassen und ergänzen.
Methode | Erläuterung |
getSearchContext() | Cockpits müssen diese Methode ändern, damit den Suchfeldern ein geeigneter Suchkontext zur Verfügung steht. |
getDefaultCockpitSearchName() | @Deprecated V5R0M0-PD-03
Die Verknüpfung einer Cockpit-Anwendung mit einer Suche erfolgt seit V5R0M0-PD-03 direkt in dem Entwicklungsobjekt „Anwendung“. Das Ändern dieser Methode ist daher nur noch dann notwendig, wenn in der Anwendung keine Suche eingetragen wurde. In diesem Fall ist der vollqualifizierte Name der Suche zurückzugeben. |
preInit() | Wird einmalig aufgerufen, bevor die interne Initialisierung beginnt. |
postInit() | Wird einmalig aufgerufen, nachdem die interne Initialisierung abgeschlossen ist. Cockpits sollten diese Methode ändern, um eigene Initialisierungen durchzuführen (z. B. Aktionen für Hintergrundverarbeitungen registrieren, siehe Kapitel „Aktionen registrieren“). |
preSearch() | Wird aufgerufen, bevor eine Suche auf der Datenbank ausgeführt wird. Cockpits können diese Methode ändern, um die Suchmerkmale zu ändern (z. B. Werte für programmierte Filter setzen).
Hinweis: Anstatt diese Methode zu ändern, kann im Entwicklungsobjekt „Suche“ auch eine Implementierung von „com.cisag.pgm.search.SearchHook“ registriert werden (siehe Kapitel „Hook“). Diese Vorgehensweise ist besonders dann empfehlenswert, wenn eine Suche auch in einem anderen Kontext verwendet werden kann. |
postSearch() | Wird aufgerufen, nachdem die Suche auf der Datenbank ausgeführt wurde. Cockpits können diese Methode ändern, um auf Informationen aus dem Suchergebnis zu reagieren. |
validateFilter() | Cockpits können diese Methode ändern, um zusätzliche Prüfungen für Filter zu implementieren. Die Filter werden automatisch vor dem Ausführen einer Suche geprüft.
Benutzer können eine Prüfung explizit anfordern, indem sie auf den „Prüfen“-Button drücken. Entwickler können die Methode „validateFilter()“ an der Klasse „GridSearchSupport“ aufrufen, um eine Prüfung zu veranlassen (siehe Kapitel „GridSearchSupport“). Die Methode muss „true“ zurückliefern, wenn bei der Prüfung keine Fehler festgestellt wurden. Wenn Fehler festgestellt wurden, dann muss die Methode „false“ zurückliefern und vorher entsprechende Meldungen über „GridSearchSupport#sendMessage()“ ausgeben. |
clearResult() | Wird aufgerufen, nachdem das Suchergebnis gelöscht wurde. Cockpits können diese Methode ändern, um auf Änderungen an dem Suchergebnis zu reagieren. Das Suchergebnis wird automatisch vor jeder neuen Suche gelöscht.
Benutzer können das Suchergebnis löschen, indem sie auf den „Neu“-Button drücken. Entwickler können die Methode „clearResult()“ an der Klasse „GridSearchSupport“ aufrufen, um das Suchergebnis zu löschen (siehe Kapitel „GridSearchSupport“). |
resetFilter() | Wird aufgerufen, nachdem alle Filter auf ihre Vorschlagswerte zurückgesetzt wurden. Cockpits können diese Methode ändern, um eigene Vorschlagswerte zu setzen, jedoch sollten die ursprünglichen Vorschlagswerte nicht geändert werden. Eine automatische Initialisierung der Filter erfolgt nur bei der Aktion „Leer öffnen“.
Benutzer können die Filter explizit auf ihre Vorschlagswerte zurücksetzen, indem sie auf den „Neu“-Button drücken. Entwickler können die Methode „resetFilter()“ an der Klasse „GridSearchSupport“ aufrufen, um die Filter zurückzusetzen (siehe Kapitel „GridSearchSupport“). |
5.1.2 Weitere Methoden
Methode | Erläuterung |
setExecuteActions(List<Action>) | Alle über diese Methode registrierten Actions werden in der Standard-Symbolleiste in dem für Cockpits gebräuchlichen Menübutton „Aktion wählen“ zusammengefasst. Der Aufruf dieser Methode sollte einmal bei der Initialisierung der Anwendung erfolgen (siehe Methode „postInit()“). |
load(boolean record) | Über diese Methode kann das Öffnen (Aktualisieren) der Objekte direkt per Programmierung ausgelöst werden. |
refreshUi() | Über diese Methode kann die Anzeige aktualisiert werden, ohne die Objekte neu zu öffnen. |
getGridSearchSupport() | Diese Methode liefert eine Instanz der Klasse „GridSearchSupport“ zurück, die einen erweiterten Zugriff auf die Konfiguration und die Steuerung erlaubt. Siehe dieses Kapitel: GridSearchSupport |
getBatchApplicationParameters() | Liefert die Objekte zurück, die an eine Hintergrundverarbeitung übergeben werden müssen. |
5.1.3 Aktionen und Hintergrundverarbeitung
Massenoperationen sollten grundsätzlich immer als Hintergrund-Anwendung implementiert werden und nicht in einem Dialog ausgeführt werden. Nur so kann sichergestellt werden, dass keine Probleme mit dem Speicherbedarf entstehen und der Benutzer nicht zu lange warten muss, bis das System wieder eingabebereit ist. Außerdem erlauben nur die Hintergrund-Anwendungen, dass alle Objekte bearbeitet werden, die den Suchmerkmalen entsprechen, während die Auswahl auf eine maximale Anzahl von Objekten beschränkt wird (z. B. 1000).
Die folgenden Code-Ausschnitte zeigen, wie eigene Actions so registriert werden, dass sie
- in der Standard-Symbolleiste in den Aktionen-Button aufgenommen werden,
- die aktuellen Suchmerkmale und die Benutzerauswahl als Parameter an eine Hintergrund-Anwendung übergeben und diese Parameter in der Hintergrund-Anwendung verwendet werden, um über die ausgewählten Objekte zu iterieren.
Siehe Kapitel „Hintergrund-Anwendungen“.
Aktionen registrieren
@Override
protected void postInit() {
Action action1 = new Action(…);
Action action2 = new Action(…);
List<Action> actions = new ArrayList<Action>();
actions.add(testBatch1Action);
actions.add(testBatch2Action);
setExecuteActions(actions);
}
Parameter an Hintergrund-Anwendung übergeben
protected void performAction(Action action) {
switch (action.getId());
case …
CisBatchJob batch = …
CisParameterList params = getBatchApplicationParameters();
batch.setParameters(params);
:
:
}
ResultSet in Hintergrund-Anwendung abfragen
public CisParameterList run(int action, CisParameterList parms) {
CisBatchJobSearchStatement stmt =
om.getBatchJobSearchStatement(parms);
CisResultSet rs = stmt.execute();
int position = stmt.getResultPosition(“bookGuid”);
try {
while (rs.next()) {
byte[] guid = rs.getGuid(position);
:
}
} catch (SQLException ex) {
throw new RuntimeException(ex);
} finally {
rs.close();
}
:
}
5.2 AbstractOrderCockpit
Cockpits für „Belege“ (u. a. Aufträge, Anfragen, Rechnungen, …) sollten diese Klasse als Basis verwenden:
com.cisag.app.general.cockpit.ui.AbstractOrderCockpit
Diese Klasse stellt spezielle Unterstützung für die folgenden Funktionen bereit:
- Basis und Positionen
- Belegkette
- Berechtigungen (inhaltsbezogene Berechtigungen)
- Löschkennzeichen
- SearchContext
- Detailsuchen
Beispiele für die Verwendung dieser Klasse sind die Implementierungen für die folgenden Cockpits:
- cisag.app.sales.cockpit.ui.CustomerProposalCockpitBase
- cisag.app.sales.cockpit.ui.CustomerProposalCockpitDetail
- cisag.app.purchasing.cockpit.ui.RequestForProposalCockpitBase
5.3 SearchView
Mithilfe der Klasse „com.cisag.pgm.search.gui.SearchView“ kann die Funktionalität der Cockpits als Komponente in andere Anwendungen integriert werden. Die Schnittstelle und die Verwendung dieser Klasse sind vergleichbar mit der Klasse „CisCustomizableCockpit“ mit folgenden Unterschieden und Erweiterungen:
- Die Klasse stellt eine konkrete Klasse dar, die auch direkt, d. h. ohne Ableiten einer eigenen Klasse, genutzt werden kann. Ein Ableiten ist nur notwendig, wenn Methoden wie „preSearch()“ geändert werden sollen.
- Die Verknüpfung mit dem zugehörigen Entwicklungsobjekt „Suche“ erfolgt über den Aufruf dieser Methode: init(String searchName)
- Über folgende Methoden kann der Komponente eine Überschrift zugewiesen werden: setTitle(String) und setTitleFrom(…)
- Mit folgender Methode legen Sie fest, ob die Komponente einen Abfragebereich zur Verfügung stellt: setAllowFilter(boolean)
- Detailsuchen werden nicht angeboten.
- Über folgender Methode kann gesteuert werden, wo die zu dieser Komponente definierten Ansichten (Layouts) gespeichert werden: sollen.setStorageLocation(StorageLocation)
Zur Auswahl stehen diese Werte:
- SEARCH
StorageLocation.SEARCH ist der Standardwert und bedeutet, dass die Ansichten in Bezug auf das Entwicklungsobjekt „Suche“ gespeichert werden. In diesem Fall werden die gespeicherten Ansichten von allen Cockpits und SearchViews gemeinsam verwendet, die sich auf dasselbe Entwicklungsobjekt Suche beziehen.
- APPLICATION.
SearchViews, die als Editor in einer Anwendung mit anpassbarer Bedienungsoberfläche verwendet werden, können alternativ den Wert StorageLocation.APPLICATION benutzen. In diesem Fall werden die Ansichten als Teil der jeweiligen Anwendungsansichten gespeichert und sind unabhängig von anderen Verwendungen des Entwicklungsobjekts „Suche“.
5.4 GridSearchSupport
Die Schnittstelle „com.cisag.pgm.search.gui.GridSearchSupport“ erlaubt die Konfiguration der Suche sowie das Abfragen von Suchergebnis und Auswahl.
Konfiguration
Methode | Erläuterung |
getSearch() | Liefert den vollqualifizierten Namen der aktuellen Hauptsuche. |
setSearch(String) | Erlaubt die Neuinitialisierung mit einer neuen Suche. |
addDetailSearch(…) | Fügt eine weitere Detailsuche hinzu (siehe Kapitel „Detailsuchen“). |
addHeaderElement(VisualElement) | Fügt ein VisualElement in die Symbolleiste über der Ergebnisliste ein. |
getShowFilterPanel()
setShowFilterPanel() |
Legt fest, ob der Abfragebereich angezeigt wird. |
isMultipleRowSelectionEnabled()
setMultipleRowSelectionEnabled() |
Legt fest, ob eine Mehrfachauswahl erlaubt ist. Die Einstellung gilt immer nur für die Hauptsuche. Wenn die Mehrfachauswahl erlaubt ist, dann wird jeweils am Zeilenanfang eine Checkbox angezeigt, mit der die einzelnen Zeilen vom Benutzer ausgewählt werden können. |
getAllowEditFilterExpression()
setAllowEditFilterExpression() |
Legt fest, ob die Benutzer den Filterausdruck für eine bestimmte Spalte ändern dürfen. |
getDoubleClickAction()
setDoubleClickAction() |
Erlaubt die Registrierung einer Action, die bei Doppelkick auf eine Zeile ausgeführt wird. |
getRendererFactory()
setRendererFactory() |
Erlaubt das Ändern der über den logischen Datentyp vorgegebenen „RendererFactory“ für eine bestimmte Spalte. |
getFilterExpressionEditorFactory()
setFilterExpressionEditorFactory() |
Erlaubt das Ändern der über den logischen Datentyp vorgegebenen „FilterExpressionEditorFactory“ für eine bestimmte Spalte. |
getSearchContextProvider()
setSearchContextProvider() |
Erlaubt das Ändern von „SearchContextProvider“ für die Suche.
Hinweis: „SearchContextProvider“ wird üblicherweise von der Anwendung zur Verfügung gestellt (durch Implementieren dieser Schnittstelle). |
Filterung
Methode | Erläuterung |
getFilterExpression()
setFilterExpression() |
Erlaubt das Lesen oder Setzen von Filterausdrücken für eine bestimmte Spalte. |
getFilterExtension()
setFilterExtension() |
Erlaubt das Lesen oder Setzen von speziellen Filtererweiterungen für die Hauptsuche oder eine bestimmte Detailsuche (siehe Kapitel „FilterExtension“). |
resetFilter() | Setzt alle Filterausdrücke auf die Vorschlagswerte zurück.
Hinweis: Die Vorschlagswerte werden über die Ansicht für den Filterbereich festgelegt. Zusätzlich wird die gleichnamige Hook-Methode an der Klasse „CisCustomizableCockpit“ aufgerufen, die ggf. weitere Änderungen an den Filterausdrücken vornehmen kann. Siehe dieses Kapitel: CisCustomizableCockpit |
validateFilter() | Prüft alle Filterausdrücke auf ihre syntaktische Gültigkeit.
Hinweis: Zusätzlich wird die gleichnamige Hook-Methode an der Klasse „CisCustomizableCockpit“ aufgerufen, die ggf. weitere Prüfungen an den Filterausdrücken vornehmen kann. Siehe dieses Kapitel: CisCustomizableCockpit |
sendMessage(CisMessage, String…) | Erlaubt das Senden von Meldungen für eine oder mehrere Spalten. Diese Methode ist zu verwenden, wenn Meldungen aus der eigenen Implementierung von validateFilter()ausgegeben werden sollen. |
Suchergebnis
Methode | Erläuterung |
clearResult() | Löscht die Ergebnisliste. |
getRowCount() | Liefert die Anzahl der gefundenen Objekte (nur Hauptsuche) |
getBatchApplicationParameters() | Liefert die Objekte zurück, die an eine Hintergrundverarbeitung übergeben werden müssen.
Hinweis: Die Klasse „CisCustomizableCockpit“ ruft intern diese Methode auf und liefert dasselbe Ergebnis. Siehe dieses Kapitel: CisCustomizableCockpit |
Auswahl
Methode | Erläuterung |
clearSelection() | Setzt die aktuelle Auswahl zurück. |
selectAll() | Wählt alle angezeigten Objekte der Hauptsuche aus.
Hinweis: Wenn die Mehrfachauswahl nicht erlaubt ist, dann hat die Methode keine Auswirkungen. |
getFocusedDetailSearch() | Liefert den Namen (Alias) der Detailsuche, die zu der aktuell fokussierten Zeile gehört. Die Methode liefert null, wenn sich der Fokus in der Hauptsuche befindet. |
getFocusedRowData(String…) | Liefert die Werte der angegebenen Spalten für die aktuell fokussierte Zeile.
Hinweis: Die angegebenen Spalten müssen zu der aktuell fokussierten Suche passen (siehe: getFocusedDetailSearch()) und im Entwicklungsobjekt „Suche“ als „Rückgabeschlüssel“ gekennzeichnet sein (siehe Kapitel „Checkbox: Rückgabeschlüssel“). |
getSelectedRowCount() | Liefert die Anzahl der aktuell ausgewählten Objekte.
Hinweis: Die Auswahl bezieht sich immer auf die Hauptsuche. Wenn für die Hauptsuche keine Mehrfachauswahl erlaubt ist, dann entspricht die Auswahl immer der aktuell fokussierten Zeile. |
getSelectedRowData(String…) | Liefert die Werte der angegebenen Spalten für die aktuell ausgewählten Zeilen.
Hinweis: Die Auswahl bezieht sich immer auf die Hauptsuche. Die angegebenen Spalten müssen im Entwicklungsobjekt „Suche“ als „Rückgabeschlüssel“ gekennzeichnet sein (siehe Kapitel „Checkbox: Rückgabeschlüssel“). |
Hinweis:
Bei allen Methoden der Klasse „GridSearchSupport“, die einen Spaltennamen als Parameter erwarten, muss der Spaltenname um den Präfix der Detailsuche erweitert werden, wenn sich der Spaltenname auf eine Detailsuche bezieht. Beispiel: detail:guid
5.4.1 Filterung von Objekten
Die Filterung von Objekte ist sowohl durch den Benutzer als auch durch die Anwendung möglich. Die Spalten, die ausschließlich von der Anwendung zur Filterung genutzt werden sollen, müssen in der Suche als „Programmierte Filter“ gekennzeichnet werden (siehe Kapitel „Checkbox: Programmierter Filter“).
5.4.1.1 Vorschlagswerte
Die Vorschlagswerte für Filter sollten möglichst immer über den Abfragebereich festgelegt und gespeichert werden und nicht in der Anwendung programmiert werden, da programmierte Vorschlagswerte mit den benutzerdefinierten Abfragebereichen und -mustern in Konflikt geraten können. Die Anwendung sollte vorhandene Vorschlagswerte nicht ändern.
Beispiel
@Overwrite
protected void resetFilter() {
// Initialisierung mit gespeicherten Werten
super.resetFilter();
GridSearchSupport gss = getGridSearchSupport();
// bereits definierte Werte nicht ändern
if (gss.getFilterExpression(“partnerNumber”) == null) {
gss.setFilterExpression(“partnerNumber”,
StringFilterExpressionList.create(
“com.cisag.app.edu:CockpitEmployeeNumber.lt”, “A*”));
}
}
Hinweis:
Die Vorschlagswerte sollten immer mit der Methode „resetFilter“ der Klasse „CisCustomizableCockpit“ oder „SearchView“ gesetzt werden, damit die Benutzer sie als solche erkennen und die vorgeschlagenen Werte ändern können (siehe Methode „resetFilter()“).
Wir empfehlen, mit dem Entwicklungsobjekt „Suche“ auch einen Abfragebereich zu speichern, der geeignete Vorschlagswerte enthält. Die Vorschlagswerte sollten sinnvoll für die Benutzer sein und eine möglichst geringe Datenbankauslastung verursachen.
5.4.1.2 Manipulation der Datenbankabfrage
Die Erfüllung bestimmter Anforderungen benötigt die gezielte Manipulation einer Datenbankabfrage. Zu diesen Anforderungen zählen beispielsweise die Durchsetzung von Berechtigungen oder das Suchen eines Ausdrucks in mehreren Spalten (ODER-verknüpft).
In einfachen Fällen lassen sich solche Anforderungen bereits mit als „Programmierte Filter“ gekennzeichnete Spalten lösen (siehe Kapitel „Checkbox: Programmierter Filter“). Für die Umsetzung von komplexeren Anforderungen kann zusätzlich ein Filterausdruck definiert werden (siehe Kapitel „FilterExtension“).
Beim Setzen von programmierten Filtern oder einer „FilterExtension“ ist wichtig, wann das endgültige Ergebnis der Datenbankabfrage bereitgestellt wird. Für Anwendungen bestehen folgende Möglichkeiten die Datenbankabfrage zum richtigen Zeitpunkt zu manipulieren:
- das Ändern der Methode preSearch() der Klasse CisCustomizableCockpit (oderSearchView)
- das Implementieren eines SearchHooks
Die Implementierung eines SearchHookss bietet den Vorteil, dass die darin ausgelagerte Funktionalität für alle Verwender des Entwicklungsobjekts „Suche“ gilt.
5.4.2 Detailsuchen
Mit einer Hauptsuche können beliebig viele Detailsuchen verknüpft werden und mit jeder Detailsuche können wiederum beliebig viele andere Detailsuchen verknüpft werden. Diese Art von Verknüpfung lässt sich über beliebig viele Ebenen fortsetzen.
In der Praxis sollte die Gesamtanzahl der Detailsuchen jedoch nicht mehr als fünf betragen, weil für jede Detailsuche zusätzlicher Platz im Filter- und Anzeigebereich des Cockpits benötigt wird. Darüber hinaus wird die Datenbank mit jeder Detailsuche zusätzlich belastet, da für jedes Objekt der jeweils übergeordneten Suche eine zusätzliche Datenbankabfrage für die Detailsuche durchgeführt werden muss. Für eine Hauptsuche mit nur einer Detailsuche sind also bereits 1+N Datenbankabfragen notwendig, wobei N die Anzahl der gefundenen Objekte ist. Für eine Hauptsuche mit M parallelen Detailsuchen sind demnach 1+NM Datenbankabfragen notwendig. Noch stärker wächst die Zahl der Datenbankabfragen bei ineinander geschachtelten Detailsuchen. Die Verwendung von mehr als einer Detailebene ist daher zu vermeiden.
Die Datenbankabfrage und der Spaltenvorrat von Detailsuchen werden über eigene Entwicklungsobjekte des Typs „Suche“ festgelegt. Die Verknüpfung zwischen „Hauptsuche“ und Detailsuche erfolgt über die Methode addDetailSearch() der Klasse GridSearchSupport:
GridSearchSupport.addDetailSearch(
String parent, // null für Hauptsuche
String alias,
String searchName,
Action action,
String[] parentColumnNames,
String[] detailColumnNames)
Parameter | Erläuterung |
parent | Als parent ist immer der Alias der übergeordneten Suche anzugeben. Wird eine Detailsuche direkt mit der Hauptsuche verknüpft, dann ist für parent der Wert null zu verwenden. |
alias | Für jede hinzugefügte Detailsuche ist ein eindeutiger alias zu vergeben. |
searchName | Als searchName ist der vollqualifizierte Name des zugehörigen Entwicklungsobjekts „Suche“ anzugeben. |
action | Als action ist eine Instanz von com.cisag.pgm.gui.Action zu übergeben, die den Text für den Button zum Ein- und Ausblenden der Detailsuche liefert. |
parentColumnNames | Als parentColumnNames ist ein Array mit den Spaltennamen aus der übergeordneten Suche zu übergeben, über welche die Verknüpfung mit der Detailsuche erfolgen soll. Die spezifizierten Spalten müssen in der übergeordneten Suche als „Rückgabeschlüssel“ gekennzeichnet sein (siehe dieses Kapitel: Checkbox: Rückgabeschlüssel). |
detailColumnNames | Als detailColumnNames ist ein Array mit den Spaltennamen aus der Detailsuche zu übergeben, über welche die Verknüpfung mit der übergeordneten Suche erfolgen soll. Die spezifizierten Spalten müssen in der Detailsuche als „Rückgabeschlüssel“ gekennzeichnet sein (siehe dieses Kapitel: Checkbox: Rückgabeschlüssel). Zudem müssen die spezifizierten Spalten in der Detailsuche als „Filterbar“ gekennzeichnet sein (siehe dieses Kapitel: Checkbox: Filterbar) und sollten darüber hinaus auch als „Programmierte Filter“ gekennzeichnet werden (siehe dieses Kapitel: Checkbox: Programmierter Filter).
Die Anzahl und die Datentypen der spezifizierten Spalten müssen mit der Anzahl und den Datentypen der in parentColumnNames spezifizierten Spalten übereinstimmen. |
Beispiel
Das folgende Beispiel zeigt eine Hauptsuche, die mit einer Detailsuche verknüpft wird. Diese Detailsuche wird anschließend mit einer weiteren Detailsuche verknüpft.
Hauptsuche (com.example.search.Main):
Name | Ausdruck | Programm. Filter |
Filter- bar |
Rückgabe-schlüssel |
guid | M:guid | x | ||
code | M:name | |||
description | M:description |
Detailsuche 1 (com.example.search.Detail):
Name | Ausdruck | Programm. Filter |
Filter- bar |
Rückgabe-schlüssel |
guid | D:guid | x | ||
parent | D:parent | x | x | x |
code | D:name | |||
description | D:description |
Detailsuche 2 (com.example.search.Subdetail):
Name | Ausdruck | Programm. Filter |
Filter- bar |
Rückgabe-schlüssel |
guid | S:guid | x | ||
parent | S:parent | x | x | x |
code | S:name | |||
description | S:description |
Code:
Action a1 = new Action(0, “com.example.search.DetailAction1”);
Action a2 = new Action(0, “com.example.search.DetailAction2”);
String[] parentColumnNames = {“guid”};
String[] detailColumnNames = {“parent”};
GridSearchSupport gss = getGridSearchSupport();
gss.addDetailSearch(null, “D1”,
“com.example.search.Detail”, a1,
parentColumnNames, detailColumnNames);
gss.addDetailSearch(“D1”, “D2”,
“com.example.search.Subdetail”, a2,
parentColumnNames, detailColumnNames);
5.5 Format
Die abstrakte Klasse „java.text.Format“ dient zur Umwandlung von Objekten in eine (ggf. sprach- und regionsspezifische) Textrepräsentation (Formatierung) sowie der Rückwandlung solcher Texte in Objekte (Parsing). Konkrete Subklassen müssen mindestens die folgenden beiden Methoden implementieren:
public StringBuffer format(Object obj, StringBuffer toAppendTo,
FieldPosition pos);
public Object parseObject(String source, ParsePosition pos);
Die Cockpit-Infrastruktur nutzt Unterklassen der Klasse „Format“ für die Visualisierung der Daten im Anzeigebereich, für den Export (PDF/MS Excel) und auch für einige „FilterExpressionEditoren“. Für die Erzeugung von Format-Instanzen ist die Klasse „FormatFactory“ zuständig, deren Implementierung an der Data-Description der jeweiligen Spalte registriert werden muss (siehe Kapitel „FormatFactory“).
Die Format-basierten Implementierungen werden jedoch nicht generell für alle Datentypen genutzt, sondern nur dann, wenn für den jeweiligen Datentyp eine Textrepräsentation sinnvoll und erwünscht ist. Anderenfalls werden stattdessen Renderer oder speziellere FilterExpressionEditoren verwendet. Welche Implementierung für eine Spalte genutzt wird, entscheiden die am logischen Datentyp hinterlegten Factories.
5.5.1 FormatFactory
Instanzen der Klasse „Format“ werden von der Cockpit-Infrastruktur grundsätzlich über spezielle Factory-Klassen erzeugt. Die Factory-Klassen müssen dazu die Schnittstelle „com.cisag.pgm.gui.FormatFactory“ implementieren und über einen Default-Konstruktor verfügen. Die Schnittstelle definiert die folgende Methode:
public Format createFormat(DataDescription dataDescription);
Konkrete Klassen, die diese Schnittstelle implementieren, sollten die Informationen aus der übergebenen Data-Description nutzen, um die erzeugte Instanz der Format-Klassen zu parametrisieren.
Die Registrierung einer „FormatFactory“ erfolgt durch Eintragung ihres vollqualifizierten Klassennamens in der Data-Description zu einem logischen Datentyp.
Hinweis:
Für einen logischen Datentyp (oder eine Data-Description) kann nicht nur eine FormatFactory, sondern auch eine RendererFactory registriert werden (siehe Kapitel „RendererFactory“). Wenn für einen logischen Datentyp sowohl eine FormatFactory als auch eine RendererFactory registriert wurde, dann wird die FormatFactory ignoriert. Dies gilt auch dann, wenn die Factories auf unterschiedlichen Hierarchieebenen registriert wurden.
5.5.2 FormatFactoryRegistry
Über die Klasse „com.cisag.pgm.gui.FormatFactoryRegistry“ können zu einem logischen Datentyp die passenden Instanzen von Format (siehe Kapitel „Format“) bzw. FormatFactory (siehe Kapitel „FormatFactory“) erzeugt werden. Der logische Datentyp kann dabei über seinen vollqualifizierten Pfad oder seine GUID spezifiziert werden. Bei der Auflösung durchsucht die Klasse die Vererbungskette des logischen Datentyps bis zu dem Wurzeldatentyp. Die erste dabei gefundene FormatFactory wird zurückgeliefert und für die Erzeugung einer Format–Instanz verwendet. Wenn für einen logischen Datentyp keine Registrierung vorliegt, dann wird null zurückgeliefert.
In der FormatFactoryRegistry sind FormatFactories für fast alle primitiven Datentypen sowie für alle Zeit- und Datumswerte registriert.
Die FormatFactoryRegistry wird insbesondere von der Cockpit-Infrastruktur selbst genutzt. Sie kann darüber hinaus beispielsweise auch dazu verwendet werden, eine bereits vorhandene Implementierung in einer eigenen FormatFactory wiederzuverwenden (Delegation).
5.5.3 Virtuelle Spalten
Bei virtuellen Spalten (siehe Kapitel „Checkbox: Virtuell“) erfolgt die Übergabe der Attributwerte an den Formatter als java.util.Map, wobei die relativen Namen der Unterattribute als Schlüssel genutzt werden.
Beispiel:
Eine virtuelle Spalte mit dem Namen vattr verfügt über die beiden Unterattribute vattr.attr1 und vattr.attr2 und alle Attribute sind als anzeigbar gekennzeichnet. Im Formatter sollte der Zugriff auf die Werte dieser Unterattribute folgendermaßen erfolgen:
public public StringBuffer format(Object value,
StringBuffer toAppendTo, FieldPosition pos) {
if (value instanceof Map) {
Map values = (Map) value;
Object attr1 = values.get(“attr1”);
Object attr2 = values.get(“attr2”);
…
}
return toAppendTo;
}
Hinweis:
In der Map sind nur die als anzeigbar gekennzeichneten Unterattribute enthalten. Die Werte der Attribute können auch null sein.
5.6 Renderer
Wenn die Visualisierung als Text nicht genügt oder die Klasse „Format“ aus anderen Gründen nicht verwendet werden kann, dann kann ein spezieller „Renderer“ implementiert werden. Im Unterschied zu der Klasse „Format“ können mit Renderern nicht nur Textrepräsentationen erzeugt werden, sondern auch Icons, Tooltips, Links (URIs) und Instanzen der Klasse „VisualElement“. Jede dieser Visualisierungsarten basiert auf einer eigenen Schnittstelle. Konkrete Implementierungen eines Renderers können diese Schnittstellen bei Bedarf auch kombinieren (z. B. IconRenderer kombiniert mit ToolTipRenderer).
Für die Erzeugung von Renderer-Instanzen ist die Klasse RendererFactory (siehe dieses Kapitel: RendererFactory) zuständig, deren Implementierung an der Data-Description der jeweiligen Spalte registriert werden muss.
5.6.1 RendererFactory
Instanzen von Renderer werden von der Cockpit-Infrastruktur grundsätzlich über spezielle Factory-Klassen erzeugt. Die Factory-Klassen müssen dazu das Schnittstelle „com.cisag.pgm.gui.RendererFactory“ implementieren und über einen Default-Konstruktor verfügen. Die Schnittstelle definiert die folgende Methode:
public Renderer createRenderer(byte[] guid,
DataDescription dataDescription)
Konkrete Klassen, die diese Schnittstelle implementieren, sollten die Informationen aus der übergebenen Data-Description nutzen, um die erzeugte Instanz von Renderer zu parametrisieren.
Die Registrierung einer RendererFactory erfolgt durch Eintragung ihres vollqualifizierten Klassennamens in der Data-Description zu einem logischen Datentyp.
Hinweis:
Für einen logischen Datentyp (oder eine Data-Description) kann nicht nur eine RenderFactory, sondern auch eine FormatFactory registriert werden (siehe Kapitel „FormatFactory“). Wenn für einen logischen Datentyp sowohl eine RendererFactory als auch eine FormatFactory registriert wurde, dann wird die FormatFactory ignoriert. Dies gilt auch dann, wenn die Factories auf unterschiedlichen Hierarchieebenen registriert wurden.
5.6.2 RendererFactoryRegistry
Über die Klasse „com.cisag.pgm.gui.RendererFactoryRegistry“ können zu einem logischen Datentyp die passenden Instanzen von RendererFactory erzeugt werden. Bei der Auflösung durchsucht die Klasse die Vererbungskette des logischen Datentyps bis zu dem Wurzeldatentyp. Die erste dabei gefundene RendererFactory wird zurückgeliefert. Wenn für einen logischen Datentyp keine Registrierung vorliegt, dann wird null zurückgeliefert.
In der RendererFactoryRegistry sind spezielle RendererFactories für den primitiven Datentyp Boolean, das Löschkennzeichen (com.cisag.pgm.datatype.DeleteState) und User-GUIDs registriert.
Die RendererFactoryRegistry wird insbesondere von der Cockpit-Infrastruktur selbst genutzt. Sie kann darüber hinaus beispielsweise auch dazu verwendet werden, eine bereits vorhandene Implementierung in einer eigenen RendererFactory wiederzuverwenden (Delegation).
5.6.3 TextRenderer
Die Schnittstelle „com.cisag.pgm.gui.Renderer.TextRenderer“ erlaubt, ähnlich zu der Klasse „Format“, die Umwandlung eines Objektes in eine Textrepräsentation. Wenn eine reine Textrepräsentation genügt, dann kann statt einem TextRenderer auch eine entsprechende Format-Klasse implementiert werden.
Konkrete Klassen müssen die folgende Methode implementieren:
public String getText(Object value);
Beispiel:
public class SimpleTextRendererFactory
implements RendererFactory {
public Renderer createRenderer(byte[] guid,
DataDescription dataDescription) {
return new SimpleTextRenderer();
}
private class SimpleTextRenderer
implements Renderer.TextRenderer {
public String getText(Object value) {
if (value != null) {
return String.valueOf(value);
}
return null;
}
}
}
5.6.4 IconRenderer
Die Schnittstelle „com.cisag.pgm.gui.Renderer.IconRenderer“ erlaubt die Umwandlung eines Objektes in eine Instanz von „com.cisag.pgm.gui.Icon“.
Konkrete Klassen müssen die folgende Methode implementieren:
public Icon getIcon(Object value);
Beispiel:
public class DocumentKindRendererFactory
implements RendererFactory {
private final Icon iconDocument = Icon.getIcon(…);
private final Icon iconNote = Icon.getIcon(…);
public Renderer createRenderer(byte[] guid,
DataDescription dataDescription) {
return new DocumentKindRenderer();
}
private class DocumentKindRenderer
implements Renderer.IconRenderer {
public Icon getIcon(Object value) {
if (value != null) {
switch ((Short) value) {
case DocumentKinds.DOCUMENT:
return iconDocument;
case DocumentKinds.NOTE:
return iconNote;
}
}
return null;
}
}
}
Hinweis:
Für die Darstellung von Gruppenüberschriften und für den Export wird für jede Spalte auch eine Textrepräsentation benötigt. Daher sollte der IconRenderer mit einem ToolTipRenderer kombiniert werden (siehe Kapitel „ToolTipRenderer“).
5.6.5 LayeredIconRenderer
Die Schnittstelle „com.cisag.pgm.gui.Renderer.LayeredIconRenderer“ erlaubt die Umwandlung eines Objektes in ein Array von „com.cisag.pgm.gui.Icon“-Instanzen, welche bei der Visualisierung übereinander gelegt werden. Bei Verwendung von teiltransparenten Icons können so beispielsweise verschiedene Teilstatus-Icons zu einem Gesamtstatus-Icon kombiniert werden.
Konkrete Klassen müssen die folgende Methode implementieren:
public Icon[] getIcons(Object value);
Hinweis:
Für die Darstellung von Gruppenüberschriften und für den Export wird für jede Spalte auch eine Textrepräsentation benötigt. Daher sollte der LayeredIconRenderer mit einem ToolTipRenderer kombiniert werden (siehe Kapitel „ToolTipRenderer“).
Hinweis:
Das Laden und Rendern von vielen Icons kann die Leistung des Browsers beeinträchtigen.
5.6.6 ToolTipRenderer
Die Schnittstelle „com.cisag.pgm.gui.Renderer.ToolTipRenderer“ erlaubt die Umwandlung eines Objektes in einen Text, der im Cockpit als Tooltip angezeigt wird.
Konkrete Klassen müssen die folgende Methode implementieren:
public String getToolTip(Object value);
5.6.7 URIRenderer
Die Schnittstelle „com.cisag.pgm.gui.Renderer.URIRenderer“ erlaubt die Umwandlung eines Objektes in eine URI. Im Cockpit werden diese URIs dann als Links angeboten. Bei URIs, die ein Business Entity identifizieren, stellt das Cockpit zudem auch das für dieses Entity übliche Kontextmenü zur Verfügung.
Konkrete Klassen müssen die folgende Methode implementieren:
public URI getUri(Object value);
Beispiel:
public URI getUri(Object value) {
if (value != null) {
CisObject obj = … // parameter “value” in CisObject umwandeln
BusinessEntityLink link = LinkUtility.createBusinessEntityLink(
LinkUtility.DEFAULT_FLAGS, env.getDatabaseGuid(), o);
if (link != null) {
return URI.create(LinkUtility.toUrlString(link));
}
}
return null;
}
5.6.8 VisualElementRenderer
Die Schnittstelle „com.cisag.pgm.gui.Renderer.VisualElementRenderer“ erlaubt die Umwandlung eines Objektes in eine Instanz von „om.cisag.pgm.gui.VisualElement“.
Konkrete Klassen müssen die folgende Methode implementieren:
public VisualElement getVisualElement(Object value);
Hinweis:
Die Verwendung von „VisualElementRenderern“ ist mit vielen Nachteilen verbunden. Hauptnachteil ist der höhere Ressourcenverbrauch, sowohl auf dem Server als auch im Browser. Zudem wird der Datenexport (PDF, Excel, REST) nicht angeboten. „VisualElementRenderer“ können nicht mit anderen Renderern kombiniert werden. „VisualElementRenderer“ können nicht zur Interaktion genutzt werden, d. h. Links, Kontextmenüs usw. funktionieren nicht.
5.6.9 Virtuelle Spalten
Bei virtuellen Spalten (siehe Kapitel „Checkbox: Virtuell“) erfolgt die Übergabe der Attributwerte an den Renderer als java.util.Map, wobei die relativen Namen der Unterattribute als Schlüssel genutzt werden.
Beispiel:
Eine virtuelle Spalte mit dem Namen vattr verfügt über die beiden Unterattribute vattr.attr1 und vattr.attr2 und alle Attribute sind als anzeigbar gekennzeichnet. Im Renderer sollte der Zugriff auf die Werte dieser Unterattribute folgendermaßen erfolgen:
public String getText(Object value) {
if (value instanceof Map) {
Map values = (Map) value;
Object attr1 = values.get(“attr1”);
Object attr2 = values.get(“attr2”);
…
return …
}
return null;
}
Hinweis:
In der Map sind nur die als anzeigbar gekennzeichneten Unterattribute enthalten. Die Werte der Attribute können auch null sein.
5.7 FilterExpression
Alle Filterausdrücke basieren auf der abstrakten und generischen Basisklasse „com.cisag.pgm.search.filter.FilterExpression<T>“. Das Package „com.cisag.pgm.search.filter“ stellt für alle relevanten logischen Datentypen konkrete Subklassen bereit. Diese Subklassen verfügen jeweils über entsprechende statische Factory-Methoden, die für die Erzeugung von Instanzen genutzt werden können.
Hinweis:
Bei der Erzeugung von Filterausdrücken ist immer ein logischer Datentyp anzugeben, wobei der angegebene Datentyp zu dem Datentyp der Spalte passen muss, auf die sich der Filterausdruck bezieht.
5.7.1 FilterExpressionList
Die generische Klasse „com.cisag.pgm.search.filter.FilterExpression
List<T>“ dient als gemeinsame Basisklasse für fast alle Datentypen, deren Filterausdrücke sich durch kommaseparierte Einzeldrücke zusammensetzen lassen. Statt auf einer kommaseparierten Liste von Strings, basiert die Klasse jedoch auf einer „java.util.List“ mit Elementen des Typs „Expression<T>“. Der Typ „Expression<T>“ ist ebenfalls eine generische innere Schnittstelle der Klasse „FilterExpressionList“. Als konkrete Implementierungen dieser Schnittstellen stehen die Klassen „SingleExpression<T>“, „RangeExpression<T>“ und „InvalidExpression<T>“ zur Verfügung, die generische innere Klassen von „FilterExpressionList“ sind.
SingleExpression
Die Klasse „SingleExpression“ dient zur Repräsentation von Filterausdrücken, die aus einem Vergleichsoperator und einem Wert bestehen. Die möglichen Vergleichsoperatoren sind durch den Enum-Typ „ExpressionType“ definiert:
- EQUALS
- UNEQUAL (<>)
- LESS (<)
- GREATER (>)
- LESS_EQUAL (<=)
- GREATHER_EQUAL (>=)
Hinweis:
Einige der aufgeführten Vergleichsoperatoren sind nicht für alle Datentypen zulässig.
RangeExpression
Die Klasse „RangeExpression“ dient zur Repräsentation von Wertebereichen. Ein Wertebereich wird durch einen unteren und einen oberen Wert definiert, wobei einer dieser Werte fehlen kann (offenes Intervall). Der untere Wert (wenn vorhanden) ist immer inklusiv, der obere Wert (wenn vorhanden) kann inklusiv oder auch exklusiv sein (abhängig von dem jeweiligen Datentyp).
InvalidExpression
Mit der Klasse „InvalidExpression“ werden ungültige Filterausdrücke repräsentiert. Sie enthält den ungültigen Wert als String und (optional) eine Liste von Meldungen (CisMessage). Üblicherweise werden solche Instanzen erzeugt, wenn Benutzereingaben bzw. übergebende Strings nicht in einen gültigen Filterausdruck geparst werden konnten.
5.7.1.1 StringFilterExpressionList
Die Klasse „com.cisag.pgm.search.filter.StringFilterExpressionList“ ist eine konkrete Unterklasse von „FilterExpressionList“ und beschreibt Filterausdrücke für Spalten, deren logische Datentypen auf Strings bzw. Texten basieren. Die Klasse verfügt über verschiedene statische Factory-Methoden, mit denen Instanzen dieser Klasse erzeugt werden können.
5.7.1.2 ByteFilterExpressionList, ShortFilterExpressionList, IntegerFilterExpressionList, LongFilterExpressionList und DecimalFilterExpressionList
Die folgenden Klassen aus dem Package „com.cisag.pgm.search
.filter“ sind konkrete Unterklassen von „FilterExpressionList“ und beschreiben Filterausdrücke für Spalten, deren logische Datentypen auf Bytes, Shorts, Ints, Longs bzw. CisDecimal basieren:
- ByteFilterExpressionList
- ShortFilterExpressionList
- IntegerFilterExpressionList
- LongFilterExpressionList
- DecimalFilterExpressionList
Die genannten Klassen verfügen über jeweils verschiedene statische Factory-Methoden, mit denen Instanzen dieser Klassen erzeugt werden können.
Beispiele
Nach exaktem Wert suchen:
FilterExpression expr =
IntegerFilterExpressionList.create(path, 1);
Nach mehreren exakten Werten suchen:
FilterExpression expr =
IntegerFilterExpressionList.create(path, 1, 3, 5);
Nach einem Bereich suchen:
FilterExpression expr =
IntegerFilterExpressionList.createRange(path, 1, 9);
Nach einem Bereich ohne obere Grenze suchen:
FilterExpression expr =
IntegerFilterExpressionList.createRange(path, 1, null);
Nach Werten „ungleich 0“ suchen:
FilterExpression expr = IntegerFilterExpressionList.create(path,
IntegerFilterExpressionList.createSingleExpression(
ExpressionType.UNEQUAL, 0));
Hinweis:
Für Valuesets, die auf der Datenbank ebenfalls als Short abgebildet werden, steht die Klasse „ValueSetFilterExpression“ zur Verfügung (siehe Kapitel „ValueSetFilterExpression“).
Float und Double werden nicht angeboten.
5.7.1.3 DateAndTimeFilterExpressionList
Die Klasse „com.cisag.pgm.search.filter.DateAndTimeFilterExpression
List“ ist eine konkrete Unterklasse von „FilterExpressionList“ und beschreibt Filterausdrücke für Spalten, deren logische Datentypen auf Datumswerten oder Zeitpunkten basieren.
Die Klasse „DateAndTimeFilterExpressionList“ verfügt über verschiedene statische Factory-Methoden, mit denen Instanzen dieser Klasse erzeugt werden können. Die Angabe von Zeit- und Datumswerten kann entweder als normalisierte String-Repräsentation oder durch Instanzen von „com.cisag.pgm.search.filter.DateAndTimeValue“ erfolgen. In beiden Fällen können sowohl konkrete als auch symbolische Werte (z. B. „today“) genutzt werden. Alle Datums- und Zeitangaben sind grundsätzlich auf einen „CisCalendar“ bezogen, dieser kann bei den Factory-Methoden bei Bedarf explizit übergeben werden. Ohne die explizite Angabe wird der „CisCalendar“ intern ermittelt über: CisEnvironment.getInstance().getContext().getCisCalendar()
5.7.1.4 QuantityFilterExpressionList, DurationFilterExpressionList
Die Klassen „com.cisag.pgm.search.filter.QuantityFilterExpression
List“ und „com.cisag.pgm.search.filter.DurationFilterExpressionList“ sind konkrete Unterklassen von „FilterExpressionList“ und beschreiben Filterausdrücke für Spalten, deren logische Datentypen auf Mengenwerten bzw. Zeitspannen basieren.
Die Klassen verfügen jeweils über eine statische Factory-Methode, mit der Instanzen dieser Klassen erzeugt werden können. Die Factory-Methode erwartet eine Liste von „Expression<CisDecimal>“ sowie die GUID einer Mengen- oder Zeiteinheit als Parameter. Die notwendigen Listen von „Expression<CisDecimal>“ lassen sich über verschiedene statische Factory-Methoden erzeugen.
5.7.1.5 DomesticAmountFilterExpressionList, ForeignAmountFilterExpressionList
Die Klassen „com.cisag.pgm.search.filter.DomesticAmountFilter
ExpressionList“ und „com.cisag.pgm.search.filter.ForeignAmountFilter
ExpressionList“ sind konkrete Unterklassen von „FilterExpressionList“ und beschreiben Filterausdrücke für Spalten, deren logische Datentypen auf Beträgen in Haus- oder Fremdwährungen basieren.
Die Klassen verfügen jeweils über eine statische Factory-Methode, mit der Instanzen dieser Klassen erzeugt werden können. Die Factory-Methode erwartet eine Liste von „Expression<CisDecimal>“ sowie die GUID einer Währungseinheit als Parameter. Die Factory-Methode für „DomesticAmountFilterExpressionList“ benötigt zudem noch die GUID einer Organisationseinheit. Die notwendigen Listen von „Expression<CisDecimal>“ lassen sich über verschiedene statische Factory-Methoden erzeugen.
5.7.1.6 BinaryFilterExpressionList, GuidFilterExpressionList
Die Klassen „com.cisag.pgm.search.filter.BinaryFilterExpressionList“ und „com.cisag.pgm.search.filter.GuidFilterExpressionList“ sind konkrete Unterklassen von „FilterExpressionList“ und beschreiben Filterausdrücke für Spalten, deren logische Datentypen auf Binärdaten bzw. GUIDs basieren. Die Klassen verfügen jeweils über verschiedene statische Factory-Methoden, mit denen Instanzen dieser Klassen erzeugt werden können.
5.7.1.7 BooleanFilterExpressionList
Die Klasse „com.cisag.pgm.search.filter.BooleanFilterExpressionList“ ist eine konkrete Unterklasse von „FilterExpressionList“ und beschreibt Filterausdrücke für Spalten, deren logischer Datentyp auf Booleans basiert. Die Klasse verfügt über eine statische Factory-Methode, mit der Instanzen dieser Klasse erzeugt werden können.
5.7.2 CompoundFilterExpression
Die Klasse „com.cisag.pgm.search.filter.CompoundFilterExpression“ wird zum Filtern von virtuellen Spalten benötigt (siehe Kapitel „Checkbox: Virtuell“). Bei virtuellen Spalten erfolgt die Filterung immer auf der der Basis der als filterbar gekennzeichneten Unterattribute. Für jedes zu filternde Unterattribut muss eine, zum Datentyp des Unterattribut passende, „FilterExpression“ erstellt werden. Mithilfe der Klasse „CompoundFilterExpression“ werden die einzelnen „FilterExpression“ in einer einzigen „FilterExpression“ gebündelt. Dazu müssen die einzelnen „FilterExpression“ vorher in einer „java.util.Map“ zusammengefasst werden, wobei die relativen Namen der Unterattribute als Schlüssel zu verwenden sind.
Beispiel:
FilterExpression attr1 = …
FilterExpression attr2 = …
Map<String, FilterExpression> map =
new HashMap<String, FilterExpression>();
map.put(“attr1”, attr1);
map.put(“attr2”, attr2);
String path = …
FilterExpression vattr =
CompoundFilterExpression.create(path, map);
5.7.3 DeleteStateFilterExpression
Die Klasse „com.cisag.pgm.search.filter.DeleteStateFilterExpression“ dient zum Herausfiltern von Objekten mit Löschkennzeichen (siehe Kapitel „Löschkennzeichen“). Objekte mit Löschkennzeichen sind an dem Wert des Attributes „updateInfo.deleteTime“ zu erkennen. Alle Objekte, bei denen dieses Attribut einen gültigen Zeitpunkt enthält, gelten als zum Löschen gekennzeichnet.
Da das Attribut updateInfo.deleteTime einen Zeitstempel aufnimmt, müsste ein Filterausdruck für dieses Attribut eigentlich auf der Klasse „com.cisag.pgm.search.filter.DateAndTimeFilterExpressionList“ basieren. Um die Filterung nach dem Löschkennzeichen zu vereinfachen, wurde zusätzlich die Klasse „DeleteStateFilterExpression“ eingeführt, die sich ähnlich nutzen lässt wie eine „ValueSetFilterExpression“ für Valuesets.
Beispiel:
String path = “com.cisag.pgm.objsearch:DeleteState.lt”;
FilterExpression expression =
DeleteStateFilterExpression.create(path,
DeleteStateFilterExpression.MARKED_FOR_DELETION);
5.7.4 ValueSetFilterExpression
Die Klasse „com.cisag.pgm.search.filter.ValueSetFilterExpression“ ist eine spezielle Unterklasse von „FilterExpression“ zum Filtern von Valuesets. Die Klasse verfügt über eine Factory-Methode, mit der eine Instanz der Klasse über eine Aufzählung von short-Werten erzeugt werden kann.
Ein Filterausdruck mit allen möglichen Werten ist äquivalent zu einer Suche ohne Bedingungen, d. h. der Verwendung von null als Filterausdruck. Jedoch ist zu beachten, dass Valuesets, deren Werte über eine Data-Description reduziert wurden, speziell behandelt werden: Damit garantiert nur die Werte von der Datenbank gelesen werden, die in der Data-Description festgelegt wurden, werden diese festgelegten Werte automatisch in die WHERE-Klausel der Datenbankabfrage aufgenommen. Wenn jedoch sichergestellt ist, dass in der betroffenen Spalte tatsächlich nur die in der Data-Description festgelegten Werte auftreten können, dann sollte die betroffene Spalte über den Hook „BusinessObjectRegistryHook“ als vollständig gekennzeichnet werden (siehe Kapitel „BusinessObjectRegistryHook“). Dadurch kann die in diesem Fall unnötige und teure Erweiterung der WHERE-Klausel vermieden werden.
Beispiel 1: Filterung der Spalte bookFormat nach den Werten Format.CD oder Format.DVD
import com.cisag.app.edu.Format;
…
FilterExpression expr = ValueSetFilterExpression.create(
“com.cisag.app.edu:Format.lt”, Format.CD, Format.DVD);
gridSearchSupport.setFilterExpression(“bookFormat”, expr);
Beispiel 2: Suche ohne Bedingung („Alle“)
gridSearchSupport.setFilterExpression(“bookFormat”, null);
Hinweis:
Wenn die Werte eines Valuesets über die Data-Description reduziert wurden, dann werden automatisch alle in der Data-Description festgelegten Werte in die WHERE-Klausel der Datenbankabfrage aufgenommen.
5.8 FilterExpressionEditor
Um einen Filterausdruck im Cockpit anzeigen oder bearbeiten zu können, wird ein „FilterExpressionEditor“ benötigt. FilterExpressionEditoren sind Klassen, welche die Schnittstelle „com.cisag.pgm.search.gui.FilterExpressionEditor<T>“ implementieren und über eine FilterExpressionEditorFactory erzeugt werden (siehe Kapitel „FilterExpressionEditorFactory“).
Methode | Erläuterung |
VisualElement getVisualElement() | Liefert das vom Editor bereitgestellte VisualElement zurück. Diese Methode wird von der Cockpit-Infrastruktur aufgerufen und das zugelieferte Element wird anschließend dem Abfragebereich des Cockpits hinzufügt. |
void initVisualElement() | Diese Methode kann von dem Editor zur Initialisierung des bereitgestellten Elements genutzt werden. Die Cockpit-Infrastruktur ruft diese Methode direkt nach dem Hinzufügen des vom Editor bereitgestellten Elements auf. |
T getValue(); | Die Aufgabe von getValue() besteht darin, den gerade angezeigten Wert des bereitgestellten VisualElements in eine Instanz von „FilterExpression“ umzuwandeln, die dem Datentyp der zugehörigen Spalte entspricht (siehe dieses Kapitel: FilterExpression).
Hinweis: Die Methode muss null zurückliefern, wenn im bereitgestellten VisualElement kein Filter eingetragen oder „Alle“ ausgewählt wurde. |
setValue(T value); | Die Aufgabe von setValue() besteht darin, die übergebene „FilterExpression“ in dem bereitgestellten VisualElements darzustellen (siehe dieses Kapitel: FilterExpression).
Hinweis: Der Wert null ist gleichbedeutend mit „kein Filter“ bzw. „Alle“. |
Hinweis:
Die Implementierungen der Methoden getValue() und setValue() müssen so aufeinander abgestimmt werden, dass:
- der Aufruf von getValue() nach einem Aufruf von editor.setValue(X) eine FilterExpression liefert, die gleich (equals) zu X ist, solange der Benutzer den Filterausdruck zwischenzeitlich nicht verändert hat,
- der Aufruf von setValue(X) mit einem Filterausdruck X, der irgendwann zuvor mit X = editor.getValue() ermittelt wurde, zu derselben Visualisierung führt, wie zum Zeitpunkt des Aufrufs von editor.getValue().
Beispiel
Hinweis:
Das nachfolgende Beispiel zeigt die Implementierung für einen „FilterExpressionEditor“ für Spalten, die auf einem (beliebigen) Valueset basieren, sowie die Implementierung der zugehörigen „FilterExpressionEditorFactory“. Eine eigene Implementierung für Valuesets ist jedoch nicht erforderlich, da eine entsprechende Implementierung bereits durch die Infrastruktur bereitgestellt wird.
package com.example;
import com.cisag.pgm.datatype.Guid;
import com.cisag.pgm.dialog.model.DataDescription;
import com.cisag.pgm.dialog.model.DefaultDataDescription;
import com.cisag.pgm.gui.ValueSetField;
import com.cisag.pgm.gui.VisualElement;
import com.cisag.pgm.search.filter.ValueSetFilterExpression;
import com.cisag.pgm.search.gui.FilterExpressionEditor;
import com.cisag.pgm.search.gui.FilterExpressionEditorFactory;
public class ValueSetFilterExpressionEditorFactory implements
FilterExpressionEditorFactory {
public ValueSetFilterExpressionEditorFactory() {
}
public FilterExpressionEditor createFilterExpressionEditor(
byte[] guid, DataDescription dataDescription) {
return new ValueSetFilterExpressionEditor(guid,
dataDescription);
}
private class ValueSetFilterExpressionEditor implements
FilterExpressionEditor<ValueSetFilterExpression> {
private final String path;
private final ValueSetField field;
private ValueSetFilterExpressionEditor(byte[] guid,
DataDescription dataDescription) {
this.path =
(String) dataDescription.getValue(DataDescription.PATH);
field = new ValueSetField(Guid.toHexString(guid),
dataDescription);
field.setSelectionMode(true);
}
public VisualElement getVisualElement() {
return field;
}
public void initVisualElement() {
}
public ValueSetFilterExpression getValue() {
short[] values = (short[]) field.getSelection();
if ( values != null && values.length > 0 ) {
boolean allEntriesSelected = false;
short[] entries = field.getEntries();
if (entries != null && entries.length == values.length) {
allEntriesSelected = true;
for (int i = 0; i < values.length; i++) {
boolean found = false;
for (int j = 0; j < entries.length; j++) {
if (values[i] == entries[j]) {
found = true;
break;
}
}
if (!found) {
allEntriesSelected = false;
break;
}
}
}
if (!allEntriesSelected) {
return ValueSetFilterExpression.create(path, values);
}
}
return null;
}
public void setValue(ValueSetFilterExpression value) {
if (value != null) {
short[] values = value.getSelection();
field.setSelection(values);
} else {
field.setSelection(new short[0]);
}
}
}
}
5.8.1 FilterExpressionEditorFactory
Instanzen von „FilterExpressionEditor“ werden von der Cockpit-Infrastruktur grundsätzlich über spezielle Factory-Klassen erzeugt. Die Factory-Klassen müssen dazu die Schnittstelle „com.cisag.pgm.search.gui.FilterExpressionEditorFactory“ implementieren und über einen Default-Konstruktor verfügen. Die Schnittstelle definiert die folgende Methode:
public FilterExpressionEditor createFilterExpressionEditor(
byte[] guid, DataDescription dataDescription)
Konkrete Klassen, die diese Schnittstelle implementieren, sollten die Informationen aus der übergebenen Data-Description nutzen, um die erzeugte Instanz von „FilterExpressionEditor“ entsprechend zu parametrisieren.
Die Registrierung einer „FilterExpressionEditorFactory“ erfolgt durch Eintragung ihres vollqualifizierten Klassennamens in der Data-Description zu einem logischen Datentyp.
Hinweis:
Für alle primitiven Datentypen und „Special-Parts“ werden generische Implementierungen von der Cockpit-Infrastruktur bereitgestellt. Für alle anderen Datentypen, d. h. virtuelle Spalten, Business Objects und Parts müssen geeignete „FilterExpressionEditorFactories“ bereitgestellt werden, wenn die zugehörige Spalte im Entwicklungsobjekt „Suche“ als filterbar gekennzeichnet wurde (siehe Kapitel „Checkbox: Filterbar“) und nicht gleichzeitig ein programmierter Filter betroffen ist (siehe Kapitel „Checkbox: Programmierter Filter“).
5.8.2 FilterExpressionEditorFactoryRegistry
Über die Klasse „com.cisag.pgm.search.gui.FilterExpressionEditor
FactoryRegistry“ können zu einem logischen Datentyp die passenden Instanzen von „FilterExpressionEditorfactory“ erzeugt werden. Bei der Auflösung durchsucht die Klasse die Vererbungskette des logischen Datentyps bis zu dem Wurzeldatentyp. Die erste dabei gefundene „FilterExpressionEditorFactory“ wird zurückgeliefert. Wenn für einen logischen Datentyp keine Registrierung vorliegt, dann wird null zurückgeliefert.
Die „FilterExpressionEditorFactoryRegistry“ wird insbesondere von der Cockpit-Infrastruktur selbst genutzt. Sie kann darüber hinaus beispielsweise auch dazu verwendet werden, eine bereits vorhandene Implementierung in einer eigenen „FilterExpressionEditorfactory“ wiederzuverwenden (Delegation).
5.8.3 Virtuelle Spalten
Bei virtuellen Spalten (siehe Kapitel „Checkbox: Virtuell“) erfolgt der Austausch der Filterausdrücke mit dem „FilterExpressionEditor“ immer über Instanzen von „CompoundFilterExpression“, wobei die relativen Namen der Unterattribute als Schlüssel genutzt werden (siehe Kapitel „CompoundFilterExpression“).
Beispiel
Eine virtuelle Spalte mit dem Namen „vattr“ verfügt über die beiden Unterattribute „vattr.attr1“ und „vattr.attr2“. Das Unterattribut „attr1“ basiert auf dem Valueset „com.example.ValueSet1“ und das Unterattribut „attr2“ basiert auf dem Valueset „com.example.ValueSet2“. Zudem sind beide Unterattribute im Entwicklungsobjekt „Suche“ als filterbar gekennzeichnet. Ein „FilterExpressionEditor“ für diese virtuelle Spalte könnte wie folgt aussehen:
package com.example;
import com.cisag.pgm.datatype.Guid;
import com.cisag.pgm.dialog.model.DataDescription;
import com.cisag.pgm.gui.MultiValueSetField;
import com.cisag.pgm.gui.VisualElement;
import com.cisag.pgm.search.filter.CompoundFilterExpression;
import com.cisag.pgm.search.filter.FilterExpression;
import com.cisag.pgm.search.filter.ValueSetFilterExpression;
import com.cisag.pgm.search.gui.FilterExpressionEditor;
import java.util.HashMap;
import java.util.Map;
public class VAttrFilterExpressionEditor
implements FilterExpressionEditor<
CompoundFilterExpression<FilterExpression<?>>> {
private final String path;
private final MultiValueSetField field;
public VAttrFilterExpressionEditor(byte[] guid,
DataDescription dataDescription) {
this.path = (String)
dataDescription.getValue(DataDescription.PATH);
this.field =
new MultiValueSetField(Guid.toHexString(guid), path);
}
public VisualElement getVisualElement() {
return field;
}
public void initVisualElement() {
field.addValueSet(“com.example:ValueSet1.lt”);
field.addValueSet(“com.example:ValueSet2.lt”);
}
public CompoundFilterExpression<FilterExpression<?>> getValue() {
short[][] selection = field.getValue();
if (selection != null && selection.length > 0) {
Map<String, FilterExpression<?>> map =
new HashMap<String, FilterExpression<?>>();
if (selection[0] != null && selection[0].length > 0) {
map.put(“attr1”, ValueSetFilterExpression.create(
“com.example:ValueSet1.lt”, selection[0]));
}
if (selection[1] != null && selection[1].length > 0) {
map.put(“attr2”, ValueSetFilterExpression.create(
“com.example:ValueSet2.lt”, selection[1]));
}
return CompoundFilterExpression.create(path, map);
}
return null;
}
public void setValue(
CompoundFilterExpression<FilterExpression<?>> value) {
if (value != null) {
short[][] selection = new short[2][];
ValueSetFilterExpression expr1 =
(ValueSetFilterExpression) value.getExpression(“attr1”);
if (expr1 != null) {
selection[0] = expr1.getSelection();
} else {
selection[0] = … // TODO add all values of VS1
}
ValueSetFilterExpression expr2 =
(ValueSetFilterExpression) value.getExpression(“attr2”);
if (expr2 != null) {
selection[1] = expr2.getSelection();
} else {
selection[1] = … // TODO add all values of VS2
}
field.setValue(selection);
} else {
field.setValue(null);
}
}
}
5.9 FilterExtension
Die WHERE-Klausel einer Suchabfrage wird generisch aus den mit „setFilterExpression()“ definierten Filterausdrücken zusammengestellt, wobei die Filterausdrücke untereinander UND-verknüpft werden. Zusätzlich kann die WHERE-Klausel um einen booleschen OQL-Ausdruck erweitert werden (UND-verknüpft mit dem generischen Teil der WHERE-Klausel). Das Setzen dieser Erweiterung ist über die Methode „setFilterExtension()“ der Klassen „GridSearchSupport“ oder „SearchInterface“ möglich (siehe Kapitel „GridSearchSupport“ und „SearchHook“). Der boolesche OQL-Ausdruck ist dazu der Methode „setFiterExtension()“ als Instanz der Klasse „com.cisag.pgm.search.filter.FilterExtension“ zu übergeben.
Innerhalb des OQL-Ausdrucks kann sowohl auf die gesetzten Filterausdrücke als auch auf die Attribute der in der Suche definierten Business Objects zugegriffen werden.
Um innerhalb des OQL-Ausdrucks auf den Filterausdruck einer Spalte zuzugreifen, muss der zugehörige Spaltenname in geschweifte Klammern gesetzt werden.
Beispiel:
new FilterExtension(“{attr1} OR {attr2}”);
Die Verwendung eines Filterausdrucks innerhalb der „FilterExtension“ führt automatisch zu seiner Entfernung aus dem generischen Teil der WHERE-Klausel. Wenn beispielsweise in dem zuvor genannten Beispiel der generische Teil aus den Filterausdrücken für „attr1“, „attr2“ und „attr3“ bestanden hätte, dann bliebe im generischen Teil nur noch „attr3“ übrig und die gesamte WHERE-Klausel sähe wie folgt aus: ({attr3}) AND ({attr1} OR {attr2}).
Hinweis:
Innerhalb des OQL-Ausdrucks können nur Filterausdrücke verwendet werden, die einen von null abweichenden Wert haben.
Die Schreibweise {attr} referenziert nicht nur auf den Namen der Spalte oder den Filterausdruck, sondern den vollständigen booleschen OQL-Ausdruck (erzeugt auf der Basis des definierten Filterausdrucks und den Metadaten der Spalte).
Um innerhalb des OQL-Ausdrucks auf ein Attribut eines in der Suche definierten Business Objects zuzugreifen, muss die Schreibweise alias:attributName verwendet werden, wobei als „alias“ der in der Suche definierte Alias für das Business Object zu verwenden ist.
Beispiel:
new FilterExtension(“book:employee is not null”);
Zusätzlich können in dem OQL-Ausdruck benannte Parameter verwendet werden, deren Werte anschließend über typbezogene Methoden von „FilterExpression“ gesetzt werden können. In dem OQL-Ausdruck sind die verwendeten Parameternamen in geschweifte Klammern zu setzen.
Beispiel:
FilterExtension ext =
new FilterExtension (“book:employee = {paramEmployeeGuid}”);
ext.setGuid(“paramEmployeeGuid”,
partnerLogic.getCurrentPartnerGuid());
Hinweis:
Die Namen der Parameter können frei definiert werden. Achten Sie jedoch darauf, dass Konflikte mit den Spaltennamen vermieden werden. Wir empfehlen, entsprechende Namenskonventionen für Parameter und Spalten zu verwenden, damit Namenskonflikte generell vermieden werden können.
Zudem ist wichtig, dass für die verwendeten Parameternamen auch tatsächlich Werte gesetzt werden.
5.10 SearchHook
Eine Datenbankabfrage kann vor ihrer Ausführung manipuliert werden (siehe Kapitel „Manipulation der Datenbankabfrage“). Diese Manipulation kann wahlweise durch das Ändern der Methode „preSearch()“ (siehe: preSearch) oder durch die Implementierung eines „SearchHooks“ erfolgen. Ein SearchHook ist eine Java-Klasse, die von der abstrakten Basisklasse „com.cisag.pgm.search.SearchHook“ abgeleitet ist und bei der das Entwicklungsobjekt „Suche“ als Hook eingetragen ist.
Die Basisklasse „com.cisag.pgm.search.SearchHook“ folgt demselben Muster wie die Klasse „com.cisag.pgm.objsearch.SearchHook“ (der Basisklasse für OQL-Suchen), d. h. konkrete Unterklassen legen in ihrem Konstruktor fest, welche Hooks sie tatsächlich implementieren. Aktuell definiert die Basisklasse jedoch nur einen solchen Hook: PRE_SEARCH sowie die dazugehörige Methode preSearch(SearchInterface). Die innere Schnittstelle „SearchInterface“ dient dabei als Wrapper für die aktuelle Datenbankabfrage und erlaubt das Abfragen und Setzen von Filterausdrücken und einer „FilterExtension“.
Beispiel:
public class BookSearchHook extends SearchHook {
public BookSearchHook() {
this(0, 0);
}
protected BookSearchHook(int activeHooks, int inactiveHooks) {
super(activeHooks|PRE_SEARCH, inactiveHooks);
}
@Override
public void preSearch(SearchInterface si) {
super.preSearch(si);
PartnerLogic partnerLogic = PartnerLogic.getInstance();
FilterExtension ext =
new FilterExtension(“book:employee = {paramEmployeeGuid}”);
ext.setGuid(“paramEmployeeGuid”,
partnerLogic.getCurrentPartnerGuid());
si.setFilterExtension(ext);
}
}
Eine SearchHook-Implementierung kann auch für mehr als eine Suche als Hook registriert werden. In diesem Fall sollte die Implementierung zusätzlich die init-Methode der Basisklasse ändern, damit sie Zugriff auf den Namen der Suche und das Basisobjekt der Suche bekommt. Das jeweilige Basisobjekt ist dazu bei der jeweiligen Suche zu hinterlegen (siehe Kapitel „Hook“).
Hinweis:
Für eine Suche sollte ein SearchHook und das Ändern der preSearch()-Methode (siehe: preSearch) von CisCustomizableCockpit“ (siehe: CisCustomizableCockpit) bzw. SearchView (siehe: SearchView) möglichst nicht miteinander kombiniert werden, da sonst die Gefahr besteht, dass die eine Implementierung die Einstellungen der anderen Implementierung unbeabsichtigt nicht beachtet.
5.11 BusinessObjectRegistryHook
Sind die Werte eines Valuesets durch eine Data-Description reduziert, dann führt das zu komplexeren Datenbankabfragen, weil in diesem Fall alle in der Data-Description festgelegten Werte automatisch in die WHERE-Klausel der Datenbankabfrage aufgenommen werden (siehe Kapitel „ValueSetFilterExpression“). Das ist auch dann der Fall, wenn die Spalte nicht als Filter verwendet wird. Wenn sichergestellt ist, dass in der betroffenen Spalte tatsächlich nur die in der Data-Description festgelegten Werte auftreten können, dann sollte die betroffene Spalte über den Hook „com.cisag.pgm.appserver.hook.BusinessObject
RegistryHook“ als vollständig gekennzeichnet werden. Damit kann die in diesem Fall unnötige Erweiterung der WHERE-Klausel vermieden werden.
Um ein Valueset als vollständig zu kennzeichnen, muss in der initialize()-Methode der Hook-Implementierung eine der registerValueSetComplete-Methoden aufgerufen werden.
Beispiel:
public class BusinessObjectRegistryHookImpl
implements BusinessObjectRegistryHook {
public BusinessObjectRegistryHookImpl() {
}
public void initialize(BusinessObjectRegistry registry) {
registry.registerValueSetComplete(
SalesOrderDetail.class, “accountOriginType”);
}
}
Hinweis:
Die Kennzeichnung bezieht sich immer auf ein konkretes Attribut eines Business Objects und nicht allgemein auf einen logischen Datentyp oder eine Data-Description.
Über den Hook können nur Attribute beeinflusst werden, die in demselben Namensraum gehören, in dem auch der Hook liegt.
5.12 DataDescriptionFilter
Mit einem „DataDescriptionFilter“ lassen sich bestimmte Eigenschaften einer Data-Description zur Laufzeit verändern. Ein „DataDescriptionFilter“ lässt sich beispielsweise dazu nutzen, die Sichtbarkeit einer Spalte in Abhängigkeit von einer Funktion bzw. einer Customizing-Einstellung zu steuern.
Um einen „DataDescriptionFilter“ für einen logischen Datentyp zu registrieren, muss an der zugehörigen Data-Description der vollqualifizierte Name einer Klasse hinterlegt werden, welche die folgende Schnittstelle implementiert:
com.cisag.pgm.datatype.DataDescriptionFilter
Diese Schnittstelle definiert die Methode „getModification()“, die eine entsprechend konfigurierte Instanz von „DataDescriptionModification“ zurückliefern muss (siehe Kapitel „DataDescriptionModification“).
Beispiel
public class ExampleDataDescriptionFilter
implements DataDescriptionFilter {
public DataDescriptionModification getModification(
DataDescription dataDescription) {
CisSystemManager sm =
CisEnvironment.getInstance().getSystemManager();
if (sm.isAvailable(“com.example.Example”)) {
return DataDescriptionModification.UNMODIFIED;
} else {
return DataDescriptionModification.EXCLUDED;
}
}
Hinweis:
Die „DataDescriptionFilter“ werden nicht nur von den Cockpits ausgewertet, sondern gelten auch für die Attribute von „Objektsichten“ in den anpassbaren Anwendungen.
5.12.1 DataDescriptionModification
Die Klasse „com.cisag.pgm.datatype.DataDescriptionModification“ beschreibt die an einer Data-Description durchzuführenden Änderungen. Instanzen von „DataDescriptionModification“ können über die statische Factory-Methode „getInstance(Flags)“ erzeugt und konfiguriert werden. Zusätzlich stellt die Klasse häufig benötigte Konfigurationen auch als statische Konstanten zur Verfügung.
Flag | Konstante | Erläuterung |
UNMODIFIED | UNMODIFIED | Keine Änderung. Die in der Data-Description festgelegten Einstellungen gelten. |
HIDDEN | HIDDEN | Das Attribut oder die Spalte wird auf der Bedienungsoberfläche nicht angezeigt. |
DISABLED | DISABLED | Das Attribut oder die Spalte wird auf der Bedienungsoberfläche „deaktiviert“ dargestellt. |
READ_ONLY | READ_ONLY | Das Attribut oder die Spalte kann auf der Bedienungsoberfläche nicht verändert werden. |
REQUIRED | REQUIRED | Für das Attribut ist eine Eingabe erforderlich. |
EXCLUDED | EXCLUDED | Wie „HIDDEN“, jedoch mit der Bedeutung, dass das betroffene Attribut generell nicht zur Verfügung steht, während mit „HIDDEN“ nur die Anzeige unterdrückt wird. |
Hinweis:
Für Cockpits sind nur die Werte „UNMODIFIED“ und „EXCLUDED“ (oder „HIDDEN“) von Bedeutung. Alle anderen Werte sind nur für die Steuerung von Attributen in Objektsichten bzw. einer anpassbaren Anwendung relevant.
5.12.2 RepositoryOnly
Das System geht bei „DataDescriptionFiltern“ grundsätzlich davon aus, dass sie einen Kontext mit OLTP-Datenbank benötigen und ruft sie deshalb nur dann auf, wenn ein solcher Kontext auch tatsächlich vorliegt. Wenn kein Kontext mit OLTP-Datenbank vorhanden ist, dann wird der Filter ignoriert. „DataDescriptionFilter“ können jedoch zusätzlich die Schnittstelle „RepositoryOnly“ implementieren, wenn sie keinen Kontext mit OLTP-Datenbank benötigen und auch dann aufgerufen werden möchten, wenn kein Kontext mit OLTP-Datenbank existiert.
Hinweis:
Der Fall, dass kein Kontext mit OLTP-Datenbank vorhanden ist, liegt beispielsweise dann vor, wenn ein Application-Server ohne OLTP-Datenbanken gestartet wird (Installieren von Softwareaktualisierungen) oder sich ein Benutzer ohne OLTP-Datenbank anmeldet.
6 Einstellungen
6.1 Berechtigungen
Für Cockpits (inkl. SearchView) sind folgende Fähigkeiten relevant:
Fähigkeit | Erläuterung |
com.cisag.sys.objsearch.IgnoreSearchTimeout | Der Benutzer darf eine Suche auch dann ausführen, wenn ihre Ausführungszeit den vorgegebenen Maximalwert überschreitet (Standardwert: 1 Minute). |
com.cisag.sys.search.ChangeSettingsForAllUsers | Der Benutzer darf Ansichten auf der Datenbankebene erfassen, ändern und löschen. |
com.cisag.sys.search.ChangeMetadata | Der Benutzer darf den Spaltenvorrat erweitern oder reduzieren. |
6.2 Propertys
Das Standardverhalten der Cockpits lässt sich über folgende Propertys beeinflussen:
Property | Erläuterung |
com.cisag.pgm.search. gui.GridSearchSupport. defaultBufferSize |
Maximale Anzahl an Datensätzen, die als Ergebnis in einem Cockpit angezeigt werden.
Der Standardwert für die Anzahl an Objekten ist 1000. Werte größer als 1000 sollten nur verwendet werden, wenn der Application-Server tatsächlich über genügend Ressourcen, d. h. CPUs und Hauptspeicher verfügt, um auch die zusätzliche Last abzuarbeiten. |
com.cisag.pgm.search. gui.GridSearchSupport. asynchBufferSize |
Maximale Anzahl der Objekte, die beim „Aktualisieren (Im Hintergrund)“ geöffnet werden. Der Standardwert für die Anzahl an Objekten ist 100000. |
com.cisag.pgm.search. gui.GridSearchSupport.maximumDetailRows |
Maximale Anzahl der Detaildatenobjekte, die pro Hauptobjekt geöffnet werden. Der Standardwert für die Anzahl an Objekten ist 10. |
com.cisag.pgm.gui. DefaultExportLimit |
Maximale Anzahl von Objekten, die ein Benutzer ohne Administratorrechte exportieren kann. Der Standardwert ist 231-1, höchstens jedoch der Wert von „MaximumExportLimit“. |
com.cisag.pgm.gui. MaximumExportLimit |
Maximale Anzahl von Objekten, die aus dem Cockpit heraus exportiert werden können (unabhängig von den Benutzerrechten). Der Standardwert ist 231-1. |
Debugging
Zur Analyse von Datenbankabfragen können mithilfe des Kommandozeilen Tools „Klasse debuggen“ (dbgcls) weitere Informationen auf der Konsole ausgegeben werden:
dbgcls -class:com.cisag.sys.kernel.caching.CisComplexOqlCache
Weitere Informationen zum Tool finden Sie in dieser Dokumentation: Klasse debuggen