Das Dokument erläutert die Programmierung von virtuellen Tabellen, virtuellen Funktionen und virtuellen Beziehungsspalten für die Abfrage von Daten über die ODBC-Schnittstelle.
1 Zielgruppe
- Entwickler
2 Begriffsbestimmung
Im Folgenden werden die Begriffe virtuelle Tabellen, virtuelle Funktion und virtuelle Beziehungsspalte kurz erläutert. Weitere Informationen finden Sie in der Dokumentation Benutzung von Semiramis-ODBC.
Eine virtuelle Tabelle existiert nicht in der Datenbank, sondern wird vom SAS emuliert. Prinzipiell kann auf virtuelle Tabelle wie auf eine Datenbanktabelle zugegriffen werden. Der Inhalt einer virtuellen Tabelle wird zur Laufzeit der Abfrage bezüglich der Eingabewerte berechnet. Als Anfrageergebnis können beliebig viele Zeilen zurückgeben werden. Eine virtuelle Tabelle sollten verwendet werden, wenn das Anfrageergebnis mehr als eine Zeile umfassen kann und vom Application-Server berechnet werden soll.
Virtuelle Funktion
Eine virtuelle Funktion ist eine spezielle Tabelle, die zu einer Menge von Eingabewerten eine Ergebniszeile zurückgibt, welche zur Laufzeit der Abfrage berechnet wird. Sie existiert nicht auf der Datenbank, sondern wird vom SAS emuliert. Eine virtuelle Tabelle sollten verwendet werden, wenn ein Anfrageergebnis um vom Application-Server berechnete Spalten erweitert werden soll.
Virtuelle Beziehungsspalte
Eine virtuelle Beziehungsspalte erweitert die Tabelle eines Business Object um eine Spalte, deren Inhalt sich zur Laufzeit der Abfrage berechnet. Die virtuelle Beziehungsspalte basiert auf einer Beziehung des Quell-Business-Objects zu einem konkreten Ziel-Business-Object, die bestimmten Bedingungen genügen muss. Zu beachten ist, dass sie die Tabellen aller Quell-Business-Objects, die so eine Beziehung zu dem Ziel-Business-Object besitzen, um die zusätzliche Spalte erweitert.
3 Programmierung von virtuellen Tabellen und Funktionen
Eine virtuelle Tabelle bzw. Funktion wird von einer eigenen Klasse implementiert. Diese muss von der Klasse com.cisag.pgm.base.CisODBCVirtualTable bzw. com.cisag.pgm.base.CisODBCVirtualFunction abgeleitet werden, welche die Basisfunktionalität zu Verfügung stellt. Deren abstrakten Methoden müssen von der abgeleiteten Klasse implementiert werden. Im Folgenden werden die zu implementierenden Methoden beschrieben.
3.1 Festlegen des Tabellennamens
Der Name der virtuellen Tabelle bzw. Funktion wird durch einen logischen Datentyp definiert. Dessen Pfad und der Name bestimmen den Namen der Tabelle. Die Methode getTablePath() muss den Semiramis-Pfad des logischen Datentyps zurückgeben.
Der Typ des logischen Datentyps ist frei wählbar und wird nicht ausgewertet. Zu dem logischen Datentyp ist eine Data Description zu erfassen. Das Label der Data Description wird als Beschreibung für die Tabellen verwendet. Aus dem Namen des logischen Datentyps leitet sich der Name der virtuellen Tabelle wie folgt ab:
Beispiel:
Die Methode getTablePath() der virtuellen Tabelle app_general_ItemImage ist wie folgt implementiert:
public String getTablePath() {
return “com.cisag.app.general:ItemImage.lt”;
}
Der Pfad der virtuellen Tabelle wird um “com.cisag.”, bei Systemen vom Partner nur um „com.“ verkürzt. Der Unterstrich „_“ ersetzt den Trennpunkt „.“.
Der logische Datentyp muss so gewählt werden, dass der daraus gebildete Tabellenname weder von einem Business Object, noch einem View oder einer anderen virtuellen Tabelle oder Funktion verwendet wird. Zur besseren Kennzeichnung wird empfohlen, dass Suffix „VirtualTable“ bzw. „VirtualFunction“ im Namen zu verwenden.
3.1.1 Auflösung von Namenskonflikten
Die Namen von Business-Object-Tabellen sowie von virtuellen Tabellen und Funktionen werden aus dem Business-Object-Namen bzw. aus dem logischen Datentyp bestimmt. Wenn zu einem Namen mehrere Tabellen existieren, dann greift folgende Konfliktauflösung:
- Wenn zu dem Namen eine Business-Object-Tabelle existiert, dann wird diese Tabelle benutzt. Andere Tabellen mit dem gleichen Namen werden ignoriert und auf diese kann nicht zugegriffen werden.
- Wenn zu dem Namen mehrere virtuelle Tabellen oder Funktionen existieren, dann wird die erste gefundene virtuelle Tabelle benutzt. Alle weiteren Tabellen werden ignoriert.
- Wenn zu dem Namen mehrere virtuelle Funktionen existieren, dann wird die erste gefundene virtuelle Funktion benutzt.
Zusätzlich wird eine Fehlermeldung in das Meldungsprotokoll des Application Servers geschrieben.
3.2 Festlegen der Tabellenspalten
In der Methode getAttributes() werden die Tabellenspalten definiert. Jedes Element des Arrays definiert eine Spalte der Tabelle sowie deren Position in der Tabelle. Ein Element ist eine Zeichenkette mit dem folgenden Aufbau: “Spaltenname/LDT-Pfad/Parametertyp”.
3.2.1 Spaltenname
Der Spaltenname legt den Namen der Spalte in der Tabelle fest. Der Name muss innerhalb der Tabelle eindeutig sein.
3.2.2 Datentyp
Der logische Datentyp definiert den Typ der Spalte sowie das Spaltenlabel über die zugehörige Data Description. Es sind alle primitiven Semiramis-Datentypen (ohne SBLOB) verwendbar, sowie komplexe logische Datentypen, die auf den folgenden Special-Parts bzw. speziellen logischen Datentypen basieren:
- cisag.app.general.obj.DomesticAmount
- cisag.app.general.obj.Quantity
- cisag.app.general.obj.ForeignAmount
- cisag.app.general.obj.Duration
- cisag.app.general.obj.PointInTime
- cisag.pgm.datatype.CisAttributeTimeStamp
- cisag.pgm.datatype.CisAttributeDate
Der Parametertyp legt fest, ob eine Tabellenspalte als Eingabe- oder Ausgabeparameter verwendet werden darf.
3.2.3 Ausgabeparameter
Wenn eine Spalte ein Ausgabeparameter ist, dann liefert sie einen berechneten Wert zurück. Als Parametertyp sollte bei der Spaltendefinition die Klassenkonstante OUTPUT_PARAM angegeben werden. Wenn kein Parametertyp angegeben wird, dann ist die Spalte immer ein Ausgabeparameter.
3.2.4 Eingabeparameter
Über einen Eingabeparameter ist es möglich, einen Wert an die virtuelle Tabelle zu übergeben, der für die Berechnung des zurückgegebenen Tabelleninhaltes benutzt wird. Der Name einer Spalte, die als Eingabeparameter benutzbar ist, muss mit dem Präfix „in_“ (Klassenkonstante INPUT_PARAM_PREFIX) beginnen. Ein Eingabeparameter darf nur auf primitiven Semiramis-Datentypen basieren. Ein Eingabeparameter ist immer optional und muss nicht mit einem Wert belegt werden. Wenn ein Wert gesetzt wurde, dann ist dieser nicht mehr änderbar, d. h. dieser Wert wird wieder als Ausgabewert für die Spalte zurückgegeben.
Wenn eine Spalte ausschließlich als Eingabeparameter dienen soll, muss als Parametertyp die Klassenkonstante INPUT_PARAM angegeben werden.
Soll eine Spalte wahlweise als Eingabe- oder Ausgabeparameter dienen, dann muss als Parametertyp die Klassenkonstante INPUT_OUTPUT_PARAM angegeben werden. Wurde die Spalte mit einem Wert belegt, so ist dieser nicht mehr änderbar. Wurde kein Wert gesetzt, so kann der Spalte ein berechneter Wert zugewiesen werden, der dann ausgegeben wird.
3.2.5 Beispiel
Die abgebildete Methode getAttributes() der virtuellen Tabelle app_general_ItemImage definiert die Spalten in_itemGuid, description, number und content. Dabei ist die Spalte in_itemGuid ein Eingabeparameter, die anderen Spalten sind Ausgabeparameter.
public String[] getAttributes() {
return new String[] {
“itemGuid/com.cisag.app.general:OdbcItemGuid.lt/”+
INPUT_PARAM,
“description/com.cisag.app.general:OdbcItemDescription.lt/”+
OUTPUT_PARAM,
“number/com.cisag.app.general:OdbcItemNumber.lt/”+
OUTPUT_PARAM,
“content/com.cisag.app.general:OdbcItemContent.lt/”+
OUTPUT_PARAM
};
}
3.3 Festlegen der Datenbanktypen
Für eine virtuelle Tabelle bzw. Funktion muss ähnlich wie für ein Business Object festgelegt werden, auf welchen Datenbanktypen sie zur Verfügung stehen soll. Dazu dient die Methode getDatabaseContentTypes(). Diese gibt ein Array mit den unterstützten Datenbanktypen zurück. Es können die folgenden Datenbanktypen angegeben werden:
- OLTP-Datenbank
- OLAP-Datenbank
- Repository-Datenbank
- Konfigurations-Datenbank
Die entsprechenden Konstanten stellt die Klasse com.cisag.pgm.base.
CisODBCExtensionLogic bereit.
Beispiel
Die abgebildete Methode getDatabaseContentTypes () der virtuellen Tabelle app_general_ItemImage legt fest, dass die Tabelle nur für OLTP-Datenbanken zur Verfügung steht.
public short[] getDatabaseContentTypes() {
return new short[] {
CisODBCExtensionLogic.DB_CONTENT_TYPE_OLTP
};
}
3.4 Festlegen von Berechtigungen
Um den Zugriff auf eine virtuelle Tabelle bzw. Funktion einzuschränken, kann eine Berechtigung vergeben werden. Dazu dient die Methode getBaseBusinessObjectGuid(), die die Class-Guid des Basis-Business Objects liefert. Die Berechtigungen für das Basis-Business Object werden von der virtuellen Tabelle bzw. Funktion übernommen.
Beispiel
Die abgebildete Methode getBaseBusinessObjectGuid() der virtuellen Tabelle app_general_ItemImage gibt die Class-Guid des Business Objects com.cisag.app.general.obj.Item zurück. Dadurch werden die zum Zeitpunkt der Abfrage geltenden Berechtigungen für dieses Business Object auch auf die virtuelle Tabelle angewandt.
public byte[] getBaseBusinessObjectGuid() {
return CisObjectUtility.getClassGuid(
com.cisag.app.general.obj.Item.class);
}
3.5 Gültigkeit der Klasseninstanz
Für jede SQL-Abfrage, in der eine virtuelle Tabelle bzw. Funktion verwendet wird, wird eine Instanz der zugehörigen Klasse erzeugt. Die Lebensdauer der Instanz ist an die Lebensdauer der SQL-Abfrage gebunden. Daraus folgt, dass der Inhalt von Instanzvariablen nicht abfrageübergreifend erhalten bleibt, sondern nur solange zur Verfügung steht, bis das Abfrageergebnis für die virtuelle Tabelle bzw. Funktion komplett berechnet wurde.
3.5.1 Initialisieren von Instanzvariablen
Eine virtuelle Tabelle bzw. Funktion darf Instanzvariablen besitzen. Allerdings dürfen Instanzvariablen nicht im Konstruktor der Klasse instanziiert werden. Dazu ist die Methode init() zu implementieren, die vor dem ersten Aufruf der run()-Methode ausgeführt wird. In dieser Methode kann die Initialisierung aller Instanzvariablen erfolgen, da zu diesem Zeitpunkt der entsprechende Session- und OLTP-Kontext gesetzt ist.
3.6 Berechnen des Tabellenergebnisses
Der Inhalt der Tabelle einer virtuellen Tabelle bzw. virtuellen Funktion wird zur Laufzeit der ODBC-Abfrage berechnet. Dazu wird vom System die run()-Methode aufgerufen, in er die Implementierung für die Berechung hinterlegt werden muss.
3.6.1 Virtuelle Tabelle
Eine virtuelle Tabelle besitzt einen Ausführungstyp, der bestimmt, wie die run()-Methode vom System aufgerufen wird. Der Entwickler muss sich bei der Implementierung der virtuellen Tabelle für einen Ausführungstyp entscheiden.
3.6.1.1 Einmalige Ausführung
Dies ist der Standardausführungstyp. Die run()-Methode der virtuellen Tabelle wird pro Datenbankabfrage genau einmal aufgerufen, um den Tabelleninhalt zu ermitteln. Das Ergebnis wir komplett im Hauptspeicher gehalten, so dass dieser Ausführungstyp nur verwendet werden darf, wenn das zu berechende Ergebnis relativ klein sein wird.
3.6.1.2 Blockweise Ausführung
Die implementierende Klasse muss die Methode getExecutionType() überschreiben und die Konstante CisODBCVirtualTable.TYPE_EXECUTION_BLOCK zurückgeben. Die run()-Methode der virtuellen Tabelle kann pro Datenbankabfrage mehrmals aufgerufen werden, um den Tabelleninhalt zu ermitteln. Jeder Aufruf liefert dann einen Block von Ergebniszeilen zurück. Dieser Ausführungstyp muss dann verwendet werden, wenn das zu berechnende Ergebnis sehr groß werden kann und deshalb zuviel Hauptspeicher belegen würde.
3.6.1.3 Ausführung der run()-Methode
Die virtuelle Tabelle bekommt beim Aufruf der run()-Methode ein CisODBCVirtualTableResult-Objekt übergeben, welches die Eingabeparameterwerte enthält und die Zeilen des berechneten Ergebnisses aufnimmt. Dies kann aus 0 bis n Ergebniszeilen bestehen und wird zeilenweise an das Tabellenergebnis-Objekt übergeben.
Über die Methode createRow() des Tabellenergebnis-Objektes wird ein neues Ergebniszeilenobjekt der Klasse CisODBCVirtualTableRow erzeugt. Dieses Objekt ist ein Container für die Spaltenwerte einer Tabellenzeile. In diesem sind immer die bei der Abfrage angegebenen Werte für die benutzten Eingabeparameter gesetzt. Mit der dem Spaltentyp entsprechenden set()- oder get()-Methode kann unter Angabe des Spaltennamens ein Wert abgefragt oder gesetzt werden. Der Wert eines benutzten Eingabeparameters kann nicht mehr geändert werden. Über die Methode isParameterLocked() des CisODBCVirtualTableResult-Objektes kann abgefragt werden, ob der angegebene Eingabeparameter gesperrt ist, d. h. er wurde mit einem Wert belegt und ist deshalb nicht mehr änderbar.
Wenn das Ergebniszeilenobjekt gefüllt ist, dann wird es über die Methode addRow() des Tabellenergebnis-Objektes dem Ergebnis hinzugefügt. Alternativ kann man auch mehrere Ergebniszeilen-Objekte mit der Methode addAllRows() dem Ergebnis hinzufügen anstatt mehrerer addRow()-Aufrufe.
Nur wenn eine Ergebniszeile über eine dieser Methoden zum Ergebnis hinzugefügt wurde, geht diese in das Ergebnis ein.
Besonderheiten bei der blockweisen Ausführung
Sind alle Ergebniszeilen ermittelt worden, muss die Methode setAllRowsLoaded() am Tabellenergebnis-Objekt aufgerufen werden. Dadurch wird dem System mitgeteilt, dass die Berechnung des Tabellenergebnisses abgeschlossen wurde. Die run()-Methode wird dann nicht mehr aufgerufen. Wird ein Tabellenergebnis-Objekt mit 0 Ergebniszeilen zurückgegeben, wird die run()-Methode ebenfalls nicht erneut aufgerufen.
Über die Methode getFetchSize() am Tabellenergebnis-Objekt kann die vom System gewünschte Zeilenanzahl des Ergebnisblockes für den aktuellen Aufruf der run()-Methode abgefragt werden. Bei Beachtung dieser Größe wird das Ergebnis mit der minimal benötigten Roundtrip-Anzahl vom Server zum Client übertragen. Die tatsächliche Zeilenanzahl eines Blockes darf aber von dem gewünschten Wert abweichen, kann dadurch aber mehr Roundtrips benötigen.
Sortierung der Tabellenzeilen
Die Reihenfolge des Hinzufügens entspricht der Ausgabereihenfolge der Tabellenzeilen.
Wenn in der SQL-Datenbankanweisung über die ORDER-BY-Klausel eine Sortierung festgelegt wurde, dann werden die Ergebniszeilen bei einer Tabelle mit dem Typ „einmalige Ausführung“ vom System entsprechend der Sortierung zurückgegeben.
Bei einer Tabelle mit dem Typ „blockweise Ausführung“ ist der Entwickler der virtuellen Tabelle für die Sortierung verantwortlich. Im Normalfall wird so eine Tabelle nur eine fixe Sortierung unterstützen, welche durch die Reihenfolge beim Hinzufügen der Ergebniszeilen bestimmt wird. Soll eine variable Sortierung unterstützt werden, kann über die Methode getSortOrder() und getSortDirections() die in der SQL-Datenbankanweisung angegebene Sortierung abgefragt werden. In der Dokumentation zur virtuellen Tabelle sollte der Entwickler angeben, wie die Tabelle das Ergebnis sortiert und ob abweichende Sortierungen unterstützt werden.
3.6.1.4 Beispiel: Ermittlung der Eingabeparameterwerte
In diesem Beispiel wird ein Ergebniszeilenobjekt erzeugt, an welchem der Eingabeparameterwert für die Spalte in_itemGuid abgefragt wird. Wenn kein Wert gesetzt ist, dann kann kein Ergebnis berechnet werden und die virtuelle Tabelle liefert keine Ergebniszeilen.
public void run(CisODBCVirtualTableResult result) {
CisODBCVirtualTableRow resultRow= result.createRow();
byte[] itemGuid= resultRow.getGuid(“in_itemGuid”);
if (itemGuid == null) {
return;
}
// retrieve table result rows
…
}
3.6.1.5 Beispiel: Hinzufügen einer Zeile zum Ergebnis-Objekt
Wenn eine Item-Guid als Eingabeparameter ermittelt wurde, dann wird die zugehörige Item-Instanz geladen. Und wenn diese gefunden wurde, dann werden die Werte für die Ausgabespalten in das Ergebniszeilenobjekt resultRow gesetzt. Anschließend wird dieses dem Ergebnis hinzugefügt. In diesem Beispiel liefert die virtuelle Tabelle zu einer Item-Guid eine Ergebniszeile.
public void run(CisODBCVirtualTableResult result) {
…
// retrieve table result rows
Item item= (Item) om.getObject(Item.buildPrimaryKey(itemGuid));
if (item == null) {
return;
}
resultRow.setString(NUMBER, item.getNumber());
resultRow.setString(DESCRIPTION, item.getDescription());
…
result.addRow(resultRow);
}
3.6.2 Virtuelle Funktion
Die run()-Methode einer virtuellen Funktion wird blockweise aufgerufen. Das übergebene CisODBCVirtualFunctionResult-Objekt enthält die zu füllenden Ergebniszeilen des aktuellen Blocks, in denen die entsprechenden Werte der Eingabeparameter gesetzt sind. Die Bestimmung des Wertes eines Eingabeparameters einer Zeile erfolgt durch die Auswertung der Join-Klausel des zugehörigen Joins. In dieser ist angegeben, aus welcher Spalte des Join-Partners ein Eingabeparameter der virtuellen Funktion seinen Wert bezieht. Die Ergebniszeilen des Join-Partners wurden zuvor in der Datenbank berechnet. In der run()-Methode müssen nun zu jeder Ergebniszeile die Werte der Ausgabespalten der virtuellen Tabelle berechnet und diese der Ergebniszeile zugewiesen werden. Der Aufruf geschieht deshalb blockweise, um eine performante Implementierung zu ermöglichen. Der Aufruf kann je nach Blockanzahl mehrmals erfolgen.
Jede virtuelle Funktion verwendet pro Abfrage einen Cache, der die Ergebniszeilen enthält. Wenn zu einer Kombination von Eingabeparameterwerten bereits eine Ergebniszeile im Cache existiert, dann wird die Ergebniszeile kein weiteres Mal berechnet, sondern der Cache-Inhalt zurückgegeben. Es ist daher wichtig, dass innerhalb einer Abfrage die Ergebniszeile nur von den Eingabeparameterwerten und nicht von vorhergehenden Ergebniszeilen abhängt.
Eine Ergebniszeile wird durch ein CisODBCVirtualFunctionRow-Objekt repräsentiert. Mit der dem Spaltentyp entsprechenden set()- oder get()-Methode kann unter Angabe des Spaltennamens ein Wert abgefragt oder gesetzt werden. Der Wert eines belegten Eingabeparameters kann nicht mehr geändert werden. Über die Methode isParameterLocked() des CisODBCVirtualFunctionResult-Objektes kann abgefragt werden, ob der angegebene Eingabeparameter gesperrt ist, d. h. er wurde mit einem Wert belegt und ist deshalb nicht mehr änderbar.
Die Methode getRowCount() des CisODBCVirtualFunctionResult-Objektes liefert die Anzahl der zu bearbeitenden Ergebniszeilen, die Methode getRow() liefert das CisODBCVirtualFunctionRow-Objekt zur angegebenen Ergebniszeile. Anschließend können die Werte der Eingabeparameter abgefragt, die Werte der Ausgabespalten berechnet und in das Ergebniszeilenobjekt gesetzt werden.
3.6.2.1 Beispiel
In diesem Beispiel wird mit einer Schleife über die Ergebniszeilen iteriert und jeweils die Ausgabespalten berechnet.
Die Eingabeparameter “in_organizationalUnit” und “in_item” werden für die Berechnung zwingend benötigt. Wenn keine Werte gesetzt sind, dann können für diese Ergebniszeile keine Werte berechnet werden und es wird mit der nächsten Ergebniszeile fortgesetzt. Der Eingabeparameter “in_customer” ist optional und muss nicht belegt sein. Anschließend werden die Werte für die Ausgabespalten bestimmt und diese über die dem jeweiligen Typ entsprechenden set()-Methoden in das Ergebniszeilenobjekt gesetzt.
public void run(CisODBCVirtualFunctionResult virtualFunctionResult) {
for(int i= 0,s= virtualFunctionResult.getRowCount(); i<s; i++) {
CisODBCVirtualFunctionRow row= virtualFunctionResult.getRow(i);
byte[] organization= row.getGuid(“in_organizationalUnit”);
if (organization == null) continue;
byte[] item= row.getGuid(“in_item”);
if (item == null) continue;
byte[] customer= row.getGuid(“in_customer”);
// calculate values for output columns
SalesItemData data= Items.retrieveSalesItemData(
organization, item, customer);
if (data == null) continue;
…
// set output column values in result row
row.setGuid(“text”, data.getText());
row.setDecimal(“minMargin”, data.getMinMargin());
…
}
}
3.6.3 Ermitteln des Laufzeitkontextes
Über die Methode getODBCContext() kann während der Ausführung der run()-Methode der Laufzeitkontext abgefragt werden. Am Kontextobjekt kann über die Methode getOrganizationGuid() die Guid der aktiven Organisation ermittelt werden, in deren Kontext der Bericht ausgeführt wird. Über die Methode isMultiSite() kann festgestellt werden, ob die aktive OLTP-Datenbank in eine Multi-Site-Umgebung läuft. Die Methode getDatabaseGuid() liefert die Guid der Datenbank, auf die der aktuelle ODBC-Zugriff erfolgt.
3.7 Registrierung von virtuellen Tabellen bzw. Funktionen
Damit eine virtuelle Tabelle bzw. Funktion über ODBC zugreifbar ist, muss die zugehörige Klasse registriert werden. Dazu dient der Hook „com.cisag.pgm.appserver.hook.ODBCRegistryHook“, welcher im Hook Contract „com.cisag.pgm.appserver.Server“ definiert ist. Über eine eigene Implementierung dieses Hooks können sie virtuelle Tabellen bzw. Funktionen konfliktfrei registrieren.
3.8 Automatische Konvertierung des Inhalts einer Spalte vom Datentyp „Text“
Bei einer Spalte vom primitiven Datentyp „Text“ ist es möglich, den Inhalt vom ODBC-Server automatisch konvertieren zu lassen. Momentan wird das nur für die Ausgabe von HTML-Texten unterstützt. Diese können HTML-Tags enthalten, welche von der ODBC-Client-Anwendung (z.B. Crystal Reports) nicht unterstützt werden und so der Text nicht wie erwartet dargestellt wird (z.B. fehlender Fett-oder Kursivdruck). Um die automatische Konvertierung für eine Spalte vom Typ „Text“ einer virtuellen Tabelle/Funktion zu aktivieren, muss diese registriert werden. Dazu dient der Hook „com.cisag.pgm.appserver.hook.ODBCRegistryHook“, welcher im Hook Contract „com.cisag.pgm.appserver.Server“ definiert ist. Über eine eigene Implementierung dieses Hooks können Sie eine Spalte vom Datentyp „Text“ konfliktfrei registrieren.
Die automatische Konvertierung sollte aus Performance-Gründen nur verwendet werden, wenn Probleme mit der Darstellung von HTML-Texten in der ODBC-Client-Anwendung auftreten.
4 Programmierung von virtuellen Beziehungs-Spalten
Sie haben die Möglichkeit, eine Tabelle eines Business Object um eine virtuelle Spalte zu erweitern, deren Inhalt sich zur Laufzeit der Abfrage berechnet. So eine virtuelle Spalte basiert auf einer Beziehung des Business Objects zu einem konkreten Business Object.
Für eine virtuelle Beziehungs-Spalte ist eine eigene Klasse zu implementieren. Diese muss von der Klasse com.cisag.pgm.base.CisODBCVirtualColumn abgeleitet werden, welche die Basisfunktionalität zur Verfügung stellt. Deren abstrakten Methoden müssen von der abgeleiteten Klasse implementiert werden.
4.1 Spaltenname
Der Spaltenname setzt sich aus dem Namen des Quellattributes der Beziehung und dem Suffix verbunden mit dem Unterstrich zusammen.
4.2 Datentyp
Der Datentyp der virtuellen Spalte wird über einen logischen Datentyp festgelegt. Es sind alle primitiven Semiramis-Datentypen (außer BLOB, SBLOB, Text) erlaubt. Das Label der zum logischen Datentyp zugehörigen Data Description bestimmt das Spalten-Label.
4.3 Berechnung des Rückgabewertes
Die Methode getValueByKey() wird zur Berechnung des Wertes der virtuellen Beziehungsspalte pro Ergebniszeile der Business Obejct-Tabelle aufgerufen. Es werden der Persistenzdienstschlüssel, der zum Laden der Ziel-Business-Object-Instanz dient, und die Datenbanksprache übergeben. Mit dem Persistenzdiesntschlüssel kann dann die Business Object-Instanz unter Berücksichtigungder Datenbanksprache geladen und der Rückgabewert ermittelt werden. Primitive Java-Typen müssen als Objekt zurückgegeben werden.
Diese Methode wird für jede Ergebniszeile der Tabelle des Quell-Business Objects aufgerufen.
Beispiel
Der Quelltext zeigt die Implementierung der virtuellen Beziehungsspalte für das Business Object com.cisag.app.general.obj.UnitOfMeasure. Im Konstruktor wird der Konstruktor der Vaterklasse aufgerufen und das Suffix für den Namen der Spalte sowie der Pfad des logischen Datentyps übergeben.
In der Methode getValueByKey() wird zum übergebenen Persistenzdienstschlüssel die UnitofMeasure-Instanz geladen und der Wert des Attribute code als Ergebnis zurückgegeben.
public static class UnitOfMeasureBK extends CisODBCVirtualColumn {
private CisEnvironment env= CisEnvironment.getInstance();
private CisTransactionManager tm= env.getTransactionManager();
private CisObjectManager om= env.getObjectManager();
public UnitOfMeasureBK() {
super(“BK”, “com.cisag.app.general:UomCode.lt”);
}
public Object getValueByKey(byte[] key, String language) {
byte[] ta = tm.beginNew(CisTransactionManager.OLTP);
try {
UnitOfMeasure uom= (UnitOfMeasure) om.getObject(key,
CisObjectManager.READ, language);
return uom != null ? uom.getCode() : null;
} finally {
tm.rollback(ta);
}
}
}
4.3.1 Ermitteln des Laufzeitkontextes
Über die Methode getODBCContext() kann während der Ausführung der run()-Methode der Laufzeitkontext abgefragt werden. Am Kontextobjekt kann über die Methode getOrganizationGuid() die Guid der aktiven Organisation ermittelt werden, in deren Kontext der Bericht ausgeführt wird. Über die Methode isMultiSite() kann festgestellt werden, ob die aktive OLTP-Datenbank in eine Multi-Site-Umgebung läuft. Die Methode getDatabaseGuid() liefert die Guid der Datenbank, auf die der aktuelle ODBC-Zugriff erfolgt.
4.3.2 Gültigkeit der Klasseninstanz
Für jede SQL-Abfrage, in der eine virtuelle Beziehungsspalte verwendet wird, werden ein oder mehrere Instanzen der zugehörigen Klasse erzeugt, wobei der entsprechende Session- und OLTP-Kontext gesetzt ist. Die Lebensdauer einer Instanz ist an einen ODBC-Roundtrip gebunden. Daraus folgt, dass der Inhalt von Instanzvariablen nicht erhalten bleibt und sich somit kein Zustand gemerkt werden darf.
4.3.3 Registrierung der virtuellen Beziehungsspalte
Damit eine virtuelle Beziehungsspalte über ODBC zugreifbar ist, muss die zugehörige Klasse registriert werden. Dazu dient die Methode getVirtualRelationshipColumns() der Klasse com.cisag.app.general.log.CisODBCExtensionLogicImpl. Diese Methode wird vom SAS während der Initialisierung der ODBC-Schnittstelle abgefragt. Sie bekommt als Parameter einen voll qualifizierten Business Object-Namen übergeben. Durch Erweiterung der if-Anweisung um ein else-if-Konstrukt wird eine neue Beziehungsspalte für ein Business Oject hinzugefügt. Wenn der Name des angegebenen Business Objects mit dem Namen des übergebenen Business Object überein stimmt, dann gibt die Methode ein Array mit den Klassen der virtuellen Beziehungsspalten zurück.
Beispiel
Dieses Beispiel zeigt die Registrierung von einer virtuellen Beziehungsspalte für das Business Object „com.cisag.app.general.obj.UnitOfMeasure” und von zwei Beziehungsspalten für das Business Object “com.cisag.app.shipping.obj.UnitLoad”.
public Class[] getVirtualRelationshipColumns(String fullBoName) {
if (“com.cisag.app.general.obj.UnitOfMeasure”.equals(fullBoName))
{
return new Class[]{UnitOfMeasureBK.class};
}
…
} else
if (“com.cisag.app.shipping.obj.UnitLoad”.equals(fullBoName)) {
return new Class[] {UnitLoadPath.class,
UnitLoadSsccPath.class};
}
return null;
}