1 Themenübersicht
In Semiramis ermöglichen Suchen das gesteuerte Auffinden von Objekten in einer Datenbank. Für den Benutzer erscheinen Suchen als unterstützendes Hilfsmittel in Form von Wertehilfen für Felder und als anwendungsbezogene Suchen im Navigationsbereich. Darüber hinaus werden Suchen aber auch in Anwendungen, wie z. B. in Abfrageanwendungen verwendet.
In diesem Dokument wird erklärt, was unter der Oberfläche passiert, wie sich der Ablauf einer Suche gestaltet und welche Eingriffsmöglichkeiten sich dem Entwickler bieten. Es wird auch auf die Spezialfälle, wie z. B. Spezialparts, Wiederaufsetzen usw. eingegangen.
2 Zielgruppe
Anwendungsentwickler
3 Beschreibung
3.1 Verwendung von Suchen
Suchen werden in Semiramis an verschiedenen Stellen und in unterschiedlichen Formen angeboten.
Wertehilfen
Wertehilfen werden entweder als Auswahlliste oder Dialogsuche dargestellt.
Eine Auswahlliste wird immer direkt an dem Feld visualisiert, für das die Wertehilfe aufgerufen wurde. Das Suchergebnis kann durch den Inhalt des Feldes eingeschränkt werden. Die Auswahlliste enthält zwischen 2 und 60 Einträge. Je nach Einstellung können ein oder mehrere Einträge ausgewählt und in das aufrufende Feld übertragen werden.
In der Dialogsuche kann das Suchergebnis durch Eingabe mehrerer Suchmerkmale eingegrenzt werden. Der Suchdialog ist in einen Abfrage- und einen Ergebnisbereich aufgeteilt. Im Abfragebereich können die erwähnten Suchmerkmale erfasst werden. Die Suchmerkmale lassen sich zudem als Suchmuster abspeichern und zu einem späteren Zeitpunkt wieder verwenden. Der Ergebnisbereich enthält eine Auflistung aller gefundenen Elemente. Je nach Einstellung können ein oder mehrere Einträge aus dem Suchergebnis ausgewählt und in das aufrufende Feld übernommen werden.
Anwendungsbezogene Suche im Navigationsbereich
Im Navigationsbereich befindet sich der Karteireiter „Suchen“. Der Inhalt dieses Karteireiters ist abhängig von der aktuellen Anwendung. Über ein Auswahlfeld lassen sich unterschiedliche Suchen auswählen. Die Darstellung einer Suche in diesem Karteireiter entspricht in etwa einer Dialogsuche. Da die Breite der Suche stark eingeschränkt ist, werden die Feldbezeichnungen im Abfragebereich oberhalb ihrer Felder dargestellt.
Abfrageanwendungen
Für die Suche nach Objekten werden auch Abfrageanwendungen entwickelt. Von den gebotenen Möglichkeiten der Suche in Semiramis werden hier nur noch die reinen Suchoperationen genutzt. Gestaltung und Aufbau der Suche unterliegen keinen Einschränkungen. Abfrageanwendungen werden in diesem Dokument nicht weiter behandelt.
3.2 Ablauf einer Wertehilfe
Eine Wertehilfe läuft im Wesentlichen in vier Schritten ab: Vorbereitungen, Vorabsuche, Visualisierung und Ergebnisübernahme. Zuerst wird die Suche vorbereitet, indem Metadaten geladen und Laufzeitdaten initialisiert werden. Je nach Startart und Benutzereingabe kann es möglich sein, eine Vorabsuche durchzuführen. Liefert die Vorabsuche genau ein Ergebnis, wird dieses in das Feld übernommen. In allen anderen Fällen wird eine Visualisierung, eine Auswahlliste oder ein Dialog, angezeigt. In der Auswahlliste kann der Benutzer einen der angezeigten Werte auswählen und in das Feld übernehmen. In der Dialogsuche kann das Suchergebnis durch Eingabe von Suchmerkmalen weiter eingeschränkt und in das Feld übernommen werden. Bei einem mehrwertigen Feld kann in den beiden Visualisierungen auch mehr als ein Datensatz ausgewählt und übernommen werden.
In den folgenden Abschnitten werden die einzelnen Schritte einer Wertehilfe genauer erklärt.
Hinweis:
Die zur Verfügung stehenden Eingriffsmöglichkeiten werden in der Ablaufbeschreibung nur erwähnt. Genauere Informationen finden sich in späteren Kapiteln dieses Dokumentes.
Hinweis:
Die verwendeten Grafiken verwenden verschiedene Farben, um bestimmte Aspekte zu verdeutlichen. Der Ablauf ist in einem Orange-Ton gehalten. Hooks sind blau hinterlegt und sonstige Klassen grün.
Übersicht über den Ablauf einer Wertehilfe
3.2.1 Suche ausführen
In den folgenden Kapiteln findet sich in einigen Diagrammen der Begriff „Suche ausführen“. Dahinter verbirgt sich folgender Ablauf.
In den Laufzeitdaten ist ein SearchIterator registriert. Dieser wird mit Suchmerkmalen parametrisiert. Die Suchmerkmale können dabei z. B. aus den Laufzeitdaten oder aus dem Suchdialog kommen.
Anschließend wird die Suche ausgeführt. Das Ausführen der Suche lässt sich mit dem ExecuteHook beeinflussen.
Es werden nicht alle Daten auf einmal aus der Datenbank ausgelesen. Stattdessen werden die Daten in Blöcken mit je 60 Datensätzen bereitgestellt. Die erhaltenen Datensätze können mit dem ResultHooks verändert oder entfernt bzw. es können auch neue Datensätze erstellt und dem Datenblock hinzugefügt werden. Sollte die Anzahl der Datensätze aufgrund der Hooks unter 60 sinken, wenn zugleich weitere Datensätze am Iterator verfügbar sind, so werden weitere 60 Datensätze gelesen, um mindestens 60 Datensätze zu erhalten.
Ablauf einer Suche
3.2.2 Vorbereitungen
Beim Start der Anwendung werden zu allen Feldern, die eine Suche verwenden, die eingetragenen SearchManager initialisiert. Im Normalfall handelt es sich dabei um die Klasse „DefaultSearchManager“.
Hinweis:
Wenn statt des DefaultSearchManagers eine eigene Implementierung des SearchManagers verwendet wird, dann entscheidet diese Implementierung das weitere Verhalten der Suche. Die folgenden Ausführungen beziehen sich deswegen auf die Verwendung des DefaultSearchManager.
Wenn der Entwickler den DefaultSearchManager beerbt und nur die gekennzeichneten Methoden überschreibt, kann davon ausgegangen werden, dass die Beschreibung auch für diese Klasse zutreffen wird.
Sobald die Suche für ein VisualElement aufgerufen wird, wird am SearchManager die Methode „searchValueFor“ aufgerufen.
Aus der Repository-Datenbank werden die Laufzeitdaten einer Suche für die Suche erstellt. Dabei können mithilfe eines eigenen SearchManagers Attribute der Suche vorbelegt werden.
Als nächstes wird der SearchIterator erzeugt. Im Normalfall handelt es sich dabei um einen „CisSearchIterator“. Der Iterator wird den Laufzeitdaten hinzugefügt. Durch Implementierung des SearchIteratorHooks kann eine eigene Implementierung des SearchIterators hinterlegt werden.
Eingabe auslesen und Suche vorbereiten mit Angabe der Eingriffspunkte
3.2.3 Vorabsuche
Eine OQL-Suche wird in der Repository-Datenbank definiert. Für die weitere Betrachtung sind vor allem drei Einstellungen interessant: Darstellung, Spezialverhalten Identifikation und Spezialverhalten Bezeichnung
In der Suche ist die Art der Darstellung festgelegt. Mögliche Werte sind Dialog und ComboBox. Einhergehend mit dieser Einstellung verhält sich die zu erwartende Datenmenge. Liegt sie unter 60 Datensätzen, so sollte hier ComboBox gewählt werden. Ab 60 Datensätzen sollte die Einstellung Dialog gewählt werden. Außerdem wird bei der Einstellung ComboBox die Suche garantiert ausgeführt, d. h. beim Öffnen der Wertehilfe wird dem Benutzer immer eine Auswahl angeboten, ohne dass dieser in irgendeiner Form das Ergebnis eingeschränkt oder die Suche aktiv gestartet hat. Bei der Einstellung Dialog kann dies nicht gemacht werden, da nicht abzuschätzen ist, wie stark die Ausführung der Suche das System belastet.
Zwei Attribute einer Suche können mit dem Spezialverhalten Identifikation und Bezeichnung versehen werden. Zugleich müssen diese Attribute sowohl als Suchmerkmal, Anzeige- und Rückgabewert eingetragen sein. Wenn die beiden Spezialverhalten angegeben sind, kann aus dem Eingabefeld heraus die Wertehilfe eingeschränkt werden.
Eine Suche kann über verschiedene Tastenkombinationen oder über einen Klick auf das Wertehilfesymbol des Felds gestartet werden.
Vier verschiedene Startarten werden unterschieden:
- Identifikationssuche
- Bezeichnungssuche
- Erweiterte Identifikationssuche
- Erweiterte Bezeichnungssuche
Die folgende Tabelle listet die Tastenkombinationen und Aktionen mit der Maus für die jeweilige Startart auf.
Startart | Tastenkombination/Maus |
Identifikationssuche | ALT + Pfeil oben
Mausklick auf Wertehilfesymbol |
Bezeichnungssuche | STRG + ALT + Pfeil oben
STRG + Mausklick auf Wertehilfesymbol |
Erweiterte Identifikationssuche |
ALT + Pfeil unten |
Erweiterte Bezeichnungssuche |
STRG + ALT + Pfeil unten |
Wenn in den Metadaten kein Attribut mit dem Spezialverhalten Bezeichnung vorhanden ist, wird bei den beiden Bezeichnungssuchen die Identifikationssuche durchgeführt. Ist in den Metadaten auch kein Attribut mit dem Spezialverhalten Identifikation gekennzeichnet, so wird die Vorabsuche übergangen und die Suche visualisiert.
Identifikations- und Bezeichnungssuche
Bei der Identifikations- und Bezeichnungssuche wird die Vorabsuche nur ausgeführt, wenn der Feldinhalt ein Wildcard „*“ oder „?“ enthält oder bei den Metadaten der Suche ComboBox eingetragen ist. Je nach Einstellung wird für die Visualisierung eine Auswahlliste oder ein Suchdialog verwendet. Der Suchdialog wird auch verwendet, wenn die Einstellungen in den Metadaten auf ComboBox stehen und die Datenmenge größer als 59 ist.
Identifikations- und Bezeichnungssuche
Erweiterte Identifikations- und Bezeichnungssuche
Die erweiterte Identifikations- und Bezeichnungssuche hat eine zweistufige Vorabsuche. Zuerst wird versucht, zu der Eingabe des Benutzers einen Datensatz zu finden. Gelingt dies nicht, so wird die Eingabe bei der Identifikationssuche um den Platzhalter „*“ am Ende und bei der Bezeichnungssuche um den Platzhalter „*“ jeweils am Anfang und am Ende erweitert und die Suche erneut ausgeführt. Die Wahl der Visualisierung ist bei einem nicht leeren Feld ausschließlich von der Datensatzmenge abhängig. Unter sechzig Datensätzen wird die Auswahlliste und ab sechzig der Suchdialog angezeigt. Bei einem leeren Feld werden wiederum die Einstellungen in den Metadaten für die Visualisierung genutzt.
Ablauf der erweiterten Identifikations- und Bezeichnungssuche
3.2.4 Visualisierung als Dialogsuche
Der Dialog besteht hauptsächlich aus zwei Bereichen, dem Abfrage- und dem Ergebnisbereich.
Im Abfragebereich werden in Abhängigkeit zu den Datentypen der Attribute geeignete Felder eingebaut. Mit dem SelectionFieldHook können die Eigenschaften der verwendeten Felder geändert werden. Es besteht auch die Möglichkeit, statt der vorgeschlagenen Felder eigene zu verwenden.
Im Ergebnisbereich wird zur Darstellung des Suchergebnisses ein Grid-Control verwendet. Anhand der Metadaten werden die Spaltennamen vergeben. Auch hier bieten sich für den Entwickler einige Eingriffsmöglichkeiten. Mit den VisualisationHooks kann die Darstellung manipuliert, bzw. gegen eine Liste ausgetauscht werden.
Eventuell vorhandene Vorgaben für die Suchmerkmalfelder werden aus den Laufzeitdaten einer Suche ausgelesen und gesetzt. Der Benutzer hat nun die Möglichkeit, die Suche durch Eingabe von Suchmerkmalen einzuschränken.
Mit den Suchmerkmalen wird die Suche ausgeführt.
Die erhaltenen Datensätze werden im Anzeigebereich dargestellt. Der Benutzer hat nun mehrere Möglichkeiten. Er kann die Suche durch Vergabe anderer Suchmerkmale verändern und erneut ausführen. Er kann bei mehr als 60 gefundenen Datensätzen weitere Datensätze nachladen. Zudem besteht die Möglichkeit, die verwendeten Suchmerkmale und Sortierungen als Abfragemuster abzuspeichern.
Wenn der Benutzer den oder die gesuchten Datensätze gefunden hat, müssen diese ausgewählt werden. Betätigt er anschließend die zur Übernahme ausgezeichnete Schaltfläche, werden die gewählten Datensätze in den Laufzeitdaten gespeichert und der Dialog geschlossen.
Ablauf zur Anzeige des Suchdialoges mit Angabe der Eingriffsmöglichkeiten
3.2.5 Visualisierung als Auswahlliste
Bei der Visualisierung als Auswahlliste muss zwischen der Einfach- und Mehrfachauswahl unterschieden werden. Bei der Einfachauswahl wird in der Regel eine ComboBox am Feld angezeigt. Dieses ist in maximal zwei Spalten aufgeteilt. Die linke Spalte beinhaltet die Werte aus dem Rückgabeattribut, welches als Identifikation gekennzeichnet ist. Die rechte Spalte beinhaltet die Werte aus dem Rückgabeattribut, welches als Bezeichnung gekennzeichnet ist. Ist keine Bezeichnung angegeben, wird diese Spalte nicht angezeigt. Der Benutzer kann nun mit Tastatur oder Maus den gewünschten Datensatz auswählen. Die ComboBox schließt sich und der gewählte Datensatz wird in den Laufzeitdaten gespeichert.
Bei der Mehrfachauswahl wird statt einer einfachen ComboBox ein Popup mit einer Liste und zwei Schaltflächen angezeigt. In der Liste können mehrere Werte ausgewählt werden. Betätigt der Benutzer anschließend die zur Übernahme ausgezeichnete Schaltfläche, werden die gewählten Datensätze in den Laufzeitdaten gespeichert und das Popup wird geschlossen.
Wenn in der Suche der ListViewCreator-Hook angegeben ist, wird statt der ComboBox immer ein Popup mit einer Liste angezeigt.
Anzeigen der Popups mit Angabe der Eingriffsmöglichkeiten
3.2.6 Ergebnisübernahme
Als letzter Punkt in der Suche bleibt die Ergebnisübernahme. Hier kann der Entwickler das vom Benutzer ausgewählte Ergebnis durch Überschreiben der Methode „takeValue“ des SearchManagers modifizieren.
In der SelectionGroup einer Anwendung können Felder als „Key“ oder „Key with load“ eingetragen werden. Handelt es sich bei dem Feld um ein „Key with load“-Feld, so wird nach dem Eintragen des Wertes im Feld die Anwendung aufgefordert, diesen Datensatz anzuzeigen.
Ergebnisübernahme mit Angabe der Eingriffsmöglichkeit
3.3 Standardsuche
Ein Spezialfall der Auswahlliste ist die Standardsuche. Für die Standardsuche existieren keine Metadaten in der Repository-Datenbank. Per Definition zeigt die Suche im Ergebnis nur zwei Spalten an. Die Ergebnismenge wird vom Programm vorgegeben. Die Darstellung erfolgt immer in einer Auswahlliste und die Ergebnismenge ist nicht auf 60 Zeilen beschränkt. Ein Dialog wird bei mehr als 60 Zeilen nicht dargestellt. Die Sortierung der Datensätze sollte mit dem datenbankspezifischen Comparator erfolgen, der vom TransactionManager mit der Methode „getComparator“ abgeholt werden kann.
Für die Standardsuche ist es erforderlich, eine Ableitung vom DefaultSearchManagers zu implementieren, in der die Methode „getResultList“ überschrieben werden muss. In dieser Methode muss die Ergebnisliste in Form von „CisListPartMutable“-Objekten aufgebaut werden. Die Daten der zwei Spalten der Suche werden als Objekte unter den Konstantennamen „DEFAULT_CODE_NAME“ und „DEFAULT_DESCRIPTION_NAME“ in das CisListPartMutable abgelegt.
class MyDefaultSearchManager extends DefaultSearchManager {
List searchData = …;
public List getResultList(SearchInfo searchInfo) {
List result = new ArrayList();
Iterator iterator = searchData.iterator();
while (iterator.hasNext()){
MyValue value = (MyValue) iterator.next();
CisListPartMutable entry =
new CisListPartMutable();
entry.setObject(
DefaultSearchManager.DEFAULT_CODE_NAME,
value.getName());
entry.setObject(
DefaultSearchManager.DEFAULT_DESCRIPTION_NAME,
value.getExpression());
result.add(entry);
}
return result;
}
}
Für das Feld, welches die Standardsuche verwenden soll, muss der Suchname mit einem bestimmten Wert vorgegeben und der SearchManager eingetragen werden.
MyDefaultSearchManager mySearchManager =
new MyDefaultSearchManager();
myFld.setSearchName(DefaultSearchManager.EMPTY_SEARCH_DEFINITION);
myFld.setSearchManager(mySearchManager);
3.4 Anwendungsbezogene Suche im Navigationsbereich
Im Navigationsbereich befindet sich ein Karteireiter für anwendungsbezogene Suchen. Der Inhalt des Karteireiters ist abhängig von der aktuellen Anwendung. Der Karteireiter kann mehrere Suchen besitzen, die über ein Auswahlfeld gewählt werden können.
Die einzelne Ansicht einer Suche entspricht der Dialogansicht, mit dem Unterschied, dass der zur Verfügung stehende Platz geringer ist. Das hat zur Folge, dass die Feldbezeichnungen oberhalb der Felder stehen und in speziellen Fällen einzelne Felder zwei Spalten einnehmen dürfen.
Welche Suchen angezeigt werden sollen, wird in der Anwendung bestimmt, indem jede verwendete Suche mit ihrem voll qualifizierten Namen angegeben wird. Diese Auswahl der Suchen kann nur einmalig während der Initialisierung der Anwendung festgelegt und nachträglich nicht verändert werden.
Zu jeder einzelnen Suche können Attribute einmalig vorbelegt werden. Darüber hinaus ist es möglich, einzelne Attribute zu kennzeichnen, sodass die resultierenden Felder entweder nicht eingabebereit oder nicht sichtbar sind. Für die Belegung eines Attributs, muss der in der Suche eingetragene Name des Attributs genommen werden. Zur Steuerung der genannten Kennzeichen, muss dem Attribut ein konstanter Term angehängt werden. Die benötigten Kostanten befinden sich in der Klasse „ObjectSearchConstants“.
public interface ObjectSearchConstants{
public static final String FIX = “_FIX_PRESET”;
public static final String HIDDEN = “_HIDDEN”;
}
Beispiel:
Im folgenden Beispiel ist der Aufbau einer Suche dargestellt. An der ersten Suche werden drei Attribute vorgegeben. Das erste Attribut
„userGuid“ wird vorgegeben und soll nicht sichtbar sein, d. h. das resultierende Feld wird in der Suche nicht dargestellt. Das zweite Attribut „state“ erhält einen Vorgabewert, der zwar angezeigt, aber vom Benutzer nicht verändert werden kann. Beim dritten Attribut werden zwei Short-Werte vorgegeben, die aber vom Benutzer geändert werden können.
private static final String SEARCHES = new String[] {
“com.cisag.sys…XSearch”,
“com.cisag.sys…YSearch”
};
private void initLocatorSearches() {
CisParameterList[] paramArray = new
CisParameterList[SEARCHES.length];
paramArray[0] = new CisParameterList();
paramArray[0].setString(“userGuid” +
ObjectSearchConstants.HIDDEN,
SelectionSupport.toGuidSelection(env.getUserGuid()));
paramArray[0].setString(“state” +
ObjectSearchConstants.FIX,
SelectionSupport.toShortSelection(State.A));
paramArray[0].setString(“extrastate”,
SelectionSupport.toShortSelection(
new short[]{ExtraState.A, ExtraState.B}));
setSearchNames(SEARCHES, paramArray);
}
Hinweis:
Für die Vergabe der Attributwerte sollte immer eine entsprechende Methode an der Klasse „SelectionSupport“ aufgerufen werden. Zumeist resultieren die Ergebnisse dieser Methoden in einem String, sodass als Attribut in der Parameterliste ein String anstelle des ursprünglichen Datentyps gewählt werden muss. Im Beispiel wird das bei den beiden verwendeten Zuständen (states) deutlich.
Werteübernahme in die Anwendung
Die Suche im Navigationsbereich unterstützt die Werteübernahme in die Anwendung durch einfachen oder doppelten Mausklick. Zusätzlich kann in manchen Suchen der Wert per Kontextmenü oder Drag&Drop verwendet werden. Damit das funktioniert, muss für die Metadaten der Suche in der Repository-Datenbank ein Basisobjekt definiert sein und eine Zuordnung zwischen dem oder den Primärschlüsseln des Basisobjektes und den Ergebnisspalten der Suche bestehen. Der Anwendung werden die Werte des Primärschlüssels an die SelectionGroup übergeben und damit das gewünschte Objekt geladen.
Beispiel:
Als Basisobjekt wird für die Suche „A“ das Objekt „O“ eingetragen. „O“ besitzt als Primärschlüssel das Attribut „guid“ vom Typ „byte[]“. Als Ergebnis der Suche „A“ sind die Spalten „guid, name, description“ vorgesehen, wobei „guid“ dem Primärschlüssel des Objektes „O“ entspricht. Genau diese Zuordnung muss für die Suche definiert werden, d. h. in der Liste der Primärschlüsselzuordnung muss zu dem Attribut „guid“ des Objektes „O“ der Spaltenname „guid“ der Suche „A“ eingetragen werden.
Für Drag&Drop bieten sich verschiedene Ziele an. Zum einen kann dies in einem entsprechenden EntityField oder im Identifikationsbereich der Anwendung erfolgen. Für den Identifikationsbereich muss das Basisobjekt als Übergabeparameter der Anwendung definiert sein.
Zeitabhängige Objekte
Zeitabhängige Objekte können in mehreren Versionen vorliegen. Für die Suchen besteht die Option, entweder alle Versionen anzuzeigen oder aber nur die zum Zeitpunkt der Ausführung der Suche aktuelle Version.
Bestimmt wird diese Fähigkeit über die Zuordnung des Attributs „validFrom“, welches bei einem zeitabhängigen Objekt zum Primärschlüssel gehört.
Sollen alle Versionen eines Objektes angezeigt werden, so muss dem Attribut „validFrom“ des Basisobjektes der entsprechende Parameter in der Suche zugeordnet werden.
Wenn nur die jeweils aktuelle Version angezeigt werden soll, dann darf dem Attribut „validFrom“ kein Parameter der Suche zugeordnet werden. Der Kernel erstellt automatisch die benötigte Bedingung für die Ermittlung der aktuellen Version.
3.5 SearchManager
Zu einem Eingabefeld in Semiramis kann ein SearchManager eingetragen werden. Hier muss unterschieden werden, dass es in Semiramis zwei Ebenen im UserInterface gibt, die in den Packages „com.cisag.pgm.gui“ und „com.cisag.pgm.dialog“ zu finden sind. Die Klassen im Package „gui“ sind für die Erfordernisse der Anwendungsentwicklung optimiert worden. Dabei bauen die Klassen dieses Packages auf den Klassen im Package „dialog“ auf. Eine direkte Vererbung zwischen diesen Klassen ist bewusst vermieden worden.
Auszug aus dem Klassendiagramm zum SearchManager
SearchManager im Package „com.cisag.pgm.dialog“
Im Package „dialog“ liegen die Interfaces SearchManager und CommonSearchManager. Beide arbeiten eng mit dem SearchTarget zusammen, einem weiteren Interface in diesem Package.
Das SearchTarget-Interface identifiziert Objekte, die das oder die Ergebnisse einer Suchanfrage an den SearchManager akzeptieren. Das Interface stellt Methoden bereit, welche vom SearchManager genutzt werden, um das Ergebnis zurückzugeben und um weitere Metadaten und Parameter zu erhalten. Ein
VisualElement, welches einen SearchManager unterstützen möchte, wie z. B. Textfelder und Tabellen, können dieses Interface entweder direkt implementieren oder einen Adapter hierfür verwenden.
Der CommonSearchManager hat eine Methode, mit der die KeyStrokes für das Feld registriert werden, die eine Suche auslösen. Zusätzlich wird immer der Mausklick auf die Wertehilfe verwendet. Die Methode search wird immer dann aufgerufen, wenn einer der KeyStrokes gedrückt oder mit der Maus auf das Wertehilfesymbol gedrückt wurde. Innerhalb der Methode search wird entschieden, ob die Suche eine eigene Visualisierung aufbaut. Wenn nicht, besteht die Option, als Rückgabe eine Liste mit Objekten zurückzugeben. Die Objekte müssen die toString-Methode implementieren. Diese Strings werden in einer Auswahlliste (ComboBox) angezeigt. Wenn ein zweispaltiges Layout erwünscht ist, so müssen alle toString-Methoden einen Tabulator in der Zeichenkette aufweisen. Der Text links vom Tabulator wird für die erste Spalte, der Text rechts vom Tabulator für die zweite Spalte genutzt. Wählt der Anwender aus dieser Liste einen Wert aus, so wird die Methode setSearchResult aufgerufen. Der Parameter Code enthält das gewählte Objekt. Für den Fall, dass keiner der Werte ausgewählt wurde, wird die Methode mit null als Code aufgerufen. Dieser Aufruf kann dafür genutzt werden, um nicht mehr benötigte Objekte aus dem SearchManager zu entfernen.
public interface SearchTarget {
Object getCurrentValue();
void setSearchResult(Object value, String description);
VisualElement getVisualElement();
PopupWindow getPopupWindow();
DataDescription getDataDescription();
}
SearchManager im Package „com.cisag.pgm.objsearch“
Für das Package „gui“ ist ein eigener SearchManager erstellt worden, der sich im Package „com.cisag.pgm.objsearch“ befindet. Dieser SearchManager stellt einige Methoden bereit, die vom Entwickler überschrieben werden können, um gezielt an einigen Stellen im Ablauf der Suche Veränderungen vornehmen zu können. Der SearchManager selbst implementiert nicht das Interface SearchManager aus dem Package com.cisag.pgm.dialog. Stattdessen referenziert er ein Objekt, welches den SearchManager implementiert. Der (objsearch) SearchManager implementiert zusätzlich das Interface SearchResultHandler.
Der SearchManager ist als abstrakte Klasse implementiert und beinhaltet im Wesentlichen folgende Methoden:
public boolean isSearchExecutable()
public void initSearch(SearchInfo searchInfo)
public List getResultList(SearchInfo searchInfo)
Die Methode „isSearchExecutable“ kann genutzt werden, Bedingungen zu berücksichtigen, die eine Ausführung der Suche erlauben oder verhindern.
Die Methode „initSearch“ kann genutzt werden, um weitere Parameter für die Suche vor deren Ausführung zu setzen. Neben dem Setzen von Parametern kann das entsprechende Feld fest vorbelegt oder unsichtbar geschaltet werden. Fest vorbelegte Felder können in der Suche nicht geändert werden.
Beispiel:
Eine Suche soll so initialisiert werden, dass aus dem PartnerField der
Oberfläche die Partnernummer als Vorbelegung für den Parameter „number“ eingetragen wird. Der Parameter „user“ soll fest vorbelegt und der Parameter „type“ zwar vorbelegt, aber nicht angezeigt werden.
public void initSearch(SearchInfo searchInfo) {
if (partnerField != null) {
if (searchInfo.isSelectionField(“number”)) {
String partnerName = partnerField.getValue();
searchInfo.setParameter(“number”, partnerName);
}
if (searchInfo.isSelectionField(“user”)) {
searchInfo.setParameter(“user”, userName,
SearchInfo.FIX_PRESET);
}
if (searchInfo.isSelectionField(“type”)) {
searchInfo.setParameter(“type”, types,
SearchInfo.HIDDEN_FIELD);
}
}
}
Die Methode „getResultList“ ermöglicht es, das Ergebnis einer Suche vorzubestimmen. In der Liste sind die Ergebnisse in Form von CisListPartMutable abgespeichert. Die Werte müssen passend zu den definierten Attributnamen über „setObject(attrName, attrWert)“ am jeweiligen CisListPartMutable eingetragen werden. Die CisListPartMutables werden in der Suche als Ergebnis angezeigt. Ist die Liste gefüllt, so wird keine Suche in der Datenbank ausgeführt.
SearchResultHandler
Das Interface SearchResultHandler dient dazu, das Ergebnis einer Suche, welches in die SearchInfo eingetragen wurde, an das SearchTarget weiterzureichen. Hierfür stehen drei Methoden zur Verfügung. Die beiden Methoden mit dem Zusatz …AndLoad rufen zusätzlich die Load-Action der Anwendung auf.
public void takeValue(SearchInfo searchInfo)
public void takeValueAndLoad(SearchInfo searchInfo)
public void takeValueAndLoad(Object code, String description)
Beispiel:
Bei der Übernahme des Ergebnisses soll die Partnernummer des ausgewählten Datensatzes in das PartnerField geschrieben werden.
public void takeValue(SearchInfo searchInfo) {
Object partnerName = searchInfo.getResult(“number”);
if (partnerField!=null && partnerName instanceof String) {
partnerField.setValue((String) partnerName);
}
}
3.5.1 DefaultSearchManager
Das Standardverhalten der Suchen in Semiramis ist in der Klasse „com.cisag.pgm.objsearch.DefaultSearchManager“ hinterlegt. Diese Klasse implementiert das Interface „com.cisag.pgm.objsearch.SearchManager“.
Der DefaultSearchManager speichert intern ein Mapping zwischen SearchTarget- und SearchInfo-Objekten.
Der Entwickler kann seine eigene Klasse von dieser Klasse erben lassen, um ein vom Standard abweichendes Suchverhalten zu erzielen.
3.5.2 BaseGuiSearchManager
Diese Klasse stellt die Verbindung zu dem Package „com.cisag.pgm.dialog“ her, indem es den CommonSearchManager dieses Packages implementiert. Das Aufgabengebiet dieser Klasse umfasst hauptsächlich den Zugriff auf das der Suche zugrundeliegende SearchTarget.
3.5.3 Einen eigenen SearchManager implementieren
Wird ein eigener SearchManager benötigt, so kann der Entwickler entscheiden, ob er eine Ableitung vom „DefaultSearchManager“ verwenden möchte oder aber direkt die abstrakte Klasse „com.cisag.pgm.objsearch.SearchManager“ beerbt.
Entscheidet er sich für die Ableitung vom „DefaultSearchManager“, so bleibt das Verhalten der Suchen in großen Bereichen erhalten. Wenn er hingegen einen komplett eigenen SearchManager erstellt, muss er entweder das Verhalten nachbilden bzw. das gewünschte Verhalten implementieren.
Empfohlen wird die Ableitung vom „DefaultSearchManager“.
3.5.4 Anzeige mehrer Ansichten oder Suchen in einer Dialogsuche
Der DefaultSearchManager bietet die Möglichkeit, mehr als eine Suche bzw. Ansicht in der Dialogsuche anzuzeigen. Die Auswahl der Ansichten erfolgt über eine Auswahlliste in der Symbolleiste der Dialogsuche. Diese Auswahlliste wird nur angezeigt, wenn mehr als eine Ansicht vorhanden ist.
Folgende Methoden stehen für die Registrierung von Ansichten am DefaultSearchManager zur Verfügung.
public void initSearchViews()
protected final void addSearchView(SearchView searchView)
protected final void setDefaultSearchView(SearchView searchView)
Um mehr als eine Ansicht zu erhalten, muss die Methode initSearchViews() am DefaultSearchManager überschrieben werden.
Mithilfe der SearchViewFactory können SearchViews für OQL-Suchen erstellt werden.
public class SearchViewFactory {
static public SearchView createSearchView(
byte[] databaseGuid,
String searchName,
SearchResultHandler resultHandler,
SearchTarget target)
}
Alternativ kann auch eine eigene SearchView implementiert werden, die von der abstrakten Klasse SearchView erbt.
Eine SearchView wird durch Aufruf der Methode addSearchView(…) am DefaultSearchManager registriert. Wenn alle Ansichten registriert sind, kann mit der Methode setDefaultSearchView(…) die SearchView festgelegt werden, welche dem Benutzer beim Öffnen der Dialogsuche angezeigt wird. Die Reihenfolge der Registrierung bestimmt auch die Reihenfolge der Ansichten in der Auswahlliste.
Soll die ursprüngliche Suche ebenfalls in die Auswahlliste aufgenommen werden, so muss die Methode initSearchViews(…) am DefaultSearchManager an der gewünschten Position aufgerufen werden. Beim Aufruf der Methode wird diese SearchView automatisch als DefaultSearchView definiert. Wenn eine andere Ansicht als Default verwendet werden soll, muss diese wie bereits beschrieben festgelegt werden.
Beispiel:
Es soll neben der zum Feld gehörenden Suche die Suche „com.cisag.app.general.MySearchName“ angezeigt werden. Die zusätzliche Suche soll in der Auswahlliste vor der zum Feld gehörenden Suche angezeigt und beim Öffnen der Dialogsuche auch als erstes sichtbar werden.
public class MySearchManager extends DefaultSearchManager {
public void initSearchViews() {
SearchView searchView = SearchViewFactory.createSearchView(
getDatabaseGuid(),
“com.cisag.app.general.MySearchName”,
this,
getSearchTarget());
addSearchView(searchView);
super.initSearchViews();
setDefaultSearchView(searchView);
}
}
3.5.4.1 SearchViews und Auswahlliste
Für die Vorabsuche wird die „DefaultSearchView“ verwendet. Wenn die „DefaultSearchView“ nicht auf einer Standardsuche basiert, so wird für die Vorabsuchen die erste SearchView genutzt, die auf einer Standardsuche basiert. Wenn die Vorabsuche kein Ergebnis liefert, wird die Dialogsuche geöffnet und die „DefaultSearchView“ wird angezeigt.
3.6 SearchIterator
Der SearchIterator wird nur intern bei der Suche verwendet. Für den Zugriff auf die Datenbank mithilfe eines Iterators sollte deswegen auf die folgende Klasse zurückgegriffen werden:
com.cisag.pgm.util.ResultSetAdapter
Alternativ zu einem Iterator lässt sich das CisOqlSearchStatement verwenden. Als Ergebnis der Suche erhalten Sie ein CisResultSet.
3.7 Suchmerkmalfelder
Für die Suchen gibt es spezielle Suchmerkmalfelder. Diese befinden sich im Package com.cisag.pgm.gui. Suchmerkmalfelder zeichnen sich dadurch aus, dass sie am Label eine Schaltfläche besitzen, über die eine Liste angezeigt werden kann. Die Liste stellt mögliche Eingabemuster in Form von kurzen Ausdrücken dar, z. B. „beginnt mit ABC“. Wird einer der Einträge aus der Liste ausgewählt, so wird das Muster in das Feld übertragen und kann vom Benutzer angepasst werden. In dem genannten Beispiel würde „ABC*“ in das Feld eingetragen.
In den generierten Suchen werden diese Suchmerkmalfelder automatisch genutzt.
Folgende Suchmerkmalfelder stehen zur Verfügung:
Suchmerkmalfeld | Datentyp |
TextSelectionField | Zeichenketten |
DecimalSelectionField | natürliche und reelle Zahlen |
AbstractCisQuantitySelectionField | natürliche und reelle Zahlen mit Einheit |
CisDateSelectionField | Datum und Uhrzeit |
Von der abstrakten Klasse „AbstractCisQuantitySelectionField“ gibt es für die Spezialparts besondere Ausprägungen.
Suchmerkmalfeld | Datentyp |
QuantitySelectionField | Mengen |
DurationSelectionField | Dauer |
ForeignAmountSelectionField | Fremdwährungen |
DomesticAmountSelectionField | Datum und Uhrzeit |
Diese Suchmerkmalfelder finden sich im Package com.cisag.app.general.gui.
3.7.1 Eingabemöglichkeiten in Suchmerkmalfeldern
In ein Suchmerkmalfeld kann der Benutzer ein oder mehrere Suchmerkmale eingeben. Semiramis unterstützt dabei die folgende Grammatik:
Selection = [CaseSensitiv | CaseInsensitiv] [OrList];
OrList = Parameter {ParameterSeparator Parameter};
Parameter = Range | SingleOperator;
ParameterSeparator = “, “;
Range = LowerRange | UpperRange | FullRange;
LowerRange = “- ” UpperValue;
UpperRange = LowerValue ” -“;
FullRange = LowerValue ” – ” UpperValue;
SingleOperator = [Operator] Value;
Operator = “<” | “>” | “!=”;
CaseSensitiv = “=”;
CaseInsensitiv = “~”;
Value = ? Alle Zeichenfolgen die nicht “, ” oder ” – ” enthalten ?;
UpperValue = ? Alle Zeichenfolgen die nicht “, ” enthalten ?;
LowerValue = ? Alle Zeichenfolgen die nicht “, ” oder ” – ” enthalten ?;
Die Werte für Value, UpperValue und LowerValue sind vom Datentyp abhängig. Bei Suchmerkmalen für Zahlen sind z. B. nur Zahlen, Tausendertrennzeichen und Dezimaltrennzeichen zulässig.
Bei Suchmerkmalen für Zeit und Datum werden „Operator“, „CaseSensitiv“ und „CaseInsentiv“ nicht unterstützt. Weiterhin werden „Value“, „UpperValue“ und „LowerValue“ ausgewertet. Die Beschreibung der Möglichkeiten von Zeit und Datum ist nicht Teil dieses Dokumentes.
3.8 Hooks
In Semiramis stehen mehrere Hooks zur Verfügung, um Einfluss auf den Ablauf einer Suche nehmen zu können. In den Entwicklungsobjekten kann zu einer OQL-Suche eine Java-Klasse als Hook angegeben werden. Die Java-Klasse muss von der Abstrakten Klasse com.cisag.pgm.objsearch.SearchHook erben. Die darin enthaltenen Hooks lassen sich in fünf Gruppen aufteilen.
Hookgruppen | Kurzbeschreibung |
SearchIteratorHook | Austausch des SearchIterators |
SelectionFieldHook | Austausch und Manipulation von Suchmerkmalfeldern |
ExecuteHook | Manipulation und Erweiterung des OQL-Statements vor der Ausführung |
ResultHook | Manipulation der Ergebnisliste |
VisualisationHook | Verwenden einer eigenen Visualisierung für die Ergebnisdarstellung |
Einige der Hookgruppen sind noch weiter unterteilt. Die darin enthaltenen Hooks können mal in Kombination, mal nur exklusiv genutzt werden. Näheres wird in der folgenden Dokumentation zu den einzelnen Hooks erklärt.
Alternativ zu der Verwendung der abstrakten Klasse SearchHook kann der Hook auch auf Basis von Interfaces implementiert werden. Die Interfaces sind mit Semiramis 4.3 auf deprecated gestellt worden. Hooks, die die Interfaces verwenden,sollten bei Gelegenheit auf die abstrakte Klasse umgestellt werden. Weitere Informationen zur Umstellung auf die abstrakte Klasse SearchHook sind im Abschnitt „Umstellung der InterfaceHooks auf die abstrakte Klasse SearchHook“ zu finden.
3.8.1 SearchHook
Um einen Hook aus der Klasse com.cisag.pgm.objsearch.SearchHook verwenden zu können, müssen zum einem die entsprechenden Methoden des Hooks implementiert werden, zum anderen muss der Hook aktiviert werden. Die Aktivierung eines Hooks erfolgt im Konstruktor der ableitenden Klasse durch Aufruf des folgenden Konstruktors am SearchHook:
protected SearchHook(int activeHookTypes, int inactiveHookTypes)
Der Konstruktor enthält zwei Parameter zum Setzen von HookTypen. Es ist möglich Hooks zu aktivieren und Hooks zu deaktivieren. Das Deaktivieren von Hooks ist in Ableitungen eines implementierten Hooks von Interesse um z. B. eine andere Visualisierung zu erzwingen.
Die möglichen HookTypen sind in der inneren Klasse HookType als Konstanten aufgeführt.
public static final class HookType {
public static final int SEARCH_ITERATOR = …;
public static final int SELECTION_FIELD = …;
public static final int EXECUTE = …;
public static final int RESULT_CHANGE_CONTENT = …;
public static final int RESULT_FILTER = …;
public static final int VISUALISATION_LIST = …;
public static final int VISUALISATION_TABLE = …;
public static final int VISUALISATION_COLUMN = …;
}
Folgendes Muster ist für die Erstellung der Konstruktoren einzuhalten:
public class MySearchHook extends SearchHook {
public MySearchHook() {
this(0, 0);
}
protected MySearchHook(int activeHookType,
int inactiveHookTypes){
super(HookType.SELECTION_FIELD |
HookType.VISUALISATION_COLUMN | activeHookType,
inactiveHookType);
}
…
}
Jeder SearchHook muss einen parameterlosen Konstruktor besitzen. Dieser Konstruktor wird bei der Erzeugung der Klasse aus der Suche aufgerufen. Die Standardimplementierung des Konstruktors ruft dabei immer this(0, 0) auf.
Der Konstruktor mit den beiden int-Werten ruft seinerseits den Konstruktor der Elternklasse mit den übergebenen int-Werten auf. Dabei kann jeder der Werte um die benötigten Hook-Typen erweitert werden. Diese werden mit einem „|“ verkettet. In dem vorherigen Beispiel wurde der SelectionFieldHook und der VisualisationColumnHook aktiviert.
Wird der Hook als Basis für einen weiteren Hook benutzt, so kann von diesem abgeleitet werden. Wird nun ein anderer VisualisierungsHook benötigt, so muss der Hook der Elternklasse deaktiviert und der neue VisualisierungsHook akiviert werden. Der SelectionFieldHook der Elternklasse bleibt dabei aktiviert.
public class AdaptedSearchHook extends MySearchHook {
public AdaptedSearchHook () {
this(0, 0);
}
protected AdaptedSearchHook(int activeHookType,
int inactiveHookTypes){
super(HookType.VISUALISATION_LIST | activeHookType,
HookType.VISUALISATION_COLUMN | inactiveHookType);
}
…
}
3.8.2 SearchIteratorHook
HookType: SEARCH_ITERATOR
public SearchIterator createSearchIterator(SearchInfo searchInfo)
Mit diesem Hook kann der SearchIterator ausgetauscht werden. Ein Anwendungsbeispiel dafür wäre der Zugriff auf ein Dateisystem anstelle der Datenbank.
3.8.3 SelectionFieldHook
HookType: SELECTION_FIELD
public Field manipulateSelectionFieldBefore(String name,
Field field)
public void manipulateSelectionFieldAfter(String name, Field field)
In der Dialogsuche und in der Suche im Navigationsbereich werden für die Eingabe von Suchmerkmalen Eingabefelder verwendet. Normalerweise werden die angezeigten Felder automatisch und typabhängig gewählt. Für ein Valueset wird z. B. ein ValueSetField erzeugt.
Werden für die Eingabe der Suchmerkmale spezielle Felder benötigt, so können mit diesem Hook die verwendeten Felder manipuliert bzw. ausgetauscht werden.
In Semiramis wird beim Aufbau einer Oberfläche zwischen zwei Zeitpunkten unterschieden. So können einige Einstellungen für ein Feld nur vor dem Hinzufügen zur Oberfläche und andere erst danach vorgenommen werden. Um diesem Zustand Rechnung zu tragen, stehen zwei Methoden zur Verfügung.
Die Methode „manipulateSelectionFieldBefore“ ermöglicht es, ein Feld auszutauschen bzw. das vorgeschlagene Feld zu modifizieren. Mit der Methode „manipulateSelectionFieldAfter“ lassen sich die Eigenschaften eines bestehenden Feldes verändern.
Jeder Methode werden zwei Parameter übergeben. Zum einen der Name des Parameters, so wie er in den Metadaten der OQL-Suche bei den einzelnen Attributen unter „Name“ eingetragen wurde, und zum anderen das Feld, welches von der Suche verwendet wird. Anhand des Namens kann entschieden werden, ob an dem übergebenen Feld Veränderungen vorgenommen werden sollen.
public Field manipulateSelectionFieldBefore(String name,
Field field) {
if (name.equals(“definition”)) {
field = new MySpecialField(Guid.toHexString(
WebInterface.getInstanceGuid(field)),
field.getOriginalPath(),…);
field.setSelectionMode(true);
}
return field;
}
public void manipulateSelectionFieldAfter(String name,
Field field) {
if (name.equals(“definition”)) {
field.setSearchManager(null);
}
}
Wird ein Feld ersetzt, so ist darauf zu achten, dass das Feld im SelectionMode verwendet wird, da innerhalb der Suchen ausschließlich die Methode „getSelectionString“ an dem entsprechenden Feld aufgerufen wird. Die Methode „getValue“ des Feldes findet keine Berücksichtigung.
Weiterhin sollten GUID und Pfad des ursprünglichen Feldes zum Erzeugen des neuen Feldes verwendet werden.
3.8.3.1 SimpleEntityFields und GuidSelection
In Semiramis lassen sich Relationen technisch bedingt nicht datenbankübergreifend anlegen. Möchten Sie dennoch auf einen Datensatz in einer anderen Datenbank verweisen, so kann lediglich der Schlüssel auf diesen Datensatz in der Tabelle abgelegt werden. Die Information über das Ziel der Referenz kann nicht mit Semiramismitteln abgebildet werden. Erst innerhalb des Sourcecodes des Entwicklers ist diese Information enthalten.
OQL-Suchen können ebenfalls nicht datenbankübergreifend ausgeführt werden. Der Entwickler hat auch keine Möglichkeit, im Sourcecode die generisch erzeugten Suchen derart abzuändern, dass über die Datenbanken hinweg das Ergebnis ermittelt wird.
Relation zwischen Tabellen unterschiedlicher Datenbanken
Beispiel:
Zwischen den Objekten A und B besteht eine Relation. B kann von n A-Objekten und umgekehrt referenziert werden. A existiert in der Datenbank DB1 und B auf der Datenbank DB2.
Wird eine OQL-Abfrage über die Tabelle A auf der Datenbank DB1 durchgeführt, so kann nicht auf Attribute, z. B. „name“, der Tabelle B in der Datenbank DB2 zugegriffen werden.
Die einzige Information, die Objekt A vom Objekt B bekannt ist, ist der Schlüssel von B in Form eines Byte-Arrays. Die Information darüber, dass der Schlüssel auf ein Objekt in Tabelle B verweist, kann nicht in Form einer Relation zwischen A und B abgebildet werden.
Um eine Abfrage auf der Tabelle A durchführen zu können, muss der Benutzer in irgendeiner Form die GUIDs der einschränkenden B-Objekte kennen und eingeben, was dem Benutzer aber nicht zuzumuten ist. Der Benutzer soll vielmehr solche Daten eingeben, deren Eingabe menschenlesbar ist, wie z. B. das Attribut „name“ in Tabelle B. Diese Attribute stehen aber bei einer Abfrage auf Tabelle A nicht zur Verfügung.
Dem Benutzer muss also eine Möglichkeit gegeben werden, an die GUIDs der Tabelle B zu gelangen. Die Lösung dieses Problems findet sich in einem Kompromiss.
Ein SimpleEntityField erlaubt es dem Benutzer, einen Text einzugeben, der als Business Key ein Objekt in einer Tabelle eindeutig referenziert. Neben diesem Business Key existiert zumeist ein technischer Schlüssel in Form einer GUID. SimpleEntityFields mappen die Eingabe des Benutzers auf eine GUID, die für das Feld mithilfe der Methode getGuid() abgefragt werden kann.
Die OQL-Suchen in Semiramis erlauben mithilfe des SelectionFieldHooks das Austauschen von Eingabefeldern. Anstelle des automatisch erstellten Feldes kann dann ein SimpleEntityField verwendet werden.
Das SimpleEntityField muss für dieses spezielle Verwendung entsprechend eingestellt werden, damit beim Aufruf der Methode „getSelectionString“ der richtige Wert, also die GUID, zurückgegeben wird. Hierfür ist der SelectionMode auszuschalten und stattdessen der GuidSelectionMode einzuschalten.
public Field manipulateSelectionFieldBefore(String name,
Field field ) {
if (name.equals(“definition”)) {
SimpleEntityField result = new MySimpleEntityField(…);
result.setSelectionMode(false);
result.setGuidSelectionMode(true);
field = result;
}
return field;
}
Der Kompromiss besteht darin, dass maximal ein Objekt der Tabelle B für die Suche als einschränkender Parameter genutzt wird. Es ist nicht möglich, mehrere B-Objekte zu verwenden.
Beispiel:
Es ist eine OQL-Suche über die Tabelle A erstellt worden, die den Parameter b (byte[]) enthält. Über den SelectionFieldHook wird für diesen Parameter ein SimpleEntityField für Objekte der Tabelle B für die Anzeige eingebaut und entsprechend manipuliert.
Der Benutzer erhält die Suche, z. B. in Form eines Dialogs, angezeigt. In das SimpleEntityField für „b“ kann der Benutzer nun den Code-Schlüssel eingeben oder aber komfortabel mithilfe einer hinterlegten Suche ein Objekt auswählen.
Beim Starten der Suche wird dann die GUID dieses Objekts ausgelesen und in die Suche für den Parameter „b“ eingesetzt.
Die Suche liefert dann nur die Objekte von A, die bei b die GUID von B eingetragen haben.
Mithilfe dieses Features lässt sich aber auch noch ein anderes Problem lösen.
Beispiel:
Die beiden Tabellen A1 bis An liegen in der gleichen Datenbank. Allen Tabellen gemein ist, dass sie über viele Attribute verfügen und sehr komplex miteinander verbunden sind. Eine Abfrage über die Tabellen führt zu einem sehr großen Join, der die Ressourcen stark belastet. Wenn Sie einige der Joins einsparen möchten, so können Sie dies mithilfe der beschriebenen Methode unter Berücksichtigung der Einschränkungen durchführen.
3.8.4 ExecuteHook
HookType: EXECUTE
public String prepareExecute(CisOqlSearchStatement stmt)
Der ExecuteHook erlaubt es, die Verknüpfung der Suchmerkmale zu beeinflussen bzw. weitere Parameter und Suchmerkmale bei der Suche zu berücksichtigen.
Werden in ein Eingabefeld mehrere Suchmerkmale eingegeben, so werden diese in der Suche mit einem „Oder“ verknüpft.
Beispiel:
In einem Eingabefeld A trägt der Benutzer folgenden Text ein: „1, a*, *b“. Daraus ergibt sich folgende Verknüpfung.
A=1 OR A LIKE ‘a*’ OR A LIKE ‘*b’
Die Suchmerkmale mehrerer Felder werden mit einem „Und“ verknüpft.
Beispiel:
Eine Suche besitzt die Eingabefelder zu den Parametern A, B und C. In das Feld A trägt der Benutzer den Text „1 , 2“, in B „3“ und in C „a* , b*“ ein. Daraus ergibt sich folgende Verknüpfung:
(A=1 OR A = 2) AND B=3 AND (C LIKE ‘a*’ OR C LIKE ‘b*’)
Manchmal ist es aber auch wünschenswert, einige der Eingabefelder mit einem „Oder“ zu verknüpfen bzw. weitere Parameter hinzuzunehmen, z. B. um Berechtigungen zu berücksichtigen.
Beispiel:
Die Attribute A und B des vorangegangenen Beispiels sollten mit einem „Oder“ anstelle des „Und“ verknüpft sein.
((A=1 OR A = 2) OR B=3) AND (C LIKE ‘a*’ or C LIKE ‘b*’)
Um dieses Verhalten zu erzielen, kann einem CisOqlSearchStatement beim Aufruf der Methode „execute“ eine zusätzliche Bedingung in Form eines Strings mitgegeben werden. Diese Bedingung kann sowohl zusätzliche als auch schon bekannte Parameter enthalten, die mithilfe von OQL verknüpft sind. Die Bedingung wird vom Statement untersucht. Alle darin gefundenen Parameter werden beim Aufbau der Bedingung nicht weiter berücksichtigt. Die verbliebenen Parameter werden wie gehabt mithilfe von „Und“ verknüpft.
Mit dem ExecuteHook erhält der Entwickler Zugriff auf das Statement. In der Methode „prepareExecute“ kann eine zusätzliche Bedingung aufgebaut und als Ergebnis der Methode zurückgegeben werden. Die Werte zusätzlicher Parameter müssen am Statement gesetzt werden.
Beispiel:
Die nachfolgende Modifikation erlaubt es, wenn eine notwendige Berechtigung nicht vorhanden ist, nur Datensätze anzuzeigen, die der aktuelle Benutzer selber angelegt hat oder bei denen er im Feld „userGuid“ eingetragen ist.
public String prepareExecute(CisOqlSearchStatement stmt) {
if (!Logic.getInstance().isAllowedDisplayOthersObjects()){
String condition =
“ac:updateInfo.createUser={userGuid} OR EXISTS ”
+ “(SELECT w:guid FROM com.cisag….obj.XYZ w ”
+ “WHERE w:userGuid={userGuid})”;
CisEnvironment env = CisEnvironment.getInstance();
stmt.setGuid(“userGuid”, env.getUserGuid());
return condition;
} else {
return “”;
}
}
3.8.5 ResultHooks
Mit den ResultHooks kann das Ergebnis einer Suche manipuliert werde. Diese Manipulation findet zu einem Zeitpunkt statt, an dem das Ergebnis noch nicht visualisiert wurde.
Der ResultHook unterteilt sich in die Hooks ResultChangeContentHook, zum inhaltlichen Verändern des Suchergebnisses, und ResultFilterHook, zum Ändern der Ergebnismenge. Die beiden Hooks können miteinander kombiniert werden, wobei in der Aufrufreihenfolge der ResultFilterHook vor dem ResultChangeContentHook steht.
3.8.5.1 ResultChangeContentHook
HookType: RESULT_CHANGE_CONTENT
public void changeContentInResultList(java.util.List list)
Mit den ResultChangeContentHook kann der Inhalt eines Datensatzes manipuliert werden. Es können keine Datensätze gelöscht oder zum Ergebnis hinzugefügt werden. Die übergebene Liste enthält bis zu sechzig „CisListPartMutable“-Objekte. Die Liste selbst ist eine „unmodifiable List“, so dass Änderungen an der Größe, z. B. durch entfernen von Elementen zu Exceptions führen.
Dadurch, dass sich die Ergebnisgröße nicht ändert, kann dieser Hook eingesetzt werden, ohne die Funktion „Datensätze zählen“ in der Dialogsuche zu behindern.
Jedes CisListPartMutable in der Liste stellt einen Datensatz dar. Der Zugriff auf die einzelnen Ergebnisspalten erfolgt über den Namen des Attributs wie er in den Metadaten der Suche definiert wurde. Alle primitiven Datentypen (z. B. double, short) werden durch ihre Wrapperklassen (z. B. Double, Short) im CisListPartMutable abgebildet. Ein Spezialpart wird immer als Specialpart im CisListPartMutable abgelegt, auch wenn diese Parts mehrere Attribute in der Datenbank besitzen.
Im folgenden Beispiel werden zwei Strings zusammengefasst.
public void changeContentInResultList(List list) {
Iterator iterator = list.iterator();
CisListPartMutable row;
while (iterator.hasNext()) {
row = (CisListPartMutable) iterator.next();
code = (String) row.getObject(“code”);
description = (String) row.getObject(“desc”);
row.setObject(“xyz”, code + “-” + description);
}
}
3.8.5.2 ResultFilterHook
HookType: RESULT_FILTER
public List filterResultList(java.util.List list)
Mit den ResultFilterHook kann das Ergebnis einer Suche gefiltert werden. Dabei können sowohl Datensätze entfernt, als auch hinzugefügt werden. Die übergebene Liste enthält bis zu sechzig „CisListPartMutable“-Objekte. Analog zum ResultChangeContentHook stellt jedes „CisListPartMutable“-Objekt einen Datensatz dar und der Zugriff auf die Spalten erfolgt über die gleichen Wege. Bei der zurückgegeben Liste kann es sich um die übergebene oder eine neu erstellte Liste handeln.
Der ResultFilterHook sollte nur dann eingesetzt werden, wenn die Ergebnismenge geändert wird, da mit dem Einsatz diese Hooks die Funktion „Datentsätze zählen“ in den Suchen ausgeschaltet werden muss. Hintergrund dieser Einschränkung ist, dass das Zählen von Datensätzen auf der Datenbank erfolgt. Wird nachträglich die Ergebnismenge verändert, so passen Zählergebnis und angezeigte Datenmenge nicht mehr zueinander. Um ein korrektes Zählergebnis zu erreichen müsste der Hook für alle Datensätze durchlaufen werden, was aus Performance- und Resourcengründen in keinem Verhältnis zur ermittelten Information steht.
Im folgenden Beispiel werden alle Datensätze entfernt, bei denen das Attribut „type“ den Wert TYPE_A, TYPE_B oder TYPE_C hat. Die Datensätze, bei denen das Attribut „type“ den Wert TYPE_D besitzt, werden auf den Wert TYPE_A gemappt.
public List filterResultList(List list) {
for (Iterator i = list.iterator(); i.hasNext();) {
CisListPartMutable row = (CisListPartMutable) i.next();
Short type = (Short) row.getObject(“type”);
switch (type.shortValue()) {
case TYPE_A:
case TYPE_B:
case TYPE_C:
i.remove();
break;
case TYPE_D:
row.setObject(“type”,
CisNumberFactory.getShort(TYPE_A));
break;
}
}
return list;
}
3.8.6 VisualisationHooks
Mit Semiramis 4.3 wird für die Visualisierung des Suchergebnisses im Standard das Grid-Control verwendet. Das Grid-Control ist der Ersatz für die Liste und die Tabelle. Es vereint die Eigenschaften der Tabelle mit den Eigenschaften der Liste und besitzt darüber hinaus weitere Möglichkeiten zur Darstellung. In einem Grid-Control lassen sich z. B. die Spalten verschieben, in ihrer Breite ändern und in mehreren Zeilen anordnen. Auch lassen sich Spalten aus der Darstellung entfernen und zu einem späteren Zeitpunkt wieder einfügen. Spalten, die im Datenmodell als Sortierbar gekennzeichnet sind, lassen sich durch Klick auf den Kopf der Spalte in die Sortierung aufnehmen.
Die verwendeten Spalten werden aufgrund der Meta-Daten der OQL-Suche bestimmt. Dabei werden typabhängig die Felder für die Anzeige ermittelt und am Grid-Control gesetzt.
Für den Fall, dass die generische Visualisierung nicht den geforderten Ansprüchen genügt, kann mithilfe der VisualisationHooks die Darstellung manipuliert, bzw. ausgetauscht werden.
Für diese Aufgabe stehen drei Hooks zur Verfügung. Speziell für die Visualisierung auf Basis des Grid-Controls gibt es den VisualisationColumnHook. Mit diesem Hook können gezielt einzelne Spalten im Grid-Control manipuliert werden.
Soll die Darstellung als Liste erfolgen, so kann der VisualisationListHook eingesetzt werden.
Der VisualisationTableHook ist mit der Einführung des VisualisationColumnHooks praktisch bedeutungslos geworden und dient lediglich dazu das GridContol wie eine Tabelle mit Daten füllen zu können.
Die Hooks zur Visualisierung können nicht miteinander kombiniert werden. Es darf nur einer der Hooks aktiviert werden.
3.8.6.1 VisualisationColumnHook
HookType: VISUALISATION_COLUMN
public static abstract class ColumnModel {
public abstract short getVisualisationTarget();
public abstract Column getColumn(String name);
public abstract Column createColumn(String name, String guid,
String displayFieldPath);
public abstract boolean isColumnDefinied(String name);
public abstract java.util.List getColumnNames();
}
public static abstract class Column {
public static final int GENERIC_FIELD = 1;
public static final int SPECIAL_FIELD = 2;
public abstract short getVisualisationTarget();
public abstract String getName();
public abstract String getGuid();
public abstract String getDisplayFieldPath();
public abstract void setDisplayFieldPath(
String displayFieldPath);
public abstract void setDisplayType(int displayType);
public abstract int getDisplayType();
public abstract void setDeleted(boolean state);
public abstract boolean isDeleted();
public abstract Field getDisplayField();
}
public void manipulateColumnModel(ColumnModel model)
public Field createDisplayField(Column column)
public void manipulateDisplayField(Column column)
public void setDisplayFieldValue(Column column,
CisListPartMutable data)
Der VisualisationColumnHook dient der gezielten Manipulation von Spalten in der Ergebnisvisualisierung. Der Hook besteht aus vier Methoden und zwei inneren Klassen. Die inneren Klassen beschreiben das Datenmodell, welches für die Manipulationen Verwendet wird. Den Kopf bildet dabei die Klasse ColumnModel. Diese besitzt für jede Spalte eine Instanz von Column.
Ein Column-Objekt besitzt einen innerhalb der Suche eindeutigen Namen. Als Name wird dabei der Name des Attributs aus den Metadaten der OQL-Suche verwendet. Der Zugriff auf eine Spalte im ColumnModel erfolgt über die Methode ColumnModel#getColumn(String name).
Um das Spaltenmodell zu manipulieren ist die Methode manipulateColumnModel(ColumnModel model) zu implementieren. Diese bekommt als einigen Parameter das ColumnModell, wie es aufgrund der Metadaten der OQL-Suche entstanden ist, übergeben. Enthalten in diesem Modell sind nur die Spalten, die in den Metadaten eine Anzeigeposition zugeordnet haben.
Die Methoden createDisplayField(…), manipulateDisplayField(…) und setDisplayFieldValue(…) müssen nur implementiert werden, wenn statt der generisch erzeugten Felder für die Darstellung des Wertes in einer Spalte ein anderes Feld verwendet werden soll.
Die Verbindung zwischen den Daten, den Spalten und der Sortierbarkeit besteht über den Namen des Attributes in der OQL-Suche. Jede Ergebniszeile ist in einem CisListPartMutable gekapselt. Innerhalb davon erfolgt der Zugriff auf einen konkreten Wert über den Namen des Attributs. Das CisListPartMutable enthält alle Attribute, die eine Rückgabeposition eingetragen haben, d. h. es können mehr Attribute enthalten sein, als es Attribute mit Anzeigepositionen gibt. Über den logischen Datentypen des Attributs, der in der Anzeige verwendet wird, ist definiert, um welchen primitiven Datentypen es sich bei dem hinterlegten Wert handelt. Daraus bestimmt sich das Feld für die Anzeige. Der Wert wird auf den entsprechenden Datentypen gecastet und über die zugehörige Methode am Feld gesetzt. Dieses Wissen kann man sich zu nutze machen, wenn man zusätzliche Spalten in der Suche anzeigen möchte.
Grundsätzlich sollten Manipulationen an Spalten nur dann vorgenommen werden, wenn die generische Darstellung nicht ausreicht. Dies ist zum Beispiel der Fall, wenn für die Darstellung Icons und EntityFields benötigt werden. Bei neuen Spalten sollte überprüft werden, ob das generische Verhalten ausreicht. In diesem Fall muss nur sichergestellt werden, dass das CisListPartMutable zu dem Spaltennamen einen Wert mit dem richtigen Datentyp vorhält. Dies kann z. B. über die Verwendung des ResultChangeContentHooks erfolgen, indem zu dem Spaltennamen ein Wert abgelegt wird.
Im Folgenden werden anhand konkreter Aufgaben mögliche Implementierungen gezeigt.
Eine Spalte von der Anzeige ausschließen
Um eine Spalten von der Anzeige auszuschließen, muss diese als gelöscht markiert werden. In einem Grid-Control können Spalten ausgeblendet werden. Diese Spalten können bei Bedarf vom Benutzer wieder hinzugefügt werden. Eine Spalte, die hier als gelöscht markiert wird, wird dem Grid-Control nicht hinzugefügt und kann deshalb auch nicht durch den Benutzer in die Anzeige übernommen werden.
public void manipulateColumnModel(ColumnModel model) {
model.getColumn(“name”).setDeleted(true);
}
Eine zusätzliche Spalte anzeigen
Eine zusätzliche Spalte kann über die Methode createColumn(…) dem Modell hinzugefügt werden. Der verwendete Name der Spalte kann, muss aber nicht, in der Suche als Attribut bekannt sein.
Benötigt werden auf jeden Fall eine GUID und ein logischer Datentyp. Mit diesen wird im generischen Fall das Feld gebaut, welches für die Anzeige der Daten genutzt wird.
public void manipulateColumnModel(ColumnModel model) {
model.createColumn(“name”,
“0000541C2E541D1082DD846464090000”,
“com.cisag.app.general.ui:PartnerName.lt”);
}
Für die Datenbeschaffung gibt es nun drei Varianten:
- Es gibt in der OQL-Suche ein gleichnamiges Attribut, welches eine Rückgabeposition eingetragen hat.
- Es gibt in der OQL-Suche ein gleichnamiges Attribut, welches keine Rückgabeposition eingetragen hat.
- Es gibt in der OQL-Suche kein Attribut mit dem angegeben Namen.
Im ersten Fall befindet sich im CisListPartMutable bereits das Attribut mit dem benötigten Wert für die Darstellung. In den anderen beiden Fallen muss über den ResultChangeContentHook ein Wert für das Attribut hinterlegt werden.
Sofern das Attribut in der Suche als Sortierbar gekennzeichnet ist, muss sichergestellt werden, dass die angezeigten Werte zur Sortierung passen, d. H. wenn der Benutzer auf den Spaltenkopf klickt, werden die Daten mit der neuen Sortierung von der Datenbank abgefragt. Werden dann in dem Feld statt der Daten, auf denen die Sortierung erfolgte, andere Werte angezeigt, so kann es sein, dass die Werte nicht zur Sortierung passen.
Austauschen eines Feldes für die Anzeige
Wie bereits geschildert sollten Felder für die Anzeige nur dann ausgetauscht werden, wenn die generisch erstellen Felder nicht den geforderten Ansprüchen genügen. Dies ist in der Regel der Fall bei der Anzeige von ValueSets als Icons oder wenn ein EntityFeld benötigt wird.
In diesen Fällen kann an der Spalte ein entsprechendes Flag gesetzt werden.
public void manipulateColumnModel(ColumnModel model) {
model.getColumn(“name”).setDisplayType(Column.SPECIAL_FIELD);
}
Mit setzen des Flags müssen die Methoden createDisplayField(…), manipulateDisplayField(…) und setDisplayFieldValue(…) für die entsprechende Spalte implementiert werden. Diese Methoden werden ausschließlich für die Spalten aufgerufen, deren Flag auf Column.SPECIAL_FIELD umgestellt wurde.
Analog zu dem Suchmerkmalen, gibt es zwei Methoden zum Erzeugen und zum manipulieren des Anzeigefeldes. Die Methode createDisplayField(…) erzeugt das Anzeigefeld. Dabei sind möglichst die Guid und der Pfad aus der Column zu verwenden. Die Methode manipulateDisplayField(…) wird aufgerufen, nachdem das erzeugte Feld dem Grid-Control hinzugefügt wurde. Hier können die Manipulationen am Feld vorgenommen werden, die erst nach dem Hinzufügen zur Oberfläche möglich sind.
public Field createDisplayField(Column column) {
if (column.getName().equals(“name”)) {
return new MySpecialField(column.getGuid(),
column.getDisplayPath());
}
}
public void manipulateDisplayField(Column column) {
if (column.getName().equals(“name”)) {
MySpecialField field = (MySpecialField)
column.getDisplayField();
field.set…;
}
}
Nachdem das Feld erzeugt wurde, muss das Setzten des Wertes in das Feld implementiert werden. Die Methode setDisplayFieldValue(…) bekommt neben der Column, die das erzeugte Feld enthält, das CisListPartMutable der aktuellen Zeile. Die benötigten Daten zum Füllen des Feldes können aus beliebigen Einträgen der Zeile ermittelt werden. Diese kann genutzt werden, um z. B. ein mehrschichtiges IconField, basierend auf mehreren Status, mit Daten zu versorgen.
public void setDisplayFieldValue(Column column,
CisListPartMutable part) {
if (column.getName().equals(“name”)) {
MySpecialField field = (MySpecialField)
column.getDisplayField();
String value1 = (String) part.getObject(“value1”);
String value2 = (String) part.getObject(“value2”);
…
field.setValue(value1, value2,…);
}
}
3.8.6.2 VisualisationListHook
HookType: VISUALISATION_LIST
public void initList(List list, short type)
public ListView createView(short type)
public static final class VisualisationTarget {
public static final short DIALOG = …;
public static final short LOCATOR = …;
}
Reicht zur Darstellung des Ergebnisses die Standardlösung nicht aus, so kann eine eigene Liste erstellt werden.
Die Methode „createView“ erzeugt in Abhängigkeit des übergebenen Typen eine ListView. Als mögliche Werte werden entweder VisualisationTarget.DIALOG oder VisualisationTarget.LOCATOR übergeben. Dadurch kann die Darstellung an den jeweiligen Verwendungsort angepasst werden. In anwendungsbezogenen Navigator (LOCATOR) steht weniger Platz in der Breite zur Verfügung als im Dialog (DIALOG).
Die Methode „initList“ ermöglicht das Setzen von Spaltennamen und –breiten. Analog zur Methode „createView“ kann das Erscheinungsbild in Abhängigkeit zum VisualisationTarget gestaltet werden.
Hinweis:
Bei der Implementierung ist darauf zu achten, dass möglichst nur Labels für die Anzeige der Daten verwendet werden. Labels erhalten in der Regel keinen Fokus, wodurch die jeweilige Zeile fokussiert werden kann. Bei Textfeldern hingegen erhält das Textfeld den Fokus; die Zeile wird nicht markiert. Kann für die Darstellung kein Label genommen werden, so muss an dem Feld für die Anzeigen „setRequestFocusEnabled(false)“ gesetzt werden.
Hinweis:
Für die Darstellung der Liste sollte immer der ListStyle SEARCH verwendet werden. Dieser Style bewirkt beim Markieren einer Zeile, dass die entsprechende Hintergrundfarbe bei der ListView gesetzt wird.
3.8.6.3 VisualisationTableHook
HookType: VISUALISATION_TABLE
public void initTable(Table table, short type)
public void dataToTable(CisListPartMutable data, short type)
public static final class VisualisationTarget {
public static final short DIALOG = …;
public static final short LOCATOR = …;
}
Reicht zur Darstellung des Ergebnisses die Standardlösung nicht aus, so kann eine eigene Tabelle erstellt werden. Die Methode „initTable“ ermöglicht das Setzen von Spaltennamen und –breiten.
Analog zur Listendarstellung wird über den Parameter „type“ angegeben, welchen Verwendungszweck die Tabelle haben wird. Als möglicher Wert wird eine der beiden Kostanten aus der innern Klasse VisualisationTarget übergeben.
Die Methode „dataToTable“ setzt die Daten in die einzelnen Felder der Tabelle.
public void dataToTable(CisListPartMutable part, short type) {
switch(type) {
case (VisualisationTarget.DIALOG):
….
break;
case (VisualisationTarget.LOCATOR):
….
break;
}
}
Hinweis: Der VisualisationTableHook sollte nicht mehr verwendet werden, da für die Darstellung das Grid-Control verwendet wird. Der VisualisationTableHook stellt ein Interface zur Verfügung, so dass sich das Grid-Control in seiner Verwendung analog zur Tabelle verhält. Das heißt aber auch, dass alle Spalten definiert werden müssen. Eine Sortierung auf dem Spaltenkopf kann aufgrund dieser Tatsache nicht gewährleistet werden. Es wird deshalb empfohlen statt des VisualisationTableHooks den VisualisationColumnHook zu verwenden.
3.8.7 Umstellung der InterfaceHooks auf die abstrakte Klasse SearchHook
Mit Semiramis 4.3 sind die InterfaceHooks auf deprecated gestellt worden. Die InterfaceHooks funktionieren auch ab Semiramis 4.3 uneingeschränkt weiter. Es empfiehlt sich aber, insbesondere um die Möglichkeiten des Grid-Controls vollständig nutzen zu können, die Suchen auf die abstrakte Klasse SearchHook umzustellen. Die Umstellung auf die abstrakte Klasse SearchHook ist im Allgemeinen problemlos möglich. Wichtig dabei ist, dass, neben der Implementierung der Methoden, der Hook im Konstruktor registriert wird.
Die folgende Tabelle listet in Kurzform auf, welcher InterfaceHook in welchen HookType des SearchHooks umgewandet werden muss.
CisObjectSearchIteratorCreator und SearchIteratorCreatorHook
Der CisObjectSearchIteratorCreator ist bereits seit Semiramis 4 auf deprecated. und sollte wenn möglich bereits auf den SearchIteratorCreatorHook umgestellt sein.
Anstelle des SearchIteratorCreatorHook ist der SearchIteratorHook zu verwenden. Der Methodenname ist gleich und die Funktionsweise nicht geändert.
public SearchIterator createSearchIterator(SearchInfo searchInfo)
CisObjectSearchFieldManipulation
Anstelle des CisObjectSearchFieldManipulation-Hooks ist der SelectionFieldHook zu verwenden. Für die Umstellung sind nur die Methodennamen anzupassen. Die Funktionsweise ist nicht geändert.
Die beiden Methoden des CisObjectSearchFieldManipulation-Hooks:
public Field manipulateFieldBefore(String name, Field field)
public void manipulateFieldAfter(String name, Field field)
Die beiden Methoden des SelectionFieldHooks:
public Field manipulateSelectionFieldBefore(String name,
Field field)
public void manipulateSelectionFieldAfter(String name, Field field)
CisObjectSearchExecuteHook
Anstelle des CisObjectSearchExecuteHook ist der ExecuteHook zu verwenden. Der Methodenname ist gleich und die Funktionsweise nicht geändert.
public String prepareExecute(CisOqlSearchStatement stmt)
SearchResultManipulation
Statt des SearchResultManipulation-Hooks sollte einer der beiden ResultHooks ResultChangeContentHook oder ResultFilterHook verwendet werden.
Der SearchResultManiputalion-Hook besitzt folgende Methodensignatur.
public List getManipulatedList(List list)
Wenn die Menge der Datensätze geändert werden soll, so muss der ResultFilterHook verwendet werden. Die Implementierung der Methode kann dabei unverändert bleiben.
public java.util.List filterResultList(java.util.List list)
Wenn nur der Inhalt der Datensätze manipuliert wird, so ist der ResultChangeContentHook zu verwenden. Beim ResultChangeContentHook wird eine unmodifialble List übergeben, so dass ggf. in der Implementierung entsprechende Anpassungen vorgenommen werden müssen.
public void changeContentInResultList(java.util.List list)
Hinweis: Für den SearchResultManipulation-Hook ist das Zählen von Datensätzen in den Suchen frei geschaltet. Der Hook bekommt eine Liste mit Datensätzen übergeben und kann diese Liste beliebig manipulieren, incl. entfernen und hinzufügen von Datensätzen. Durch diese Eigenschaften kann aber nicht garantiert werden, dass das Zählergebnis, welches in der Datenbank ermittelt wird, der tatsächlichen Datensatzmenge entspricht, die dem Benutzer letztendlich angezeigt wird.
ResultListHook (SearchListManager, ListViewCreator)
Für die Darstellung als Liste sind mehrere Interfaces vorhanden. Dabei ist der ListViewCreator die Basis, auf der der ResultListHook und der SearchListManager aufbauen. Der SearchListManager ist seit Semiramis 4 auf deprecated und als Ersatz sollte SearchResultList verwendet werden. Die beiden Interfaces unterscheiden sich lediglich durch einen zusätzlichen Parameter, der angibt, ob die Liste für den anwendungsbezogenen Navigator oder für den Dialog gebaut werden soll.
ListViewCreator
public ListView createView(String viewName)
SearchListManager
public void initList(List list)
SearchResultList
public void initList(List list, String type)
Als Ersatz für diese drei Interfaces gibt es im SearchHook den VisualisationListHook mit folgenden Methoden:
public ListView createView(short type)
public void initList(List list, short type)
Bei der Umstellung ist zu beachten, dass ein Short als Ziel für die Visualisierung angegeben werden muss. Die Konstanten hierfür finden sich in der Klasse VisualisationTarget. Die Funktionsweise der beiden Methoden ist gleich, so dass außer der Behandlung des Parameters type nichts geändert werden muss.
public static final class VisualisationTarget {
public static final short DIALOG = …;
public static final short LOCATOR = …;
}
Hinweis: Die Visualisierung als Liste wird auch im SearchHook unterstützt. Es wird aber empfohlen, statt einer Liste die Visualisierung als Grid-Control zu verwenden. Hierfür steht der VisualisationColumnHook zur Verfügung.
SearchTableManager (TableDataManager)
Als direkten Ersatz für den SearchTableManager gibt es den VisualisationTableHook. Der SearchTableManger erbt vom TableDataManager.
SearchTableManager
public void initTable(Table table)
TableDataManager
public void dataToTable(CisListPartMutable data)
public void dataFromTable(CisListPartMutable data)
Die Methode dataFromTable des TableDataManagers ist in den Suchen immer als leere Methode implementiert worden, da es die Funktionalität, Daten aus der Tabelle zurück in das Modell zu übertragen, in diesem Fall nicht gibt.
Die Methoden des VisualisationTableHooks sind deshalb auf zwei Methoden reduziert worden. Zusätzlich gibt es einen Parameter vom Typ short, der angibt, ob die Tabelle im anwendungsbezogenen Navigator oder im Dialog angezeigt wird.
public void initTable(Table table, short type)
public void dataToTable(CisListPartMutable data, short type)
Hinweis: Mit der Einführung des Grid-Controls in den Suchen werden auch die Tabellen in den Suchen als Grid-Control dargestellt. Der VisualisationTableHook ermöglicht die Manipulation des Grid-Controls über das Interface einer Tabelle. Über diese Schnittstelle kann allerdings nicht der volle Funktionsumfang des Grid-Controls genutzt werden. So ist z. B. das Sortieren über Klicken auf den Spaltenkopf nicht möglich. Für den Benutzer ist es nicht ersichtlich, ob in der Programmierung der VisualisationColumnHook oder der VisualisationTableHook verwendet wurde. Der Benutzer wird nur feststellen, dass in dieser Suche einige Funktionen offensichtlich nicht verfügbar sind. Es ist also anzuraten, statt auf den VisualisationTableHook auf den VisualisationColumnHook zu wechseln.
3.9 Laufzeitdaten einer Suche
In den Entwicklungsobjekten sind für die Suchen in Semiramis Metadaten erfasst. Eine Suche ist, wie jedes andere Entwicklungsobjekt auch, über die Kombination aus Namensraum und Namen eindeutig gekennzeichnet. Aus den Metadaten werden für die Suchen Laufzeitdaten erstellt, die in der „SearchInfo“ abgelegt werden.
Die SearchInfo umfasst dabei den Namen der Suche, den voll qualifizierten Namen und den Anzeigenamen. Des Weiteren lassen sich Vorgabeparameter mithilfe der Methoden setParameter und getParameter erfassen und abfragen. Diese Informationen können bei der Implementierung eines eigenen SearchManagers in der Methode initSearch genutzt werden.
In den Metadaten kann ein Klassenname für einen Hook hinterlegt werden. Ist dieser Name vorhanden, so wird eine Instanz erzeugt und in der SearchInfo abgelegt.
Neben dem Hook wird ein SearchIterator erzeugt und an der SearchInfo abgelegt. Durch Verwendung des SearchIteratorHooks kann ein eigener
SearchIterator an der SearchInfo hinterlegt werden.
Jedes Attribut der Suche in den Metadaten wird in einem CisSearchField abgebildet. Auf ein CisSearchField hält die SearchInfo je nach Verwendung des CisSearchField mehrere Referenzen. Mögliche Verwendung sind Such-, Anzeige-, Sortierungs-, Abfrage- oder Rückgabefeld. Für jede dieser Verwendungen existiert eine Liste an der SearchInfo, die abgefragt werden kann.
Die vom Benutzer in der Ergebnisliste ausgewählten Datensätze werden für die Übernahme in das aufrufende Feld an der SearchInfo hinterlegt.
Auszug aus den Laufzeitdaten
3.9.1 Spezialverhalten
Einzelne Attribute in den Metadaten können mit einem Spezialverhalten versehen werden.
Identifikation und Bezeichnung
Attribute mit dem Spezialverhalten „Identifikation“ oder „Bezeichnung“ werden in den Suchen an mehreren Stellen abgefragt. Jedes Spezialverhalten darf nur einem Attribut in der Suche zugeordnet sein. Sowohl „Identifikation“ als auch „Bezeichnung“ müssen als Suchmerkmal, Anzeige- und Rückgabewert definiert sein.
Die Bedeutung dieser beiden Spezialverhalten wurde zum Teil bereits im Kapitel Vorabsuche erklärt.
Bei der Verwendung des Löschkennzeichens wird der „Identifikations“-Parameter ebenfalls genutzt. Näheres hierzu findet sich in der Beschreibung des Löschkennzeichens.
Nachdem der Benutzer aus der Ergebnisliste die gewünschten Datensätze ausgewählt und „Übernehmen“ gedrückt hat, wird bei der Auswahl eines einzelnen Datensatzes der Inhalt von „Identifikation“ und „Bezeichnung“ an das aufrufende Feld übergeben. Bei mehreren ausgewählten Datensätzen wird der Inhalt vom Identifikations-Feld jedes Datensatzes miteinander verkettet und an das aufrufende Feld übergeben. Der Inhalt der Bezeichnungsfelder wird nicht berücksichtigt.
Löschkennzeichen
Dieses Spezialverhalten kann nur Attributen zugeordnet werden, die auf einem Timestamp basieren. In der Regel handelt es sich dabei um das Attribut deleteTime des UpdateInformation-Parts eines Business Objects. Zusätzlich muss das Attribut als Abfrage- und Anzeigefeld markiert sein. Innerhalb einer Suche kann nur einem Attribut das Spezialverhalten „Löschkennzeichen“ zugeordnet sein.
Sind die genannten Bedingungen erfüllt, so wird im Abfragebereich der Suche ein ValueSet-Field mit den Einträgen „Mit Löschkennzeichen“ und „Ohne Löschkennzeichen“ angezeigt.
Der Benutzer kann dadurch gezielt nach Objekten suchen, die mit einem Löschkennzeichen versehen sind, bzw. nach Objekten, die nicht mit einem Löschkennzeichen versehen sind, ohne den Löschzeitpunkt eingeben zu müssen.
Im Ergebnisbereich wird bei Objekten, die mit einem Löschkennzeichen versehen sind, ein entsprechendes Icon angezeigt. Das Icon wird immer in der Spalte am jeweiligen Feld am rechten Rand dargestellt, welche das Spezialverhalten „Identifikation“ trägt. Das Attribut mit dem Spezialverhalten „Löschkennzeichen“ erhält in der Anzeigeliste keine eigene Spalte.
Unsichtbar
Attribute mit dem Spezialverhalten „Unsichtbar“ können als versteckte Abfrageparameter verwendet werden. Diese Felder werden erzeugt und der Oberfläche hinzugefügt. Anschließend werden die Felder auf nicht sichtbar umgeschaltet. Das Spezialverhalten ist nur für die Abfrage nutzbar.
Suchkontext
Für diese Attribute werden keine Suchmerkmalfelder erzeugt. Sie werden ausschließlich für den Suchkontext verwendet.
3.10 Suchkontext
Werden über eine Menge von Suchen immer die gleichen einschränkenden Kriterien benötigt, so kann dies mit dem Suchkontext erreicht werden. Die Menge der Suchen beinhaltet auch rekursiv die Suchen, die innerhalb einer Suche aufgerufen werden können.
Der Suchkontext liefert zu besonders gekennzeichneten Suchmerkmalen einschränkende Bedingungen. Für diese Suchmerkmale werden keine Eingabefelder im Abfragebereich der Suche erstellt. Erst beim Parametrisieren des Suchiterators werden die Bedingungen vom Suchkontext abgefragt.
Der Suchkontext kann auf Ebene einer Anwendung oder eines Feldes bzw. dessen Suchmanager vom Programmierer bereitgestellt werden.
3.10.1 Einstellungen in den Suchdefinitionen
Die Funktionalität des Suchkontextes basiert darauf, dass es einen Satz von Attributnamen gibt, der in der Menge der Suchen die gleiche Bedeutung hat. Aus diesem Satz von Attributnamen kann eine Suche Attribute verwenden. Nur die verwendeten Parameter werden im Suchkontext abgefragt.
Attribute, die für den Suchkontext verwendet werden, müssen Suchmerkmal sein und mit dem Spezialverhalten „Suchkontext“ gekennzeichnet sein. Wichtig ist hierbei, dass der Parametername, also der Alias im OQL, in allen Suchen gleich ist.
3.10.2 Interfaces
Das Thema Suchkontext umfasst drei Interfaces: SearchContextProvider, SearchContext und SearchContextData.
SearchContextProvider
Eine Anwendung kann das SearchContextProvider implementieren, um einen SearchContext zur Verfügung zu stellen.
public interface SearchContextProvider {
public SearchContext getSearchContext();
}
SearchContext
Der SearchContext liefert zu einem Suchmerkmal einen Suchstring. Dieser Suchstring wird am Suchiterator gesetzt. Um einen Suchstring zu erstellen, sollte die Klasse com.cisag.pgm.objsearch.SelectionSupport verwendet werden.
public interface SearchContext {
String getSelectionString(SearchContextData searchContextData);
}
SearchContextData
Instanzen des SearchContextData werden erzeugt und dem SearchContext übergeben. Neben dem Attributnamen werden noch der Name der Suche und die GUID der Datenbank, in der die Suche ausgeführt wird, bereitgestellt. Anhand dieser Informationen kann das einschränkende Suchmerkmal erstellt werden, welches bei getSelectionString(…) vom SearchContext zurückgegeben wird.
public interface SearchContextData {
public byte[] getDatabaseGuid();
public String getSearchName();
public String getParameterName();
}
3.10.3 Utilityklasse SearchContextUtility
Die Utilityklasse SearchContextUtility ermöglicht es, einen SearchContextProvider an einem Feld zu registrieren und die Information wieder auszulesen.
public class SearchContextUtility {
static public void setSearchContextProvider(
Field guiField, SearchContextProvider provider)
static public SearchContextProvider
getSearchContextProvider(Field guiField)
static public SearchContextProvider
getSearchContextProvider(
com.cisag.pgm.dialog.VisualElement dialogElement)
}
3.10.4 Ermittlungsreihenfolge
Wird eine Suche auf einem Feld geöffnet, so wird in der Reihenfolge „SearchManager“, „Field“, „Application“ versucht, einen SearchContextProvider zu ermitteln.
Zuerst wird am SearchManager mithilfe der Methode getSearchContextProvider() versucht, einen Provider zu erhalten. Wenn diese Methode nicht durch einen eigenen SearchManager überschrieben wurde, liefert sie „null“ als Ergebnis.
searchManager.getSearchContextProvider()
Wurde am SearchManager kein SearchContextProvider gefunden, so wird für das Feld über die Utilityklasse SearchContextUtility gesucht.
SearchContextUtility.getSearchContextProvider(field)
Erst wenn für das Feld kein SearchContextProvider ermittelt werden konnte, wird untersucht, ob die aktuelle Anwendung, zu der das Feld gehört, das SearchContextProvider-Interface implementiert.
application instanceof SearchContextProvider
Konnte ein SearchContextProvider in einem der drei Schritte ermittelt werden, so wird dieser nach dem Öffnen der Suche jedem Suchmerkmalfeld mithilfe der SearchContextUtility-Klasse zugeordnet.
Wird nun eine Suche auf einem der Suchmerkmalfelder geöffnet, so wird zuerst der SearchManager des Feldes überprüft. Wurde die Methode getSearchContextProvider() nicht überschrieben, so wird mithilfe der SearchContextUtility-Klasse überprüft, ob ein SearchContextProvider dem Feld zugeordnet ist.
3.10.5 Suchkontext und eigene Ansichten
Werden eigene Ansichten in den Suchen verwendet, so ist darauf zu achten, dass der SearchContext für alle Suchmerkmalfelder mit der SearchContextUtility-Klasse gesetzt wird.
3.10.6 Suchkontext in der Anwendungsbezogenen Navigatorsuche
Implementiert eine Anwendung das Interface SearchContextProvider, so benutzten auch deren Navigatorsuchen diesen Suchkontext. Grundsätzlich sollten die Schlüsselfelder einer Anwendung und die Navigatorsuche den gleichen SearchContextProvider verwenden.
Wenn in einer Anwendung alle Felder, außer den Schlüsselfeldern, einen spezielleren SearchContextProvider benötigen, z. B. weil die Felder abhängig vom geladenen Objekt sein sollen, so muss der Entwickler mit den bisher genannten Möglichkeiten an jedem Feld der Anwendung den SearchContextProvider setzen. Dies widerspricht dem Ansatz hinter dem Suchkontext, dass nur für Ausnahmen der SearchContextProvider manuell gesetzt werden sollte.
In diesem Fall kann wie folgt vorgegangen werden:
Der SearchContextProvider der Anwendung wird für den speziellen Fall, der aber an der Mehrzahl der Felder benötigt wird, ausgelegt.
Die Schlüsselfelder, in ihrer Anzahl zumeist gering, bekommen einen eigenen SearchContextProvider gesetzt.
Die Navigatorsuchen benötigen ebenfalls einen eigenen SearchContextProvider. Um diesen bereitzustellen kann an der Klasse CisUiApplication folgende Methode überschrieben werden.
public class CisUiApplication {
…
public SearchContextProvider getLocatorSearchContextProvider(){…}
…
}
In der Ermittlungsreihenfolge des SearchContextProviders in den Navigatorsuchen wird zuerst diese Methode aufgerufen. Liefert die Methode, wie im Standard, null, so wird geprüft, ob die Anwendung das Interface SearchContextProvider implementiert. Wenn dem so ist, wird dieser an den Navigatorsuchen verwendet.
Hinweis: Üblicherweise benutzen die Schlüsselfelder und die anwendungsbezogenen Navigatorsuchen den gleichen SearchContextProvider. Die Methode getLocatorSearchContextProvider sollte aber nicht für das Setzen des SearchContextProviders an den Schlüsselfeldern verwendet werden, da diese ausschließlich für die anwendungsbezogenen Navigatorsuchen bestimmt ist.
3.11 Spezialparts
In den Suchen werden einige ausgezeichnete Parts gesondert behandelt. Für diese Parts werden spezielle Felder für die Eingabe verwendet. Auch bei der Suche und Aufbereitung des Suchergebnisses werden diese Parts gesondert behandelt. Diese Parts werden in den Suchen Spezialparts genannt.
Spezialparts dürfen in der Suche nicht als Key- oder Sortierattribut eingetragen werden. In beiden Fällen funktioniert dann das Wiederaufsetzen der Suche nicht mehr richtig.
Im Folgenden soll kurz aufgezeigt werden, welche Spezialparts unterstützt werden.
DomesticAmount
Semiramis kann mit drei Währungen zugleich rechnen, d. h. bei der Erfassung eines Betrages wird eine Währung mit angegeben. Diese kann aus drei vorgegebenen Währungen gewählt werden. Die Beträge für die anderen zwei Währungen werden zumeist automatisch berechnet. Bei weiterführenden Berechnungen werden für alle drei Währungen getrennte Berechnungen durchgeführt. Dadurch werden Rundungsfehler beim Umrechnen zwischen den Währungen vermieden.
Aufgrund der Währung, eine drei möglichen, kann ermittelt werden, zu welcher Spalte in der Datenbank der eingegebene Suchstring gehört. Es kann immer nur mit einer Währung gesucht werden. Im Multi-Site-Fall kann immer nur mit der Hauswährung, die über alle Organisationen gleich ist, gesucht werden.
ForeignAmount, Quantity, Duration
Beim ForeignAmount handelt es sich um eine Fremdwährung, die neben einem Betrag eine Währung besitzt.
Mengen werden in Semiramis mit dem Part Quantity angegeben. Eine Menge besitzt neben einem Betrag eine Einheit.
Eine Dauer besteht aus einem Betrag und einer Einheit. Die Dauer wird im Part Duration abgebildet.
Allen drei Parts gemein ist, dass sie in den Suchen durch einen Suchstring für den Betrag und eine GUID für die Währung bzw. Einheit bestimmt werden.
StorageLocation_RLB (Row-Level-Bin)
Die Lagerplatzverwaltung speichert in diesem Part einen Lagerort mit genauer Angabe des Lagerplatzes ab. Dabei wird zwischen Reihe, Ebene und Platz unterschieden. Zur Darstellung werden die einzelnen Bereiche in der genannten Reihenfolgen mit einem Trennstrich aufbereitet, „10-2-4“. Die Eingabe einer StorageLocation_RLB unterstützt Wildcards nur am Ende, z. B. „10-2-*“.
Bei der Eingabe eines Row-Level-Bins gibt es eine Konvention bzgl. der Menge der eingegebenen Teile:
Wird nur ein Teil der Daten angegeben, so wird dieser wie die Eingabe eines Lagerplatzes, also „Bin“, behandelt (z. B. 70*). Bei zwei Teilen der Daten werden die Teile wie die Angabe von Reihe und Platz, „Row-Bin“, behandelt (z. B. 10-70*). Erst bei drei Teilen der Daten werden die Teile wie die Angabe von Lagerreihe, -ebene und –platz, „Row-Level-Bin“, behandelt (z. B. 10-5-70*).
Duration
Ein Zeitraum wird durch die zwei Zeitpunkte Anfang und Ende beschrieben. Sind beide Zeitpunkte angegeben, so wird von einem geschlossenen Zeitraum gesprochen. Fehlt einer der Werte oder ist keiner der Werte angegeben, so wird von einem offenen Zeitraum gesprochen. Die Suchen in Semiramis werten den eingegeben Zeitraum aus.
3.12 Valueset ‚0’-Werte
In Semiramis sollten Valuesets per Definition einen Wert, der größer ist als „0“, haben. Das Anlegen von Konstanten für ein Valueset ist abhängig von der Versionierungsstufe des Systems, auf dem entwickelt wird.
Ein Valueset besteht somit aus einer Sammlung von Konstanten, z. B. „1, 2, 3, 4, 5, 6“. Valuesets können über die Vererbungshierarchie der logischen Datentypen eingeschränkt werden, was die Menge der möglichen Konstanten weiter einschränkt, z. B. „1, 2, 4, 5“.
In der Datenbank ist immer genau ein Wert aus dem Valueset in dem entsprechenden Feld eingetragen. Da beim Speichern seitens des Kernels nicht geprüft wird, ob der eingetragene Wert aus dem möglichen Valueset kommt, kann auch der Wert „0“ eingetragen sein.
Das Valueset-Feld liefert bei der Methode „getSelectionString“ eine Aufzählung der ausgewählten Valueset-Konstanten, getrennt durch ein Komma und ein Leerzeichen. (z. B. „1, 2, 3, 4“).
Hinweis:
Die Rückgabe der Methode getSelectionString() liefert eine Zeichenkette. Der Inhalt der Zeichenkette kann sich zukünftig ändern und ist hier nur zu Anschauungszwecken aufgeführt. Für die Konvertierung von short-Arrays aus und in einen Suchstring sind die entsprechenden Methoden an der Klasse com.cisag.pgm.objsearch.SelectionSupport zu verwenden. Es darf nicht der Inhalt des Suchstrings geparst werden.
In der Data-Description kann zu einem logischen Datentypen festgelegt werden, ob die Eingabe erforderlich ist oder nicht. Nur wenn die Eingabe nicht erforderlich ist, liefert die Methode „getSelectionString“ neben den ausgewählten Valueset-Konstanten auch den Wert „0“ mit, z. B. („0, 1, 2, 3, 4“)
In der Suche wird der erhaltene Suchstring des Valuesets untersucht. Wird der Wert „0“ gefunden, so wird die Zeichenkette um „null“ als weiteren möglichen Wert erweitert. Dieses Verhalten ist per Definition festgelegt.
Die Werte des Suchstrings werden mit einem „OR“ verknüpft und als Bedingung in die „Where“-Klausel aufgenommen.
WHERE … (a = 0 OR a = 1 OR a = 4 OR a = 6 OR a is NULL) …
Warum wird der Wert „null“ aufgenommen?
Ein Valueset-Feld liefert immer eine Zeichenkette mit Parametern für die Einschränkung des Suchergebnisses. Dadurch verhält es sich anders, als die meisten anderen Felder, die ggf. auch eine leere Zeichenkette liefern können, die dazu führt, dass der zugehörige Parameter in der „Where“-Klausel nicht aufgenommen wird.
Ein Outer Join liefert auch dann Datensätze zurück, wenn zu einem Datensatz kein entsprechender Datensatz in der Join-Tabelle gefunden werden kann. Befinden sich im „Select“ Attribute der Join-Tabelle, so werden diese mit „null“ gefüllt.
Zur Klärung der Frage wird nun folgende Annahme getroffen: Sowohl im „Select“ als auch im „where“ befindet sich dasselbe Valueset der Join-Tabelle. In der Data-Description des Valuesets ist eingetragen, dass die Eingabe erforderlich ist. Gemäß den oben festgelegten Konventionen, liefert das Valueset nur die vom Benutzer ausgewählten Konstanten als Abfrageparameter.
Wird nun diese Einschränkung auf einen Datensatz angewendet, dessen Attribute der Join-Tabelle aus genannten Gründen null sind, so wird der Datensatz aus der Ergebnismenge entfernt, da null keiner der möglichen Konstanten des Valuesets entsprechen kann. Es ist also genau das Verhalten eingetreten, welches mit einem Outer Join nicht erreicht werden sollte.
Wenn die Eingabe zu einem Valueset hingegen nicht erforderlich gewesen wäre, dann wäre in der „Where“-Klausel ein „is Null“ hinzugekommen, und der Datensatz wäre in der Ergebnismenge erhalten geblieben.
Beispiel 1:
Ein Valueset besitzt die Konstantenwerte „1, 2, 3, 4, 5, 6“ und wird eingeschränkt auf die Werte „1, 2, 4, 6“.
Im ersten Fall ist in der verwendeten Data-Description die Eingabe auf „erforderlich“ eingestellt. Der Benutzer hat den Wert „(Alle)“ im Valueset-Feld ausgewählt. Die Methode „getSelectionString“ liefert nun folgende Zeichenkette als Rückgabe: „1, 2, 4, 6“.
Beispiel 2:
Der zweite Fall unterscheidet sich vom ersten Fall in der Auswahl der möglichen Valueset-Werte. Der Benutzer wählt nur die Werte „1, 2“. Folglich liefert die Methode „getSelectionString“ nun folgende Zeichenkette als Rückgabe: „1, 2“.
Beispiel 3:
Im dritten Fall, wiederum basierend auf Fall 1, wird in der Data-Description die Eingabe auf „nicht erforderlich“ eingestellt. Die Methode „getSeletionString“ liefert nun „0, 1, 2, 4, 6“. In einem nachfolgenden Arbeitsschritt wird aufgrund der vorhandenen „0“ noch der Wert „null“ an die Zeichenkette angehängt, sodass die Zeichenkette nun folgenden Inhalt besitzt: „0, 1, 2, 4, 6, null“.
3.13 Wiederaufsetzen von Suchen
Eine Suche in Semiramis lädt in der Regel nie den vollständigen Satz an Daten. Stattdessen wird immer nur eine begrenzte Anzahl von Datensätzen eingelesen. In den bereitgestellten Suchenvisualisierungen, Dialogsuche, Suche im Navigationsbereich etc. ist die Anzahl auf 60 Datensätze begrenzt. Werden weitere Datensätze benötigt, so muss die Suche an der alten Stelle wiederaufsetzen und die nächsten Datensätze nachladen.
Um wiederaufsetzen zu können, müssen an dem aus den Metadaten erstellten OQL-Statement einige Erweiterungen vorgenommen werden. Anhand eines Beispiels soll dieser Vorgang verdeutlicht werden.
Die Tabelle „Table1“ besitzt die Attribute „a, b, e, f, k, m“. Die Attribute „k“ und „m“ bilden die Schlüsselattribute eines Datensatzes. Aus den Meta-Daten einer auf der genannten Tabelle basierenden Suche lässt sich folgendes OQL erzeugen.
SELECT a, b
FROM Table1
WHERE (a = ‘abc’ AND f=y)
ORDER BY e, f
Für das Wiederaufsetzen ist es nötig, die Sortierung der Tabelle eindeutig zu erhalten. Es kann sein, dass zu den getroffenen Sortierkriterien mehr als ein Datensatz vorhanden ist. Um die Reihenfolge eindeutig festzulegen, wird das „ORDER BY“ um die Attribute des Primärschlüssels, „k“ und „m“, erweitert.
SELECT a, b
FROM Table1
WHERE (a = ‘abc’ AND f=y)
ORDER BY e, f, k, m
Um später wiederaufsetzen zu können, muss der letzte gelesene Datensatz gemerkt werden. Da die Attribute „a“ und „b“ nicht ausreichen, um den Datensatz in der Datenbank unter Berücksichtigung der Sortierung zu bestimmen, werden alle Attribute des „ORDER BY“ auch in den „SELECT“ mit aufgenommen.
SELECT a, b, e, f, k, m
FROM Table1
WHERE (a = ‘abc’ AND f=y)
ORDER BY e, f, k, m
Mit diesem OQL kann nun die erste Abfrage erfolgen. Der letzte gelesene Datensatz „n“ enthält nun als Ergebnis folgende Daten:
an, bn, en, fn, kn, mn
Für das Wiederaufsetzen muss nun eine weitere Bedingung erstellt werden, mithilfe derer die auf den Datensatz „n“ folgenden Datensätze bestimmt werden können.
e > en
OR (e = en AND f > fn)
OR (e = en AND f = fn AND k > kn)
OR (e = en AND f = fn AND k = kn AND m > mn)
Hinweis:
Die Verwendung von „<“ oder „>“ richtet sich nach der in der Suche definierten Sortierreihenfolge. Aufsteigende Sortierattribute „ASC“ erhalten ein „>“, absteigende Sortierattribute „DESC“ ein „<“. Wenn weder „ASC“ noch „DESC“ für ein Attribut im „ORDER BY“ angegeben ist, so ist die Reihenfolge immer „ASC“.
Eingebaut in das OQL ergibt sich für die nachfolgenden Suchen folgender Aufruf:
SELECT a, b, e, f, k, m
FROM Table1
WHERE (a = ‘abc’ AND f=y)
AND e > en
OR (e = en AND f > fn)
OR (e = en AND f = fn AND k > kn)
OR (e = en AND f = fn AND k = kn AND m > mn)
ORDER BY e, f, k, m
Hinweis:
Als Sortierkriterium dürfen keine Attribute genommen werden, die den Wert „null“ enthalten können, da sonst das Wiederaufsetzen nicht funktioniert.
Hinweis:
Spezialparts dürfen nicht als Key-Attribut oder Sortierkriterium verwendet werden, da sonst das Wiederaufsetzen nicht funktioniert.
3.14 Sortierungen
Eine Suche kann eine Vielzahl von Datensätzen liefern. Um mit dieser Menge besser umgehen zu können, ist es erforderlich, eine Sortierung durchzuführen. Die Sortierung erfolgt bereits auf der Datenbankebene.
3.14.1 Metadaten
Bei der Erfassung der Metadaten einer Suche in der Repository-Datenbank lassen sich die Attribute festlegen, die als sortierbar eingestuft werden. Aus diesen Attributen wiederum lassen sich diejenigen bestimmen, die für die Sortierung verwendet werden sollen, d. h. wenn der Benutzer keine anderen Einstellungen vornimmt, so werden die gefundenen Datensätze aufgrund dieser Einstellungen sortiert.
Neben der Auswahl der Sortierung lässt sich auch noch die Sortierrichtung zu jedem Attribut einstellen. Die Sortierung kann entweder aufsteigend oder absteigend erfolgen.
Hinweis:
Es dürfen keine Attribute als Sortierkriterium verwendet werden, die aus Outer-Join-Tabellen stammen. Diese Attribute können potenziell den Wert „null“ annehmen und sind deswegen für die Sortierung ungeeignet. Dieses führt beim Wiederaufsetzen von Suchen zu Problemen.
Hinweis:
Spezialparts können nicht für die Sortierung eingesetzt werden. Anders als bei der Abfrage und Anzeige wird der Part im OQL nicht expandiert, d. h. das OQL wird nicht um die Attribute des Parts erweitert. Soll trotzdem nach dem Part sortiert werden, so müssen die für die Sortierung relevanten Attribute des Parts gesondert als Attribute der Suche erfasst werden.
3.14.2 Sortierdialog
In der Dialogsuche und in der Suche im Navigationsbereich wird dem Benutzer die Möglichkeit geboten, die Sortierung des Ergebnisses zu verändern. Zur Veränderung der Sortierung öffnet sich ein Dialog. Dieser Dialog besteht aus zwei Listen mit verschiedenen Buttons zwischen den Listen.
In der linken Liste befinden sich die Attribute, die zurzeit für die Sortierung verwendet werden. Dabei ist entscheidend, in welcher Reihenfolge diese Attribute aufgenommen sind. Der oberste Parameter ist zugleich auch das erste Attribut in der Sortierung. Ein Symbol neben jedem Attribut gibt die Sortierrichtung, aufsteigend oder absteigend, an.
In der rechten Liste befinden sich hingegen die Attribute, die zwar für die Sortierung zur Verfügung stehen, aber zurzeit nicht verwendet werden.
Zusammen ergeben die Attribute in den Listen genau die Menge, die in den Metadaten der Suche als sortierbar angegeben sind.
Mit den Buttons lassen sich die Attribute von der einen in die andere Liste übertragen. Ebenfalls lässt sich nach Auswahl eines Attribut der ersten Liste dessen Sortierrichtung ändern.
3.14.3 Datenbank-Sortierreihenfolge und Semiramis
Wie schon erwähnt, erfolgt die Sortierung nach der Datenbank-Sortierreihenfolge. Unterschiedliche Datenbanken können unterschiedliche Sortierungen haben. Die Sortierung einer Datenbank basiert auf der Datenbanksprache. Gleiche Datenbestände auf unterschiedlichen Datenbanken können somit unterschiedlich sortiert werden.
Um diesem Umstand Rechnung zu tragen, lässt sich am TransactionManager für jede am System angeschlossene Datenbank ein „Comparator“ abholen. Dieser kann verwendet werden, um Listen mit Datensätzen zu vergleichen.
Alternativ steht die Klasse „com.cisag.pgm.objsearch.DefaultComparator“ bereit, die „CisListPartMutables“ miteinander datenbankspezifisch vergleichen kann. Intern basiert diese Klasse auf dem beschriebenen Comparator des TransactionManagers.
Diese Sortierung wird innerhalb der Suchen bei der DefaultSearch verwendet.
3.14.4 SortOrderAction
SortOrderActions lassen sich nutzen, um einen SortOrderDialog z. B. in den Kopfzeilen von Tabellen anzuzeigen. In Verbindung mit einer SortOrder und einem Comparator können auch Sortierungen im Hauptspeicher ausgeführt werden.
Eine SortOrder kann für eine Suche aufgebaut werden, indem Sie den Suchnamen als Parameter für die SortOrder verwenden.
SortOrder sortOrder = new SortOrder(“com.cisag….”);
Alternativ lassen sich auch alle relevanten Daten manuell in Form von Array-Listen setzen. Die Namen sind dabei keine Pfade oder Verweise auf Stringtabellen, sondern die tatsächlich angezeigten Namen im Dialog.
String[] availableDisplayNames = …;
String[] availableFieldNames= …;
String[] defaults= …;
boolean[] defaultDirections = …;
SortOrder sortOrder = new SortOrder(availableDisplayNames,
availableFieldNames,
defaults,
defaultDirections);
Um für die erzeugte SortOrder eine SortOrderAction zu erstellen, muss die
SortOrderAction erzeugt und die SortOrder an der Action gesetzt werden. Der registrierte ActionListener wird aufgerufen, sobald die Sortierung im Dialog geändert und dieser geschlossen wurde.
SortOrderAction sortOrderAction = new SortOrderAction();
sortOrderAction.setSortOrder(sortOrder);
sortOrderAction.addActionListener(this);
Die so erzeugte SortOrder kann dann z. B. als HeaderElement einer Liste an der Oberfläche angezeigt werden.
List list = new List(…);
Button sortOrderButton = list.addHeaderElement(sortOrderAction);
In Abfrageanwendungen sollte die SortOrderAction immer in die SelectionGroup mit aufgenommen werden. Werden die Parameter der Anwendung gespeichert, so werden auch die Einstellungen der Sortierung gespeichert.
CisUiApplication application = …;
SelectionGroup result = application.getSelectionGroup();
result.setSortOrderAction(sortOrderAction);
Wenn der Sortierdialog geschlossen wird, wird eine Aktion mit der ID Action.SORTORDER_CHANGED ausgeführt. Diese kann dann im registrierten ActionListener abgefragt werden.
Wird eine eigene Sortierung der Daten benötigt, so ist ein Comparator zu implementieren, der diese Aufgabe übernimmt. Der Comparator wird der Liste an die Methode sort(…) übergeben.
public void performAction(Action action) {
if (action.getId() == Action.SORTORDER_CHANGED) {
list.sort(new AComparator(sortOrderAction.getSortOrder()));
}
}
Die empfohlene Implementierung für einen eigenen Comparator könnte wie folgt aussehen. Zum Vergleichen sollte, wie bereits erwähnt, der Comparator der Datenbank genutzt werden.
class AComparator implements Comparator {
Comparator databaseComparator;
public ACompatator() {
CisTransactionManager tm = …;
databaseComparator = tm.getComparator(…);
}
public int compare(Object o1, Object o2) {
CisListPartMutable row1 = (CisListPartMutable) o1;
CisListPartMutable row2 = (CisListPartMutable) o2;
MyObject obj1 = (MyObject) row1.getData();
MyObject obj2 = (MyObject) row2.getData();
String[] attrs = sortOrder.getSelectedFieldNames();
boolean[] dirs = sortOrder.getSelectedDirections();
// dirs[] = true –> asc-sort else desc-sort
for (int i = 0; i < attrs.length; i++) {
int r = 0; // -1:(o1<o2), 0:(o1=o2), +1:(o1>o2)
if (“code”.equals(attrs[i])) {
r = databaseComparator.compare(obj1.getCode(),
obj2.getCode());
} else if (“description”.equals(attrs[i])) {
r = databaseComparator.compare(obj1.getDescription(),
obj2.getDescription());
} …
if (r != 0) {
return dirs[i] ? r : -r;
}
}
return row1.getIndex() – row2.getIndex();
}
}
3.15 Groß- und Kleinschreibung
In Semiramis-Systemen ist für jedes Zeichenketten-Attribut ein Eingabe- und Suchverhalten vorgegeben, das sich auf die dafür erzeugten Eingabefelder auswirkt. Sofern keine Ausnahme vorliegt, enthalten Identifikationen (Business Keys) nur Großbuchstaben. Alle übrigen Zeichenketten-Attribute enthalten sowohl Klein- als auch Großbuchstaben.
Indexe werden bei Business Objects meist zur Leistungssteigerung verwendet. Wenn jedoch unabhängig von der Groß-/Kleinschreibung gesucht wird, so kann die Datenbank den Index nicht verwenden. Aus diesem Grund wird, sofern keine Ausnahme vorliegt, bei der Suche auf Attributen, auf denen ein Index existiert, die Groß-/Kleinschreibung beachtet. Bei der Suche nach allen übrigen Attributen, d. h. Attribute ohne Index, Ausnahmen oder Identifikationen, wird die Groß-/Kleinschreibung nicht beachtet. Insbesondere alle Bezeichnungsfelder werden somit unabhängig von der Groß-/Kleinschreibung gesucht.
Folgende Ausnahmen bestehen:
- Bis auf die Konfigurationsdaten wird bei allen Attributen der System-Business-Objects die Groß-/Kleinschreibung bei der Eingabe und beim Suchen unabhängig von den Systemeinstellungen beachtet.
- Bei den folgenden Business Objects wird bei der Identifikation die Groß-/
Kleinschreibung beachtet: - Sprachen
- Anreden
- Titel
- Einheiten
- Partnerbeziehungen
In der Hook-Implementierungsklasse com.cisag.app.general.log.CaseSensitivityRegistryHookImpl
sind diese Abweichungen von dem Such- und Eingabeverhalten registriert. Diese Klasse enthält die Ausnahmen für den Standard.
Für Partner- oder Kundenentwicklung oder für Apps muss jeweils eine eigene Hook-Implementierung des Hooks com.cisag.pgm.appserver.hook.CaseSensitivityRegistryHook, der im Hook Contract com.cisag.pgm.appserver.Server definiert ist, implementiert werden.
In der Klasse com.cisag.app.general.log.CaseSensitivityRegistry können zusätzliche Ausnahmen registriert werden, die nicht ein bestimmtes Entwicklungspräfix besitzen müssen. Das kann notwendig sein, wenn zum Beispiel eine Partnerentwicklung keine Implementierung des Hooks besitzt aber in der Kundenadaptierung eine Ausnahme aus der Partnerentwicklung registriert werden soll.
- Die Konstanten in Nummerkreisen müssen großgeschrieben werden, damit nur Nummern in Großbuchstaben erzeugt werden.
3.15.1 Einstellen der CaseSensitivityRegistry
Die Klasse com.cisag.app.general.log.CaseSensitivityRegistry beinhaltet eine Methode, die eine Liste für die Standardeinstellung für die Groß- und Kleinschreibung bereitstellt.
Jede Ausnahme wird mithilfe einer zusammengesetzten Zeichenkette bestimmt. Als erster Wert, gefolgt von einem Semikolon, kann entweder „PATH“ oder „PARENT“ angegeben werden.
PATH wird verwendet, wenn genau ein Attribut eines Business Objects von der Regel betroffen ist. In diesem Fall folgen nach Path der vollständige qualifizierte Name des Business Objects, ein Doppelpunkt und der Name des Attributs (also ein Attribut-Path). Nach einem weiteren Semikolon wird nun das Suchverhalten angegeben. Hier stehen „CASE_SENSITIV“, „UPPER“ und „CASE_INSENSITIV“ zur Verfügung.
PARENT kann nur bei Hierarchien und Gruppen verwendet werden. Gefolgt von PARENT und einem Semikolon werden der Name des Parts und das Suchverhalten angegeben. Alle Business Objects, die von dem jeweiligen Part „erben“, verwenden dann das angegebene Suchverhalten.
Beispiel:
public static void initCaseSensitivityDefaults(
List caseSensitivityDefaults) {
caseSensitivityDefaults.add(“PATH;” +
“com.cisag.app.general.obj.Language:isoCode;” +
“CASE_SENSITIVE”);
caseSensitivityDefaults.add(“PATH;” +
“com.cisag.app.general.obj.NumberRange:” +
“format[0].constant;” +
“UPPER”);
caseSensitivityDefaults.add(“PARENT;” +
“com.cisag.app.general.obj.Code;” +
“UPPER”);
…
}
Die Registrierung von Ausnahmen in einer Hook-Implementierung erfolgt in ähnlicher Art und Weise, siehe dazu die Java-Dokumentation des Interfaces com.cisag.pgm.appserver.hook.CaseSensitivityRegistryHook.CaseSensitivityRegistry. Es ist nicht möglich, Ausnahmen von Typ PARENT in einer Hook-Implementierung zu registrieren.
3.16 SearchActions
SearchActions stellen eine spezielle Form von Aktionen dar. Diese Aktionen führen bei Aufruf eine Dialogsuche durch. Wenn der Benutzer ein oder mehrere Elemente in der Suche ausgewählt und den „Accept“-Button betätigt hat, wird der „acceptListener“ benachrichtigt. Der Text des Buttons kann vom Entwickler vorgegeben werden.
Im Konstruktor der SearchAction wird ein ActionListener angegeben, der benachrichtigt wird, wenn der Anwender eine Auswahl getroffen und den Bestätigen-Button betätigt hat. Das ActionEvent enthält, neben der acceptId, im State ein Array mit CisParameterLists. Bei SingleSelection enthält das Array genau eine CisParameterList. In der Parameterliste sind die Ergebnisse der Zeile unter den Attributnamen, so wie in der Suche definiert, gespeichert.
Im Folgenden findet sich eine Aufstellung der zur Verfügung stehenden Methodenfür die SearchAction.
public SearchAction(int acceptId, String actionPath, ActionListener
acceptListener)
public void setSearch(String search)
public void setAcceptName(String acceptName)
public void setAcceptNameFrom(String absPath, Object[] parameters)
public void setVisualElement(VisualElement visualElement)
public void setMultiSelection(boolean multiSelection)
public void setSearchManager(SearchManager searchManager)
public void startSearch()
3.17 Zwischenspeichern von Suchergebnissen im Cache
Die Dialogsuche, die Auswahlliste und die anwendungsbezogene Suche im Navigationsbereich unterstützen das Zwischenspeichern von Suchergebnissen im Cache. Das Zwischenspeichern im Cacheist in das CisOqlSearchStatement integriert. Die Auswahllisten zeigen immer nur aktuelle Daten an, die entweder aus der Datenbank oder aus dem Cache kommen. Die anwendungsbezogene Suche im Navigationsbereich und die Dialogsuche können auch veraltete Daten anzeigen. Wenn die angezeigten Daten aus dem Cache kommen, wird im Kopf der Ergebnisliste ein Hinweis angezeigt. Sind die Daten eventuell nicht mehr aktuell, so wird dies zusätzlich im Tooltip angezeigt. Durch nochmaliges Starten der Suche werden die Daten aus der Datenbank geladen. Der Cache wird hierbei aktualisiert.
Ob Daten aus dem Cache aktuell sind oder nicht, wird anhand der verwendeten Business Objects ermittelt. Wurde eine der Tabellen, in denen die Busines Objects abgelegt sind, verändert, indem Datensätze eingefügt, gelöscht oder manipuliert wurden, so wird der Eintrag im Cache beim nächsten Zugriff auf „veraltet“ gesetzt. Diese Kennzeichnung muss aber nicht bedeuten, dass die Daten im Cache nicht mehr aktuell sind. Die im Cache zwischengespeicherten Daten müssen von den Änderungen in den Tabellen nicht unbedingt betroffen sein.
Der Cache verwendet als Schlüssel die Datenbank, die Sprache, das verwendete OQL inklusive der Datentypen und der Suchmerkmale. Zu einem Cacheeintrag werden maximal 61 Datensätze gespeichert. Pro Semiramis Application Server gibt es einen Suchcache, sodass auch anderen Benutzern bei gleicher Anfrage die bereits im Cache zwischengespeicherten Daten angezeigt werden.
Im Standard ist die Cachegröße auf 300 Einträge eingestellt. Bei hoher Benutzerzahl sollte die Cachegröße verändert werden. Hierfür muss die Property com.cisag.sys.objsearch.SearchCacheMaxSize auf einen höheren Wert gestellt werden.
3.18 Vorbelegung
Soll eine Suche vorbelegt werden, so sind einige Regeln zu beachten.
Vorbelegungen müssen sowohl bei der Auswahlliste als auch bei der Dialogsuche genutzt werden. Um dies zu gewährleisten, muss die Vorbelegung mithilfe eines eigenen SearchManagers erfolgen. Wie schon beschrieben, kann in der Methode „initSearch“ zu jedem einzelnen Attribut der Suche eine Vorbelegung durchgeführt werden. Vorbelegungen können aber nur für einfache Attribute wie Strings, Valuessets etc. vorgenommen werden. Spezialparts sowie Zeit und Datum sind von einer Vorbelegung ausgeschlossen.
Im SelectionFieldHook kann in der Methode manipulateSelectionFieldAfter ebenfalls eine Vorbelegung für ein Feld vorgenommen werden. Da diese Vorbelegung aber nicht in Vorabsuchen verwendet werden kann, sollten hier keine Vorbelegungen vorgenommen werden.
3.19 Debugging
Ausgabe des OQLs auf der Konsole
Mithilfe der in Semiramis integrierten Debug-Fähigkeit kann das intern verwendete OQL einer Suche auf der Konsole ausgegeben werden. Das Statement wird im Level Debug.FINE ausgegeben.
Geben Sie in die Konsole eines laufenden Semiramis Application Servers (SAS) den folgenden Befehl ein.
dbgcls –class:com.cisag.sys.objsearch.log.CisOqlSearchStatement
–level:100
Auf der Konsole werden beim Ausführen einer OQL-Suche das OQL und die Belegung der Parameter ausgegeben.
3.20 Ändern der maximalen Datensatzanzahl
Der Standardwert für die maximale Anzahl an Datensätzen, die in einer Wertehilfe oder in der anwendungsbezogenen Navigatorhilfe angezeigt werden, ist 500. In einigen Fällen kann es erforderlich sein, diese Anzahl gezielt für einzelne Suchen zu verändert. Dabei kann sowohl der Wert kleiner als auch größer als 500 eingestellt werden. Die Einstellung erfolgt über die Property com.cisag.sys.objsearch.SearchResultSize wie sie im Dokument Semiramis Poperties im Kapitel Persistenzdienst angegeben ist.
3.21 Performance
Im Allgemeinen erhöht jeder zusätzliche Join die Komplexität der Datenbankanfrage. Durch die Verwendung von Fragmenten werden für die Selektion oder Sortierung benötigte Tabellen nur dann in der Datenbankanfrage verwendet, wenn diese auch benötigt werden. Es ist daher empfehlenswert, die OQL-Suche in möglichst viele Fragmente aufzuteilen. Eine hohe Anzahl von Fragmenten hat keinen negativen Einfluss auf die Performance der Suche.
Die Wahl der Sortierreihenfolge bzw. die Verwendung von DISTINCT kann negative Auswirkungen auf die Performance einer Suche haben. Bei der Verwendung von DISTINCT und häufig beim Sortieren muss das vollständige Anfrageergebnis berechnet werden. Insbesondere bei großen Ergebnismengen kann dies zu sehr langen Laufzeiten führen. DISTINCT sollten Sie daher nur verwenden, wenn eine Notwendigkeit dafür vorliegt.