Technische Referenz - Catalogs

Aufbau

Catalogs sind Container für CatalogItems. Die CatalogItems können innerhalb des Catalogs verschiedene Zustände haben.

Ein Catalog kennt alle seine CatalogItems, zusätzlich steht in jedem CatalogItem, zu welchem Catalog es gehört. Im Folgen wird dafür die Bezeichung "Elternkatalog" verwendet.

Die vier oben beschriebenen Zustände werden intern durch 4 HashMaps repräsentiert:

mItemsenthält die CatalogItems dieses Catalogs.
mTemporaryRemovedenthält CatalogItems, die mit Hilfe eines DataBaskets entfernt wurden, bis ein commit/rollback ausgeführt wird.
mTemporaryAddedenthält CatalogItems, die mit Hilfe eines DataBaskets hinzugefügt wurden, bis ein commit/rollback ausgeführt wird.
mEditingItemsenthält CatalogItems, die momentan editierbar sind.

Editierbarkeit eines Kataloges

Da Kataloge selbst CatalogItems sind, ist es möglich, sie hierarchisch zu schachteln. Der Katalog, in dem ein anderer enthalten ist, ist für diesen der Elternkatalog. Es gelten für die Editierbarkeit von Katalogen die selben Regeln wie für die Editierbarkeit von CatalogItems, ein Katalog ist also editierbar wenn:

Für Details siehe Holen eines CatalogItems und Geschachtelte Kataloge.


Hinzufügen eines CatalogItems

CatalogItems werden einem Catalog mittels der add(CatalogItem ci, DataBasket db) Methode hinzugefügt. Zuerst wird überprüft, ob der Catalog überhaupt editierbar ist, falls nicht, wird mit einer NotEditableException abgebrochen.

Dann wird nachgeschaut, ob das CatalogItem in einer der Maps steht, also schon im Katalog vorhanden ist. Falls das CatalogItem schon in mItems oder mTemporaryAdded steht, wird eine DuplicateKeyException geworfen und die Operation abgebrochen. CatalogItems dürfen nicht zweimal im selben Catalog vorkommen.

Befindet sich das CatalogItem in mTemporaryRemoved, hätte ein Hinzufügen den Effekt, dass das temporäre Entfernen rückgängig gemacht wird. Voraussetzung ist, dass Entfernen und Hinzufügen den selben DataBasket benutzen. Ist dies der Fall, wird ein rollback() auf das temporäre Entfernen ausgeführt und der Modifikationszähler erhöht, andernfalls wird eine DataBasketConflictException geworfen. Falls das temporär entfernte CatalogItem aber zwischenzeitlich einem anderen Katalog hinzugefügt wurde, resultiert das ebenfalls in einer DataBasketConflictException.

Nachdem diese Tests abeschlossen sind, findet das eigentliche Hinzufügen statt. Wurde null als DataBasket verwendet, wird das CatalogItem direkt in mItems gespeichert, andernfalls in mTemporaryAdded. Im zweiten Fall muss zusätzlich ein Eintrag im DataBasket angelegt werden, der das Hinzufügen beschreibt. Es wird für das CatalogItem ein CatalogItemDataBasketEntry erzeugt, der den aktuellen Katalog als Ziel hat. Es ist allerdings auch möglich, dass für das CatalogItem schon ein DataBasketEntry existiert. Das ist der Fall, wenn es temporär aus einem anderen Katalog entfernt wurde. In dem Fall wird der Entry entsprechend aktualisiert, so dass das temporäre Hinzufügen auch verzeichnet ist.

Zum Schluss wird der Modifikationszähler erhöht, das CatalogItem erhält eine Referenz auf den Catalog, zu dem es jetzt gehört, und Listener werden benachrichtigt, dass ein CatalogItem hinzugefügt wurde.


Entfernen eines CatalogItems

CatalogItems werden aus einem Catalog mittels der Methoden remove(CatalogItem ci, DataBasket db) oder remove(String sKey, DataBasket db) entfernt. Das entfernte CatalogItem wird dabei zurückgeliefert.

Die Methode remove(String sKey, DataBasket db) führt im Wesentlichen einige Tests durch und ruft remove(CatalogItem ci, DataBasket db) auf. Darum soll letztere genauer betrachtet werden.

Vor dem Entfernen wird überprüft, ob der Catalog editierbar ist, wenn nicht, wird eine NotEditableException geworfen und das Entfernen abgebrochen. Anschließend wird festgestellt, ob und in welcher Form das CatalogItem im Katalog vorhanden ist.

Befindet es sich in mTemporaryAdded, hat ein Entfernen unter Umständen den selben Effekt wie ein Rückgängigmachen des temporären Hinzufügens. Voraussetzung ist, dass beim Entfernen der selbe DataBasket wie für das Hinzufügen verwendet wird. Ist das der Fall, werden Listener befragt, ob das CatalogItem entfernt werden darf. Kommt kein Veto, so wird ein rollback() des temporären Hinzufügens ausgeführt.
Falls im DataBasket aber steht, dass das CatalogItem nicht nur zu diesem Katalog hinzugefügt, sondern auch temporär aus einem anderen Katalog entfernt wurde, so darf kein richtiges rollback() stattfinden. Der Grund ist, dass bei einem rollback() auch das temporäre Entfernen aus dem anderen Katalog rückgängig gemacht würde. Stattdessen wird das rollback() des temporären Hinzufügens "von Hand" nachgebildet: Das destination Feld des DataBasketEntrys wird auf null gesetzt, das CatalogItem wird aus mTemporaryAdded entfernt, der Modifikationszähler erhöht und Listener über die Änderungen informiert. Zuletzt wird die Referenz des CatalogItems auf seinen Catalog gelöscht und das CatalogItem wird zurückgeliefert.

Falls das CatalogItem in mTemporaryRemoved steht, kann es nicht mehr entfernt werden. Deshalb wird eine DataBasketConflictException geworfen.

Als nächstes wird getestet, ob sich das CatalogItem in mItems befindet. Falls das nicht zutrifft, ist das CatalogItem nicht im Katalog enthalten und kann deshalb auch nicht entfernt werden. Die remove() Methode wird mit return null verlassen. Wurde das CatalogItem gefunden, wird es nun entfernt.

Zuerst werden Listener befragt, ob das CatalogItem überhaupt entfernt werden darf. Falls kein Listener Einwände hat, wird das CatalogItem aus mItems gelöscht. Ist der benutzte DataBasket nicht null, so ist das Item nur temporär zu entfernen. Deshalb wird es in mTemporaryRemoved gespeichert. Falls im DataBasket ein Eintrag existiert, nach dem das CatalogItem bereits einem anderen Katalog temporär hinzugefügt wurde, so wird im source Feld dieses Eintrags der aktuelle Katalog eingetragen. Das beschreibt eine Verschiebeoperation, wie sie zum Beispiel mit TwoTableFormSheets vorgenommen werden kann. Ansonsten wird ein neuer DataBasketEntry für das Entfernen aus dem Katalog im DataBasket gespeichert.

Zuletzt wird der Modifikationszähler erhöht, aus dem Item wird die Referenz auf den Katalog entfernt, Listener werden über das Entfernen des Items aus dem Katalog informiert und das CatalogItem wird zurückgeliefert.

Seiteneffekte und Veto beim Entfernen eines CatalogItems

Beim Entfernen eines CatalogItems wird Listenern die Möglichkeit gegeben, Einspruch einzulegen. Damit soll Datenkorruption verhindert werden. Nur ausgewählte Listener machen von ihrem Einspruchsrecht Gebrauch, nämlich die zu CountingStockImpl und StoringStockImpl gehörenden.

Die Stocks selbst implementieren nicht das Interface CatalogChangeListener, welches auf die Katalogänderungen hört, sondern überlassen das einer anonymen inneren Klasse. Diese wird jeweils in der Methode private void initReferentialIntegrityListener() definiert.

Werden CatalogItems entfernt, müssen auch die entsprechenden StockItems vernichtet werden. Allerdings gibt es Situationen, in denen das nicht möglich ist. In diesen Situationen, die nachfolgend erläutert werden, werden VetoExceptions geworfen, um das Entfernen des CatalogItems zu verhindern. CatalogItems dürfen nicht entfernt werden wenn:

Dies gilt sowohl für CountingStocks als auch für StoringStocks. Aufgrund der unterschiedlichen Struktur der Stocks ist lediglich die Implementierung etwas abweichend voneinander.


Holen eines CatalogItems

CatalogItems können mit get(String sKey, DataBasket db, boolean fForEdit) aus einem Catalog geholt werden. Beim Holen eines CatalogItems wird mit dem Parameter fForEdit unterschieden, ob es editierbar sein soll oder nicht. Das CatalogItem kann zwar auch editiert werden, wenn es als nicht editierbar geholt wurde, allerdings bietet der DataBasket nur für editierbare Items die Möglichkeit, Änderungen mit einem rollback() rückgängig zu machen.

Wie beim Hinzufügen und Entfernen von CatalogItems wird auch hier zunächst eine Reihe von Tests durchgeführt. Zuerst wird überprüft, ob der Katalog selbst editierbar ist. Ist er das nicht, das zurückzuliefernde CatalogItem soll aber editierbar sein (fForEdit ist true), so wird eine NotEditableException geworfen.

Befindet sich das CatalogItem in mTemporaryAdded und es gibt einen zugehörigen Eintrag im DataBasket (das heißt, der aktuell verwendete DataBasket ist der selbe, mit dem das CatalogItem temporär hinzugefügt wurde), so wird es im Fall, dass es nicht editierbar sein soll, einfach zurückgeliefert. Soll es dagegen editierbar sein, wird vorher noch getestet, ob das temporäre Hinzufügen Teil einer Verschiebeoperation war. Denn dann gibt es einen Katalog, aus welchem das CatalogItem temporär entfernt wurde. Änderungen am CatalogItem müssten auch in diesen Katalog übernommen werden und umgekehrt. Dieser Fall wird vom Framework nicht unterstützt und stattdessen eine DataBasketConflictException geworfen. Falls keine Verschiebeoperation vorliegt, werden Listener befragt, ob das CatalogItem editiert werden darf. Wenn kein Listener ein Veto einlegt, wird das CatalogItem in mEditingItems gespeichert, Listener werden über das Editierbarmachen des CatalogItems informiert und das Item wird zurückgeliefert. Damit ist die get() Methode beendet.

Als nächstes wird überprüft, ob das CatalogItem in mItems vorkommt. Wenn ja, und falls es nicht editierbar sein soll, wird es einfach zurückgeliefert. Wird allerdings ein editierbares Item gewünscht, müssen Vorbereitungen getroffen werden, die ein späteres rollback() ermöglichen. Aus diesem Grund ist auch zu beachten, dass der DataBasket nicht null sein darf, sonst gibt es keine Möglichkeit für ein späteres rollback().

Zunächst werden Listener befragt, ob das CatalogItem editiert werden darf. Falls keine Einsprüche kommen, wird zunächst ein ShallowClone des CatalogItems erzeugt. Dazu wird die Methode getShallowClone() des CatalogItems verwendet, welche der Anwendungsentwickler selbst implementieren muss. Das originale CatalogItem wird aus mItems entfernt und in mTemporaryRemoved verschoben, also temporär entfernt, der ShallowClone wird in mTemporaryAdded und mEditingItems gespeichert, also temporär hinzugefügt und als editierbar gehandhabt. Anschließend werden noch zwei DataBasketEntries erzeugt, die dieses temporäre Hinzufügen und Entfernen festhalten. Aus dem originalen CatalogItem wird die Referenz auf den Katalog entfernt, für den ShallowClone wird eine Referenz auf den Katalog gesetzt. Anschließend werden Listener benachrichtigt, dass CatalogItems aus dem Katalog entfernt, zu ihm hinzugefügt und editierbar gemacht wurden. Dann wird der Modifikationszähler erhöht und der ShallowClone zurückgeliefert.

Alle Änderungen am CatalogItem betreffen nun den ShallowClone. Bei einem commit() wird das originale CatalogItem entfernt und der ShallowClone samt Änderungen wird das neue Original, bei einem rollback() wird der ShallowClone gelöscht und das Original wird zurück nach mItems kopiert.

Nachbetrachtungen

Im erwähnten Fall, dass das CatalogItem, welches geholt werden soll, in mTemporaryAdded steht, wird kein ShallowClone angelegt. Ist trotzdem ein commit() oder rollback() der Änderungen möglich?
Die Antwort lautet natürlich ja. Werden Änderungen durchgeführt und mit einem commit() bestätigt, wird das temporär hinzugefügte und editierte CatalogItem nach mItems kopiert, bei einem rollback() einfach entfernt. Ein ShallowClone ist nicht nötig, da es kein Original im Catalog gibt, welches wiederhergestellt werden müsste.

Wenn ein CatalogItem editierbar gemacht wurde, werden zukünftige get() Aufrufe immer den ShallowClone zurückliefern. Dabei ist es egal, ob fForEdit true oder false ist. Die einzige Möglichkeit, das CatalogItem wieder nichteditierbar zu machen, ist ein commit() oder rollback() auszuführen.

Veto beim Editieren eines CatalogItems

Wenn CatalogItems editiert werden sollen, können CatalogChangeListener von Stocks oder Catalogs ein Veto einlegen. Vetos von Stocks kommen, wenn StockItems, die zum zu editierenden CatalogItem gehören, Transaktionen unterliegen, also gerade temporär hinzugefügt oder entfernt sind. Kataloge legen ein Veto ein, wenn sie einen ShallowClone haben. Dieser ist zu benutzen, um editierbare CatalogItems zu holen.


Namensänderungen von CatalogItems

SalesPoint bietet Interfaces und Implementierungen, die es ermöglichen, Namenskonventionen flexibel durchzusetzen. Diese sollen zunächst grob erläutert werden, da das Umbennnen von CatalogItems auf ihnen aufbaut.

Ein Objekt, welches einen Namen haben soll, der bestimmten Regeln genügt, muss das Interface Nameable implementieren. Die eigentlichen Regeln werden in einem Objekt, das das Interface NameContext implementiert, festgelegt. In der Methode setName(String sName, DataBasket db) des benennbaren Objekts, in der das eigentliche Umbenennen stattfindet, sollten folgende Schritte ablaufen:

Die Implementierung von Nameable wurde in der Klasse AbstractNameable vorgenommen. CatalogItemImpl, StockItemImpl und deren Unterklassen sind von AbstractNameable abgeleitet.
Die Implementierung des NameContext Interfaces fand dementsprechend in CatalogImpl und StockImpl statt, welche ihren Items Namensänderungen erlauben oder verbieten müssen. Die Regel, die definiert wird, ist hauptsächlich, dass keine doppelten Namen vergeben werden dürfen. StockImpl verbietet übrigens grundsätzlich das Umbennen von StockItems, Namensänderungen gehen immer von CatalogItems aus.
CatalogItems erhalten eine Referenz auf ihren NameContext über die Methode setCatalog(), also immer, wenn ihnen ein Elternkatalog zugewiesen wird.

checkNameChange()

Die Methode checkNameChange(DataBasket db, String sOldName, String sNewName) der Klasse CatalogImpl überprüft, ob das CatalogItem, dessen Name geändert werden soll, editierbar ist, ob der neue Name noch nicht vergeben ist und ob der richtige DataBasket übergeben wurde.

Im Detail passiert folgendes:

nameHasChanged()

Nach der eigentlichen Namensänderung wird nameHasChanged(DataBasket db, String sOldName, String sNewName) ausgeführt. Dabei werden alle umbenannten CatalogItems aus ihren jeweiligen Maps entfernt und mit einem neuen Key, nämlich dem neuen Namen, wieder eingefügt. Anschließend wird die Namensänderung in den DataBasket übernommen.

Im Detail läuft das folgendermaßen ab:

Zum Schluss werden Listener benachrichtigt, dass der Name des CatalogItems geändert wurde.


Commit und Rollback

Wenn ein commit() oder rollback() auf einen DataBasket angewendet wird, resultiert das in einem commit() bzw. rollback() für jeden betroffenen DataBasketEntry (siehe DataBaskets). Wenn die betroffenen DataBasketEntries einen Catalog als Quelle oder Ziel haben, werden für diesen spezielle Methoden aufgerufen, welche die entsprechenden CatalogItems neu in die Maps mItems, mTemporaryAdded und mTemporaryRemoved einsortieren.

Commit

Wird auf einem DataBasketEntry ein commit() ausgeführt, so gibt es zwei Möglichkeiten, dies zu behandeln. War das zugehörige CatalogItem temporär hinzugefügt, so wird commitAdd() im Katalog ausgeführt, war das CatalogItem dagegen temporär entfernt, so resultiert das in einem commitRemove().

commitAdd()

Der Methode werden als Argumente der betroffene DataBasketEntry und der DataBasket, in welchem sich der Entry befindet, übergeben. Es wird zuerst überprüft, ob das destination Feld des DataBasketEntrys dem aktuellen Katalog entspricht. Dann wird das betroffene CatalogItem aus mTemporaryAdded entfernt und mItems hinzugefügt. Das CatalogItem bekommt eine Referenz auf den Katalog, der Modifikationszähler wird erhöht und Listener werden über das erfolgte commitAdd() informiert. Falls sich in mEditingItems ein CatalogItem mit gleichem Key befindet, so wurde eben der ShallowClone fest in den Katalog übernommen. Das originale CatalogItem wird aus mEditingItems gelöscht, dann werden Listener benachrichtigt, dass ein commit() auf das Editieren eines CatalogItems erfolgte. Zum Schluss wird das destination Feld des DataBasketEntrys auf null gesetzt.

commitRemove()

Mit dieser Methode wird ein CatalogItem aus mTemporaryRemoved entfernt. Es wird getestet, ob der Katalog im Feld source des DataBasketEntrys dem aktuellen Katalog entspricht. Dann wird das betroffene CatalogItem aus mTemporaryRemoved entfernt und die Referenz auf den Katalog wird gelöscht. Anschließend wird das source Feld des DataBasketEntrys auf null gesetzt und Listener werden über das erfolgte commitRemove() informiert.

Rollback

Wie beim commit gibt es auch beim rollback() zwei Möglichkeiten: Das rollback() des temporären Hinzufügens oder des temporären Entfernens. Die Methoden, die dafür aufgerufen werden, heißen entsprechend rollbackAdd() und rollbackRemove().

rollbackAdd()

Das Rückgängigmachen des temporären Hinzufügens eines CatalogItems beinhaltet das Entfernen des Items aus mTemporaryAdded inklusive Löschen der Referenz auf den Katalog. Es findet ein Update des DataBasketEntrys statt, sein source Feld wird auf null gesetzt. Danach werden der Modifikationszähler erhöht und Listener über das rollbackAdd() informiert. Falls das CatalogItem editierbar war, wird es aus mEditingItems entfernt und Listener werden benachrichtigt, dass ein rollback() auf das Editieren des Items stattgefunden hat.

rollbackRemove()

Es wird das temporäre Entfernen eines CatalogItems rückgängig gemacht. Dazu wird das betroffene Item aus mTemporaryRemoved entfernt und mItems hinzugefügt. Das CatalogItem erhält eine Referenz auf den Katalog, der Modifikationszähler wird erhöht und Listener werden über das erfolgte rollbackRemove() informiert. Zuletzt wird der DataBasketEntry aktualisiert, sein source Feld wird zu null.


Geschachtelte Kataloge

Da Catalogs von CatalogItems abgeleitet sind, ist es möglich, Kataloge zu schachteln (Entwurfsmuster Composite). Um Unterkatalogen CatalogItems hinzufügen zu können, müssen sie erst editierbar gemacht werden. Das lässt sich problemlos mit der get() Methode erreichen (siehe Holen eines CatalogItems). Dabei wird, wie bei normalen CatalogItems, mit einem ShallowClone des Catalogs gearbeitet.

Kopie eines Catalogs erzeugen

Kopien eines Catalogs bestehen standardmäßig aus einem CatalogImpl mit dem gleichen Namen wie das Original und beinhalten die selben CatalogItems. Der Ausgangspunkt für das Erzeugen einer Kopie ist die get() Methode, von der aus getShallowClone() aufgerufen wird. Diese macht folgendes:

Die Methode getShallowClone() liefert standardmäßig einen Katalog vom Typ CatalogImpl. Es kann vorkommen, dass man einen speziellen Katalog benutzt (eine Unterklasse von CatalogImpl), dieser muss auch für den ShallowClone verwendet werden. Allerdings sollte nicht getShallowClone überschrieben werden, da dort Methoden zur Anwendung kommen, die der Datenintegrität dienen. Zudem sind sie als private deklariert, so dass man sie nicht einfach selbst aufrufen kann. Um seinen eigenen Katalog für den ShallowClone bereitzustellen, überschreibt man stattdessen die Hook-Methode createPeer(). Diese wird von getShallowClone() aufgerufen, um die leere Kopie des Katalogs zu erzeugen.

Tief geschachtelte Kataloge

Bei tieferen Schachtelungen können die Kataloge nicht sofort editierbar gemacht werden, weil auch ihre Elternkataloge in einem Katalog stecken und deshalb selbst noch nicht editierbar sind.

Mit der Methode getEditableCopy(DataBasket db) werden rekursiv alle Elternkataloge editierbar gemacht, bis der gewünschte Katalog editierbar ist. Anschließend liefert sie eine editierbare Kopie des Katalogs zurück. Im Detail passiert Folgendes:


Der Modifikationszähler

Bei Veränderungen am Katalog wird der Modifikationszähler erhöht. Das dient dazu, Modifikationen festzustellen, wenn gerade ein Iterator über ihn läuft.

Beim Erzeugen des Iterators wird in diesem der aktuelle Wert des Modifikationszählers gespeichert. In den Methoden next() und remove() wird überprüft, ob der gespeicherte Modifikationszähler und der aktuelle im Wert übereinstimmen. Sind sie nicht mehr gleich, haben Veränderungen am Katalog stattgefunden. Als Resultat wirft der Iterator eine ConcurrentModificationException. Dies ist das Konzept des fail-safe iterator. Es wird bei Änderungen an den Daten lieber kontrolliert abgebrochen, als möglicherweise falsche Ergebnisse zu liefern.


previous DataBasketsProzesse next



by s6200595