12.1 ERP-System-spezifische Collections
Im ERP-System sind die meisten Schlüssel vom Typ „byte-Array“, wie zum Beispiel die GUID. Um eine korrekte Verwendung in Java zu ermöglichen, bietet das ERP-System spezielle Collection-Klassen, die byte-Arrays als Schlüssel unterstützen. Die Standard-Collections von Java wie java.util.HashMap/Hashtable/HashSet/TreeMap/TreeSet sind ungeeignet, da sie nur einen Instanzvergleich für byte-Arrays über die equals()-Methode der Klasse java.lang.Object vornehmen. Das bedeutet, es werden nicht die einzelnen Bytes verglichen, sondern nur die Zeiger auf die Objekte. Im ERP-System wird ein Vergleich auf Byte-Ebene benötigt, z. B. um festzustellen, ob zwei GUIDs identisch sind.
Die Collections CisHashMap, CisHashSet und CisArrayList implementieren das Interface „CisCollection“ im Java-Package com.cisag.pgm.util und ermöglichen die Verwendung von byte-Arrays als Schlüssel bzw. als Set- oder Listen-Element. Sie vergleichen den Inhalt der byte-Arrays, um festzustellen, ob zwei Schlüssel bzw. Elemente identisch sind.
Die Collections CisHashMap und CisHashSet unterstützen weder null-Keys noch null-Values.
Die Implementierungen sind nicht synchronisiert.
Die Klasse com.cisag.pgm.util.CisCollection stellt ähnlich der Klasse java.util.Collections weitere Operationen auf „CisCollections“ zur Verfügung.
12.2 Debug-Ausgaben
Mit der Klasse com.cisag.pgm.util.Debug wird eine einfache Logging-Funktionalität zur Verfügung gestellt. Sie soll einerseits das Eingrenzen von Fehlern ohne Debugger der Java-IDE ermöglichen und andererseits eine Alternative zu den System.out.println()-Anweisungen während der Neuentwicklung bieten. Gleichzeitig soll die Leistung im Echtbetrieb nicht beeinträchtigt werden. Das Logging lässt sich im laufenden Betrieb über die Konsole einschalten. Unterschiedliche Levels und Einschränkungen bezüglich der Package-Struktur lassen ein gezieltes Verfolgen von Programmabläufen zu.
Beispiele zur Verwendung:
- Erzeugung eines Debug-Objektes für die aktuelle Klasse
private static final Debug debug = new Debug(MyClass.class);
- Logging einer Fortschrittsinformation mit zwei Parametern
debug.log(Debug.INFO, “Process order={1}, detail={0}”,
detail.getNumber(), order.getNumber());
- Aus Leistungsgründen sollte bei aufwendigeren Aktionen zur Parameterermittlung immer zunächst abgefragt werden, ob überhaupt „geloggt“ wird.
if (debug.isEnabled()) {
Item item = detail.retrieveItem();
// u. U. Datenbankzugriff
debug.log(Debug.WARNING, “Item {0} is
invalid!”,
item != null ? item.getNumber() : null);
}
- oder alternativ unter Berücksichtigung eines Debug-Levels
if (debug.isEnabled(Debug.FINEST)) {
Item item = detail.retrieveItem();
// u. U. Datenbankzugriff
debug.log(Debug.WARNING, “Item {0} is
invalid!”,
item != null ? item.getNumber() : null);
}
Das Debugging kann auf einem Application-Server über das Tool „Klasse debuggen“ (dbgcls) aktiviert werden.
Nachfolgend sind einige Beispiele für die Verwendung des Tools aufgeführt.
- Alle Debug-Ausgaben (Level 100) für die Klasse „BookMaintenance“ einschalten:
dbgcls –class:com.cisag.app.edu.ui.BookMaintenance –level:100
- Alle Debug-Ausgaben mit dem Level größer gleich 5 (Information) für das Package „com.cisag.app.edu“ und alle darunter liegenden Packages einschalten:
dbgcls –prefix:com.cisag.app.edu –level:5
- Alle Debug-Ausgaben für das Package „om.cisag.app.edu“ und alle darunter liegenden Packages ausschalten:
dbgcls –prefix:com.cisag.app.general –level:1
12.3 Iterator-Klassen
Da häufig über Daten aus der Datenbank iteriert werden muss und dabei mitunter Datenbankverbindungen benötigt werden, die nach der Verarbeitung oder beim Abbrechen geschlossen werden müssen, wurde das java.util.Iterator-Interface um die Methoden setMaxRows(int) und close() zum Interface CisIterator[1] erweitert. Dieses Interface wird von den Iteratoren CisObjectIterator[2] und CisSearchIterator[3] implementiert, sodass Methoden-Signaturen in Anwendungen das CisIterator-Interface ohne Kenntnis der konkreten Implementierung verwenden können. Des Weiteren exis-tieren etliche spezielle Iteratoren, die das CisIterator-Interface implementieren. Diese werden nachfolgend vorgestellt:
Die Klassen CisResultSet[4] und CisOQLSearchStatement[5] können aufgrund ihrer Struktur (ähnlich zu java.sql.ResultSet/Statement) dieses Interface nicht implementieren. Mithilfe von Adapter-Klassen können auch sie als „CisIteratoren“ verwendet werden.
In der nachstehenden Abbildung ist die Klassenhierarchie der Iterator-Klassen im Package „com.cisag.pgm.util“ abgebildet:
12.3.1 EmptyIterator
Der „EmptyIterator“ liefert keine Elemente (analog zu java.util.Collections.EMPTY_…). Diese Klasse wird nicht neu instanziiert, sondern über die Konstante INSTANCE kann eine statische Instanz abgefragt werden.
EmptyIterator.INSTANCE.hasNext(); // false
12.3.2 SingletonIterator
Der „SingletonIterator“ ist ein Iterator genau über ein Objekt (analog zu java.util.Collections.singleton…).
CisIterator i = new SingletonIterator(myObject);
if (i.hasNext()) System.out.println(i.next());
// gibt myObject aus
i.hasNext();
// false, da myObject bereits abgeholt wurde
12.3.3 CisIteratorAdapter
Die Klasse „CisIteratorAdapter“ ist ein Wrapper für die Klasse java.util.Iterator, um Instanzen dieser Klasse als „CisIterator“ zu nutzen.
Iterator i = myList.iterator();
CisIterator j = new CisIteratorAdapter(i);
j.setMaxRows(10);
// es werden max. 10 Elemente zurückgeliefert
12.3.4 ResetIterator
Der „ResetIterator“ kann auf eine zuvor gemerkte Position zurückgesetzt werden.
ResetIterator i = new ResetIterator(abc.retrieveXyz());
try {
if (i.hasNext()) {
i.mark();
init(i.next());
// Initialisierung mit erstem Element
i.reset();
foo(i);
// das erste Element steht nocheinmal zur Verfügung
}
} finally {
i.close();
}
12.3.5 ChainIterator
Der „Chainiterator“ kann mehrere Iteratoren verketten.
ChainIterator i = new ChainIterator();
i.add(iterator1);
// Wenn iterator1 ‘abgelaufen’ ist,
i.add(iterator2);
// werden die Elemente von iterator2 geliefert.
12.3.6 MergeIterator
Der „MergeIterator“ kann die Elemente aus zwei sortierten Iteratoren wiederum sortiert zurück liefern.
Comparator comparator = new MyComparator();
Collections.sort(list1, comparator);
Collections.sort(list2, comparator);
MergeIterator i = new MergeIterator(comparator, list1.iterator(),
list2.iterator());
12.3.7 FilterIterator
Der „Filter-Iterator“ kann Elemente überspringen. Dazu wird dem Iterator ein Filter-Objekt übergeben, in dessen Methode accept() die Filterkriterien implementiert sind. Der Iterator ruft zum Filtern der Ergebnisses diese accept()-Methode des Filters objektweise auf. Soll ein Objekt ignoriert werden, liefert der Filter sich selbst zurück. Daraufhin ignoriert der Iterator das Objekt.
FilterIterator.Filter filter = new FilterIterator.Filter() {
public Object accept(Object obj) {
if (obj instanceof Partner && isCustomer((Partner) obj)) {
return obj;
// obj soll zurückgeliefert werden
}
return this;
// aktuelles obj soll ignoriert werden
}
}
try (CisIterator<Partner> i = new FilterIterator<Partner>(filter, partner.retrieve_instances())) {
while(i.hasNext()) {
Partner customer = i.next();
// ist ein Kunde
foo(customer);
}
}
12.3.8 BufferedObjectIterator
Der „BufferedObjectIterator“ liest Objekte gleichen Typs mithilfe eines Array-Fetch.
FilterIterator.Filter keys = new FilterIterator.Filter() {
public Object accept(Object obj) {
OrderReference ref = (OrderReference) obj;
return Order.buildPrimaryKey(ref.getTargetHeader());
}
}
Iterator i = OrderReferenceLogic.getInstance().iterator(…);
try (CisIterator<Order> j = new BufferedObjectIterator<Order>(keys, i);
// Von der Datenbank werden dann die Orders mithilfe eines ArrayFetch
// (jeweils 16 en bloc) gelesen.
while(j.hasNext()) {
foo(j.next());
}
}
12.3.9 ParallelObjectIterator
Der „ParallelObjectIterator“ ist eine Erweiterung des „CisObjectIterators“, der Objekte unterschiedlichen Typs mithilfe eines Array-Fetch lesen kann.
FilterIterator.Filter keys = new FilterIterator.Filter() {
public Object accept(Object obj) {
OrderReference ref = (OrderReference) obj;
byte[] guid = ref.getTargetHeader();
byte[] orderKey = Order.buildPrimaryKey(guid);
byte[] salesKey = SalesOrder.buildPrimaryKey(guid);
return new byte[][] {orderKey, salesOrderKey};
}
}
Iterator i = OrderReferenceLogic.getInstance().iterator(…);
try (CisIterator<Object[]> j = new ParallelObjectIterator(keys, 2, i, 16)); {
// Von der Datenbank werden dann die Orders und SalesOrders mithilfe eines ArrayFetch (jeweils 16 en bloc) gelesen.
while(j.hasNext()) {
Object[] objs = j.next();
Order order = (Order) objs[0];
SalesOrder sales = (SalesOrder) objs[1];
foo(order, sales);
}
}
12.3.10 ResultSetAdapter
Der „ResultSetAdapter“ ist ein Wrapper für das „CisResultSet“. Der Mapper dient zum Abbilden des Ergebnisses des „ResulSets“ auf die entsprechende Datenstruktur.
ResultSetAdapter.Mapper mapper = new ResultSetAdapter.Mapper() {
CisObjectManager om =
CisEnvironment.getInstance().getObjectManager();
public Object map(CisResultSet rs) throws Exception {
byte[] header = rs.getGuid(1);
byte[] detail = rs.getGuid(2);
if (detail == null || Guid.equals(detail, Guid.ZEROGUID)) {
return om.getObject(Order.buildPrimaryKey(header));
}
byte[] key = OrderDetail.buildPrimaryKey(header, detail);
OrderDetail detail = om.getObject(key);
if (detail.getStatus() == OrderDetail.ORDER_COMPLETED) {
return this; // skip completed order details
}
return detail;
}
}
try (CisResultSet rs = …;
CisIterator i = new ResultSetAdapter(mapper, rs);) {
while(i.hasNext()) {
Object obj = i.next();
if (obj instanceof Order) {
foo((Order) obj);
} else {
foo((OrderDetail) obj);
}
}
}
12.3.11 BufferedResultSetAdapter
Der „BufferedResultSetAdapter“ verhält sich wie der „ResultSetAdapter“, wobei die Objekte genau eines Typs mithilfe eines Array-Fetch geladen werden. Wichtig ist, dass im Gegensatz zum „ResultSetAdapter“ nicht beliebige Objekte zurückgeliefert werden können, sondern ausschließlich Business Objects. Zudem muss vom Mapper der technische Schlüssel erzeugt werden, während beim normalen „ResultSetAdapter“ das resultierende Business Object zurückgeliefert wird.
ResultSetAdapter.Mapper mapper = new ResultSetAdapter.Mapper() {
private Date now = new Date();
public Object map(CisResultSet rs) throws Exception {
if (now.compareTo(rs.getDate(1)) < 0) {
return this; // skip
}
return Order.buildPrimaryKey(rs.getGuid(1));
}
}
try (CisResultSet rs = …;
CisIterator i = new BufferedResultSetAdapter(mapper, rs, 16)) {
while(i.hasNext()) {
Order order = (Order) i.next();
foo(order);
}
}
12.3.12 ParallelResultSetAdapter
Der „ParallelResultSetAdapter“ verhält sich wie der „ResultSetAdapter“, wobei die Objekte unterschiedlichen Typs mithilfe eines Array-Fetch geladen werden.
ResultSetAdapter.Mapper mapper = new ResultSetAdapter.Mapper() {
public Object map(CisResultSet rs) throws Exception {
byte[] guid = rs.getGuid(1);
byte[] orderKey = Order.buildPrimaryKey(guid);
byte[] salesKey = SalesOrder.buildPrimaryKey(guid);
return new byte[][] {orderKey, salesOrderKey};
}
}
try (CisResultSet rs = …;
CisIterator i = new ParallelResultSetAdapter(mapper, 2, rs, 16, CisObjectManager.READ_UPDATE)) {
try (CisTransaction txn=tm.create()) {
while(i.hasNext()) {
Object[] objs = (Object[]) i.next();
Order order = (Order) objs[0];
SalesOrder sales = (SalesOrder) objs[1];
if (validateDelete(order, sales)) {
i.remove();
// Löscht sowohl ‘order’ als auch ‘sales’
}
}
txn.commit();
}
}
12.3.13 OqlSearchAdapter
Der „OqlSearchAdapter“ ist ein Wrapper für das „CisOqlSearchStatement“ mit der Möglichkeit des Nachladens von der Datenbank. So müssen nicht alle Elemente im Speicher gehalten werden.
ResultSetAdapter.Mapper mapper = new ResultSetAdapter.Mapper() {
public Object map(CisResultSet rs) throws Exception {
return Order.buildPrimaryKey(rs.getGuid(1));
}
}
CisOqlSearchStatement statement = …;
try (OqlSearchAdapter<Order> i = new OqlSearchAdapter<Order>(mapper, statement)) {
i.setFetchSize(100);
do {
try (CisTransaction txn=tm.createNew())
{
while(i.hasNext()) {
foo(i.next());
}
txn.commit();
}
i.proceed();
} while(!i.isAtEnd());
}
Der „OqlSearchAdapter“ implementiert eine Reihe von Funktionalitäten, die den Entwickler bei seiner Benutzung entlasten. Die zu seiner Benutzung wichtigen Methoden sind:
- Über die Methode setFetchSize() wird die Anzahl der zu lesenden Sätze pro Block eingestellt. Wird die Satzanzahl erreicht, wird automatisch suspend()
- Die Methode next() liefert das nächste Datenobjekt. Mit der Methode hasNext() ist feststellbar, ob weitere Datenobjekte existieren.
- Über die Methode isAtEnd() kann abgefragt werden, ob alle Sätze zur OQL-Abfrage geliefert wurden.
- Über die Methode suspend() wird die Datenbankverbindung unterbrochen. Dabei wird sich die letzte Position gemerkt. Diese Methode sollte im finally-Block immer explizit aufgerufen werden.
- Über die Methode proceed() wir die Datenbankverbindung wieder hergestellt und das Lesen der Sätze an der gemerkten Position fortgesetzt.
- Die Methode close() schließt den Adapter. Es muss sichergestellt werden, dass der Adapter von der Anwendung geschlossen wird. Der „OqlSearchAdapter“ ist „AutoClosable“, weshalb dieser, wenn der „OqlSearchAdapter“ im „try“ zugewiesen wurde, automatisch beim Verlassen des try-Blocks geschlossen wird. Ein expliziter Aufruf der close()-Methode ist in diesem Fall nicht notwendig.
Bei der Erzeugung einer Instanz des „OqlSearchAdapters“ wird dem Konstruktor der Mapper und das „OqlSearchStatement“ übergeben:
new OqlSearchAdapter(Mapper mapper, CisOqlSearchStatement stmt);
Der Mapper implementiert die map()-Methode zur Abbildung eines gelesenen Satzes vom „ResultSet“ auf ein selbstdefiniertes Datenobjekt. Dieses Datenobjekt ist vom Programmierer zu implementieren.
12.4 Abfragen von Repository-Informationen
Über die statischen Methoden der Klasse „com.cisag.pgm.util.RepositoryInfo“ können Informationen zu Entwicklungsobjekten aus der Repository-Datenbank abgefragt werden. Beispielsweise können dort Data-Descriptions und Namen zu Valueset-Elementen abgefragt werden. Die folgenden Methoden liefern z. B. die Namen zu einzelnen Valueset-Elementen bzw. die Namen zum Valueset:
public static String getValueSetExternalName(nameSpace, valueSetName, constantValue);
public static Map getValueSetExternalName(nameSpace, valueSetName);
12.5 Pattern
Die Klasse „Pattern“ dient dem einfachen Vergleich von Zeichenketten mit einem Muster. Das Muster kann die aus der Suche bekannten Ersetzungszeichen „?“ und „*“ für genau ein bzw. für beliebig viele Zeichen enthalten.
Beispiele:
Pattern pattern= new Pattern(„471?“);
pattern.matches(„4711“); // true
pattern.matches(„4712“); // true
pattern.matches(„0815“); // false
Pattern pattern= new Pattern(„a*d“);
pattern.matches(„ad“); // true
pattern.matches(„axd“); // true
pattern.matches(„abcd“); // true
[1]com.cisag.pgm.util.CisIterator
[2]com.cisag.pgm.appserver.CisObjectIterator
[3]com.cisag.pgm.objsearch.CisSearchIterator
[4]com.cisag.pgm.appserver.CisResultSet
[5]com.cisag.pgm.appserver.CisOqlSearchStatement