Das EPoint Framework Datenbank - Universität der Bundeswehr

Werbung
Das EPoint Framework
Datenbank- und RMI-basierte
Neuimplementierung des SalesPoint-Framework
Diplomarbeit von
Danny Poppe
828345
UniBwM ID 13/2001
Aufgabenstellung & Betreuung:
Dr. Lothar Schmitz
Universität der Bundeswehr München
Fakultät für Informatik
München, 26. Februar 2002
ii
Präambel
In meiner Diplomarbeit beschäftige ich mich mit der Erweiterung des seit 1997 an der TUDresden unter dem Namen SalesPoint 1 entwickelten Java-Frameworks. Bereits in meiner
Studienarbeit [SADP00] beschäftigte ich mich mit diesem Framework dahingehend, dass
ich die Erweiterung des vorhandenen Codes zu einer Unterstützung von Persistenz der
Daten in einer relationalen Datenbank testete. Das Ergebnis dieser Arbeit war, dass die
vorhandenen Strukturen des SalesPoint-Frameworks eine Persistenzhaltung in relationalen Datenbanken wenn überhaupt nur sehr schwer unterstützen würden.
Aufbauend auf den Erfahrungen aus meiner Studienarbeit ist das Ziel dieser Diplomarbeit eine komplette Neuimplementierung des vorhandenen Frameworks. Dabei sollen zum
einen einige der Verbesserungsvorschläge aus dem Groÿen Beleg [GBLG00] von Steen
Zschaler (dem Entwickler des SalesPoint -Framework) Berücksichtigung nden. Zum anderen sollen Probleme die während der Arbeit mit dem SalesPoint-Framework entdeckt
wurden berücksichtigt und falls möglich vermieden werden. In Zusammenarbeit mit dem
Betreuer dieser Diplomarbeit werden diese und weitere Zielstellungen als konkrete Vorgaben für das von mir in dieser Diplomarbeit entwickelte Framework in Kapitel 2 betrachtet.
Das SalesPoint-Framework, auf dem meine Diplomarbeit aufbaut, lag zu Beginn meiner
Übernahme der Diplomarbeit in Version 2.0 vor. Daher beziehen sich sämtliche Bemerkungen zum SalesPoint-Framework auf diese Version. Während meiner Arbeit hat man
aber auch an der TU-Dresden eine neuere Version 3.0 entwickelt. Bei dieser wurde jedoch
im Wesentlichen Wert auf eine Verbesserung der Dokumentation gelegt, sowie vorhandene
Fehler beseitigt. Daher wurden nur wenige der strukturellen Veränderungen vorgenommen, die in meiner Arbeit berücksichtigt wurden.
Im ersten Kapitel meiner Diplomarbeit werde ich kurz auf die Grundlagen und Konzepte
des als Vorlage für das neue EPoint-Framework verwendeten Dresdener Frameworks eingehen. Dabei werden Begrie geklärt und übernommene Konzepte erläutert.
Aufbauend auf den Zielstellungen für das neue Framework und damit für diese Diplomarbeit erläutere ich im zweiten Kapitel die neu eingeführten Konzepte im Rahmen eines
Gesamtüberblicks über das neue Framework. Dazu gehört vor allem die Persistenz in einer relationalen Datenbank, die Anwendungsverteilung mit Hilfe von RMI, sowie die sich
1 s.
[SalesPoint]
iii
iv
PRÄAMBEL
daraus ergebenden Auswirkungen auf die übernommenen Konzepte aus dem SalesPointFramework.
Im dritten Kapitel gehe ich auf den Umgang mit dem Framework ein. Darin wird erklärt, welche Bedingungen an die Benutzung des Frameworks geknüpft sind und wie der
Anwendungsprogrammierer die zur Verfügung gestellte Funktionalität des neuen Frameworks nutzen kann.
Das letzte Kapitel widmet sich schlieÿlich dem implementierungsspezischen Teil meiner Diplomarbeit und wendet sich damit an interessierte Entwickler, die das Framework
weiterentwickeln und verbessern werden. Darin wird gezeigt, auf welche Art und Weise
die Konzepte implementiert wurden und wie diese angepasst und und weiterverwendet
werden können. Auÿerdem werden dort Ideen für mögliche Weiterentwicklungen und Verbesserungsmöglichkeiten beschrieben.
Im Anhang ndet man schlieÿlich das Literatur-, Tabellen- und Bildverzeichnis sowie
eine kurze Übersicht über verwendete Begrie. Auÿerdem kann man dort einen kurzen
Abschnitt über relationale Datenbanken und eine detaillierte Übersicht über die von mir
implementierte Datenbankstruktur nachlesen. Im Anschluss daran bendet sich dort auch
ein kompletter Abdruck des Quelltextes für das neue Framework.
Auÿerdem möchte ich bereits an dieser Stelle auf die ebenfalls sehr umfangreiche JavaDocDokumentation zur Benutzung des Frameworks hinweisen. Diese bendet sich zusammen
mit den Quelltexten, einer elektronisch lesbaren Version dieser Diplomarbeit und mehreren anderen benötigten Dateien zur Benutzung des Frameworks auf der beiliegenden CD.
Mein Dank für die Unterstützung bei der Erstellung dieser Diplomarbeit gilt vor allem
Dr. Lothar Schmitz dem Betreuer dieser Diplomarbeit. Auÿerdem bedanke ich mich
bei Mirko Pracht, der sich in seiner Studienarbeit [SAMP01] mit den Möglichkeiten der
Serialisierung und Manipulation von Objekten des neuen EPoint-Frameworks in XML beschäftigte, sowie Stephan Moritz, der in seiner Studienarbeit [SASM01] die Möglichkeiten
von RMI untersuchte und Hilfestellung sowie Ratschläge für die Verwendung von RMI im
neuen Framework gab.
Inhaltsverzeichnis
Präambel
iii
Inhaltsverzeichnis
v
1 Das SalesPoint-Framework
1
1.1
Allgemeine Beschreibung . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1
1.2
Bestandteile des Frameworks . . . . . . . . . . . . . . . . . . . . . . . . . .
2
1.2.1
Datenverwaltung . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3
1.2.2
Ablaufsteuerung . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3
Transaktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4
1.3
2 Aufgabenstellung und neue Konzepte
2.1
Gesamtbetrachtung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.2
Die neuen Ziele im Einzelnen
5
8
. . . . . . . . . . . . . . . . . . . . . . . . . 10
2.2.1
Reale Anwendung vs. Studienpraktikum . . . . . . . . . . . . . . . 10
2.2.2
Persistenz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
2.2.3
Anwendungsverteilung . . . . . . . . . . . . . . . . . . . . . . . . . 11
2.3
Verwendung von XML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
2.4
Verwendung einer relationalen Datenbank . . . . . . . . . . . . . . . . . . 13
2.4.1
Transaktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
v
vi
INHALTSVERZEICHNIS
2.4.2
Allgemeines Persistenzmodell . . . . . . . . . . . . . . . . . . . . . 15
2.4.3
Persistenz wichtiger Frameworkklassen . . . . . . . . . . . . . . . . 19
2.4.3.1
Persistenz der Datenklassen . . . . . . . . . . . . . . . . . 19
2.4.3.2
Verwaltungsinformationen . . . . . . . . . . . . . . . . . . 23
2.4.3.3
Zusätzliche Informationen . . . . . . . . . . . . . . . . . . 23
2.5
Anwendungsverteilung mit RMI . . . . . . . . . . . . . . . . . . . . . . . . 24
2.6
Komplexe persistente Zustandsinformationen . . . . . . . . . . . . . . . . . 26
2.7
Äuÿere Struktur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
2.8
Kompatibilität
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
3 Verwendung durch Programmierer
3.1
29
Anleitung zur Anwendungsentwicklung . . . . . . . . . . . . . . . . . . . . 29
3.1.1
Vorbereitung und Planung . . . . . . . . . . . . . . . . . . . . . . . 30
3.1.2
Datenspezikation . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
3.1.3
Spezikation der EPoints . . . . . . . . . . . . . . . . . . . . . . . . 33
3.1.4
Zustandsdenition des EPoint . . . . . . . . . . . . . . . . . . . . . 36
3.1.5
Konguration des Frameworks . . . . . . . . . . . . . . . . . . . . . 37
3.1.6
Besonderheiten der Persistenz . . . . . . . . . . . . . . . . . . . . . 38
3.1.7
3.1.6.1
Eigene persistente Objekte . . . . . . . . . . . . . . . . . . 39
3.1.6.2
Referenzen auf persistente Objekte . . . . . . . . . . . . . 39
3.1.6.3
Persistenzstatus . . . . . . . . . . . . . . . . . . . . . . . . 43
3.1.6.4
Garbage Collection von persistenten Objekten . . . . . . . 43
3.1.6.5
Persistenz und Serialisierung durch Serializer
. . . . . . . 44
Besonderheiten von RMI . . . . . . . . . . . . . . . . . . . . . . . . 45
3.1.7.1
Funktionsweise von RMI . . . . . . . . . . . . . . . . . . . 45
3.1.7.2
Threadsicherheit . . . . . . . . . . . . . . . . . . . . . . . 47
INHALTSVERZEICHNIS
3.1.7.3
vii
Lokale Instanzen und dierenzierte Services . . . . . . . . 50
3.1.8
Transaktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
3.1.9
Listener . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
3.1.10 Logles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
3.2
Allgemeine Bemerkungen und Hinweise . . . . . . . . . . . . . . . . . . . . 57
3.3
Technische Voraussetzungen . . . . . . . . . . . . . . . . . . . . . . . . . . 57
3.3.1
3.3.2
Voraussetzungen für die Datenbankverwendung . . . . . . . . . . . 58
3.3.1.1
Installation unter Linux (RedHat)
. . . . . . . . . . . . . 58
3.3.1.2
Installation unter Windows 2000 . . . . . . . . . . . . . . 59
3.3.1.3
Konguration des DBMS PostGreSQL . . . . . . . . . . . 60
Voraussetzungen für die Verwendung von RMI . . . . . . . . . . . . 61
3.3.2.1
Technische Anforderungen . . . . . . . . . . . . . . . . . . 62
3.3.2.2
Firewall und Proxy . . . . . . . . . . . . . . . . . . . . . . 62
4 Implementierung des EPoint-Framework
65
4.1
Datenbankimplementierung . . . . . . . . . . . . . . . . . . . . . . . . . . 65
4.2
RMI-Implementierung und Datenklassen . . . . . . . . . . . . . . . . . . . 67
4.3
Transaktionsmechanismus . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
4.3.1
4.4
Konsistenzbedingungen . . . . . . . . . . . . . . . . . . . . . . . . . 72
Erweiterungsmöglichkeiten . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
4.4.1
EPoint Unterstützung . . . . . . . . . . . . . . . . . . . . . . . . . 75
4.4.2
GUI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
4.4.3
Benutzermanagement . . . . . . . . . . . . . . . . . . . . . . . . . . 76
4.4.4
Views und Filter . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
4.4.5
Wrapperklassen für RemoteObjects . . . . . . . . . . . . . . . . . . 77
4.4.6
Server für Shops
4.4.7
Sicherheit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
4.4.8
Weitere Nutzung der RMI-Möglichkeiten . . . . . . . . . . . . . . . 78
. . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
viii
INHALTSVERZEICHNIS
A Datenbank-Tabellen
79
A.1 Kataloge und Katalogeinträge . . . . . . . . . . . . . . . . . . . . . . . . . 80
A.2 Bestände und Bestandseinträge . . . . . . . . . . . . . . . . . . . . . . . . 83
A.3 Transaktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
A.4 Weitere Tabellen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
A.5 Views . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
B UML Diagramme
97
Vererbung und Implementierung der Datenklassen . . . . . . . . . . . . . . . . . 98
Vererbung und Implementierung des Transaktionsframework . . . . . . . . . . . 99
Vererbung im Package epoint.data
. . . . . . . . . . . . . . . . . . . . . . . . 100
Vererbung im Package epoint.data.rmi . . . . . . . . . . . . . . . . . . . . . . 101
C Quelltexte
103
C.1 Package epoint.data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
C.1.1 Package epoint.data.exception . . . . . . . . . . . . . . . . . . . 150
C.1.2 Package epoint.data.rmi . . . . . . . . . . . . . . . . . . . . . . . 154
C.2 Package epoint.db . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 263
C.2.1 Package epoint.db.exception . . . . . . . . . . . . . . . . . . . . 312
C.3 Package epoint.log . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 313
C.4 Package epoint.sale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 319
C.4.1 Package epoint.sale.exception . . . . . . . . . . . . . . . . . . . 338
C.4.2 Package epoint.sale.rmi . . . . . . . . . . . . . . . . . . . . . . . 339
C.5 Package epoint.test . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 343
C.5.1 Package epoint.test.server . . . . . . . . . . . . . . . . . . . . . 343
C.5.2 Package epoint.test.client . . . . . . . . . . . . . . . . . . . . . 344
C.6 Package epoint.xml . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 353
C.6.1 Package epoint.xml.exception
. . . . . . . . . . . . . . . . . . . 358
INHALTSVERZEICHNIS
ix
Abbildungsverzeichnis
361
Tabellenverzeichnis
362
Abkürzungs- und Begrisverzeichnis
364
Literaturverzeichnis
367
x
INHALTSVERZEICHNIS
Kapitel 1
Das SalesPoint-Framework
Dieses Kapitel beschreibt das Framework, auf dem meine Diplomarbeit aufbaut und von
dem wesentliche Konzepte für die Neuimplementierung übernommen wurden. Die in diesem Abschnitt geklärten Begrie werden (falls später nicht explizit anders erläutert) in
derselben Form weiterverwendet. Benutzer bezeichnet im folgenden stets den Benutzer
einer mit dem Framework erstellten Anwendung, wohingegen der Programmierer denjenigen bezeichnet, der Anwendungen mit dem Framework erstellt. Hervorgehobene Begrie
in Klammern sind als Synonym zu verstehen und zu verwenden (meist englische oder
deutsche Entsprechung bzw. Name der Klasse in der Implementierung).
Auÿerdem wird, wenn Klassennamen oder Objekte von Klassen bezeichnet werden, in
der Regel Schreibmaschinenschrift verwendetet. Steht jedoch die Semantik der verwendeten Klasse oder eines anderen Begris im Vordergrund, verwende ich Kursivschrift.
So bezeichnet EPoint die Klasse epoint.sale.EPoint und EPoint das in dieser Arbeit
behandelte Framework EPoint insgesamt oder die Klasse EPoint in ihrer Rolle als Client
unabhängig von der Implementierung.
1.1 Allgemeine Beschreibung
Das SalesPoint-Framework ist ein momentan an der TU Dresden und der UniBw München im Rahmen des Programmier-Praktikums eingesetztes Framework, zur Entwicklung
einer Simulation von Verkaufsanwendungen. Es stellt sowohl die Infrastruktur für die Verwaltung von Daten zur Verfügung, wie auch für die Simulation von Geschäftsvorgängen
in der mit diesem Framework erstellten Anwendung. Zusätzlich werden grasche Oberächenelemente (GUI - Graphical User Interface ) für die Erstellung einer Bedienoberäche
für die Anwendung zur Verfügung gestellt. Diese enthält neben Elementen zur Darstellung
der Daten auch GUI-Elemente zur Anwendungssteuerung.
Das SalesPoint-Framework stellt insgesamt bereits eine auf einem einzigen Rechner lauffähige Java-Anwendung dar, die noch um die wesentlichen Teile einer fertigen Applikation
1
2
KAPITEL 1. DAS SALESPOINT-FRAMEWORK
erweitert werden muss. Die mit dem Framework entwickelten Anwendungen sind damit
simulierte Verkaufsstellenumgebungen mit Unterstützung für Kunden oder Verkäufer
[GBLG00]. Erstellte Anwendungen laufen auf einem Rechner in einer einzigen JVM1 ab
und weisen eine einheitliche Bedienoberäche auf, die durch die Verwendung der mitgelieferten GUI-Elemente bestimmt wird.
Die wesentlichen Bestandteile einer solchen Simulation sind zum einen der Shop, welcher
die Verwaltung von Daten und die Ablaufsteuerung der Simulation übernimmt. Zum zweiten nimmt der SalesPoint die Rolle der Schnittstelle zum Benutzer ein. Am SalesPoint
können Daten von Personen in unterschiedlichen Rollen (Kunde, Verkäufer, Manager,
u.a.) eingesehen und manipuliert werden. Auf diese Weise kann der Ablauf der gesamten
Simulation gesteuert werden.
1.2 Bestandteile des Frameworks
Aufgrund der vorangegangenen Beschreibung lässt sich das SalesPoint-Framework in drei
gröÿere Bereiche aufteilen:
1. Zum ersten gibt es den groÿen Bereich der Datenverwaltung. Dieser stellt für Verkaufsanwendungen typische Datenstrukturen sowie Operationen und Hilfsmittel für
den Umgang damit zur Verfügung.
2. Zum zweiten stellt der Bereich Programmablaufsteuerung Klassen und Interfaces für
die Steuerung der Simulation zur Verfügung. Er deniert zentrale Elemente einer
mit dem Framework erstellten Applikation sowie deren Anwendungsschnittstelle.
3. Schlieÿlich stellt der letzte groÿe Bereich der graschen Benutzeroberäche (GUI)
Elemente zur Verfügung, die die Implementierung einer Anwendungsschnittstelle
zur Simulation erleichtern.
Der zuletzt genannte Bereich der graschen Oberächenelemente ist von der Implementierung der beiden zuerst genannten Bereiche weitestgehend unabhängig und setzt auf
deren Schnittstellenspezikation auf.
Da einige der speziellen Strukturen der Bereiche Datenverwaltung und Programmablaufsteuerung auch im neuen Framework verfügbar sein sollen, beschreiben die beiden nächsten Unterabschnitte diese etwas genauer.
1 Java
Virtual Machine
1.2. BESTANDTEILE DES FRAMEWORKS
3
1.2.1 Datenverwaltung
Das SalesPoint-Framework stellt für Verkaufsanwendungen typische Datenstrukturen mit
vielen Unterstützungsfunktionen für den Programmierer zur Verfügung. Das sind vor allem Kataloge (Catalogs ) mit Katalogeinträgen (CatalogItems ), in denen z.B. Verkaufsgegenstände eines Warenhauses verzeichnet werden. Dazu besitzen Katalogeinträge eine
eindeutige Bezeichnung (Key ), sowie einen dazugehörigen Wert (Value ), der z.B. den Preis
eines Verkaufsgegenstandes bezeichnet.
Basierend auf den Katalogen werden Bestände (Stocks ) zur Verfügung gestellt. Dabei bezieht sich jeder Stock auf genau einen Catalog. Zu jedem Katalogeintrag können dann
im Bestand mehrere Bestandseinträge (StockItems ) existieren. Bei den Beständen unterscheidet man CountingStocks und StoringStocks. Die Einträge von CountingStocks geben
an, wieviele Exemplare eines durch einen Katalogeintrag beschriebenen Gegenstandes tatsächlich vorhanden sind. In den StoringStocks existieren zu einem Katalogeintrag möglicherweise mehrere StockItems, die spezielle Ausführungen des Katalogeintrags darstellen
und weitere gegenstandsspezische Felder enthalten. Dies kann z.B. basierend auf einem
Katalog über Fahrzeugtypen (Hersteller) ein Bestand von Fahrzeugen sein, für die einzelne weitere Eigenschaften wie Seriennummer, Karosseriefarbe, usw. vermerkt sind.
Kataloge und Bestände werden dabei als Datenbehälter für Katalog- und Bestandseinträge aufgefasst. Diese Daten werden dann entsprechend in den Container eingefügt bzw.
aus diesem entfernt.
Eine weitere Art von Datenbehälter stellen die Datenkörbe (DataBaskets ) dar, auf die
weiter unten zum Thema Transaktionen noch näher eingegangen wird.
1.2.2 Ablaufsteuerung
Den Ablauf einer mit dem SalesPoint Framework erstellten Anwendung kann man folgendermaÿen beschreiben: Der Shop als zentrale Instanz der Anwendung verwaltet den
gesamten Datenbestand an Katalogen und Beständen. Auÿerdem ist er verantwortlich für
die Aktivierung und Deaktivierung verfügbarer SalesPoint-Instanzen. Desweiteren verwaltet er alle in einer Anwendung simulierten Kunden mit ihren Daten (Passwörter, Namen)
und kennt deren Möglichkeiten der Interaktion (Capabilities ) mit den einzelnen SalesPoints. Der Shop als globale verwaltende Instanz hat auÿerdem die Möglichkeit, den
Zustand der gesamten Anwendung/Simulation explizit abzuspeichern und diesen zu einem späteren Zeitpunkt wiederherzustellen.
Charakteristisch für die SalesPoints und damit für das Framework ist auÿerdem, dass
Abläufe an den SalesPoints (also dort, wo der Anwender in Interaktion mit der Simulation tritt) in Form von Prozessen modelliert werden. Diese Prozesse bestehen aus klar
denierten Zuständen (Gates ), an denen Entscheidungen getroen werden (meist durch
den Anwender), und aus Zustandsübergängen (Transitions ), die diese Entscheidungen
auswerten und so einen neuen Zustand herbeiführen. Insgesamt kann man einen Prozess
4
KAPITEL 1. DAS SALESPOINT-FRAMEWORK
also wie einen deterministischen Automaten betrachten. Prozesse sind dabei nicht einfach
an ein bestimmtes Objekt (z.B. den SalesPoint) gebunden, sondern zunächst nur an einen
Prozesskontext. Dieser deniert die Daten, die von dem Prozess manipuliert werden. Ein
solcher Prozesskontext ist im SalesPoint-Framework zum einen der Shop selber, in dem
Prozesse im Hintergrund ablaufen können und zum anderen jeder SalesPoint, an welchem
immer genau ein benutzergesteuerter Prozess abläuft.
Da es sich bei einer mit diesem Framework entwickelten Anwendung um ein nicht verteiltes und innerhalb einer einzigen virtuellen Maschine ablaufendes Programm handelt,
verfügt und verwaltet der Shop auÿerdem ein Display. Dieses Display kann an Objekte,
die etwas auf dem Bildschirm darstellen wollen, weitergegeben werden. So kann der jeweils
vom Benutzer betrachtete EPoint das Display erhalten und an den Prozess weitergeben.
Dieser Prozess kann dann an den Gates mit Hilfe dieses Displays in Interaktion mit dem
Anwender treten.
1.3 Transaktionen
Eine weitere besondere Fähigkeit des SalesPoint-Frameworks ist die Unterstützung von
Transaktionen. Dazu wird für den Programmierer ein neuer Datenbehälter mit der Bezeichnung Datenkorb (DataBasket ) eingeführt. In diesem werden Transaktionsgegenstände (Katalogeinträge und Bestandseinträge), die gerade aus anderen Datenbehältern (Kataloge und Bestände) entfernt oder zu diesen hinzugefügt werden verzeichnet. Mit Hilfe der
DataBaskets können beliebig viele solcher einzelnen Aktionen zusammengefasst und als
eine Transaktion ausgeführt werden. Theoretisch kann man diesen Datenbehälter so verstehen, dass er die zu transferierenden Gegenstände in sich aufnimmt, zusammen mit der
Information darüber, woher sie kommen (um die Transaktion gegebenenfalls rückgängig
machen zu können) und wohin sie gelangen sollen (um die Transaktion insgesamt ausführen zu können). Schlieÿlich führt man nach der Registrierung aller einzelnen Aktionen
nur noch eine einzige Transaktionsbestätigung (oder einen Abbruch) für alle registrierten
Aktionen aus. Der DataBasket verwendet dann die aufgezeichneten Informationen, um
alle Einzelaktionen auf einmal auszuführen.
Kapitel 2
Aufgabenstellung und neue Konzepte
In diesem Kapitel werden die Überlegungen zu den neuen Konzepten des EPoint-Frameworks vorgestellt. Basierend auf den im vorangegangenen Kapitel geschilderten Konzepten, erkläre ich hier, welche zusätzlichen Anforderungen das neue Framework erfüllen soll
und welche Veränderungen sich daraus an den alten Konzepten ergeben.
Nachdem seit der Entwicklung des SalesPoint -Frameworks viele Erfahrungen im Umgang
damit gesammelt wurden, gibt es mehrere Ideen zur Verbesserung des Frameworks. Die
Anregungen dazu stammen einerseits bereits vom Entwickler S. Zschaler, der diese in
seinem Groÿen Beleg [GBLG00] aufzählt und andererseits auch von Studenten, die im
Rahmen des Programmier-Praktikums an der UniBwM das SalesPoint-Framework verwendeten.
Auf diesen Informationen und Ideen aufbauend, wurde bei den Vorüberlegungen für
diese Diplomarbeit zunächst die folgende Aufgabenstellung formuliert: Das SalesPointFramework soll unter Weiterverwendung bewährter Konzepte vollständig neu implementiert werden. Dabei sollen im einzelnen folgende Ideen und Erweiterungsmöglichkeiten
berücksichtigt werden:
• Nach Wünschen aus Dresden soll der Simulationscharakter zugunsten des Anwendungscharakters zurückgenommen werden. Insbesondere Kunden und Kundenwarteschlangen sollen entfallen. Bleiben sollen Testmöglichkeiten wie z.B. das Auslösen
von Zeitübergängen sowie das Speichern und Laden von Spielsituationen (jedoch
in anderer, weiter unten beschriebener Form).
• Persistenz soll statt wie bisher auf Java-Serialisierung künftig auf XML und/oder
Datenbanken abgestützt werden. XML hat als Klartext-Format Vorteile beim Testen und bietet auch die Möglichkeit manueller Anpassungen während der Entwicklung. Datenbanken bringen Persistenz, Robustheit und konkurrierende Zugrie aus
nebenläugen bzw. verteilten Anwendungen.
5
6
KAPITEL 2. AUFGABENSTELLUNG UND NEUE KONZEPTE
Bei Verwendung einer Datenbank werden Kataloge, Bestände und Datenkörbe
laufend in ihrem aktuellen Zustand in der Datenbank gehalten. Das Framework soll die Möglichkeit unterstützen, dass mehrere SalesPoints mit demselben Katalog (Bestand) parallel arbeiten. Der Inhalt der Datenbank soll als
Gesamtzustand gespeichert und wiederverwendet werden können.
Wird keine Datenbank verwendet, dann werden alle aktuellen Daten direkt in
Java-Datenstrukturen gehalten. Um Persistenz zu gewährleisten, werden sämtliche Vorgänge und Prozesszustände seit dem letzten Sichern des Gesamtzustands in einer XML-Protokolldatei festgehalten. Da Prozesszustände nicht in
der DB gehalten werden, läuft der gleiche Mechanismus auch bei Verwendung
einer DB mit.
In beiden Fällen erhalten die SalesPoints ihre Katalog-, Bestands- und Datenkorbdaten vom Datenmanager des Shops. Dessen Implementierung ist nicht erkennbar
für die SalesPoints und kann sich auf eine Datenbank abstützen. Auÿerhalb des Datenmanagers können von Anwendungen aus Gründen der Bequemlichkeit und der
Ezienz unverbindliche Kopien solcher Daten benutzt werden, die aber spätestens
bei Ende eines Prozesses abgeglichen werden.
• SalesPoints sollen künftig als eigene Programme (auf beliebigen Rechnern) gestartet
werden können jede Art von SalesPoint als eigenes Programm. Zu jedem Zeitpunkt
darf es von gleichartigen SalesPoints beliebig viele Exemplare geben, auch auf einem
Rechner. Ein gestarteter SalesPoint muÿ zuerst mit dem Shop Verbindung aufnehmen. Dadurch hat der Shop als zentrales Objekt einer Framework-Anwendung u.a.
Kontrolle über die SalesPoints, die gleichzeitig laufen können.
• Wenn ein SalesPoint (oder ein Shop als SalesPoint) an mehreren Shops angemeldet sein kann, hat man gleichzeitig die Grundlage für eine einfache Form von EBusiness. Da E-Business zwischen Shops auch automatisiert (d.h. ohne interaktive
Bedienung an SalesPoints) ablaufen soll, wäre ein E-Point als Verallgemeinerung
von SalesPoint vermutlich ein nützliches Konzept.
• Innerhalb eines Shops könnte man E-Points als Träger ereignisgesteuerter Prozesse (z.B. geeignete Abschlüsse bei Tages- oder Monatswechsel) verwenden. Wenn
jeder SalesPoint auch E-Point wäre, dann könnte man die E-Point-Schnittstelle verwenden, um beim Wiederaufsetzen den SalesPoint durch stilles Abspielen seines
Protokolls wieder in den aktuellen Zustand zu bringen.
• Im Wesentlichen beibehalten werden, sollen folgende Konzepte:
Kataloge als Mengen von Katalogeinträgen. Kataloge sind XML-fähig in dem
Sinn, dass sie eine Methode toXMLString() besitzen sowie einen Konstruktor, der aus einem solchen String den Katalog wieder aufbaut. Hierfür reicht
wohlgeformtes, nicht notwendig validiertes XML.
7
∗ Hierarchische Kataloge sollen aber nicht mehr nach dem Composite-Muster
konstruiert sein, das in einem Katalog gleichzeitig Katalogeinträge und
Unterkataloge (als spezielle Katalogeinträge) enthalten kann. Stattdessen
sind Einträge von hierarchischen Katalogen nur noch (hierarchische oder
einfache) Kataloge.
∗ Jeder Katalogeintrag zerfällt künftig in die vier Rubriken Schlüssel, Bezeichnung, Preisinfo und Produktinfo.
Bestände, die sich auf Einträge eines bestimmten Katalogs beziehen und dann
angeben:
∗ wieviele Exemplare eines Katalogeintrags im Bestand tatsächlich vorhanden sind (Massenbestände);
∗ welche Katalogeinträge mit welchen Daten zu Exemplaren des Bestands
zu ergänzen sind (Individualbestände).
Auch Bestände sind XML-fähig im genannten Sinn.
Datenkörbe, die Mengen von Bestandeinträgen bzw. Katalogeinträgen (jeweils
nebst Schlüssel des Quellbestands bzw. -katalogs) enthalten. Sowohl Bestände als auch Kataloge müssen also Schlüssel (und Bezeichnungen) haben. Datenkörbe dienen u.a. der Modellierung von Bestellungen und Einkaufstaschen
von Kunden im Warenhaus. Prozesse verwenden in der Regel Datenkörbe als
Teilzustandsbehälter. Wie Kataloge und Bestände sollen Datenkörbe in der
Datenbank gehalten werden.
• Prozesse aus Zuständen (Gates) und Transitionen haben sich bei der Modellierung
und Implementierung von Use Cases bewährt und sollten daher auch im neuen
Framework verwendet werden.
• Sinnvoll wäre auch eine neue Bibliothek von Oberächenbausteinen, die aber nicht
so stark gekapselt, sondern oener in Swing-Oberächen eingebaut werden können.
• Wenn E-Points zwecks Kommunikation ohnehin Ereignis-Handler-Komponenten besitzen, dann kann man diese auch für andere Zwecke nutzbringend einsetzen, z.B.
um stille Prozesse (ohne Benutzerinteraktion) auszulösen, etwa beim Zeitmanagement (einen Monat fortschalten löst ca 30 Tageswechsel-Ereignisse aus) oder bei der
Lagerhaltung (Bestand unter Minimum löst Nachbestell-Ereignis bei Lieferanten-EPoint aus oder eine Nachricht an den Manager).
• Wenn die Katalog-/Bestands-/Datenkorb-Klassen datenbankbasiert neu implementiert werden, dann sollte man eine entsprechende konventionelle Fassung (angepasste Version der aktuellen Klassen) bereitstellen.
Ausgehend von diesen ersten Zielvorstellungen habe ich in Zusammenarbeit mit meinem
Betreuer die grundlegenden neuen Konzepte und Änderungen am SalesPoint -Framework
8
KAPITEL 2. AUFGABENSTELLUNG UND NEUE KONZEPTE
für das neue EPoint -Framework erarbeitet. In den folgenden Abschnitten sollen die eben
genannten und von mir schlieÿlich implementierten neuen Konzepte vorgestellt und diskutiert werden.
2.1 Gesamtbetrachtung
Das neue Framework soll im Wesentlichen eine Erweiterung des alten darstellen. Daraus
folgt, dass das neue Anwendungsspektrum eher gröÿer als kleiner sein wird und das alte
Spektrum im Wesentlichen abgedeckt bleibt.
Das bedeutet aber auch, dass an den grundlegenden Prinzipien einer mit dem SalesPoint Framework erstellten Anwendung keine groÿen Änderungen bei Implementierung mit dem
EPoint -Framework entstehen werden. Eine mit dem EPoint -Framework erstellte Anwendung besitzt noch immer einen Shop als zentrale verwaltende Instanz und statt der SalesPoints ab jetzt EPoints, die die entsprechende Funktionalität der SalesPoints übernehmen. Die Datenstrukturen bezüglich der Kataloge und Bestände bleiben gemäÿ ihrer
früheren Semantik und Funktionalität erhalten. Die wichtigsten Neuerungen im Einzelnen
sind:
• Der Shop läuft unabhängig von den EPoints als eigenständiges Programm ab und
tritt in der Rolle eines Servers für mehrere EPoints (ebenfalls eigenständige Programme in der Rolle der Clients ) auftritt. So kann mit Hilfe denierter Schnittstellen die Gesamtanwendung stark dezentralisiert werden. Nicht zuletzt dadurch
verlieren die mit dem EPoint -Framework erstellten Programme deutlich ihren Simulationscharakter im Vergleich zu den mit dem SalesPoint -Framework erstellten
Programmen.
• Benutzerschlangen an den EPoints fallen weg1 . Stattdessen besitzt jeder EPoint
nur noch einen zu modellierenden Prozess, der von dem gerade aktuellen Benutzer
des EPoints durchlaufen wird. Ist kein Benutzer an der Reihe, bendet sich der
Prozess in einem denierten Leerlauf-Zustand. Damit stellen EPoints (ähnlich wie
der Bankautomat einer groÿen Bank) ein eigenständiges Terminal zur Benutzung
der Anwendung dar, das immer nur von einer Person bedient werden kann.
• Alle Daten (vor allem Kataloge und Bestände), die den Zustand einer Anwendung
charakterisieren, werden in einer Datenbank gespeichert.
• Vor allem aufgrund der Persistenz der Objekte in einer Datenbank und der neuen
Client-Server Struktur, haben sich frameworkintern neue Programmierrichtlinien
ergeben. Diese wirken sich auch auf die äuÿeren Schnittstellen aus, so dass bei der
1 Dieser
Änderungswunsch wurde im SalesPoint-Framework beim Übergang zur Version 3.0 ebenfalls
bereits berücksichtigt.
2.1. GESAMTBETRACHTUNG
9
Erstellung von Programmen mit EPoint, einige (später ausführlich beschriebene)
wichtige Punkte bei der Programmierung beachtet werden müssen.
• Die grasche Oberäche, die in EPoint momentan noch fehlt, setzt im Wesentlichen
nur auf die Schnittstellenspezikationen der Datenklassen auf. Damit ist sie relativ
unabhängig vom Rest des Frameworks auch nachträglich implementierbar und kann,
aber muss sich im Äuÿeren nicht von der des SalesPoint -Framework unterscheiden.
Von den früheren Zielvorstellungen sind einige der Punkte teilweise noch nicht implementiert worden oder sind in etwas abgewandelter Form im Framework umgesetzt. Das
betrit im Einzelnen:
• Die Speicherung des Inhalts der verwendeten Datenbank ist nicht explizit umgesetzt
worden, da das letztlich verwendete PostGreSQL-DBMS diese Möglichkeit bereits
von sich aus unterstützt.
• Eine explizite Unterstützung von E-Business mit speziellen EPoints, die automatischen E-Business unterstützen, gibt es nicht. Allerdings schränkt die verwendete
Kommunikationsbasis (RMI) diese Möglichkeit keinesfalls ein, womit ein solcher
Mechanismus durchaus implementierbar ist.
• Die Unterstützung für ereignisgesteuerte Prozesse, die durch stilles Abspielen eines
aufgezeichneten Protokolls wieder in einen früheren Zustand versetzt werden, fällt
weg. Stattdessen wird ein anderer Mechanismus zur Verfügung gestellt (InformationStore ), der unter Verwendung von Transaktionen die speziellen Zustandsvariablen
eines EPoints aufzeichnen kann.
• Katalogeinträge sind nicht an eine festgelegte Datenstruktur gebunden (Schlüssel,
Bezeichnung, Preisinfo, Produktinfo), sondern verwenden weiterhin einen frei implementierbaren Value -Typ, um beliebige Daten speichern zu können.
• Datenkörbe werden nur noch als Transaktionshandle verwendet und dienen nicht
mehr als Container zur Modellierung von einsehbaren Bestellungen eines Kunden
u.ä..
• Eine Implementierung für Prozesselemente von EPoints sowie eine Bibliothek von
graschen Oberächenbausteinen ist in dieser Version des Frameworks noch nicht
enthalten.
• Eine Unterstützung für ein Zeitmanagement (zu Simulationszwecken) ist in EPoint
ebenfalls noch nicht integriert.
10
KAPITEL 2. AUFGABENSTELLUNG UND NEUE KONZEPTE
2.2 Die neuen Ziele im Einzelnen
Die einzelnen Zielstellungen für ein neues Framework ergeben sich schnell aus den Schwierigkeiten bei der Arbeit mit dem SalesPoint -Framework, sowie einigen Änderungswünschen der Programmierer und/oder Benutzer. Fast alle diese Probleme wurden bereits zu
Beginn dieses Kapitels bei der Formulierung der Zielstellungen für das neue Framework
berücksichtigt. Dabei handelt es sich hauptsächlich um Unzulänglichkeiten, deren Beseitigung Strukturveränderungen gegenüber dem SalesPoint -Framework nach sich ziehen.
Kleinere Probleme, die auf Fehlern oder unzureichenden Spezikationen in SalesPoint
beruhten, wurden von mir während der Implementierung berücksichtigt und sollen an
dieser Stelle nicht extra herausgestellt werden.
In den folgenden Abschnitten werde ich zunächst die beiden vorrangigen und umfangreichsten Anforderungen Persistenz in einer relationalen Datenbank, sowie Anwendungsverteilung für das EPoint -Framework erläutern und kurz diskutieren. Danach gehe ich
umfassender auf die Umsetzung und Beschreibung dieser und weiterer Neuerungen ein.
2.2.1 Reale Anwendung vs. Studienpraktikum
Das SalesPoint -Framework bietet zwar die Möglichkeit Verkaufsanwendungen zu simulieren, allerdings sind die damit erstellten Programme aufgrund der Warteschlangen (in
Version 2.0) an den SalesPoints, der für einen Rechner (aber mehrere SalesPoints) ausgelegten Anwendungsstruktur und anderen Eigenschaften im Normalfall eben nur Simulationen und keine ernsthaft einsetzbaren Programme2 . Daher gab es Forderungen nach
einem Framework, mit dem sich reale Umgebungen nicht nur simulieren lassen. Stattdessen sollen mit ihm Programme erstellt werden können, die im Prinzip in einer realen
Umgebung zur Unterstützung von Arbeitsabläufen eingesetzt werden könnten.
Da das Framework allerdings im Rahmen eines studienbegleitenden Praktikums verwendet werden soll, muss es aber auch über eine einfach zu benutzende Schnittstelle verfügen,
die den Lernenden nicht überfordert. Diese Schnittstelle ist jedoch bereits im SalesPoint Framework so groÿ, dass sie an vielen Stellen unübersichtlich erscheint. Das bedeutet,
dass das neue Framework den Spagat zwischen einer leicht zu verwendenden Schnittstelle
und einer hohen Flexibilität bezüglich seiner Anwendbarkeit für reale Probleme schaen
muss, was aufgrund der oben skizzierten Forderungen nur sehr schwer umzusetzen sein
wird.
2 zumindest
nicht, wenn man sich auf die Verwendung der von SalesPoint zur Verfügung gestellten
Mechanismen beschränkt
2.2. DIE NEUEN ZIELE IM EINZELNEN
11
2.2.2 Persistenz
Das neue Framework soll in der Lage sein, seine Daten in anderer Form persistent zu
machen, als dies bei SalesPoint der Fall war. Die Sicherung des gesamten Zustandes der
Anwendung mit der standardmäÿig in Java implementierten Serialisierung ist zwar ein
einfaches Verfahren, eine laufende Simulation auf einem Rechner zu unterbrechen und
später weiterzuführen allerdings hat dieser Ansatz mehrere Nachteile:
Zum einen bietet er natürlich nur schwer die Möglichkeit, den Zustand eines auf mehreren
Rechnern verteilten Programms zentral zu speichern und diese gespeicherten Daten auch
wiederzuverwenden. Zum anderen kann man aus dem erzeugten Datenstrom nur indirekt
Informationen über den Zustand des Programms gewinnen, geschweige denn den gespeicherten Zustand zum Debuggen oder zur Manipulation für Testzwecke verwenden.
Weiterhin ist es wünschenswert, die Fähigkeit zur Persistenz nicht nur auf die Datenbestände einer Anwendung zu beschränken, sondern diese dahingehend auszuweiten, dass sie
verwendet werden kann, um den gesamten Zustand eines Programms zu erfassen. Dieser
soll dann genutzt werden, um einen solchen früheren Zustand auch dann wiederherzustellen, wenn das Programm unter nicht normalen Umständen terminiert und keine explizite
Sicherung vorgenommen wurde.
2.2.3 Anwendungsverteilung
Die Unterstützung der Anwendungsverteilung durch das Framework ist zum Groÿteil eine
praktische Anforderung. Die entwickelten Programme sollen nicht mehr nur in einer einzigen virtuellen Maschine auf einem Rechner ablaufen und die Umgebung simulieren. Man
fordert schlieÿlich vom Manager eines Unternehmens nicht, dass er denselben Rechner,
den seine Angestellten verwenden, benutzt, um unternehmensrelevante Daten zu manipulieren. Mit dem neuen Framework sollen stattdessen Programme erstellt werden können,
die zusammengehörige Arbeitsabläufe an den Stellen unterstützen, wo sie in der realen
Umgebung gebraucht werden und das auch, wenn sie zusammengehörige bzw. abhängige
Prozesse an unterschiedlichen Orten darstellen.
Um ein Framework zu erstellen, das verteilte Anwendungen unterstützt, muss man sehr
genau entscheiden, an welchen Stellen ein Gesamtprogramm aus der Sicht des Frameworks
(und eben nicht nur der zu erstellenden Applikation) sinnvoll getrennt werden kann. Die
Trennung muss so erfolgen, dass die einzelnen Teile über bereits denierte Schnittstellen
kommunizieren können, um so wieder ein sinnvolles Gesamtprogramm zu ergeben. Auÿerdem muss ein Weg gefunden werden, über den die einzelnen Programmteile möglichst
unkompliziert und universell Daten und Informationen austauschen können.
Ein Vorteil, den die Anwendungsverteilung mit sich bringt ist, dass die einzelnen Programmteile schon von der Frameworkstruktur her getrennt werden und über denierte
Schnittstellen kommunizieren. Da diese Schnittstellen vom Framework vorgegeben und
bereits fertig implementiert sind, können Programmteile bereits zu Beginn einer Entwicklung weitestgehend unabhängig voneinander entwickelt und später auch leichter gewartet,
12
KAPITEL 2. AUFGABENSTELLUNG UND NEUE KONZEPTE
auf Fehler untersucht und wiederverwendet werden. Der Entwicklung umfangreicher Anwendungen durch Gruppen von Programmierern wie dies auch im Praktikum der Fall
ist kommt dieser Tatsache sehr entgegen.
2.3 Verwendung von XML
Die Entscheidung, Extensible Markup Language (XML) im neuen Framework zu verwenden, soll vor allem den Entwicklungsprozess von (mit EPoint erstellten) Programmen
erleichtern. XML hat den Vorteil einer relativ einfachen Struktur und der einfachen Lesbarkeit. Daher soll XML in allen Bereichen eingesetzt werden, in denen es auf diese einfache Lesbarkeit und vor allem auch Editierbarkeit der Daten ankommt.
Dabei bietet sich XML insbesondere für die Persistenz von Objekten an, da die genannten
Eigenschaften der Sprache eine einfache Möglichkeit zur nachträglichen Untersuchung und
Manipulation der gesicherten Objekt-Zustände bieten. In diesem Zusammenhange ist die
breite Verfügbarkeit von vielen fertigen (und in der Regel frei erhältlichen) Werkzeuge,
die den Umgang mit Dokumenten in XML-Form erleichtern, ein weiterer Punkt, der für
eine Verwendung von XML spricht.
Für die Umsetzung im Framework bedeutet dies, dass weg von der normalen JavaSerialisierung zu einer Serialisierung von Objekten in Form von XML übergangen wird.
Das heiÿt, dass sämtliche Objektzustände klar lesbar abgespeichert und unter Beachtung von Konventionen gegebenenfalls auch von Hand geändert werden können. Desweiteren gibt es die Möglichkeit, Logles in XML-Form zu verwenden. Im Vergleich zum
SalesPoint -Framework, in dem sowohl Logles, wie auch Objektzustände mit Hilfe der
normalen Java-Serialisierung gespeichert wurden, ermöglichen diese Änderungen einen
einfacheren Umgang mit den Programmen beim Debuggen und Testen.
In seiner Studienarbeit [SAMP01] beschäftigte sich Mirko Pracht mit dem Thema der
Serialisierung von Java-Objekten nach XML, insbesondere im Zusammenhang mit dem
neuen EPoint -Framework. Das Ergebnis seiner Studienarbeit ist, dass für EPoint ein von
Koala / Dyade entwickeltes Paket [KoDyXML] unter GNU Library General Public License 3 verwendet wird, um Objektzustände zu serialisieren und abzuspeichern.
In der Praxis bedeutet die Verwendung von XML im allgemeinen jedoch auch einen
Ezienz-Verlust, da XML ein höheres zu bewältigendes Datenaufkommen, sowie mehr
Aufwand bei Serialisierung und Deserialisierung bedeutet. Daher wird im Framework
letztlich die Möglichkeit geboten, auch jeden anderen Serialisierungsalgorithmus zu verwenden. Dabei kann z.B. XML problemlos wieder durch die normale Java-Serialisierung
ersetzt werden. Das macht Sinn, wenn das Programm fertig entwickelt ist und es mehr auf
Geschwindigkeit und Ezienz im Einsatz ankommt, als auf einfache Test- und DebuggingMöglichkeiten.
In seiner Studienarbeit erörtert Herr Pracht die Möglichkeiten verschiedener Arten der Serialisierung und wie die letztlich verwendete XML-Serialisierung im Framework eingesetzt
3 siehe
[GPL] im Literaturverzeichnis
2.4. VERWENDUNG EINER RELATIONALEN DATENBANK
13
wird. Auÿerdem erklärt er die Struktur des erzeugten XML und unter welchen Bedingungen die beschriebenen Objektzustände in der serialisierten Fassung editiert werden dürfen.
Wichtig und für den Programmierer unverzichtbar ist der Abschnitt über die Eigenschaften und Verwendung des Java-eigenen Serialisierungsframeworks im Allgemeinen4 , da die
verwendete XML-Serialisierung direkt darauf aufbaut.
2.4 Verwendung einer relationalen Datenbank
Um echte Persistenz von Objekten und deren Manipulierbarkeit sicherzustellen, entschieden wir uns für die Verwendung einer Datenbank. Diese bietet vor allem die Vorteile
von Transaktionssicherheit und parallelem Zugri auf gespeicherte Daten. Transaktionen
stellen dabei sicher, dass (vorausgesetzt sie werden konsequent verwendet) in der Datenbank stets der zuletzt bestätigte Zustand der Anwendung gesichert ist. Die in diesem
Zusammenhang wichtigen Eigenschaften von Transaktionen sind unter dem Begri ACID
bekannt. Dabei werden an eine Datenbank folgende Forderungen gestellt:
A tomicity: fordert, dass Transaktionen als Zusammenfassung vieler einzelner Aktionen
nur insgesamt oder gar nicht als eine einzige Aktion ablaufen.
C onsistency: fordert, dass nach Abschluss einer Transaktion ein konsistenter Zustand
bezüglich aller Regeln und Denitionen in der Datenbank vorliegt.
I solation: fordert, dass Transaktionen auch bei paralleler Ausführung zu anderen Transaktionen, denselben Eekt erzielen, als wären sie allein mit der Manipulation der
Daten beschäftigt.
D urability: fordert, dass der Eekt von Transaktionen, sofern sie einmal komplett ausge-
führt und bestätigt wurden, in jedem Fall (auch bei Systemabsturz) erhalten bleibt.
Durch die richtige Verwendung einer relationalen Datenbank mit diesen Eigenschaften
kann also sichergestellt werden, dass stets ein korrekter, der (noch anzugebenden) Zustandsdenition entsprechender Datenbestand gespeichert ist.
Die Datenbank ist direkt an ein lauffähiges Server-Programm, das in unserem Fall der
Shop ist, gekoppelt. Damit benötigt der Shop Zugri auf alle Daten der gesamten Anwendung, die in die Datenbank gespeichert werden sollen und bleibt somit auch in EPoint
zentraler Bestandteil einer Gesamtapplikation. Das bedeutet aber nicht, dass auch der
Datenbankserver auf demselben Rechner wie der Shop installiert sein muss. Eine Netzwerkverbindung zwischen Shop und Datenbank reicht dazu aus.
4 Kapitel
4, Seite 13 in [SAMP01]
14
KAPITEL 2. AUFGABENSTELLUNG UND NEUE KONZEPTE
Normalerweise bietet sich für die Persistenz von Objekten eine objektorientierte Datenbank an. Denn nur damit lieÿe sich (ohne viel Aufwand) gewährleisten, dass alle Objektzustände stets vollständig persistent und der gesicherte Gesamtzustand ein konsistenter
Anwendungszustand ist. Allerdings stand uns kein frei erhältliches und vor allem ausgereiftes OO-DBMS zur Benutzung mit Java zur Verfügung.
Aufgrund dieser Tatsache verwendet der Shop in meiner Implementierung einen Persistenzmanager auf Basis einer relationalen Datenbank. Relationale Datenbanken sind in
groÿer Zahl (und zum Teil frei) erhältlich. Auÿerdem erlaubt die Verwendung einer relationalen Datenbank gleichfalls die Verwendung von JDBC. Das ist eine von SUN entworfene und inzwischen sehr ausgereifte Schnittstellenspezikation zum Zugri auf solche
Datenbanken. Praktisch setzt die Implementierung des Framework-Persistenzmanagers
(DBPersistenceManager S. 263) auf die JDBC Version 3.0 auf und ist für die Verwendung mit einer PostGreSQL [PGSQL] Datenbank vorkonguriert (PSQLDBConnection auf
Seite 271).
PostGreSQL ist ein frei erhältliches und seit langem ständig weiterentwickeltes Datenbank
Management System (DBMS). Es wird bereits in vielen (auch professionellen) Bereichen
eingesetzt und besitzt einige objekt-relationale Eigenschaften, die zum Groÿteil jedoch
kaum für das Framework verwendet werden können. Auÿerdem würde deren Nutzung
Inkompatibilität zu anderen DBMS mit sich bringen, da diese Eigenschaften in keinem
Standard-SQL deniert sind. Diese objekt-relationalen Eigenschaften erlauben in PostGreSQL z.B. die Denition von Tabellen durch Beerbung der Tabellenstruktur anderer
Tabellen, oder auch die Denition einer Tabelle durch Angabe eines Objekts, dass in dem
zu erstellenden Tabellentyp gespeichert werden soll. Im EPoint -Framework muss die Datenbankstruktur aber bereits fest im Framework implementiert sein und eine Anpassung
der Datenbank, an die tatsächlich verwendeten Objekte, ist durch die Kapselung des Zugris auf die Datenbank auch nicht möglich.
Da das verwendete PostGreSQL-DBMS über eine eigene mitgelieferte JDBC-Schnittstelle
verfügt und der Persistenzmanager vollständig auf diese aufgesetzt, kann dieses DBMS
durch jedes andere mit JDBC-Schnittstelle, sogar unter Weiterverwendung des bisherigen
Persistenzmanagers, ersetzt werden.
2.4.1 Transaktionen
Man muss aus Sicht des EPoint-Frameworks zunächst zwischen zwei verschiedenen Arten
von Transaktionen unterscheiden. Das sind zum einen die Datenbanktransaktionen. Diese
wurden bereits im vorangegangenen Abschnitt angesprochen und dienen im Zusammenhang mit der Datenbank zur Persistenzhaltung. Diese Datenbanktransaktionen sind an
eine (Netzwerk-)Datenbankverbindung gebunden und sollten relativ kurz sein, um andere
Datenbanktransaktionen nicht zu stören.
Zum zweiten existieren die Datenkorbtransaktionen, die in der objektorientierten Schicht
zwischen den Datenbehältern (Katalog und Bestand) durchgeführt werden. Die Verwendung dieser Transaktionen wird durch den Programmierer deniert und hat Einuÿ auf
2.4. VERWENDUNG EINER RELATIONALEN DATENBANK
15
den konsistenten Datenbestand der Gesamtanwendung. Diese Art von Transaktion kann
durchaus länger andauern und sogar durch eine Beendigung des Programms unterbrochen
werden. In diesem Fall ist es problemlos möglich, sie zu einem späteren Zeitpunkt fortzusetzen. Vor allem aus diesem Grund ist es nicht sinnvoll, eine Datenkorbtransaktion von
Beginn an durch eine einzige Datenbanktransaktion umzusetzen wie dies bereits vom
Entwickler S. Zschaler des SalesPoint -Framework in seiner Belegarbeit [GBLG00] überlegt wurde. Da auÿerdem in der Regel sehr viele solcher Transaktionen parallel auftreten,
würden sich diese gegenseitig blockieren und Deadlocks provozieren.
Stattdessen wird also die Datenkorbtransaktion auf objektorientierter Ebene selbst in Zustände unterteilt. Diese werden unter Verwendung von kurzen Datenbanktransaktionen
in der Datenbank gespeichert.
Eine Trennung zwischen diesen beiden Arten von Transaktionen war im SalesPoint Framework nicht nötig. Dort gab es nur die Datenkorbtransaktion. Da ein Programm
in SalesPoint insgesamt persistent gemacht wurde, war auch die explizite Speicherung
von Transaktionszuständen unnötig.
Das bedeutet für EPoint, dass es Transaktionen in der OO-Schicht gibt, deren Verlauf
und Zustand unter Verwendung mehrerer Datenbanktransaktionen aufgezeichnet werden.
Eine aufgezeichnete Transaktion wird dann später anhand dieser Daten innerhalb einer
einzigen Datenbanktransaktion zügig ausgeführt. Diese letzte Datenbanktransaktion enthält dann sowohl die Manipulation der durch die OO-Transaktion manipulierten Daten,
als auch die vom Framework zur Aufzeichnung der Transaktion gespeicherten Daten.
Diese Trennung ermöglicht nicht nur eine feinere Granularität (und damit kürzere Dauer) der Datenbanktransaktionen, sowie persistente und lang andauernde Transaktionen
auf persistenten Datenbeständen. Daraus folgt auch eine bessere Gesamtleistung, da sich
die Datenbanktransaktionen auf kleinere Datenbankbereiche beschränken und Kollisionen
vermieden werden.
2.4.2 Allgemeines Persistenzmodell
Dieser Abschnitt soll einen allgemeinen Überblick darüber geben, wie die Datenbank verwendet wird, um die Objekte zu speichern, die den Anwendungszustand charakterisieren.
Im Anschluss daran gehe ich im nächsten Abschnitt detaillierter auf die speziellen Vorgehensweisen für einzelne Datenklassen ein.
In EPoint werden Daten nach Bedarf on-demand abgespeichert. Der Shop bedient sich
dazu des Persistenzmanagers (DBPersistenceManager, S. 263), um Objekte in der Datenbank zu speichern. Die einzelnen persistenten Objekte benutzen ebenfalls diesen Manager,
um ihn über ihre eigenen Zustandsänderungen zu informieren. Dieser kann dann die gespeicherten Versionen der Objekte aktualisieren.
Meine Implementierung des Frameworks stützt sich also bei der Speicherung von Objektzuständen nicht unmittelbar auf Direktzugrie auf die Datenbank ab. Stattdessen
verwende ich den Persistenzmanager für den Übergang zwischen der objektorientierten
Welt und der relationalen Welt der Datenbank. Die Methoden in der Schnittstelle dieses
16
KAPITEL 2. AUFGABENSTELLUNG UND NEUE KONZEPTE
Managers sind speziell für die Verwendung mit den im Framework denierten Datenklassen (Catalog, CatalogItem, DataBasket, ...) konzipiert. Dazu stellen sie für die Objekte
Möglichkeiten zur erstmaligen Speicherung, Aktualisierung, zum Auslesen und Löschen
zur Verfügung.
Damit hat der DBPersistenceManager 5 im Wesentlichen die Eigenschaften einer Schnittstelle zu einer objektorientierten Datenbank, die speziell für die Framework-Klassen ausgelegt ist. Diese kann aber auch genausogut für eine relationale Datenbank implementiert
werden so wie dies in EPoint der Fall ist. Die schematische Arbeitsweise des Persistenzmanagers kann man in Bild 2.1 betrachten. Darin greifen die Datenklassen (im Beispiel
Catalog und Stock) auf die Methoden des Managers zu, um Daten zur Speicherung und
/ oder Aktualisierung zu übergeben. Der Persistenzmanager speichert die entsprechenden
Daten oder liest sie unter Verwendung der Datenbank.
Java Virtuelle Maschine
!"$#&%(' )* ),+ %-./%(021-$1434%('
OLPRQ P SUTWV V V&XRYRZ([\V V V
]7^R_` T PV V VLa bV V V ced=PRYP/V V V
a b=O&PRY,TWa bT ZfV V V g ` Q ] P O4V V V
;=< >?=@ >A >=< B C B @ >=D&@ E4FG >;@
HI=JK?@ >$A >K< B C B @ >=DL@ E4FG >=;7@
JK>=M >7@ >$A >K< B C B @ >KD&@ E4FG >=;7@
N=>@ A >K< B C B @ >KD&@ E4FG >=;7@
576 89:
Abbildung 2.1: Persistenz mit Hilfe des DBPersistenceManager
Das Bild zeigt die Arbeitsweise des DBPersistenceManager, der Methoden für die Datenklassen zur Verfügung stellt. Die Klassen verwenden diese Methoden, um Daten zu speichern und/oder zu aktualisieren. Der Normalfall ist dann der, dass der Manager eine Datenbank verwendet, um die übergebenen Daten zu speichern bzw. zu lesen.
Die Schnittstelle DBPersistenceManager kann aber selbstverständlich auch völlig anders
implementiert werden und somit für spezielle Anwendungen wesentlich ezienter genutzt
werden! Denkbar wäre z.B. eine spätere objektorientierte Datenbank oder sogar gar keine
Datenbank, sondern nur eine Simulation von Persistenz zu Testzwecken. Dabei werden
die Objekte in Hashtabellen aufbewahrt, die bei jedem Zugri auf den Persistenzmanager
inspiziert werden können. Dieser Ansatz wird bereits umgesetzt (s. ReferenceCache,
Seite 307), dient aber parallel zur Speicherung in der Datenbank, zur Beschleunigung
des Zugris als Cache und vor allem zur Vermeidung ungewollter Klon-Objekte. Diese
können beim mehrfachen Lesen aus der Datenbank entstehen. Darauf wird jedoch in
einem späteren Kapitel zum Thema Implementierung noch genauer eingegangen.
Der beschriebene Mechanismus wird von allen Datenklassen im Framework bereits
automatisch verwendet. Da in der Regel jede Änderung von Attributen eine
5 der
Quellcode zu diesem Interface bendet sich auf Seite 263
2.4. VERWENDUNG EINER RELATIONALEN DATENBANK
17
Zustandsänderung des Objekts herbeiführt, wird in den Methoden, die diese Attribute
verwenden, jeweils auch eine Nachricht an den Persistenzmanager gesendet.
public void setName(String sName) throws RemoteException {
synchronized (getPropertyLock()) {
}
}
// ... (einige andere Integritaetstests)
this.sName = sName; // an dieser Stelle wird ein Attribut (der
Name eines Katalogs) veraendert
String sUpdatePasswd = getClass()+".updatePersistentObject";
// dieses Passwort schuetzt mehrere Threads davor,
gleichzeitig auf dieselbe Datenbankverbindung
zuzugreifen
updatePersistentObject(Arrays.asList(new Object[]{
PERSISTENT_PROPERTY_CATALOGNAME}),sUpdatePasswd); //
hier wird dann der Persistenzmanager ueber die
Aenderung der Property
PERSISTENT_PROPERTY_CATALOGNAME informiert, womit er
die Chance erhaelt, diese Aenderung auch in der
Datenbank zu aktualisieren
// ... (weitere Schritte, wie Listener informieren...)
Listing 2.1: Benachrichtigung des Persistenzmanagers bei Attributänderungen
Wie dieser Mechanismus einfach verwendet wird, kann man im dokumentierten Codeausschnitt 2.1 sehr gut sehen. Die dort verwendete Methode updatePersistentObject wird
im Interface PersistentObject (S. 138) deniert. Dieses Interface muss von allen Klassen implementiert werden, deren Objekte durch den Persistenzmanager verwaltet werden
sollen.
Die Verwendung einer relationalen Datenbank bedeutet für eine objektorientierte Sprache
wie Java, dass man theoretisch alle Datenstrukturen unwiderruich fest denieren müÿte
und keine Erweiterungen zulassen dürfte. Nur so könnten atomare Daten/Attribute 1:1
von Objekten auf die Attribute in den Tabellen einer relationalen Datenbank übertragen
werden. Allerdings stünde dies in einem starken Widerspruch zum Konzept der Weiterverwendung in objektorientierten Sprachen. Denn dabei soll ja gerade das Klassenkonzept
eine leichte Erweiterbarkeit auf spezielle Probleme zulassen.
Als Alternative habe ich mich daher für folgendes Modell entschieden: Alle bekannten
signikanten Attribute einer Klasse von Objekten, werden wie eben beschrieben in die
Tabellenstruktur der Datenbank geschrieben. (Diese Attribute bleiben dann natürlich
auch für Unterklassen signikant.) Zusätzlich wird eine komplett serialisierte Version des
Objekts in der Datenbank gespeichert. Um Redundanz zwischen den darin serialisierten
Attributen und den explizit in die Tabelle geschriebenen Attributen zu vermeiden, werden
18
KAPITEL 2. AUFGABENSTELLUNG UND NEUE KONZEPTE
diese explizit gespeicherten Attribute in der Klasse als transient markiert.
Diese Vorgehensweise hat mehrere Vorteile: Zum einen können die explizit gespeicherten
Attribute wesentlich ezienter und übersichtlicher eingesehen und bearbeitet werden, als
dies in der serialisierten Fassung möglich ist. Zum anderen stellt die serialisierte Fassung
sicher, dass alle nachträglich durch den Programmierer hinzugefügten Attribute ebenfalls
gesichert und korrekt wieder hergestellt werden können.
Als Beispiel sei dazu auf die Datenbanktabelle A.1 (Catalogs ) im Anhang verwiesen. Diese
speichert alle Catalog-Objekte aus der Anwendung. In der Tabelle wird nur der Name
des Katalogs als Attribut der Klasse erfasst. Denkbar wäre aber auch, dass sich ein Programmierer entscheidet, die Klasse epoint.data.rmi.CatalogImpl zu beerben und ein
mir unbekanntes String-Attribut sDescription, sowie jeweils eine get und set-Methode
seiner Klasse hinzuzufügen. Damit wäre der Zustand eines Katalogs um eine Beschreibung
erweitert, die ich beim Datenbankentwurf in meiner Tabellenstruktur nicht berücksichtigen konnte. Da jedoch immer auch eine serialisierte Version des Katalogs gespeichert wird,
ist auch diese Zustandserweiterung in der persistenten Fassung des Objekts erfasst. Diese
serialisierte Fassung enthält nun als einziges Attribut sDescription, da sName ja explizit
in der Tabelle gespeichert wird und von mir in der beerbten Klasse als transient markiert
ist. Es liegt nun aber auch in der Verantwortung des Programmierers, den Persistenzmanager über eventuelle Zustandsänderungen in der set-Methode zu informieren. In diesem
Fall muss er analog zu Listing 2.1 die Methode updatePersistentObject mit der geänderten Property-Bezeichnung (epoint.data.PersistentObject.PERSISTENT_PROPERTY_SERIALIZED) aufrufen.
CatName
Test-Catalog
Catalogs
Serialized
<classes>
<class name='myapplication.TestCatalog'>
<eld name='sDescription' type='java.lang.String'/>
</class>
</classes>
<object class='myapplication.TestCatalog'>
<value type='java.lang.String' name='sDescription'>
Dieses Attribut beinhaltet die Beschreibung!
</value>
</object>
Tabelle 2.1: Beispiel-Katalog in der Datenbanktabelle
Ein weiterer Vorteil ist, dass die Datenbankstruktur auf diese Weise insgesamt kompatibel zu allen nachträglichen Erweiterungen der Datenklassen bleibt, bei denen Attribute
neu hinzukommen. Dadurch wird der Programmierer dahingehend entlastet, dass er sich
eigentlich nie um die Erweiterung der Datenbankstruktur kümmern muss.
Durch die Verwendung von XML bei der Serialisierung (wie dies zuvor in Abschnitt 2.3
beschrieben wurde) hat man zusätzlich die Möglichkeit, auch den serialisierten Objektzustand lesbar darzustellen. Nachträglich eingeführte Attribute in den Objekten können
2.4. VERWENDUNG EINER RELATIONALEN DATENBANK
19
so ebenfalls einfach überprüft und manipuliert werden. Ein Katalog mit dem Namen
Test-Catalog, der in der zuvor beschriebenen Weise benutzt wird, sieht dann in der Datenbanktabelle z.B. so wie in Tabelle 2.1 aus6 .
Der Nachteil dieser Methode liegt darin, dass eine häuge Serialisierung und Deserialisierung einen Performance-Verlust bringt. Diesen kann man mit der Option der Neuimplementierung des Persistenzmanagers und somit einer expliziten Speicherung aller wesentlichen, neu hinzugekommenen Datenfelder ggf. verringern. Dies geschieht dann allerdings
unter Verlust der Datenbankkompatibilität zu anderen Anwendungen. Allerdings ist gerade die spezielle Implementierung des Persistenzmanagers für eine spezielle Anwendung/Datenbank sicher eine sehr gute Möglichkeit, um Ezienzsteigerungen zu erzielen.
2.4.3 Persistenz wichtiger Frameworkklassen
In diesem Abschnitt möchte ich aufbauend auf dem vorangegangenen erörtern, welche Daten im Speziellen persistent in der Datenbank gehalten werden. Da man immer nur einen
Momentanzustand in der Datenbank festhalten kann, muss man sich, um den Verwaltungsaufwand zu minimieren, auf wesentliche Zustandsinformationen beschränken. Diese
Zustandsinformationen sollen aber vor allem die Möglichkeit einer Wiederherstellung nach
einem Programmabsturz bieten.
Man benötigt also ein Modell, dass angibt, welche Zustände von Objekten in der Datenbank zu jedem Zeitpunkt erfaÿt sein müssen, um daraus auch in umgekehrter Richtung
einen kompletten Anwendungszustand ableiten zu können. Wie Objekte und ihre Attribute allgemein auf eine relationale Datenbank abgebildet werden, habe ich vorigen Abschnitt
bereits beschrieben.
Im Weiteren benötigt man eine Denition der verwendeten Datenbankstruktur für den
Persistenzmanager und das eben genannte Zustandsmodell, mit dem man entscheiden
kann, wann Objekte in der Datenbank gespeichert werden müssen. Zur Datenbankstruktur bendet sich im Anhang A eine explizite Aufstellung aller benutzten Tabellen und
Views der verwendeten Datenbank. Zusätzlich ndet man dort eine kurze Einführung
zum Verständnis relationaler Datenbanken, sowie eine sehr ausführliche Beschreibung der
einzelnen Tabellen und Attribute.
Zur Erklärung, wann welche Objekte in die Datenbank gespeichert werden, dienen die
folgenden Unterabschnitte.
2.4.3.1 Persistenz der Datenklassen
Zu den wichtigsten Zustandsdaten, die in der Datenbank gespeichert werden müssen,
gehören die der frameworkeigenen Datenklassen. Das sind insbesondere Kataloge, Katalogeinträge, Bestände, Bestandseinträge, Datenkörbe und deren Einträge.
6 Das
real erzeugte XML hat eine etwas andere und umfangreichere Struktur
20
KAPITEL 2. AUFGABENSTELLUNG UND NEUE KONZEPTE
Für jede dieser Objektklassen existiert in der Datenbank eine Tabelle, in der jeder Datensatz (Zeile in der Tabelle) ein Objekt in der Gesamtanwendung wiederspiegelt. Dabei
werden Attribute, die aufgrund der Implementierung eines speziellen Interfaces benötigt werden, (falls möglich) in einer eigenen Tabelle gespeichert. Diese enthält dann nur
diese Interface-spezischen Attribute, die durch Referenzierung in den anderen Tabellen
verwendet werden. Die Tabelle TransactionItems (S. 86) enthält z.B. die Daten, die aufgrund der Implementierung von TransactionItem benötigt werden. Da StockItem und
CatalogItem das gleichnamige Interface TransactionItem implementieren, werden die
Daten aus der Tabelle TransactionItems auch von den beiden Tabellen StockItems (S. 84)
und CatalogItems (S. 82) referenziert.
Referenzen zwischen Objekten auf der objektorientierten Ebene, sind in der Datenbank
durch entsprechende Schlüssel/Attribut-Referenzen abgebildet. Daher benötigt man hier
eine Menge von eindeutigen Attributen (Schlüssel) für jedes Objekt, durch die das jeweilige persistente Objekt in seiner Tabelle identiziert werden kann. Ein CatalogItem, das in
der virtuellen Maschine beispielsweise eine Referenz auf seinen Catalog hält, referenziert
in der Datenbank (Seite 82) seinen Catalog entsprechend durch einen Fremdschlüssel in
der Tabelle Catalogs (Seite 81). Der umfasst in diesem Fall nur den Namen des Catalog 7 . Ein eindeutiger Name für einen Katalog ist also nicht nur innerhalb der Anwendung
wichtig, um die Kataloge voneinander unterscheiden zu können, sondern auch für die eindeutige Identikation des persistenten Katalogs in der Datenbank. Vor allem wird er aber
im Rahmen von Schlüsselbeziehungen zur Abbildung von Objektreferenzen benötigt. An
dieser kurzen Ausführung kann man bereits die Zusammenhänge zwischen der Speicherung von Objekten in einer relationalen Datenbank und den damit zusammenhängenden
Forderungen nach z.B. eindeutigen Bezeichnern für Objekte (wie eindeutige Namen für
Kataloge) erkennen.
Durch entsprechende Regeldenitionen der referentiellen Integrität in der Datenbank ist
sichergestellt, dass solche Attribut-Referenzen stets aktuell und eindeutig sind. Ändert sich
beispielsweise der Name eines Katalogs in der Catalogs -Tabelle, so ändern sich aufgrund
einer (bei der Tabellendenition angegebenen) Regel8 automatisch auch die Fremdschlüsselreferenzen in der Tabelle CatItems für alle in diesem Katalog enthaltenen Katalogeinträge.
Zusätzlich wird, wie bereits erwähnt, jeweils eine serialisierte Fassung des Objekts gespeichert um exibel für Klassenextensionen durch den Programmierer zu bleiben. Mit dem
jetzigen Kenntnisstand bekommt aber die Markierung von Attributen mit dem Schlüsselwort transient eine weitere Bedeutung:
Logisch zusammenhängende Objekte, wie dies vor allem bei Containern und deren Elementen der Falls ist (z.B. Catalog und CatalogItem), werden zum Teil in unterschiedliche
Tabellen einer Datenbank gespeichert. Dabei werden die objektorientierten Referenzen in
7 In
einigen anderen Fällen (z.B. StockItems mit Referenz auf ihr associated item) sind komplexere
Fremdschlüssel notwendig!
8 Diese Regeln der referentiellen Integrität und Fremdschlüsselbedingungen sind ebenfalls im Anhang A
bei den einzelnen Tabellen in expliziter Form (als SQL-Befehl) und auch durch entsprechende Darstellung
in den Tabellen nachzulesen.
2.4. VERWENDUNG EINER RELATIONALEN DATENBANK
21
der Datenbank in der schon beschriebenen Weise durch Attribut-Referenzen dargestellt. Bei der Serialisierung eines Objekts werden aber auch alle nicht-transienten Attribute (die ja Referenzen auf Objekte darstellen) mitserialisiert. Für einen Katalog, der seine
Katalogeinträge nicht transient referenziert, würde das bedeuten, dass alle CatalogItems
auch in der serialisierten Fassung des Catalog in die Datenbank gespeichert werden. Der
Unterschied zwischen transienter und nicht-transienter Referenzierung am Beispiel eines
Katalogs, wird in Abb. 2.2 deutlich. Die rechts in der Grak verdeutlichte redundante Speicherung von Objekten ist nicht nur sinnlos, sondern bringt auch Probleme bei
der eindeutigen Zuordnung von persistenten Objekten zu instanziierten Objekten in der
virtuellen Maschine. Auÿerdem würde eine konsequente Fortsetzung deutlich mehr Aufwand bei Änderungen des Zustands eines einzigen CatalogItem mit sich bringen. Dieser
Sachverhalt spielt auch bei der Referenzierung von Framework-Objekten, durch vom Programmierer denierte Objekte, eine wichtige Rolle. Aber darauf wird etwas weiter unten
noch gesondert eingegangen.
! " # $ %& ')( ! " +*,! ! " - . /
Catalog
Y Z Z [ \ ] ^ Z _ ` a [ \ b c \ d a e f&\ g)h b ] a c a+i,b Z b c j k l
CatalogItem
º » ¼)½ ¾ » ¿
CatalogItem
Catalog
À ÁÂ à Ä)Å
CatalogItem
CatalogItem
CatalogItem
CatalogItem
Catalog
CatalogItem
CatalogItem
D E E F G H I E J K L F G M N G O L P&Q G R)S M H L N LUT,M E V E L W+X
0 1 1 2 3 4 5 1 6 7 8 2 3 9 : 3 ; 8 < =&3 >)? 9 4 8 : 8+@,9 1 A 1 8 B+C
m n o+p,q r q s t u v r o w+x,y z {U{ o | p,q r q s t u
– — ˜+™,š › š œ  ž Ÿ › ˜ +¡,¢ £ ¤U¤ ˜ ¥ ™,š › š œ  ž
CatalogItem
Catalog
CatalogItem
} o | { o z~x o | n q s n x n o | r y z {U{ qU{ n o
¦ ˜ ¥ ¤ ˜ £~¡ ˜ ¥ — š œ — ¡ — ˜ ¥ › ¢ £ ¤U¤ šU¤ — ˜
 o € o | o z  o z‚ } n x ƒ „ o z…n „ z o z‚r | q z x n o z r
§ ˜ ¨ ˜ ¥ ˜ £ © ˜ £…ª Ÿ ™)« ¬&› ¥ š £ ¡ — ˜ £ › ¡ — £ ¤ ­
x
n
z
{
†
n
x
r
{
o
|
x
o
|
n
q
s
n
x
n
o
|
r
U
o
,
p
q
r
q
s
t
‡
u
z
n
ƒ
„
r
ˆ
o
n
s
—
¡
›
¤
˜
¥
¡
˜
¥
—
š
œ
—
¡
—
˜
¥
›
U
˜
,
™
š
›
š
œ

U
ž
¬
˜
—
œ
š
œ
œ
˜
¥
Catalog
CatalogItem
CatalogItem
{ o |‚x o | n q s n x n o | r o z+‰ q x x y z u{ o | p,q r q s t u v r o w
¡ ˜ ¥ — š œ — ¡ — ˜ ¥ › ˜ £…™,š › š œ  ž Ÿ › ˜ Ž¢ £ ¤
y z {y w+u o Š o „ | r ‹
¢ +ž ˜ ® ˜ ¯ ¥ › °
Catalog
CatalogItem
CatalogItem
m n o‚Œ&x x t  n q r n t z~ } n x ƒ „ o z…{ o z
– — ˜‚±&¡ ¡  © — š › —  £~© ¦ — ¡ ² ¯ ˜ £…¤ ˜ £
pq r q s t u v r o wŽy z {U{ o wp,q r q s t u } n | { ‘ o | ™,š › š œ  ž Ÿ › ˜ Ž¢ £ ¤¤ ˜ ³™,š › š œ  ž ¦ — ¥ ¤ CatalogItem
CatalogItem
Catalog
{ q x Œ r r | n ‘ y r ’ p,q r “ q w+o ”n z~‘ o n { o z‚ˆ q ‘ o s o z © ¢ ¡ ´ › © œ — ² ¯+µ ¶ ˜ ¥ ¤ š ¡ ± › › ¥ — ¶ ¢ › · ™,š › ª š +˜ ¸
„ o |u o x ro s r •
— £~¶ ˜ — ¤ ˜ £‚¬ š ¶ ˜ œ ˜ £+¯ ˜ ¥ ž ˜ ¡ › ˜ œ › ¹
Abbildung 2.2: Serialisierung von transienten und nicht-transienten Attributen in die Datenbank
Die Abbildung zeigt, wie persistente Container und Elemente der Container (im Bild
Catalog und CatalogItem) gespeichert werden, wenn man Felder entweder als transient oder
als nicht-transient markiert. Objekte, die durch einen Datensatz in einer Tabelle repräsentiert werden, müssen in anderen Klassen als transiente Attribute gekennzeichnet werden.
Dadurch werden Daten-Redundanzen und mehrdeutige Exemplare eines Objektes in der
Datenbank verhindert.
Für spezielle Datenklassen gilt ansonsten folgendes: Alle Datenbehälter (Kataloge, Bestände, Datenkörbe) in der objektorientierten Schicht sind ab Erzeugung (durch den
Konstruktor) automatisch persistent in der Datenbank. Wird der Katalog nicht mehr
benötigt, kann er einfach vergessen werden und die Garbage Collection von Java beseitigt das Objekt dann automatisch. Allerdings existiert die persistente Fassung des Objekts
auch weiterhin und muss bei Bedarf explizit gelöscht werden.
22
KAPITEL 2. AUFGABENSTELLUNG UND NEUE KONZEPTE
Für die Elemente der Container (Katalog-, Bestands- und Datenkorbeinträge) gilt, dass
diese nur persistent sind, sofern sie einem der folgenden Zustände zugeordnet werden
können:
• Das Element ist im Rahmen einer schon beendeten Datenkorbtransaktion zu einem
persistenten Container hinzugefügt worden.
• Das Element wird gerade im Rahmen einer noch nicht bestätigten Datenkorbtransaktion zu einem persistenten Container hinzugefügt.
• Das Element wird gerade im Rahmen einer noch nicht bestätigten Datenkorbtransaktion aus einem persistenten Container entfernt.
Eine genauere Zustandsdenition kann man im Diagramm in Bild 4.2 auf Seite 71 betrachten. Dort werden die Zustände der Elemente in Zusammenhang mit den Operationen, die
diesen Zustand verändern, gezeigt. Der gestrichelte Rahmen kennzeichnet dabei alle Zustände, die persistent in der Datenbank sind.
Die Verantwortung für die Persistenz der Container-Elemente liegt dabei vollständig bei
den Datenbehältern. Will man beispielsweise trotzdem einen nicht persistenten Katalogeintrag auÿerhalb einer Transaktion und unabhängig von einem Katalog sichern, so muss
dieser Katalogeintrag explizit, über zur Verfügung gestellte Mechanismen (z.B. in Form
einer Property) persistent gemacht werden. Dasselbe gilt im übrigen auch für alle selbst
denierten Objekte des Programmierers, die in der Datenbank gesichert werden sollen.
Dazu ist in jedem Fall die entsprechende JavaDoc-Dokumentation zu konsultieren, welche der Framework-Objekte über transiente Attribute verfügen die bei einer expliziten
Speicherung durch reine Serialisierung verloren gehen! Im Fall des CatalogItem wären das
vor allem die folgenden: Value, Key und TransactionID. Diese müssen dann explizit gespeichert werden.
Für die unterschiedlichen Fälle, in denen Objekte persistent gemacht werden, gilt das
Folgende:
• Persistente Objektzustände der Datenklassen werden (falls notwendig) durch meine Implementierung immer automatisch aktualisiert. Sollte sich also z.B. der Wert
(Value ) eines editierten Katalogeintrags innerhalb(!) einer Transaktion ändern, so
ist ein explizites Aktualisieren des gespeicherten Zustands in der Datenbank durch
den Programmierer nicht nötig, sondern wird durch das CatalogItem selbständig
gehandhabt.
• Der erste Punkt gilt nicht für neu hinzugefügte Methoden, die hinzugekommene
persistente Attribute des Datenobjekts manipulieren. Die Handhabung solcher Attribute wurde bereits am Beispiel für einen Katalog mit einem zusätzlichen, beschreibenden Attribut in Kapitel 2.4.2 erklärt.
2.4. VERWENDUNG EINER RELATIONALEN DATENBANK
23
• Auf die Handhabung völlig neuer, eigener persistenter Objekte des Programmierers, sowie die besondere Referenzierung bereits persistenter Objekte, wird in einem
späteren Kapitel bei den Anleitungen für Programmierer eingegangen.
2.4.3.2 Verwaltungsinformationen
Zu den Verwaltungsinformationen gehören vor allem Daten über angemeldete EPoints,
die anhand ihres Namens, sowie des Ortes im Netzwerk9 identiziert werden, von dem
aus sie sich anmelden. In diesen Daten werden auch Informationen über den Zustand des
angemeldeten EPoints erfasst und können bei Bedarf zur Wiederherstellung eines alten
Zustandes genutzt werden. Dazu wird eine eigene Schnittstelle InformationStore (Seite
126) für den Programmierer zur Verfügung gestellt. Nähere Ausführungen zu den Besonderheiten des InformationStore ndet man im übernächsten Abschnitt dieses Kapitels,
zum Thema Komplexe persistente Zustandsinformationen.
2.4.3.3 Zusätzliche Informationen
Zu den zusätzlichen in der Datenbank gespeicherten Informationen gehören sogenannte
Properties. Das sind atomare Werte des Datentyps String10 , die zusammen mit einem
Schlüssel in die Datenbank gespeichert werden können. Dazu gehören vor allem auch
frameworkinterne Informationen zum Generieren von global-eindeutigen Bezeichnern, die
auch das Beenden einer Applikation überstehen müssen, um bei einem Neustart zur Verfügung zu stehen. Das kann z.B. eine Zahl sein, die im Framework als Präx verwendet
wird, um eindeutige Bezeichner zu erzeugen. Diese Zahl wird als Property in der Datenbank gespeichert und bei jeder Abfrage um 1 erhöht.
Weitere persistente Daten, die zu den zusätzlichen Informationen gezählt werden, sind
Log-Einträge die von Shop und EPoint generiert werden und in der Datenbank aufgezeichnet werden können. Auÿerdem können vom Programmierer neue Klassen deniert
werden, die im Prinzip besondere Properties darstellen. Diese Klassen implementieren das
Interface epoint.data.PersistentUserObject (welches seinerseits epoint.data.PersistentObject beerbt) und können als solche direkt von den Methoden des DBPersistenceManager bearbeitet werden. Das bedeutet, dass sie (genau wie die anderen persistenten Framework-Objekte) über eigene Persistenz-IDs verfügen, sowie über Methoden,
mit denen ihr persistenter Status aktualisiert werden kann. Im Gegensatz zu den Properties, sind sie also unter einer durch den Persistenzmanager automatisch vergebenen
OID (Object Identier ) verfügbar, anstatt unter einem explizit anzugebenen Schlüssel.
Auÿerdem muss das Objekt nicht explizit in einen String für die Property umgewandelt
werden, sondern wird durch den Persistenzmanager als Objekt verwaltet.
9 s.
Abschnitt 2.5
erlaubt z.B. die Serialisierung ganzer Objekte zu einer String-Property!
10 Das
24
KAPITEL 2. AUFGABENSTELLUNG UND NEUE KONZEPTE
2.5 Anwendungsverteilung mit RMI
Um eine Anwendungsverteilung wie zu Beginn des Kapitels beschrieben zu realisieren, war es zunächst nötig, eine Kommunikationsbasis zu schaen. Die verschiedenen
Möglichkeiten einer solchen Kommunikation wurden von Hr. Stephan Moritz in seiner
Studienarbeit [SASM01] zu diesem Thema untersucht und erläutert.
Java Virtuelle Maschine
*+,
PostGreSQL
Datenbank
Java Virtuelle Maschine
Shop
(Anwendungsserver)
!#"%$ & '()
EPoint
Java Virtuelle Maschine
EPoint
Java Virtuelle Maschine
Shop
(Anwendungsserver)
Abbildung 2.3: Modell von EPoint-Anwendungen
Die Abbildung zeigt den grundsätzlichen Aufbau, einer mit dem EPoint -Framework
erstellten Applikation. Der Shop als zentraler Bestandteil verwaltet alle persistenten Objekte
in einer Datenbank und stellt diese über den RMI-Mechanismus für entfernte EPoints zur
Verfügung. Er ist dabei gleichzeitig in der Lage auch als Server oder Client für andere Shops
aufzutreten.
Das von mir verwendete RMI-Modell wird in seiner Studienarbeit dabei in den Mittelpunkt gestellt und seine mögliche Verwendung im Zusammenhang mit EPoint beschrieben. Remote Method Invocation erlaubt den Aufruf von Methoden auf entfernten, per
Netzzugri erreichbaren, Java-Objekten. Dazu können von einem Client Referenzen auf
sogenannte RemoteObjects in anderen JVMs gehalten werden. Die Objekte, die dabei referenziert werden, müssen durch ein RemoteInterface beschrieben werden, das von der
jeweiligen Klasse implementiert werden muss. Ist dies geschehen, verwendet man den im
Standard JDK enthaltenen Compiler rmic, um diese Klassen zu kompilieren. Dabei werden neue Klassen erzeugt, die in der Lage sind, die im Interface denierten Methoden in
ein Netzwerk für entfernte Aufrufe zu exportieren.
Ich gebe im Folgenden eine kurze Beschreibung der wichtigsten Eigenschaften von RMI
und gehe im Kapitel 3 für den Programmierer genauer auf die Verwendung im Framework
ein.
Die Möglichkeit des Methodenaufrufes über RMI wird benutzt, um die Server-Dienste
des Shops für mehrere EPoints (wie in Abb. 2.3 dargestellt) zur Verfügung zu stellen. Da
2.5. ANWENDUNGSVERTEILUNG MIT RMI
25
sich der Shop um die Persistenz der gemeinsamen Datenbasis kümmern muss, ist er auch
verantwortlich für die Verwaltung des Zugris auf diese Daten. Das bedeutet:
1. Alle Objekte, die mehreren EPoints zur Verfügung stehen sollen, müssen zentral
beim Shop angelegt werden. Objekte der Datenklassen dürfen also nicht im EPoint
instanziiert werden!
2. Allein der Shop verwaltet die persistenten Kopien dieser Objekte in der Datenbank.
3. Alle diese Objekte werden bei Bedarf durch den Shop für alle Clients (EPoints) mit Hilfe von RMI zur Verfügung gestellt.
Durch die zentrale Stellung des Shops und die Bereitstellung von immer genau einem
einzigen Objekt (für jedes persistente Objekt) für mehrere Clients wird garantiert, dass
alle EPoints (wie im SalesPoint -Framework) auf denselben Daten arbeiten, auch wenn sie
von unterschiedlichen Orten gleichzeitig darauf zugreifen.
Alle wichtigen RMI-Dienste werden dazu über eine einzige Schnittstelle, die ShopServices angesprochen. Jeder EPoint verfügt deshalb zunächst nur über eine Referenz auf
ein Objekt dieses Typs. Methodenaufrufe in den ShopServices werden also durch RMIMechanismen an den Shop weitergeleitet und eigentlich dort ausgeführt. Ergebnisse, Parameter und weiterführende RMI-Aufrufe erfolgen dann über die für RMI normale Traversierung von Objektreferenzen. Diese wird zu einem späteren Zeitpunkt zu den Ausführungen
für Programmierer noch genauer erläutert.
Die ShopServices werden wie in Abb. 2.4 gezeigt, ebenfalls über eine denierte Schnittstelle, die ShopIdentServices angeboten. Der Sinn dieses zentralen Zugangs ist es, dem
Shop die Möglichkeit zur Authentisierung der Clients zu geben, um z.B. fremden EPoints
im Netzwerk den Zugri auf die Daten zu verwehren oder unterschiedlichen Clients unterschiedliche ShopServices zur Verfügung stellen zu können.
Eine wichtige Eigenschaft von RMI ist, dass entfernte Aufrufe bei einem RemoteObject
in der Regel parallel ablaufende Threads darstellen. Daher muss bei diesen Klassen sehr
groÿer Wert auf Thread-Sicherheit gelegt werden! Das bedeutet, dass alle Datenstrukturen
durch Synchronisation in den Klassen beim Server vor paralleler (gleichzeitiger) Bearbeitung geschützt werden müssen. Wie dies in der Praxis realisiert werden kann, wird im
Kapitel 3 ausführlich beschrieben!
Ein Vorteil für die Entwicklung von Programmen mit dem EPoint -Framework ist, dass
die Verwendung von RMI eine klare Denition der reinen Schnittstellen (interfaces)
erfordert. Denn nur diese Interfaces legen fest, welche Methoden später für RMI-Aufrufe
zur Verfügung stehen. Diese Tatsache erfordert vom Entwickler(-team) hohe Disziplin und
zwingt ihn (sie) bereits in der Analyse diese sehr klare Schnittstellenspezikation für die
spätere Implementierung zu entwerfen was in dieser Form bei einer Implementierung
ohne RMI zwar auch wüschenswert wäre, aber nicht unbedingt notwendig.
26
KAPITEL 2. AUFGABENSTELLUNG UND NEUE KONZEPTE
Java Virtuelle Maschine
db cegf
ymz&{g| }m~
h ikjmlongjpgqjrts&sungv wdngv x
687:9;< =>?A@ 6B>C D:E F>G “ ” • – — ˜ ™ š ›J• – — ˜ JH ILKMN OPQAR H&PS T:U VPW
8€:‚mBƒ„ …A† ‡ƒˆ
8‰ Š:‹Œ‰BŽ : ‘’
! "#
X&Y Z[\
]&^ _`a
$&% '()+* % ,&. / 012+3 / 45
Java Virtuelle Maschine
Abbildung 2.4: Referenzen von EPoints auf Shop-Objekte
In Abbildung 2.4 sieht man, das das einzige vom Shop wirklich (dh. mit Hilfe der
rmiregistry) exportierte Objekt die ShopIdentServices sind. Dieses Objekt wird als einziges vom EPoint direkt importiert. Von da an erfolgt der Zugri auf alle weiteren RemoteObjects durch Traversion von Referenzen auf andere RemoteObjects. Der EPoint hat also nie
wirklich ein Datenobjekt des Shops bei sich, sondern nur eine Netzwerkverbindung (grüne
Pfeile), mit deren Hilfe die Methodenaufrufe und deren Ergebnisse weitergeleitet werden.
Sind die Ergebnisse (hostseitig normal erreichbare und durch rote Pfeile dargestellte Referenzen) wieder RemoteObjects, wird wieder nur eine Referenz über das Netzwerk (grüne
Pfeile) zurückgegeben.
2.6 Komplexe persistente Zustandsinformationen
Als komplexe Zustände bezeichne ich im Folgenden die Zustände eines Programmablaufes, die sich nicht in den Framework-Datenklassen unterbringen lassen. Das können
zum Beispiel Benutzerentscheidungen an den EPoints sein, die sich erst zu einem späteren Zeitpunkt auf die Daten in einem Katalog auswirken. Andere externe Zustände,
wie Zeitangaben, Messungen, verfügbarer Speicherplatz oder aktuelle Position in einem
Rechenprozess fallen ebenso in diese Kategorie.
Solange sich solche Zustände nicht ändern, bieten sich bereits bekannte Mechanismen, wie
persistente Properties, zur Speicherung an. Handelt es sich jedoch um einen komplexen
Zustand, der sich während des Programmablaufs häug ändert oder der erst im Zuge der
Erfassung mehrerer Aktionen/Daten entweder bestätigt oder wieder verworfen wird, ist
die Benutzung solcher statischen Mechanismen sehr umständlich.
Daher ist speziell jedem EPoint (bei denen diese Situationen am wahrscheinlichsten auftreten) automatisch und eindeutig ein sogenannter InformationStore zugeordnet, über
den nur der EPoint allein verfügen kann. Dieser erlaubt die Persistenz von Daten auf Anwendungsebene und unterstützt die Speicherung von Standard-Datentypen unter einem
Schlüssel. Der groÿe Vorteil gegenüber den Properties liegt allerdings in der Bereitstellung einer Art Transaktion: Der EPoint kann im InformationStore Daten, die den Zustand
seines Programms charakterisieren, persistent unter einem Schlüssel speichern und diese
2.7. ÄUßERE STRUKTUR
27
mit Hilfe des Schlüssels zurückgewinnen. Auÿerdem kann er einem existierenden Schlüssel
einen neuen Wert zuweisen. Das Besondere daran ist, dass alle diese Aktionen in einer
Transaktion gesammelt werden, die dann zu jedem Zeitpunkt entweder bestätigt oder bis
zum letzten bestätigten Zustand zurückgenommen werden können.
Dieser Mechanismus erlaubt die Aufzeichnung eines Programmablaufs mit Hilfe von dessen Zustandsvariablen. Wird im Programm ein denierter Zustand erreicht11 , können
die gespeicherten Daten mit commit() bestätigt werden. Wird der EPoint aber während
der Aufzeichnung dieser Daten beendet (z.B. auch durch Absturz), so können bei einem
Neustart zunächst alle zuletzt durchgeführten Änderungen mit rollback() rückgängig
gemacht werden und dann der zuletzt bestätigte Zustand zur Wiederherstellung der Anwendung genutzt werden. In diesem Zusammenhang wäre z.B. auch eine Verwendung der
zuletzt gespeicherten Daten für eine Fehleranalyse denkbar!
2.7 Äuÿere Struktur
In diesem Abschnitt gebe ich zuletzt einen kurzen Überblick über den strukturellen Aufbau des Frameworks. Das EPoint -Framework setzt sich dabei aus mehreren Packages
zusammen, die im folgenden beschrieben werden sollen12 .
Zunächst bendet sich das gesamte Framework in einem übergeordneten Package epoint.
Darunter benden sich mehrere Sub-Packages die die Frameworkklassen beinhalten. Das
sind im einzelnen:
data umfasst alle Schnittstellendentionen für die Klassen, von denen zentrale Datenob-
jekte (Catalog, Stock, CatalogItem, ...) abgeleitet werden, oder die mit diesen Klassen unmittelbar zusammenarbeiten. Dabei handelt es sich weitestgehend um Remote -Interfaces, deren Implementationen sich im darunter liegenden Package data.rmi
benden.
db umfasst alle Schnittstellen und Implementationen, die unmittelbar in Zusammenhang
mit der Persistenz und dem Datenbankzugri stehen (z.B. DBPersistenceManager).
log umfasst alle Klassen, die mit der Erstellung und Bearbeitung von Logles zu tun
haben.
sale umfasst alle Klassen, die mit der äuÿeren Anwendungsstruktur zusammenhängen.
Das umfasst z.B. die Klassen Shop, EPoint sowie Interfaces, die zur Modellierung
der Prozesse in den EPoints dienen (Gate, Transition).
xml stellt die Klassen zur Verfügung, die in unmittelbarem Zusammenhang mit der Er-
stellung von XML stehen. Das sind vor allem die Klassen, die verwendet werden,
11 wie
zum Beispiel am Ende einer Transition, die zu einem neuen Gate im Prozess eines EPoints führt
dazu kann man auch die Dokumentationen zu den Packages in der JavaDoc lesen!
12 Zusätzlich
28
KAPITEL 2. AUFGABENSTELLUNG UND NEUE KONZEPTE
um mit Hilfe des XML-Serialisierungsframeworks von Koala [KoDyXML] Objekte
nach XML zu serialisieren.
Darüber hinaus gibt es ein Verzeichnis epoint/addons, in dem sich die notwendigen jarDateien für den CLASSPATH benden. Das sind vor allem die JDBC-Treiberklassen für
den Zugri auf die PostGreSQL-Datenbank und das Koala-Serialisierungsframework. Im
Verzeichnis epoint/docs bendet sich die Javadoc zum Framework, sowie jegliche weitere
Dokumentation, die in direktem Zusammenhang mit dem Framework steht.
Prinzipiell gilt für die Package-Struktur, dass alle Klassen in den rmi-Subpackages RemoteObjects denieren und daher zusätzlich mit dem rmic-Compiler übersetzt werden
müssen!
2.8 Kompatibilität
Natürlich stand bei der Planung von EPoint auch die Forderung nach einer gewissen
Kompatibilität zum SalesPoint -Framework im Vordergrund, so dass alte bewährte Konzepte beibehalten und somit Dokumentationen und Programmentwürfe wiederverwendet
werden können. Dazu gehört z.B. auch, dass im neuen Framework Transaktionen unterstützt werden, Daten vom Shop verwaltet werden und der Benutzer an den EPoints mit
der Anwendung in Interaktion tritt.
Einige der vorangegangenen Forderungen lieÿen sich relativ leicht umsetzen oder wurden wenigstens für zukünftige Erweiterungen berücksichtigt. Allerdings gilt für das neue
Framework auf keinen Fall die weitgehende und uneingeschränkte Wiederverwendbarkeit
von Code des alten Frameworks, wie auch von Code der damit erstellten Programme.
Ebenso ist Dokumentation zum alten Framework nur noch eingeschränkt zu verwenden.
Die zu Beginn aufgezählten Grundkonzepte und Ideen blieben zwar zum Groÿteil erhalten (was z.B. die Verwendung und Funktionsweisen mehrerer Datenklassen angeht),
allerdings haben sich aus der Art der Implementierung für RMI und Persistenz in einer
Datenbank wesentliche Neuerungen und Erweiterungen ergeben, deren Auswirkungen auf
die Programmerstellung im nächsten Kapitel ausführlicher besprochen werden. Diese Erweiterungen wirken sich an mehreren Stellen auf die vom Programmierer zu verwendende
Schnittstelle aus und machen es daher unmöglich, Programme, die mit SalesPoint erstellt
wurden, auch mit EPoint zu verwenden.
Insgesamt ergibt sich also für ein mit dem EPoint-Framework erstellten Programm die
in Abbildung 2.3 gezeigte Struktur. Dabei ist es schon jetzt ohne unmittelbare Frameworkunterstützung und später durch noch durchzuführende Erweiterungen auch mit Frameworkunterstützung möglich, über die RMI-Schnittstelle Shops als Server für andere
Shops auftreten zu lassen. Das erönet die Möglichkeit einer Implementation von ganzen
Verkaufsketten. Diese Möglichkeit wird in Kapitel 4.4.6 noch genauer beschrieben.
Kapitel 3
Verwendung durch Programmierer
Dieses Kapitel gibt zunächst eine Anleitung zur Vorgehensweise für Programmierer, die
anhand von Programmbeispielen zu mehreren Problemstellungen zeigt, wie Programme
mit dem EPoint -Framework erstellt werden. Auÿerdem folgt ein Abschnitt mit allgemeinen Hinweisen und Bemerkungen!
Im Anschluss wird dann auf die technischen Grundlagen für die Verwendung von RMI im
Netzwerk sowie auf die Einrichtung und Installation des PostGreSQL Datenbanksystems
eingegangen. Die Informationen zu den technischen Grundlagen sind unter Umständen
auch für den Anwender eines Programms, dass mit EPoint erstellt wurde, interessant.
3.1 Anleitung zur Anwendungsentwicklung
In diesem Abschnitt soll die Vorgehensweise bei der Entwicklung einer Anwendung mit
dem EPoint-Framework beschrieben werden. Dazu werden an den entscheidenden Stellen
Implementationen von Beispielen zur Verdeutlichung eingesetzt. Da aber unmöglich auf
alle Details vollständig eingegangen werden kann und die zur Verfügung gestellten Möglichkeiten an vielen Stellen einfach selbstverständlich sind, verweise ich an dieser Stelle
auf die sehr umfangreiche JavaDoc! In den Package- und Klassenbeschreibungen wird auf
fast alle Möglichkeiten eingegangen. Auÿerdem enthalten die Methodenbeschreibungen an
den entsprechenden Stellen, zusätzlich zur Beschreibung der Arbeitsweise, Erläuterungen
zu gröÿeren Zusammenhängen (das ist normalerweise in den Interfaces von epoint.data
der Fall).
In den nächsten Abschnitten sollen an kurzen Beispielen vor allem die Schritte aufgezeigt
werden, die an bestimmten Stellen der Entwicklung notwendig sind, um eine funktionierende Anwendung zu erhalten.
29
30
KAPITEL 3. VERWENDUNG DURCH PROGRAMMIERER
3.1.1 Vorbereitung und Planung
Am Anfang des Projektes ist zunächst eine gute Planung notwendig, deren Ergebnis
beschreibt, was das zu erstellende Programm leisten soll und wie die Funktionalität umgesetzt werden soll. Vorausgesetzt die Programmieraufgabe wird von mehreren Programmierern zu bewältigen sein, steht auch eine saubere Schnittstellenspezikation im Vordergrund dieser Planung.
Bei der Entwicklung von Programmen mit EPoint bekommt die Denition der Schnittstelle in der Regel eine zusätzliche Bedeutung: Da alle RMI-Objekte durch eine echte
Java-Schnittstelle (interface ) speziziert sein müssen (das wurde bereits in Abschnitt
2.5 begründet), ist es sinnvoll, diese nach der Analyse als erstes zu entwerfen und zu
denieren. Damit hat man gleich zu Beginn ein Projekt, dessen Programmteile an den
Schnittstellen sauber deniert sind und die (zumindest programmiertechnisch) problemlos zusammengefügt werden können. Auÿerdem kann die Trennung an den Schnittstellen
durch das Framework, die Trennung in einzelne Aufgabenbereiche für das Entwicklerteam
unterstützen.
3.1.2 Datenspezikation
Zunächst sollte man sich darüber im Klaren sein, welche Daten von der Applikation verwaltet werden sollen. Das sind zum einen die Daten, die unmittelbar in Zusammenhang
mit der Anwendung stehen, wie z.B. Verkaufsgegenstände, Geld, Bestände, Benutzerdaten
usw.. Hat man diese Daten speziziert, kann man untersuchen, ob das Framework diese
Daten bereits unterstützt. Allgemeine Datentypen (Gegenstände, Geldsorten, Benutzer,
usw.) werden dabei normalerweise in Katalogen gespeichert. Spezielle Typen, die auf den
allgemeinen Typen basieren, werden in den Beständen gespeichert. Das Framework unterstützt dabei bereits die Persistenz von Schlüsseln in den Katalogen (Benutzernamen,
Gegenstandsbezeichner, Warenbezeichner, Kategorien) in Form von Strings und speichert diese zusammen mit Werten (Value, S. 149) ab. Als Implementationen für Values
stehen die beiden folgenden bereits zur Verfügung:
• StringValue (S. 146) repräsentiert textuelle Werte und damit ein Wrapper für
Strings, zur Verwendung als Value.
• DecimalValue (S. 123) repräsentiert numerische Werte, die als beliebig genaue Dezimalwerte gespeichert werden.
Da Bestände auf einem Katalog wiederum eine beliebige Anzahl von StockItems mit
je einem Value zu jedem Katalogeintrag speichern können, kommt man bei einer
Erweiterung der Datenstrukturen sofern überhaupt notwendig in der Regel mit
einer neuen Implementierung von Value aus. Das erfolgt normalerweise durch Beerbung
der Klasse epoint.data.AbstractValue und deren Erweiterung um die entsprechende
3.1. ANLEITUNG ZUR ANWENDUNGSENTWICKLUNG
31
Funktionalität. Das könnte, wie im folgenden Beispiel, ein DateValue sein, das ein Datum
repräsentiert:
package myapplication;
import epoint.data.*;
public class DateValue extends epoint.data.AbstractValue {
private java.util.Date date = null;
public MyDateValue(java.util.Date date) {
this.date = date;
}
public boolean isCatalog() { // diese Methode ist abstract in der Superklasse
return false;
}
public java.util.Date getDate() {
return date;
}
public String toString() {
return date.toString();
}
}
Listing 3.1: Erstellung eines neuen Value-Typs
Eine Übersicht über die vom Framework insgesamt bereitgestellte Datenstruktur in
Form von UML ndet man mit Erklärungen in Abbildung B.1 auf Seite 98. Darin sieht
man vor allem die Vererbungsstrukturen der Klassen im Package epoint.data.rmi, die
die Interfaces aus dem Package epoint.data implementieren.
Sollten die Defaultimplementierungen dieser Datenstrukturen im Package epoint.data.rmi nicht die benötigte Funktionalität aufweisen, so müssen allgemein immer die folgenden
Schritte ausgeführt werden:
1. Beerbung des entsprechenden Interfaces aus epoint.data und Erweiterung um die
entsprechenden Methoden. Das ist notwendig, um die RMI-Schnittstelle zu spezizieren. Die Notwendigkeit dieser Schnittstellendenition wurde bereits in Abschnitt
2.5 beschrieben.
2. Beerbung der zu erweiternden Klasse aus epoint.data.rmi und Implementation
des im vorangegangenen Schritt erstellten Interfaces.
3. Gegebenenfalls eine Neudenition von Methoden der beerbten Klasse.
Diese drei Schritte sind immer dann notwendig, wenn Framework-Funktionalität erweitert oder verändert werden muss. Will man zum Beispiel ein CatalogItem dahingehend
32
KAPITEL 3. VERWENDUNG DURCH PROGRAMMIERER
verändern, dass es ein zweites Value-Objekt verwaltet und dieses über eigene Methoden
getValue2 bzw. setValue2 verfügbar macht, so geht man folgendermaÿen vor:
1. Erstellen eines neuen Interface MyCatalogItem, welches das originale Interface beerbt. Darin werden die beiden neuen Methoden deniert:
package myapplication;
import epoint.data.*;
import java.rmi.RemoteException;
public interface MyCatalogItem extends epoint.data.CatalogItem {
public Value getValue2() throws RemoteException;
public void setValue2(Value vValue) throws RemoteException;
}
Listing 3.2: Denition eines neuen CatalogItem (RemoteInterface)
Auf diese Weise hat man ein neues, für RMI-Aufrufe zur Verfügung stehendes, CatalogItem deniert, dass zusätzlich die zwei neuen Methoden zur Verfügung stellt.
2. Erstellen einer Unterklasse MyCatalogItemImpl von epoint.data.rmi.CatalogItemImpl, die das im vorangegangenen Schritt neu erstellte Interface implementiert.
Bei der Implementierung der Methoden muss sich der Programmierer dessen bewusst
sein, dass er sich in der virtuellen Maschine bendet, in der auch der Shop läuft.
Damit hat er vollen Zugri auf den DBPersistenceManager und alle Methoden des
aktuellen Shops. Auÿerdem muÿ man die Methode updatePersistentObject() der
Mutterklasse aufrufen, wenn ein neuer Wert mit der set-Methode gesetzt wird. Das
ist notwendig, da die Defaultimplementierung nichts über das neue Value-Objekt
und die neuen Methoden, die den Zustand des CatalogItem verändern, weiÿ!
package myapplication;
import epoint.data.*;
import java.rmi.RemoteException;
public class MyCatalogItemImpl implements myapplication.MyCatalogItem {
private Value vAdditionalValue = null;
public Value getValue2() throws RemoteException {
return vAdditionalValue;
}
public void setValue2(Value vValue) throws RemoteException {
// zuerst muss man auch in dieser Methode pruefen, ob das
// CatalogItem ueberhaupt editierbar ist!
if (!this.isEditable()) throw new epoint.data.exception.
NotEditableException("This CatalogItem is currently not
editable!");
// erst dann kann man den Wert aendern
this.vAdditionalValue = vValue;
// schliesslich ist fuer das Framework wichtig,
3.1. ANLEITUNG ZUR ANWENDUNGSENTWICKLUNG
}
}
33
// dass man den Persistenzmanager
// ueber die Zustandsaenderung benachrichtigt!
// Dazu brauchen wir eine exklusive
// Datenbankverbindung, die durch ein Passwort geschuetzt ist
String sPasswd = "2nd Value Update-Protection";
this.updatePersistentObject(Arrays.asList(new Object[]{this.
PERSISTENT_PROPERTY_SERIALIZED}),sPasswd);
// zuletzt muss die datenbankverbindung wieder
// freigegeben werden, damit sie wieder fuer
// andere Aufrufe zur Verfuegung steht
returnConnection(getReservedConnection(sPasswd));
Listing 3.3: Denition eines neuen CatalogItem (Implementierung der Klasse)
3. Normales Kompilieren des neuen Interfaces bzw. der Klasse mit javac und zusätzliches Kompilieren des neuen RemoteObjects mit rmic MyCatalogItemImpl:
(a) javac myapplication/MyCatalogItem.java
(b) javac myapplication/MyCatalogItemImpl.java
(c) rmic myapplication.MyCatalogItemImpl
Auf dieselbe Weise geht man auch bei allen anderen Aufgabenstellungen vor, für die
man Kataloge, Bestände oder andere (über RMI verfügbare) Standard-Datenklassen um
Funktionalität erweitern muss.
3.1.3 Spezikation der EPoints
In diesem Schritt muss man überlegen, welche Typen von EPoints (Terminals) die Applikation besitzt. Das sind typischerweise:
1. ein oder mehrere Benutzerterminals, an denen normale Benutzer bzw. Kunden in
Interaktion mit der Anwendung treten
2. Verwaltungsterminals mit besonderen Möglichkeiten für ausgewählte Benutzer zur
Dateneinsicht und -manipulation
Dabei wird in den beiden Gruppen normalerweise noch einmal dierenziert. Wichtig für
den Programmierer ist die Entscheidung darüber, inwieweit Sicherheit eine Rolle bei den
Möglichkeiten der EPoints spielt. Anhand dieser Maÿgabe muss entschieden werden, wie
in einer Unterklasse von Shop die Methode getShopServices() (Seite 327, Zeile 620)
neu deniert wird. Diese Methode gibt für einen Parameter EPointInfo, der die charakteristischen Daten des EPoint enthält, ein Objekt der Klasse ShopServices zurück.
34
KAPITEL 3. VERWENDUNG DURCH PROGRAMMIERER
Für EPoints, denen mehr Rechte beim Zugri auf Daten zugestanden werden soll, können
hier entsprechende Methoden zur Verfügung gestellt werden. Die Defaultimplementierung
erlaubt das Erstellen und Referenzieren von persistenten Objekten aller Datenklassen. Dabei wird nicht zwischen verschiedenen EPoints dierenziert.
Eine solche Dierenzierung zwischen Rechten wird wahrscheinlich immer dann notwendig
sein, wenn die Schnittstelle von einem anderen Geschäft (Shop) benutzt werden soll, der
mit diesem Shop in einer Verkaufskettenbeziehung steht. Einem anderen Geschäft soll
sicher nicht Einblick in alle Daten des eigenen Geschäftes gewährt werden.
Denkbar wäre z.B. einer reinen Verkaufsstelle nur Zugri auf relevante Verkaufskataloge bzw. Bestände zu gewähren und nicht den Zugri auf geschäftsinterne Daten, wie
Geschäftskonten oder Verkaufsdaten von eigenen Zweigstellen (EPoints) einer gröÿeren
Geschäftsimplementierung.
Um bestimmten EPoints ganz und gar den Zugri auf den Shop zu verwehren, muss
die Methode getIdentServices im Shop (Zeile 637) neu implementiert werden. ShopIdentServices ist das eigentlich exportierte Objekt des Shops und nur über ihn gelangen
EPoints nach der in dieser Klasse erfolgten Registrierung an weitere Serverobjekte. Will
man also bestimmten EPoints den Zugri ganz verwehren, kann man in der genannten
Methode die PermissionDeniedException werfen.
Wichtig ist wieder, dass man sich als Programmierer auch wieder auf der Server-Seite
bendet und somit Zugri auf den DBPersistenceManager hat (wie in Abb. 3.3 auf Seite
52 gezeigt). Daher kann die Defaultimplementierung der ShopServices (Seite 341) direkt
den Persistenzmanager benutzen, um persistente Objekte zu erzeugen.
Im folgenden Beispiel werden diese Möglichkeiten kurz gezeigt. Darin verwende ich das
EPointInfo-Objekt des EPoints, um bei der Anmeldung zu entscheiden, ob der EPoint
Zugri auf den Shop erhält oder nicht. Dabei sollen nur EPoints Zugri erhalten, die
auf dem Rechner epoint.studfb.unibw-muenchen.de laufen. Weiterhin erhalten alle
EPoints mit dem Namen Customer einen besonderen Typ ShopServices, der nur den
Zugri auf einen einzigen, festgelegten Katalog gestattet.
package myapplication; import epoint.sale.*;
public class MyShop extends epoint.sale.Shop {
// dieses Interface definiert die ShopServices fuer
// einen speziellen Typ von EPoints (Customer)
public interface CustomerServices extends ShopServices {
// wir wollen dem EPoint nur erlauben auf einen
// einzigen bestimmten Katalog zuzugreifen
public Catalog getCustomerCatalog() throws RemoteException;
}
// diese Methode liefert das eigentlich, durch die rmiregistry
// exportierte Objekt, welches den Zugriff auf die ShopServices regelt
public ShopServicesIdent getIdentServices() {
// wir definieren einen Zugangspunkt zum Shop, der es nur dem Rechner
// epoint.studfb.unibw-muenchen.de erlaubt, auf den Shop zuzugreifen
return new ShopServicesIdent() {
public ShopServices identify(EPointInfo epi) {
// hat der Rechner einen anderen Namen,
3.1. ANLEITUNG ZUR ANWENDUNGSENTWICKLUNG
};
}
}
35
// verwehren wir den Zugriff durch eine Exception
if (epi.getHostName() != "epoint.studfb.unibw-muenchen.de") throw new
epoint.sale.exception.PermissionDeniedException("An EPoint
from this host is not allowed to access the Shop!");
// ansonsten verwenden wir den aktuellen Shop, um die
// ShopServices fuer diesen EPoint zurueckzugeben
return Shop.getTheShop().getShopServices(epi);
}
// diese Methode liefert auf Basis der Identitaet eines EPoint
// die ShopServices fuer diesen EPoint
public ShopServices getShopServices(EPointInfo epi) {
// wir wollen dem EPoint mit Namen "Customer" nur den Zugriff auf
// einen einzigen Katalog gewaehren. Allen anderen EPoints soll der
// komplette Zugriff mit den Standard-ShopServices erlaubt sein
if (epi.getEPointName().equals("Customer")) {
return new CustomerServices() {
// so wie in der folgenden Methode, werden alle Methoden
// des Interfaces ShopServices fuer die Benutzung gesperrt
public Catalog getPersistentCatalog(String sName) throws RemoteException
{
throw new RuntimeException("This EPoint has not the permission to
access the method getPersistentCatalog(String)!");
}
// ... es folgen die anderen Methoden ...
// schliesslich definieren wir die neue Methode des Interfaces, die
// speziell fuer die Customer-EPoints einen Katalog zurueckliefert
public Catalog getCustomerCatalog throws RemoteException {
Catalog c = null; try {
c = Shop.getTheShop().
getDBPersistenceManager().
getCatalog("CustomerCatalog",
getDefaultConnection());
} catch (RuntimeException e) {
Shop.getTheShop().logException(e);
} nally {
returnPrivateConnection(getDefaultConnection());
return c;
}
}
};
} else return super.getShopServices(epi); // alle anderen EPoints erhalten die
"normalen" ShopServices
}
Listing 3.4: Neudenition der sicherheitsrelevanten Methoden im Shop
36
KAPITEL 3. VERWENDUNG DURCH PROGRAMMIERER
3.1.4 Zustandsdenition des EPoint
Wie bereits im SalesPoint -Framework, sollte die Arbeitsweise eines EPoint im EPoint Framework durch eine Art Automaten beschrieben werden. Dabei gibt es Gates, an denen
der EPoint einen bestimmten Zustand besitzt und an denen Ereignisse stattnden, die eine
Zustandsänderung herbeiführen sollen. Das sind normalerweise Eingaben des Benutzers,
basierend auf dem momentanen Zustand des EPoints und dem momentanen Datenbestand. In der Transition, die jedem Gate fest zugeordnet ist, werden die Eingaben und
Ereignisse am Gate ausgewertet und führen zu einem neuen Zustand (respektive Gate ).
Das EPoint-Framework fordert diese Strukturdenition momentan noch nicht. Allerdings
ist in Form von entsprechenden Interfaces (Gate, Seite 324 Transition, Seite 337) eine
Grundstruktur dafür vorgegeben.
Der Vorteil dieser Prozesse ist, dass der EPoint auf diese Weise am eektivsten Nutzen aus
dem ihm zugeordneten InformationStore (S. 126) ziehen kann. Dieser wird jedem EPoint
bei seiner Anmeldung übergeben und kann mit der Methode getInfoStore() vom EPoint
angefordert werden. Die Fähigkeiten, des speziell zur Zustandssicherung eingeführten InformationStore, wurden bereits ausführlich in Abschnitt 2.6 beschrieben.
Der InformationStore dient in diesem Zusammenhang vor allem dazu, Aktionen an den
Gates aufzuzeichnen, die dann in der Transition bestätigt (commit ) und genutzt werden
können. Der InformationStore ist so implementiert, dass man mit seiner Hilfe nach einem
ungewollten Systemabsturz des EPoints wieder in einen denierten Zustand gelangen
kann. Er bietet mehrere Methoden an, um Properties in Form von Standard-Javatypen
zu speichern und zu ändern und um diese Speicherung bzw. Änderung zu einem späteren
Zeitpunkt zu bestätigen (commit ) oder zurückzunehmen (rollback ).
Die Voraussetzung für eine Wiederherstellung ist die entsprechende Nutzung dieser Möglichkeiten durch den Programmierer. Das sollte in der Weise passieren, dass die Daten
für Zustandsänderungen vor dem Betreten des neuen Gates zu bestätigen bzw. zurückzunehmen sind. Die Zuordnung des InformationStore zum EPoint, die Persistenz und
Transaktionssicherheit der Daten im InformationStore übernimmt wieder der Shop als
zentrale Instanz. Auf diese Weise ist auch nach einem Programmabsturz die Information
über den letzten bestätigten Zustand (und sogar über die Zustandsänderungen danach)
verfügbar und lässt sich für einen Neustart verwenden.
Mit Hilfe des InformationStore lässt sich bei jedem Neustart des EPoints auch feststellen, in welchem Zustand der EPoint zuletzt war. Zu diesem Zweck dient die Methode
getPersistenceState() im EPoint (Seite 320 Zeile 125). Anhand deren Ergebnis lässt
sich feststellen, ob der EPoint:
• zum ersten Mal gestartet wurde
• zuvor normal beendet wurde
• oder ob es einen unkontrollierten Absturz gab
3.1. ANLEITUNG ZUR ANWENDUNGSENTWICKLUNG
37
Damit dieser Zustand zuverlässig festgestellt werden kann, muss man als Programmierer
den InformationStore vor Benutzung önen (mit der Methode openService()), bzw.
zur Programmbeendigung mit der Methode closeService(boolean)1 schlieÿen. Auf
diese Weise kann man die Daten aus dem InformationStore bei einem Neustart entsprechend bestätigen, zurücknehmen oder sogar für eine Fehleranalyse nutzen.
Da in dieser Version des Frameworks noch keine Struktur- und GUI-Elemente für den
EPoint zur Verfügung stehen, kann dieser momentan völlig frei implementiert werden
und ist somit einzig über die ShopServices an das Framework gebunden. Damit stellt der
EPoint in dieser Version des Frameworks ein eigenständiges Programm dar, für das momentan als einzige Unterstützung die Mutterklasse epoint.sale.EPoint zur Verfügung
steht. Diese implementiert elementare Arbeitsabläufe, mit denen sich ein EPoint bei einem
Shop anmelden muss. Ansonsten wird nur der InformationStore zur Verfügung gestellt,
dessen Nutzung in einer späteren Erweiterung des Frameworks sicher auch indirekt durch
die Prozesse erfolgt.
3.1.5 Konguration des Frameworks
Zur Konguration des Frameworks gibt es zwei Möglichkeiten:
1. durch Kongurationsdateien, die sich im CLASSPATH benden
2. durch entsprechende Befehle vor dem eigentlichen Programmablauf in der statischen
main-Methode , bzw. im Konstruktor des Shops
Die erste Vorgehensweise ist gerade für verteilte Programme sinnvoll, da sich damit die
(auf unterschiedlichen Rechnern ablaufenden) Programme einfacher kongurieren lassen
und bei einer Portierung auf ein anderes System nicht jedesmal im Quellcode Änderungen
notwendig sind.
Für das Framework habe ich die Klasse ParamFile (Seite 325) implementiert, um im
CLASSPATH nach einer Kongurationsdatei mit einem anzugegebenden Namen suchen zu
können. Wird die Datei gefunden, so werden aus ihr PropertyName = Property Zeilen
in eine HashMap übertragen. Dies kann dann mit den Methoden aus ParamFile ausgelesen
werden.
Mit Hilfe dieses Mechanismus besteht die Möglichkeit dem Shop und dem EPointInfoImpl-Konstruktor einen solchen Dateinamen zu übergeben. Im Shop können auf diese
Weise Datenbankname, Datenbankhost, Serverhost (normalerweise localhost ) und der, für
die Verwendung von XML notwendige, Klassenname des SAX-Parsers eingestellt werden.
Dazu verwendet man als Schlüssel für die Properties einfach die im Shop denierten
1 Der
soll.
boolsche Wert gibt an, ob vor Beendigung ein commit der zuletzt manipulierten Daten erfolgen
38
KAPITEL 3. VERWENDUNG DURCH PROGRAMMIERER
String-Konstanten (Shop.PROPERTY_DATABASE_NAME, PROPERTY_...). Analog verhält es
sich mit der Klasse EPointInfoImpl (Seite 339). Diese verwendet die Properties, um sich
mit den Daten beim Shop zu identizieren.
Werden die Kongurationsdateien nicht genutzt, so müssen beim Start des Shops mindestens folgende Variablen initialisiert werden:
• PSQLDBConnection.DEFAULT_BASE mit dem Namen der verwendeten Datenbank
(default: epoint)
• PSQLDBConnection.DEFAULT_HOST mit dem Rechnernamen (Hostname), auf dem
die Datenbank Anfragen entgegennimmt (default: localhost)
• Shop.DEFAULT_HOSTNAME mit dem Namen des Rechners, auf dem der Shop läuft der Defaultwert localhost ist dabei immer durch den voll qualizierten Rechnernamen (z.B. epoint.studfb.unibw-muenchen.de) zu ersetzen
Das sind die wichtigsten Einstellungen, die sich alle in statischen Variablen in den jeweiligen Klassen benden. Weitere optionale Einstellungsmöglichkeiten, wie z.B. Name des
exportierten ShopServicesIdent-Objekts oder der Befehl zum Starten der rmiregistry,
kann man den jeweiligen Klassen- und Methodenbeschreibungen in der JavaDoc (vor allem
in Shop) entnehmen.
Um einen Shop starten zu können, muss ein solcher zunächst gesetzt werden. Da jede
Applikation nur über genau einen Shop verfügen darf, wird als einzige Möglichkeit, diesen zu spezizieren, die statische Methode Shop.setTheShop(Shop) angeboten. Ein alter
Shop wird dabei automatisch geschlossen. Der neue Shop bendet sich dann zunächst im
Ruhezustand und muss explizit durch Shop.getTheShop().start() gestartet werden.
Dabei werden automatisch die ShopIdentServices exportiert, nachdem der RegistryServer
(rmiregistry.exe), für den Export von RemoteObjects mit RMI, gestartet wurde.
Im Normalfall muss bei einem Erststart des Shops die Datenbank initialisiert werden. Diese Aufgabe erledigt der DBPersistenceManager entsprechend durch Aufruf der Methode
Shop.getTheShop().getDBPersistenceManager().reset().
3.1.6 Besonderheiten der Persistenz
In diesem Abschnitt wird auf den Umgang mit Problemstellungen eingegangen, die speziell
im Zusammenhang mit der Persistenz durch den DBPersistenceManager entstehen.
3.1. ANLEITUNG ZUR ANWENDUNGSENTWICKLUNG
39
3.1.6.1 Eigene persistente Objekte
Im UML-Diagramm auf Seite 100 kann man sehr gut erkennen, dass alle persistenten
Objekte das Interface PersistentObject (Seite 138) implementieren. In diesem Interface werden grundlegende Methoden deniert, die für die Zusammenarbeit des DBPersistenceManager mit diesen Objekten notwendig sind. Diese Methoden werden zum Teil
vom DBPersistenceManager benutzt, um das gespeicherte Objekt verwalten zu können.
Auÿerdem kann das Objekt sie selber verwenden, um den DBPersistenceManager über
eine Zustandsänderung zu informieren.
Die beiden Interfaces PersistentFrameworkObject und PersistentUserObject beerben
PersistentObject und sind nur noch Tag -Interfaces, die implementierende Klassen als
Frameworkklasse oder als neue vom Programmierer denierte Klasse (mit persistenten Objekten in der Datenbank) auszeichnen. Der Persistenzmanager unterstützt die Persistenz
von Klassen, die das PersistentUserObject implementieren, durch reine Serialisierung
in die Datenbank.
Für Referenzen auf solche Objekte sollte jedoch immer, wie im folgenden Abschnitt beschrieben, der Persistenzmanager des Shops benutzt werden. Vor allem, wenn persistente
Objekte von anderen persistenten Objekten referenziert werden, muss man immer den
Object Identier (OID) des Objekts verwenden. Die Begründung dafür ndet man ebenfalls im nächsten Abschnitt, mit einem Beispiel im Listing 3.5.
Der DBPersistenceManager unterstützt die Verwaltung von PersistentUserObjects mit
eigens dafür bereitgestellten Methoden zum Speichern, Update und Löschen dieser Objekte. Einzige Voraussetzung für die Nutzung dieser Methoden ist die Implementation der
Methoden aus PersistentUserObject, wie in der JavaDoc beschrieben (und im folgenden Abschnitt im Beispiel gezeigt) wird.
Der PersistenceManager übernimmt bei PersistentUserObjects implizit die Verantwortung über den Aufruf des Persistenzkonstruktors loadPersistentData() ohne Parameter und mit der aktuellen Datenbankverbindung. Ein PersistentUserObject kann diesen
sekundären Konstruktor benutzen, um z.B. transiente Felder, die nicht mit serialisiert
wurden, zu initialisieren und ähnliche Aufgaben zu erledigen, bevor das Objekt im eigentlichen Sinne weiterverwendet wird.
3.1.6.2 Referenzen auf persistente Objekte
Das Hauptproblem bei der Speicherung von Java-Objekten in einer relationalen Datenbank ist, dass Objekte in Java nur durch eine Referenz bezeichnet werden können. Es gibt
also defaultmäÿig keinen eindeutigen Bezeichner für Objekte. Weiterhin lassen sich Referenzen ebenfalls nicht explizit erfassen und können damit ebenfalls nicht als Bezeichner
in der Datenbank verwendet werden.
Will man also Java-Objekte in einer Datenbank speichern, so benötigt man einen eindeutigen Bezeichner (Schlüssel ) für jedes persistente Objekt, unter dem es in der Datenbank
40
KAPITEL 3. VERWENDUNG DURCH PROGRAMMIERER
abgefragt werden kann. Mit Hilfe dieser Bezeichner können dann Referenzen aus der objektorientierten Schicht in die Datenbank abgebildet werden, indem man den Bezeichner
des Objekts angibt, auf das die Referenz zeigt.
Wie im Abschnitt 2.4.2 zu allgemeinen Handhabung der Persistenz beschrieben, werden
allerdings auch serialisierte Objekte in der Datenbank gespeichert. In diesen serialisierten
Objekten sind aber bekannte Attribute der Klasse durch transiente Attribute ausgeblendet, die dann explizit in die Tabelle geschrieben werden. Teilmengen dieser Attribute
dienen als die oben beschriebenen Bezeichner für die gespeicherten Objekte. So referenziert z.B. ein CatalogItem in der Datenbank seinen Catalog über dessen Namen. Diese
Referenzen werden durch die Regeldenitionen der referentiellen Integrität stets aktualisiert. Wird also der Name eines Catalog geändert, geschieht dies in Verantwortung der
Datenbank auch bei allen referenzierenden CatalogItem.
Die serialisierte Fassung der Objekte, deren Referenzen auf diese Weise abgebildet werden, enthält zur Vermeidung von Redundanz solche Referenzen nicht mehr. Andernfalls
hätte das nämlich zur Folge, dass nicht die Referenz, sondern das referenzierte Objekt,
wie schon in Abb. 2.2 dargestellt, mitserialisiert werden würde.
Um das Problem, das dadurch entsteht, zu beschreiben, stellen wir uns Folgendes vor: Ein
Programmierer instanziiert ein Objekt einer nicht im Framework vorhandenen Klasse. Dieses Objekt soll auÿerdem in die Datenbank gespeichert werden, weshalb die entsprechende
Klasse normalerweise PersistentUserObject (Seite 140) implementiert.
Für die statische Struktur unserer relationalen Datenbank bedeutet dies, dass das Objekt komplett serialisiert abgespeichert wird. Sollte dieses vom Programmierer denierte
Objekt eine Datenklasse des Frameworks referenzieren, wird diese Referenz in Form eines Attributs der Klasse bei der Serialisierung mit erfasst. Für die serialisierte Fassung
des Objekts vom Programmierer bedeutet dies jedoch, dass das referenzierte FrameworkObjekt, ohne dessen transiente Attribute (die in der Datenbank explizit gespeichert werden), mitserialisiert wird und bei der Deserialisierung des Programmierer-Objekts nicht
mehr korrekt wiederhergestellt werden kann. Sollte das möglich sein, wäre das schlecht,
weil es dann eine Kopie des in der Datenbank gehaltenen Objekts darstellen würde und so
unter Umständen zwei physisch verschiedene Versionen des semantisch gleichen Objekts
verwendet werden würden. Der Programmierer kann Referenzen auf persistente Objekte
also nur dann problemlos persistent machen, wenn sich diese in irgendeiner Form explizit
erfassen lassen!
Eine solche Möglichkeit wird von allen Klassen zur Verfügung gestellt, die das Interface
PersistentObject (S. 138) implementieren. In dem Interface wird unter anderem die Methode getOID() (Zeile 69) deniert, die eine eindeutige ObjektID zurückliefert, unter der
das jeweilige Objekt korrekt vom DBPersistenceManager angefordert werden kann. Voraussetzung für die Existenz dieser OID (ObjectIDentier ) ist die Persistenz des Objekts
durch den DBPersistenceManager womit bereits gesagt ist, dass sich der Persistenzmanager um die Verwaltung und Vergabe dieser OIDs kümmert!
Für den Programmierer besteht die Möglichkeit eigene persistente Objekte zu denieren,
indem die entsprechende Klasse das Interface PersistentUserObject implementiert. Da-
3.1. ANLEITUNG ZUR ANWENDUNGSENTWICKLUNG
41
mit kann jedes Objekt dieser Klasse explizit durch den DBPersistenceManager2 verwaltet werden.
Implizit persistente Objekte werden durch die Persistenz der sie referenzierenden Objekte verwaltet. Erweitert also ein Programmierer eine Frameworkdatenklasse um nichttransiente Attribute, wird deren Persistenz unter bestimmten Voraussetzungen in die
Verantwortung des ursprünglichen Frameworkobjekts übergeben. Diese Voraussetzungen sind zum ersten die Serialisierbarkeit der hinzugefügten Daten und zum zweiten die
Benachrichtigung über die Zustandsänderungen des Objekts mit der Methode updatePersistentObject() im Interface PersistentObject (Seite 138, Zeile 121).
An einem Beispiel soll nun gezeigt werden, wie der Programmierer eine eigene Klasse
implementiert, deren Objekte durch den Persistenzmanager gespeichert werden sollen.
Diese Klasse referenziert ihrerseits einen persistenten Katalog aus dem Framework.
package myapplication;
import epoint.data.*;
import java.rmi.RemoteException;
public class MyCatalogReference implements epoint.data.PersistentUserObject {
// dieses Attribut wird durch den DBPersistenceManager mit den Methoden
// aus dem Interface PersistentUserObject verwaltet
private String sOID = null;
// dieses Attribut enthaelt die Referenz auf den Catalog
// der durch dieses persistente Objekt verwaltet werden soll
// und wird mit diesem Objekt persistent gespeichert
private String sCatalogOID = null;
// dieses transiente Attribut wird mit Hilfe des vorangegangenen
// Attributs initialisiert und wird nach der Instanziierung
// anstatt der OID verwendet, um den Catalog zu referenzieren
transient private Catalog cCatalogCache = null;
// in PersistentObject definiert, liefert die OID dieses Objekts
public String getOID() throws RemoteException{
return sOID;
}
// in PersistentObject definiert, liefert die serialisierte Fassung
// dieses Objekts
public String getSerializedForm() throws RemoteException {
return Shop.getTheShop().getPersistenceSerializer().toString(this);
}
// in PersistentObject definiert, liefert den Persistenzstatus dieses Objekts
public boolean isPersistent() throws RemoteException {
return sOID != null;
}
// in PersistentObject definiert, wird nach Deserialisierung durch
// den Persistenzmanager aufgerufen und gibt dem Objekt die Moeglichkeit,
// Initialisierungsaufgaben wahrzunehmen, bevor es verwendet wird
public void loadPersistentData(Map mProperties, String sConPasswd) throws
RemoteException {
2 und
damit auch wieder nur seitens des Servers / Shop
42
KAPITEL 3. VERWENDUNG DURCH PROGRAMMIERER
// wir benutzen die mit diesem Objekt persistente OID des Katalogs,
// um eine "normale" Referenz auf den Katalog zu erhalten
this.cCatalogCache = Shop.getTheShop().
getDBPersistenceManager().
getCatalogByOID(sCatalogOID,
Shop.getTheShop().
getDBPersistenceManager().
getPrivateConnection(sConPasswd,
this.getClass()));
}
}
// in PersistentObject definiert, wird vom Persistenzmanager benutzt, um
// diesem Objekt eine OID zuzuweisen
public void setPersistent(String sOID) throws RemoteException {
this.sOID = sOID;
}
// in PersistentObject definiert, liefert einen String der dieses Objekt fuer
// RMI-Clients beschreibt (toString() liefert dort die Beschreibung des Stubs)
public String toRemoteString() throws RemoteException {
return "Reference to "+cCatalogCache.toRemoteString();
}
// in PersistentObject definiert und wird vom Objekt benutzt, um den
// Persistenzmanager ueber Zustandsaenderungen zu informieren
public void updatePersistentObject(List lPropertyKeys, String sConPasswd) throws
RemoteException {
// wenn dieses Objekt nicht persistent ist, braucht man nichts zu updaten
if (!isPersistent()) return;
java.sql.Connection con = Shop.getTheShop().
getDBPersistenceManager().
getPrivateConnection(sConPasswd,
this.getClass());
Shop.getTheShop().getDBPersistenceManager().updatePersistentObject(this,con);
}
// jetzt werden auch noch Methoden zur Verfuegung gestellt, die den durch dieses
// Objekt referenzierten Katalog zur Verfuegung stellen
// Sollen diese auch fuer RMI-Aufrufe zur Verfuegung stehen, muss man dazu ein
// entsprechendes Interface implementieren, dass von PersistentUserObject erbt
public Catalog getCatalog() throws RemoteException {
return cCatalogCache;
}
public void setCatalog(Catalog cNew) throws RemoteException {
this.sCatalogOID = cNew.getOID();
this.cCatalogCache = cNew;
updatePersistentObject(null,this.getClass()+" setCatalog - Passwort");
}
Listing 3.5: Referenzierung persistenter Framework-Objekte in eigenen persistenten
Objekten
3.1. ANLEITUNG ZUR ANWENDUNGSENTWICKLUNG
43
3.1.6.3 Persistenzstatus
Die Persistenz von Objekten kann jederzeit mit der Methode isPersistent() von Objekten abgefragt werden, deren Klasse das Interface PersistentObject implementiert.
Dabei gilt, dass Inhalte von Datenbehältern (Katalog, Bestand), wie in Abschnitt 2.4.3.1
ausgeführt, immer dann persistent sind, wenn sie zu einem Datenbehälter gehören. Ein
genaues Zustandsdiagramm für die Persistenz von Containerelementen kann man in Bild
4.2 auf Seite 71 betrachten.
Im Beispiel bedeutet das für einen neu instanziierten Katalogeintrag, dass dieser nicht
automatisch in der Datenbank gespeichert ist, sondern erst, nachdem er im Rahmen einer
Transaktion die noch nicht abgeschlossen sein muss zu einem Katalog hinzugefügt
wurde. Die Transaktion muss zwar noch nicht abgeschlossen sein, womit der Eintrag für
andere Benutzer des Katalogs noch nicht sichtbar ist, aber aus Sicht der Transaktion ist
der Eintrag bereits im Katalog enthalten! Dasselbe gilt für das Entfernen von Inhalten
aus den Datenbehältern. Solange die Transaktion nicht beendet ist, die einen Eintrag aus
einem Katalog entfernt, solange ist auch dieser Eintrag noch in der Datenbank gespeichert.
Für alle anderen persistenten Objekte gilt die einfache Regel, dass sie ab dem Zeitpunkt,
zu dem sie dem DBPersistenceManager zur Speicherung übergeben werden (bis zur expliziten Löschung durch den Persistenzmanager) persistent sind. Diese Anmeldung beim
Persistenzmanager passiert bei den Datenbehältern (Catalog, Stock, DataBasket ) automatisch im Konstruktor. Nur die Löschung dieser Objekte muss explizit erfolgen.
3.1.6.4 Garbage Collection von persistenten Objekten
Wichtig für die Verwendung der Persistenz im Zusammenhang mit der Verwendung von
RMI ist das Bewusstsein darüber, dass zentrale und persistente Datenobjekte stets nur
auf dem Server existieren! Manipulationen dieser Objekte nden, wegen der Weiterleitung
des Methodenaufrufes durch RMI, also nie lokal, sondern immer auf dem Server statt. Damit liegt es in der Verantwortung des Servers bzw. des Objekts auf dem Server, dass die
Manipulationen persistent gemacht werden. Für die ShopServices und andere Servicebringer bedeutet dies, dass persistente Server-Datenobjekte nicht bei jeder Anfrage eines
Clients neu aus der Datenbank generiert werden dürfen. Stattdessen muss zunächst einmal überprüft werden, ob das entsprechende Objekt bereits instanziiert in der virtuellen
Maschine vorliegt und benutzt wird! Nur so kann sichergestellt werden, dass Anfragen von
Clients auch wirklich genau dieses eine Objekt bekommen, welches sie verlangt haben und nicht eine Kopie der persistenten Fassung dieses Objekts. Die RMI-Schnittstelle von
Java sorgt von sich aus dafür, dass alle Remote-Referenzen jeweils dasselbe Objekt auf
dem Server referenzieren. Zusätzlich verhindern diese Remote-Referenzen eine automatische Garbage Collection der Server-Objekte.
Damit Objekte nicht mehrmals beim Server deserialisiert werden, habe ich den Persistenzmanager so implementiert, dass er re-instanziierte Objekte in einem ReferenceCache
44
KAPITEL 3. VERWENDUNG DURCH PROGRAMMIERER
(Seite 307) mit Hilfe schwacher Referenzen (WeakReference) vorhält. Das gibt dem Persistenzmanager die Möglichkeit, die noch in der JVM bendlichen Objekte zu identizieren
und eine erneute Deserialisierung zu vermeiden. Die schwachen Referenzen im Cache sorgen dafür, dass die Objekte sollten sie nicht mehr stark referenziert (also in Verwendung) sein von der Garbage-Collection beseitigt werden können. In diesem Fall wird die
entsprechende schwache Referenz im Cache gelöscht. Da durch den Cache jedoch auch
Lesezugrie auf die Datenbank vermieden werden, was die Ezienz deutlich erhöht, habe
ich den Cache dahingehend optimiert, dass er eine bestimmte Anzahl zusätzlicher starker
Referenzen auf einige Objekte im Cache hält. Dadurch wird ein vorzeitiges Entfernen der
Objekte durch die Garbage-Collection vermieden und es ist garantiert, dass immer eine
bestimmte Anzahl von Objekten im Cache enthalten ist, die nicht aus der Datenbank
gelesen werden müssen.
Ein Teil der persistenten Objekte wird also in der virtuellen Maschine immer implizit
durch das Framework referenziert auch wenn der Programmierer dies momentan nicht
tut. Das muss unter Umständen von Programmierern berücksichtigt werden, die Eigenschaften der Garbage-Collection (z.B. die finalize()-Methode) verwenden, um Funktionalität in ihren eigenen persistenten Objekten umzusetzen!
3.1.6.5 Persistenz und Serialisierung durch Serializer
Das Framework verwendet standardmäÿig eine Serialisierung nach XML. Da innerhalb des
Frameworks sehr oft serialisiert und deserialisiert werden muss, existiert dazu eine eigene
Schnittstelle im Shop. Mit der Methode Shop.getPersistenceSerializer() (Seite 327,
Zeile 536) wird eine Implementierung des Interface epoint.sale.Serializer (Seite 326)
zurückgegeben. Dieser Serializer wird, vor allem vom Persistenzmanager, dazu genutzt,
um Objekte in Strings umzuwandeln, bzw. aus diesen Strings wieder Objekte zu erzeugen. Damit bei Serialisierung und Deserialisierung keine Konikte durch unterschiedlich
verwendete Verfahren auftreten, muss der Programmierer ebenfalls diese Schnittstelle nutzen, um z.B. bei einer Implementierung der Methode getSerializedForm() im Interface
epoint.data.PersistentObject einen String zu erzeugen, der durch den Persistenzmanager korrekt verarbeitet werden kann.
Das Framework verwendet aus Gründen der besseren Lesbarkeit und Editierbarkeit einen
Serializer, der die Objekte mit Hilfe eines Serialisierungs-Frameworks von Koala/Dyade
[KoDyXML] in Strings umwandelt. Aus Ezienzgründen und zur Platzersparnis bei der
Speicherung der Strings, kann dieser Serializer im Shop durch Redention der Methode
getPersistenceSerializer() ersetzt werden, der z.B. die normale Java-Serialisierung
verwendet. Die normale Java-Serialisierung ist verhältnismäÿig schneller als die Serialisierung und Deserialisierung nach XML, da weniger Syntaxüberprüfungen und Klasseninformationen verwendet werden. Auÿerdem belegt ein mit dem normalen Serialisierungsmechanismus konvertiertes Objekt um mehr als die Hälfte weniger Speicherplatz (was eine
zusätzliche Beschleunigung bei Datentransfers von und zur Datenbank bringt).
Objekte, die das Interface PersistentObject implementieren, sollten, auÿer im Persis-
3.1. ANLEITUNG ZUR ANWENDUNGSENTWICKLUNG
45
tenzmanager, nie explizit durch einen Serializer persistent gemacht werden, da das zu
ungewollten Inkonsistenzen mit Objekten in der Datenbank führen könnte. Stattdessen
referenziert man diese Objekte, wie in Abschnitt 3.1.6.1 beschrieben, mit Hilfe des ObjectIdentiers, solange sie persistent sind. Sollten diese Objekte nicht persistent sein, müssen
die benötigten Attribute des Objekts explizit gespeichert werden, anstatt es zu serialisieren und später wieder zu deserialisieren.
3.1.7 Besonderheiten von RMI
In diesem Abschnitt wird auf Besonderheiten im Umgang mit RMI hingewiesen. Dazu
wird beschrieben, wie RMI funktioniert und welche Vorkehrungen zur Verwendung von
RemoteObjects notwendig sind.
3.1.7.1 Funktionsweise von RMI
Wer RMI einsetzt, sollte sich vor allem über folgende drei Punkte im Klaren sein:
• RemoteObjects, also Objekte die Methoden für den Aufruf aus anderen virtuellen
Maschinen zur Verfügung stellen, verbleiben in ihrer eigenen Maschine. Dort werden
auch die Methodenaufrufe von auÿen durchgeführt. Dabei stellt jeder Zugri auf
diese Methoden von auÿen einen eigenen Thread dar, der gleichzeitig mit anderen
auf das Objekt zugreift.
• Für die bei Methodenaufrufen verwendeten Objekte in Parametern und Rückgabewert gilt die erste Aussage sinngemäÿ: RemoteObjects verbleiben in ihrer eigenen
virtuellen Maschine und es wird nur eine entsprechende Referenz übergeben! Alle
anderen Objekte werden in serialisierter Form in die jeweils andere Maschine übertragen und existieren dort als Kopie des originalen Objekts!
• RemoteObjects beim Server werden beim Client durch eigene (sogenannte RemoteStub -Objekte) symbolisiert. Diese Tatsache ist wichtig, da dadurch z.B. eine Synchronisierung von Clients mit Hilfe der Referenzen auf die Server-Objekte nicht
möglich ist. Die Synchronisierung würde sich in diesem Fall immer nur auf die verwendete Stub -Referenz beziehen. Synchronisation muss also immer innerhalb der
Server-Objekte erfolgen!
Bei der Implementierung gilt es daher zusätzlich einige wichtige Dinge zu beachten:
1. Die von Objekten exportierten Methoden werden in Interfaces deniert, die das
java.rmi.Remote-Interface beerben und java.rmi.RemoteException in ihrer Signatur enthalten. Auÿerdem muss das Interface Serializable implementiert werden!
46
KAPITEL 3. VERWENDUNG DURCH PROGRAMMIERER
public interface ServerTime extends java.rmi.Remote, java.io.Serializable {
}
// deklariert eine Methode, die fuer Aufrufe aus anderen Maschinen
// zur Verfuegung steht
// diese Methode gibt die aktuelle Zeit des Servers zurueck
public java.util.Date getTime() throws RemoteException;
Listing 3.6: Erstellung eines RemoteInterface für Aufrufe aus anderen Maschinen
2. Die Klasse, die ein Remote-Interface implementiert, sollte im Normalfall java.rmi.server.UnicastRemoteObject beerben, da diese Klasse bereits einige Methoden zur Verfügung stellt, die für Server-Objekte unerläÿlich sind.
public interface ServerTimeImpl extends java.rmi.server.UnicastRemoteObject
implements ServerTime {
}
// der Konstruktor von UnicastRemoteObject, deklariert bereits die Remote// Exception, daher muss sie auch in jeder Unterklassen auftauchen
public ServerTime() throws RemoteException {
}
// liefert die aktuelle Zeit in der instanziierenden Maschine
public java.util.Date getTime() throws RemoteException {
return new java.util.Date();
}
Listing 3.7: Implementierung eines RemoteInterface
3. Alle Klassen, die in irgendeiner Form das Interface java.rmi.Remote implementieren, müssen nach dem normalen Übersetzungsvorgang (mit javac) mit dem RMICompiler rmic übersetzt werden:
rmic ServerTimeImpl
Dabei werden sogenannte Stub-Klassen erzeugt, deren Objekte die RemoteObjects
in der jeweils anderen JVM (Client) repräsentieren. Von dort aus leiten sie Methodenaufrufe an das originale Objekt beim Server durch einen RMI-Call weiter.
Zur Veranschaulichung der Arbeitsweise von RMI soll die Abbildung 3.1 dienen. Darin
wird gezeigt, wie der Aufruf einer exportierten Methode von einer anderen Maschine aussieht.
In der Abbildung werden als Parameter einmal ein RemoteObject der aufrufenden Maschine übergeben und einmal ein normales Objekt. Das Ergebnis ist wieder ein normales
Objekt. In der Grak kann man sehen, dass normale Objekte in die jeweils andere Maschine geklont werden und Änderungen dort keine Auswirkungen auf die aufrufende Maschine
haben. Das RemoteObject hingegen wird als Referenz übergeben und somit haben Methodenaufrufe aus beiden Maschinen Auswirkungen auf das Objekt. Entsprechend diesem
Schema werden auch alle weiteren Referenzen auf Objekte durch RMI-Methodenaufrufe
traversiert!
3.1. ANLEITUNG ZUR ANWENDUNGSENTWICKLUNG
Thread of Control
Java Virtuelle Maschine A
(Aufrufende Maschine)
Objekt
o1
Objekt
o2
47
Java Virtuelle Maschine B
Objekt
public Object compute(Object o1, Object o2) R
1.
Parametrisierter RMI-Methodenaufruf
Eigener Thread (compute)
Methodenaufrufe auf durch Remote-Referenz
übergebenes Parameter-Objekt
Objekt
Result”
Objekt
o2”
Objekt
o1
Objekt
Result
“Normales” Objekt
Remote Objekt mit Methoden für Aufrufe aus anderen Maschinen
Referenz auf das Objekt in der anderen Maschine
Abbildung 3.1: Schematischer RMI-Aufruf
Referenzen auf RemoteObjects werden bei Methodenaufrufen sowohl in Parametern, als
auch in Rückgabewerten wieder als Referenz auf die entfernten RemoteObjects übergeben.
Normale Objekte werden hingegen serialisiert und in der jeweils anderen JVM als Kopie
neu instanziiert.
Die Abbildung 3.1 zeigt, wie Maschine A die exportierte Methode compute in Maschine B
aufruft. Dabei werden als Parameter Objekt o1 (RemoteObject) und o2 (normales Objekt)
verwendet. In Maschine B wird dann für die Berechnung mit den Parametern für o1 eine
Referenz auf das Objekt in Maschine A verwendet und für o2 ein serialisiertes und damit
geklontes Objekt in Maschine B. Der Rückgabewert ist wieder ein normales Objekt, dass in
die Maschine A geklont wird.
3.1.7.2 Threadsicherheit
Wie im vorangegangenen Abschnitt bereits angedeutet, bedeutet jeder Methodenaufruf
in einem RemoteObject einen eigenen Thread im Server. In diesem Zusammenhang ist
also unbedingt zu beachten, dass die über RMI verfügbaren Objekte threadsicher
programmiert werden.
Threadsicherheit in Java basiert auf der Synchronisation auf der Ebene der Benutzung
von Objekten. Das bedeutet, dass ein Thread, der ein Objekt benutzt, indem er Methoden
oder Daten dieses Objekts aufruft, für dieses Objekt einen Lock besitzt. Dieser stellt eine
Art Semaphore für den Zugri auf dieses Objekt dar.
Der Schlüssel zu Threadsicherheit in Java ist das Schlüsselwort synchronized. Dieser
Modier kann im einfachsten Fall bei Klassendenition angegeben werden, z.B.:
48
KAPITEL 3. VERWENDUNG DURCH PROGRAMMIERER
public synchronized class FullyThreadSecure extends ... {
}
...
Das bedeutet, dass ein Objekt dieser Klasse zu jedem Zeitpunkt immer nur von einem
einzigen Thread benutzt werden kann, welcher dann den Lock dieses Objekts besitzt. Zum
einen wird dadurch immer verhindert, dass zwei Threads gleichzeitig Methoden dieses
Objekts aufrufen und somit den Zustand gleichzeitig manipulieren können. Zum anderen
wird dadurch aber auch der Zugri auf dieses Objekt zwangsweise serialisiert und läuft
damit insgesamt wesentlich langsamer ab.
synchronized kann daher auch ezienter auf Methoden angewandt werden:
public synchronized void doSomethingWithoutOthers( ... ){
}
// Code der nur von einem Thread ausgefuehrt wird
In diesem Fall können alle Threads gleichzeitig auf alle Methoden zugreifen, die nicht mit
synchronized gekennzeichnet sind. Alle Methoden, die den Modier synchronized aufweisen, können insgesamt aber immer nur von einem Thread benutzt werden. Das heiÿt
im Detail, dass wenn ein Thread auf eine mit synchronized markierte Methode zugreift,
für alle anderen Threads, alle weiteren mit synchronized markierten Methoden des Objekts, gesperrt sind3 ! Diese Art des Zugrisschutz vermindert den Performanceverlust, da
Parallelität bis zu einem bestimmten Grad erlaubt ist.
Eine noch feinere Granularität für den Zugrisschutz erlaubt die direkte Anwendung
desselben Modiers auf ein explizit anzugebendes Objekt. Man kann mit:
synchronized (oObject) {
}
// zu schuetzender Code
beliebige Code-Abschnitte vor gleichzeitigen Zugrien schützen. Synchronisiert wird dabei
anhand des Locks, von dem in Klammern angegebenen Objekt (im Beispiel oObject). Dieses kann entweder das zu schützende Datum oder ein sogenannter Monitor 4 zum Schutz
einer gröÿeren zusammengehörigen Datenstruktur sein. Im Beispiel kann man also sicher
sein, dass oObjekt innerhalb der geschweiften Klammern von keinem anderen Thread
gleichzeitig benutzt werden kann auch nicht, um eventuell selber ein Lock auf dieses
Objekt zu erhalten!
Diese Art der Absicherung bei mehreren Threads bietet die beste Performance und den
besten Schutz vor gleichzeitigen Zugri auf komplexe Datenstrukturen. Allerdings ist zu
3 Das
betrit nicht die normalen Methoden.
erstelltes Objekt, zum Schutz mehrerer Objekte ähnlich einer Semaphore
4 Explizit
3.1. ANLEITUNG ZUR ANWENDUNGSENTWICKLUNG
49
beachten, dass dadurch auch leicht Deadlocks entstehen können, falls mehrere Monitore
nacheinander angefordert werden. Zur Vermeidung von Deadlock-Situationen existieren
mehrere Möglichkeiten, die unter anderem in der Betriebssystem-Vorlesung behandelt
bzw. in den meisten Büchern zu Betriebssystemen [3] vorgestellt werden.
Sehr wichtig ist es zu beachten, dass erneute Aufrufe von einem RemoteObject auf andere RemoteObjects wieder eigene Threads darstellen und auch auf diese Weise Deadlocks
entstehen können! Ein Beispiel dafür aus eigener Erfahrung ist das folgende:
Man fügt ein CatalogItem in einen Katalog ein, wobei dieser Vorgang durch einen Monitor geschützt ist, welcher die lokalen Katalogdaten vor parallelem Zugri / Manipulation
schützt. Informiert man (während dieser Schutz aktiv ist) einen Listener über diese Aktion und der Listener greift seinerseits auf den Katalog zu, so stellt dieser Zugri (dank
RMI) einen neuen Thread dar, welcher unter Umständen erst auf die Freigabe des Schutzes warten muss. Das Resultat ist ein Deadlock, falls man auf den Listener warten muss!
Die Lösung ist, die Listener in einem eigenen Thread zu informieren, auf dessen Ergebnis
man nicht warten muss. Alternativ verschiebt man das Informieren der Listener auf einen
Zeitpunkt, zu dem die Schutzmechanismen wieder aufgehoben sind.
Wichtig: Im Zusammenhang mit RMI ist zu beachten, dass die auf Client-Maschinen
referenzierten Objekte eines Servers5 in der Client-Maschine durch ein eigenes Objekt
dargestellt werden sogenannte RemoteStub -Klassen. Eine Synchronisation auf diesen
Stubs synchronisiert also nur den parallelen Zugri der Client-Maschine auf die Stubs und
nicht den Zugri auf die referenzierten Objekte! Im Beispiel bedeutet dies, dass ein EPoint
nicht den Zugri auf z.B. einen Katalog beim Shop synchronisieren kann6 . Daher muss der
Katalog selber threadsicher implementiert sein. Sollte es dennoch unbedingt notwendig
sein ein RemoteObject für den Zugri von nur einem Client zu reservieren, so kann man
beispielsweise nachträglich ein Boolean-Flag bAccessible implementieren, das durch eine synchronisierte Methode gesetzt oder gelöscht werden kann und vor Zugrien auf das
Serverobjekt abgefragt werden muss. Durch eine auf diese Weise implementierte Semaphore erreicht man eine Art expliziten Zugrisschutz, der, vorausgesetzt er wird durch
den Programmierer konsequent genutzt, den Zugri auf die zu schützenden Funktionen
und/oder Daten kanalisiert.
Das letzte Beispiel läÿt sich durch Verwendung von Pseudo-Passwörtern noch ausbauen,
wodurch der Zugri auf die geschützten Ressourcen mit ausgewählten Threads (die über
das Passwort verfügen) geteilt werden kann. Dieses Prinzip wird im Framework bei der
Verwendung der Datenbankverbindung im PSQLDBPersistenceManager (S. 271) umgesetzt. Dort wird ein Passwort verwendet, um Zugri auf die Datenbankverbindung zu gewähren. Solange man bei einer Anfrage dasselbe Passwort verwendet, wird die Verbindung
zurückgegeben. Ist das Passwort jedoch nicht dasselbe wie bei der letzten Anfrage und
die Datenbankverbindung wurde noch nicht freigegeben, wird die Anfrage mit dem neuen
5 Das
sind alle Daten die der Shop für den EPoint zur Verfügung stellt.
wäre wohl auch nicht wünschenswert, dass Serverobjekte durch einen Client blockiert werden
können.
6 Es
50
KAPITEL 3. VERWENDUNG DURCH PROGRAMMIERER
Passwort blockiert. Dieser Mechanismus wird in Abbildung 3.2 nochmals verdeutlicht7 .
Erst wenn die aktuelle Datenbankverbindung zurückgegeben oder geschlossen worden ist,
wird der nächste Thread mit dem neuen Passwort zugelassen. Nur so lässt sich zum
einen die Datenbankverbindung von mehreren Threads kontrolliert benutzen und zum
anderen auch mit RMI verwenden. Da die Datenbankverbindung nicht als serialisiertes
Objekt übergeben werden kann, hat man auch nicht die Möglichkeit, sie als Parameter bei
RMI-Aufrufen zu verwenden. Stattdessen wird das aktuelle Passwort übergeben und jedes Objekt fordert dann die aktuelle Datenbankverbindung beim DBPersistenceManager
selbständig an.
ugv!wyx{z}|~
RST U!V W X%Y&T S(Z*[,\-\.S(]0T W [,\-^ U2Y_4_
`6[,V T
a!bcbcbd
€g!‚yƒ{„}…‡†
ˆg‰ŠŒ‹{}Ž
! " #%$& ('*),+-+.(/0 " ),+-1 2$343
56), 7%808:9<;
=>? @!A B C<D&? >(E*F,G0G%>(H? B FG0I @2DJKJ LMF,A ?
N<O0OP&Q
egf.h h i(jklcm(n4n oph qsrtKtct
 ‘“’K”s •—–™˜g•s•,‘6š’K› ˜g•
Abbildung 3.2: Schematische Darstellung des Database Connection Sharing
Die aktuelle Datenbankverbindung wird durch ein Passwort geschützt. Threads, die mit
einem anderen als dem aktuellen Passwort versuchen Zugri auf die aktuelle Connection zu
erhalten, werden blockiert, bis diese von dem Thread zurückgegeben wird, der sie zuletzt mit
seinem Passwort erhalten hat. Danach kommt der nächste Thread (z.B. Thread B) an die
Reihe und erhält die Connection unter seinem Passwort. Alle weiteren Threads bekommen
diese Verbindung wieder nur mit diesem Passwort, bis sie von B zurückgegeben wird. Meine
Umsetzung dieses Verfahrens kann man auf Seite 271 in Zeile 373 sehen.
3.1.7.3 Lokale Instanzen und dierenzierte Services
An dieser Stelle soll noch einmal darauf hingewiesen werden, welche Instanzen in welcher Umgebung erzeugt werden dürfen. Prinzipiell gilt immer, dass alle Objekte, die das
Interface java.rmi.Remote in irgendeiner Weise implementieren, Server -Objekte sind.
Aus diesem Grund müssen sie immer in der JVM des Shop instanziiert werden. EPoints
bekommen den Zugri auf diese Objekte dann mit Hilfe von RMI.
Im Wesentlichen bilden die einzige Ausnahme davon die Objekte der Klassen EPointInfo
7 Das
ist für den Programmierer immer dann relevant, wenn er eine Datenbankverbindung beim Zugri
auf den Persistenzmanager benötigt.
3.1. ANLEITUNG ZUR ANWENDUNGSENTWICKLUNG
51
(Seite 322) und ContainerListenerAdapter (Seite 221), die im Normalfall in der Umgebung des EPoints instanziiert werden. Das liegt daran, dass EPointInfo zwangsläug
durch den EPoint selbst bereitgestellt werden muss, weil das Objekt beim Shop dazu dient, den EPoint zu identizieren. Die Remote-Referenz kann im Shop dazu benutzt werden, um festzustellen, ob der EPoint noch erreichbar oder schon oine ist.
ContainerListenerAdapter ist die Superklasse für Listener, die ebenfalls beim EPoint
erstellt werden und im Fall von RemoteListenern (wird weiter unten erklärt) auch im
EPoint verbleiben.
Alle anderen Objekte müssen beim Shop über die ShopServices (Seite 335) vom EPoint
angefordert werden. Das gilt auch für Catalog-/StockItems, die in einen persistenten
Container eingefügt werden sollen! Das bedeutet für den Programmierer, dass sämtliche Funktionalität über die ShopServices bereitgestellt werden muss. Damit müssen die
ShopServices auch dann erweitert werden, wenn spezielle Unterklassen der StandardFrameworkklassen verwendet werden.
Weiterhin steht dem Programmierer für eine dynamische Implementierung (bei der Entscheidung darüber, woher ein entsprechendes Objekt zu beziehen ist) im Shop bzw. im
EPoint die Methode isServerApplication() bzw. isClientApplication() zur Verfügung. Diese Methoden geben einen entsprechenden boolschen Wert zurück, der angibt,
ob ein Shop oder ein EPoint in der aktuellen JVM instanziiert wurde. So könnte man
beispielsweise eine Klasse implementieren, deren Objekte sowohl beim EPoint, als auch
beim Shop instanziiert werden können und aus beiden Umgebungen auf die persistenten
Daten zugreifen:
public class UniqueNumber {
private long lUnique;
// erzeugt eine neue, global eindeutige Nummer
public UniqueNumber() {
if (Shop.isServerApplication()) {
// wenn wir uns in der Umgebung des Shops befinden,
// verwenden wir dessen Methode, um die Nummer zu erzeugen
lUnique = Shop.getTheShop.getGlobalUniqueNumber();
} else if (EPoint.isClientApplication()) {
// sind wir in der Umgebung des Clients, benoetigen
// wir die ShopServices zur Erzeugung dieser Nummer
lUnique = EPoint.getShopServices().getGlobalUniqueNumber();
} else
// trifft keines von beiden zu, wurde noch keine Anwendung
// initialisiert und wir koennen auch keine global eindeutige
// Nummer erzeugen
throw new RuntimeException("There is no Shop or EPoint,"+
"from which to obtain the unique number!");
}
// liefert die eindeutige Nummer in der Anwendung,
// die durch dieses Objekt dargestellt wird
public long getNumber() {
return lUnique;
}
52
}
KAPITEL 3. VERWENDUNG DURCH PROGRAMMIERER
// liefert eine String-Repraesentation dieser eindeutigen Nummer
public String toString() {
return ""+lUnique;
}
Listing 3.8: Erstellung einer Klasse zur Instanziierung in EPoint und Shop
Durch die Zentralisierung des Zugris auf den Shop mit Hilfe der ShopServices wird
nicht nur sichergestellt, dass der Shop wirklich alle Server-Objekte bei sich behält, sondern
man erhält gleichzeitig eine transparente Schnittstelle für die Funktionalität, die jedem
EPoint zur Verfügung gestellt wird. Da man mit Hilfe der ShopIdentServices zu denen
jeder EPoint bei seiner Anmeldung als erstes Kontakt aufnehmen muss die ShopServices verteilt, hat man sogar die in Abb. 3.3 dargestellte Möglichkeit, jeden EPoint(-typ)
mit ganz speziellen Fähigkeiten auszustatten. Denkbar wäre z.B. ein EPoint, der die Fä-
vtw7xly z5{|
` acd
b
e fhg5ikjlgmngolpqpqjlr stjlr u
„ …‡†qˆ ‰qŠ7‹ Œ  Ž ‹ ‰Ž   Š
>?@A>BC DE FBGH
"$#%&' () ( * &+,&-/.$+.0&'
!
1!23451768 9: ;6<=
U!VWXU7YZ [\ ]Y^T_
‘ ’$“q” •q–7— ˜ ™ š — •š › œ –
}5~€ 5‚Tƒ
IJKLIMN OP QMRTS
Abbildung 3.3: Dierenzierte ShopServices für verschiedene EPoints
Jeder EPoint kann vom Shop bei seiner Anmeldung (1a / 1b) aufgrund der übergebenen
EPointInfo einen für seine Bedürfnisse und Rechte spezizierten ShopService erhalten. Im
Bild hat EPoint A mit seinen ShopServices die Möglichkeit direkt auf den DBPersistenceManager und den Katalog xyz zuzugreifen. Wohingegen EPoint B nur auf einen einzigen
Katalog (xyz) zugreifen kann.
higkeit hat, mit Hilfe seiner ShopServices die Arbeit des Shops zu steuern. Wohingegen
anderen EPoints diese Möglichkeit nicht gegeben werden soll. Man denke dabei z.B. an
die Implementierung von Bankautomaten, deren Daten von einem zentralen Terminal
aus eingesehen werden können. Einem Bankautomaten jedoch die Möglichkeit zu geben,
die Daten anderer Terminals einzusehen, wäre hingegen sinnlos und unter Umständen
auch eine Sicherheitslücke. Ein Beispiel für dierenzierte ShopServices wurde bereits in
Abschnitt 3.1.3 gegeben.
3.1. ANLEITUNG ZUR ANWENDUNGSENTWICKLUNG
53
3.1.8 Transaktionen
Bei der Verwendung der Standard-Datenbehälter Catalog und Stock kommen Transaktionen zum Einsatz, um die Elemente dieser Container zu modizieren. Der zentrale Gegenstand einer Transaktion ist immer der DataBasket. Dabei stellt der DataBasket (oder
Datenkorb ) ein Transaktionshandle dar, welches (wie in Abschnitt 2.4.1 beschrieben) verwendet wird um eine Transaktion aufzuzeichnen. Er ist also nicht (wie in SalesPoint) als
neuer Typ von Datenbehälter zu verstehen obwohl man seine Funktionsweise so erklären
kann.
Der DataBasket wird bei allen Aktionen, die einen Catalog oder Stock sowie einen seiner
(potentiellen) Einträge betreen, übergeben8 . Er zeichnet diese Aktion im Rahmen einer
Transaktion, die mehrere solcher Aktionen beinhaltet, auf. Zu einem späteren Zeitpunkt
wird dann über das gesamte Bündel an Aktionen, die zusammen mit diesem DataBasket ausgeführt wurden, dahingehend entschieden, ob diese insgesamt ausgeführt (commit )
werden oder insgesamt nicht stattnden (rollback ).
In diesem Zusammenhang ist es wichtig, dass eine solche Transaktion die entsprechenden Ressourcen zwar belegt, aber die Auswirkungen erst nach Bestätigung der gesamten
Transaktion sichtbar werden. Das heiÿt, man kann im Rahmen einer Transaktion ein CatalogItem mit dem Key xyz aus einem Katalog entfernen, womit das CatalogItem für alle
Betrachter aus dem Katalog verschwindet. Ein neuer Katalogeintrag mit demselben Key
xyz kann aber nur über denselben DataBasket eingefügt werden, der auch verwendet wurde, um den ersten Eintrag zu entfernen. Alle Versuche anderer DataBaskets würden mit
einer DataBasketConflictException solange scheitern, bis der erste DataBasket alle seine Aktionen durch ein commit oder rollback bestätigt oder zurückgenommen hat. Analog
gilt für das erstmalige Hinzufügen eines CatalogItem mit Key abc, dass dieses CatalogItem
nur für den verwendeten DataBasket sichtbar ist, solange bis dieser seine Aktion durch ein
commit bestätigt. Allerdings ist es für andere Transaktionen bis dahin ebenso unmöglich,
ein CatalogItem mit demselben Key abc in den Katalog einzufügen.
public CatalogItem removeTheKey(Catalog c, String sKey, DataBasket db) {
CatalogItem ci = null;
try {
// wir versuchen den Katalogeintrag zu entfernen
ci = c.remove(sKey, db);
} catch (DataBasketConflictException dbce) {
// dabei ist ein Datenkorb-Konflikt aufgetreten und wir fragen
// den Nutzer, ob wir die Aktion erneut versuchen wollen
int iDecision = javax.swing.JOptionPane.showMessageDialog(
null,
"Erneut versuchen?",
"Datenkorb-Konflikt beim Zugriff auf einen Katalogeintrag!",
javax.swing.JOptionPane.YES_NO_OPTION
);
// wenn der Benutzer einen neuen Versuch starten will,
8 wird
null übergeben oder eine Methode verwendet, die keinen DataBasket erfordert, so muss in der
Regel davon ausgegangen werden, dass ein anonym erstellter Datenkorb verwendet wird
54
KAPITEL 3. VERWENDUNG DURCH PROGRAMMIERER
}
}
// verwenden wir einen rekursiven Aufruf dieser Methode
if (iDecision == javax.swing.JOptionPane.YES_OPTION) return removeTheKey(c,sKey
,db);
return ci;
Listing 3.9: Abfangen von DataBasket-Konikten
Der Programmierer sollte in seiner Implementierung stets mit solchen Konikte rechnen,
wenn mehrere EPoints gleichzeitig auf einen Datenbestand zugreifen. Daher muss er auf jeden Fall an den kritischen Stellen in einer try-catch-Umgebung die Reaktion des
Frameworks (die DataBasketConflictException) abfangen und entsprechend reagieren.
Wie das aussehen kann, ist an einem Beispiel in Listing 3.9 gezeigt.
3.1.9 Listener
Catalog und Stock implementieren beide das ListenableContainer-Interface und sind somit in der Lage, Listener zu verwalten. Listener sind in diesem Zusammenhang spezielle
Objekte, die von Aktionen in den Containern benachrichtigt werden und in manchen Fällen in der Lage sind ein Veto gegen bestimmte Aktionen, in Form einer VetoException,
einzulegen. Listener stellen damit ein wichtiges Instrument zur Einhaltung von Konsistenzbedingungen für bestimmte Datenbestände dar.
Da das Framework jedoch für die Erstellung verteilter Anwendungen entwickelt wurde,
können Listener nicht im herkömmlichen Sinne gehandhabt werden. Das Problem ist, dass
alle Datenbestände zentral vom Shop verwaltet werden und auch nur dort als Objekte
vorliegen. Allerdings sind diejenigen, die an Aktionen in den Datenbeständen interessiert
sind, im Normalfall die auf entfernten Rechnern laufenden EPoints. Diese stellen eigene
Programme dar, die beendet werden können und dann mitsamt ihrem Listener verloren
gehen. Ist der entsprechende Listener aber dafür zuständig, in bestimmten, für den EPoint
relevanten Situationen, ein Veto einzulegen, kann er dies für den Zeitraum, in dem der
EPoint oine ist, nicht mehr tun.
Naheliegend ist, die Listener an den Shop zu binden, damit sie immer bei ihren jeweiligen Containern bleiben und dort mit ihnen persistent gemacht werden können. Diese
Möglichkeit hat aber den Nachteil, dass der EPoint keinen echten Einuss mehr auf den
Listener hat. Da die Vorteil der ersten Lösung die Nachteile der zweiten sind und umgekehrt, werden beide Alternativen zur Verwendung angeboten. So kann man, wenn es auf
Interaktion mit dem Listener ankommt, einen RemoteListener beim Container anmelden,
wenn der EPoint gestartet wird. Kommt es hingegen darauf an, auch in Abwesenheit des
EPoint, Konsistenzeigenschaften im Datenbestand zu sichern, so verwendet man einen
PersistentContainerListener, der einmal beim Container angemeldet und dann mit diesem zusammen gespeichert wird.
ListenableContainer (Seite 136) deniert daher zwei Methoden:
3.1. ANLEITUNG ZUR ANWENDUNGSENTWICKLUNG
55
1. addContainerListener: zum Hinzufügen von RemoteListenern (normale Listener). Diese werden nur solange vom Container informiert, wie bei der Verwendung
von RMI kein Fehler auftritt also der EPoint erreichbar ist.
Diese Art Listener bendet sich direkt beim EPoint und der RMI-Mechanismus
wird vom Shop aus benutzt, um den Listener beim EPoint über Aktionen im Datenbestand zu informieren. Damit kann man z.B. dem Benutzer des EPoint die
Möglichkeit geben, auf Aktionen im Datenbestand zu reagieren. Der Nachteil ist,
dass der Listener bei der Beendigung des EPoint ebenfalls verschwindet.
2. addPersistentContainerListener: zum Hinzufügen von PersistentContainerListenern. Diese können zwar beim EPoint instanziiert werden, müssen bei der Registrierung am Container dann aber serialisiert und im Container re-instanziiert
werden. Von da an können sie nur noch über eine Kennung, die bei der Registrierung zurückgegeben wird, wieder entfernt werden.
Der Vorteil dieser Listener ist, dass si stets zusammen mit seinem Container persistent ist und immer vor Ort Vetos gegen bestimmte Aktionen einlegen kann. Das
gilt auch dann, wenn der erzeugende EPoint nicht online ist.
Der Nachteil liegt in der schon beschriebenen Tatsache, dass der EPoint keinen weiteren Einuÿ auf die Reaktionen des Listeners hat, der sich ja als kopiertes Objekt
im Shop bendet.
Eine besondere Möglichkeit steht allen Listener-Objekten zur Verfügung, nämlich sich
selbst völlig autonom wieder vom Container zu entfernen, indem er eine DetachListenerException wirft. Diese wird vom Container aufgefangen und als Signal verstanden, dass
er den Listener über keine Aktion mehr informieren soll. Das ist interessant für die persistenten Listener und für Ein-Weg-Listener, die nur ein bestimmtes Ereignis abwarten,
darauf reagieren und danach nicht mehr gebraucht werden.
Da beim Shop/Container persistente Listener auch einen persistenten Zustand besitzen,
der in meiner Implementierung die Zustandsdenition des jeweiligen Containers ergänzt,
benötigt man die Möglichkeit den Container über eine Zustandsänderung des Listeners
zu informieren. Das ist immer dann notwendig, wenn ein PersistentListener lokale und
nicht-transiente Attribute besitzt, die bei Änderungen auch in der persistenten Fassung
aktualisiert werden müssen.
Weil diese Listener keine eigenständigen PersistentObjects sind und der ListenableContainer für deren Verwaltung zuständig ist, stellt dieser eine Methode updatePersistentListener() zur Verfügung. Mit Hilfe dieser Methode und seiner eigenen ListenerID,
kann ein Listener den Container über eine Zustandsänderung seinerseits informieren. Der
Container wird daraufhin die persistente Fassung des Listeners aktualisieren.
56
KAPITEL 3. VERWENDUNG DURCH PROGRAMMIERER
3.1.10 Logles
Im Package epoint.log wird ein einfaches System zur Erstellung von Logles implementiert. Dazu gehört die Klasse Log (Seite 313), die Informationen in Form von LogEntryObjekten formatiert auf einen OutputStream9 schreibt. Zur Auswahl stehen verschiedene
Formate:
• normale Textzeilen, die die geloggten Informationen enthalten
• in XML formatierter Text, der die Informationen enthält
• die nach XML serialisierten LogEntry-Objekte
• normal Java-serialisierte LogEntry-Objekte
Alternativ stellt das DBPersistenceManager-Interface Methoden für das Schreiben von
LogEntry-Objekten in die Datenbank zur Verfügung.
Zum Filtern von zu schreibenden oder zu lesenden LogEntries steht das Interface LogFilter zur Verfügung. Es deniert nur eine einzige Methode isLogged(LogEntry), die
entscheidet, ob das übergebene Objekt gelesen / geschrieben werden kann oder nicht.
Ein LogEntry ist dabei vielseitig kongurierbar und enthält nicht nur Informationen über
Ursprung und Inhalt der Information, sondern auch über deren Dringlichkeit bzw. Bedeutung.
Der Shop verwendet in meiner Implementation standardmäÿig drei verschiedene Logles:
1. log.shop.error.xml speichert ein in XML formatiertes LogFile, in das aufgetretene
Exceptions geloggt werden. Informationen können mit Hilfe von logException(Exception) (Seite 327, Zeile 675) in diese Datei geschrieben werden. Die StandardImplementierung erzeugt zusätzliche Ausgaben auf stdout für jede geloggte Exception. Alle Methoden, die eine Exception innerhalb der Standardimplementierung des
Frameworks auffangen, senden die Exception an diese Methode!
2. log.shop.info.xml speichert Informationen ebenfalls in XML-Form. Die Methode
logInfo(...) (Seite 327, Zeile 691) kann dazu verwendet werden, um Informationen in diese Datei zu schreiben. Diese Methode ist normalerweise dazu gedacht,
Debug-Informationen zu speichern und ndet von der Standard-Implementierung
des Frameworks zunächst keine Verwendung.
3. log.shop.persistence.txt speichert Informationen in Textform, die von PSQLDBConnection erzeugt werden. Sie hat standardmäÿig ein sehr hohes Datenaufkommen, da die Datei alle an die Datenbank gesendeten SQL-Statements enthält.
9 in
der Implementation java.io.Writer
3.2. ALLGEMEINE BEMERKUNGEN UND HINWEISE
57
3.2 Allgemeine Bemerkungen und Hinweise
Für die XML-Serialisierung, wie sie in Abschnitt 2.3 beschrieben wird, ist das KoalaFramework [KoDyXML] notwendig. Die vom Framework verwendeten Klassen benden
sich in der Datei epoint/addons/koala_koml.jar, welche in den CLASSPATH von Java
eingebunden werden muss. An derselben Position bendet sich auch die Datei jdbc7.2dev-1.2.jar. Diese enthält die für die Benutzung des Persistenzmanager notwendigen
Treiberklassen, für die Verwendung einer PostGreSQL-Datenbank und muss ebenfalls Bestandteil des CLASSPATH sein.
Zum Lesen von XML beim Deserialisieren von Objekten, verwendet das Koala-Framework
die Simple API for XML (SAX). Das ist eine Schnittstellendenition, die das Lesen von
XML und die Verarbeitung groÿer XML-Dateien erlaubt. In Java liegt die Denition eines
SAX-Parsers bereits als Schnittstellendenition vor. Für diese Schnittstelle gibt es wieder
mehrere Implementierungen. Eine davon liegt dem Koala-Framework bei und wird standardmäÿig verwendet. Allerdings muss die Position der zu verwendenden Implementierung
durch eine System-Property bekannt gemacht werden. Das geschieht mit dem Befehl:
System.setProperty("org.xml.sax.parser",
"fr.dyade.koala.xml.sax.SAXParser");
Das zweite Argument gibt die zu verwendenden SAX-Parser-Klasse an. Diese kann auch
durch jede andere SAX-Implementierung ersetzt werden, solange sie sich im CLASSPATH
bendet.
Aus Erfahrung sollten die Einstellungen des CLASSPATH nicht nur in der verwendeten
Entwicklungsumgebung (IDE) vorgenommen werden, sondern in den Einstellungen des
Betriebssystems (z.B. in der autoexec.bat-Datei). Das liegt daran, dass die rmiregistry,
die Objekte für andere Maschinen zum Aufruf bereitstellt, ebenfalls über den CLASSPATH
auf die Klassen zugreift. Die rmiregistry wird aber unabhängig von der IDE in einem
eigenen Prozess gestartet!
3.3 Technische Voraussetzungen
In diesem Abschnitt sollen vor allem die technischen Voraussetzungen für die Verwendung
von RMI und Datenbanken erläutert werden. Das beinhaltet unter anderem eine Anleitung
zur Installation des DBMS ProstGreSQL.
58
KAPITEL 3. VERWENDUNG DURCH PROGRAMMIERER
3.3.1 Voraussetzungen für die Datenbankverwendung
Die Standardimplementierung des EPoint-Framework verwendet eine PostGreSQL Datenbank [PGSQL]. Die Installation und Konguration der Datenbank werden im folgenden
einmal für Linux und einmal für Windows erläutert. Dabei erfordert die Installation von
PostGreSQL eine UNIX-ähnliche Umgebung, auf die im Fall von Windows auch eingegangen wird. Der darauolgende Abschnitt zur Konguration ist dann wieder für beide
Systeme gültig.
3.3.1.1 Installation unter Linux (RedHat)
Für eine Installation unter Linux steht die Möglichkeit zur Verfügung die aktuelle PostGreSQL-Version in Form von *.rpm-Dateien aus dem Internet [PGSQL] herunterzuladen.
Für die zum Zeitpunkt der Erstellung dieses Dokuments aktuellste Version 7.1.3 sind
die entsprechenden Dateien für Linux-RedHat-7.1 und Mandrake-8.0 verfügbar. Benötigt
werden dazu folgende Dateien:
• postgresql-7.1.3.i386.rpm
• postgresql-contrib-7.1.3.i386.rpm
• postgresql-libs-7.1.3.i386.rpm
• postgresql-jdbc-7.1.3.i386.rpm
• postgresql-server-7.1.3.i386.rpm
Zur Installation benötigt man Superuser-Rechte unter Linux (root). Dabei kann man
folgendermaÿen vorgehen:
1. in das temporäre Verzeichnis wechseln: cd /tmp
2. ein neues Verzeichnis erstellen: mkdir posgresql-inst
3. in das neu erstellte Verzeichnis wechseln: cd postgresql-inst
4. die rpm-Dateien in das aktuelle Verzeichnis kopieren: cp <dateien> .
5. die Installation starten mit: rpm -ivh *.rpm bzw. eine vorherige Installation upgraden mit rpm -U *.rpm
Im Anschluss daran steht je nach verwendeter Linux-Distribution das PostGreSQL-DBMS
zur Verfügung und wird als Dienst bei Systemstart geladen.
Als Alternative kann man den aktuellen Quellcode als .tar.gz Datei aus dem Internet
herunterzuladen. In diesem Fall sollten unbedingt die den Quelltexten beiliegenden Dokumentationen zur Installation gelesen werden! Im Normalfall sind dann die folgenden
Schritte zur Installation notwendig:
3.3. TECHNISCHE VORAUSSETZUNGEN
59
1. Herunterladen der aktuellen Quelldateien aus dem Internet
(z.B. postgresql-7.1.3.tar.gz) in das Verzeichnis /usr/src
2. In dieses Verzeichnis wechseln: cd /usr/src
3. Entpacken der Quelldateien: tar -xzf postgresql-7.1.3.tar.gz
4. Wechseln in das neu erstellte Verzeichnis: cd postgresql-7.1.3
5. An dieser Stelle bitte die Datei INSTALL lesen und ggf. die Dateien im Verzeichnis ./doc!
6. Übersetzen der Quelldateien und Installation in folgenden Schritten:
(a) ./configure
(b) gmake (oder alternativ make)
(c) gmake install (oder alternativ make install)
(d) adduser postgres erstellt einen neuen Benutzer auf dem Linux-System, mit dessen Rechten und mit dessen
Identität die Datenbank gestartet und verwaltet wird
(e) su - postgres
(f) /usr/local/pgsql/bin/initdb -D /usr/local/pgsql/data
(g) /usr/local/pgsql/bin/postmaster -i -D /usr/local/pgsql/data >logfile 2>&1 &
(h) /usr/local/pgsql/bin/createdb test
(i) /usr/local/pgsql/bin/psql test
Die letzten beiden Schritte erstellen und testen eine Datenbank, um die erfolgreiche Installation zu überprüfen. Der Befehl davor startet den Datenbankmanager, der Anfragen
von Clients entgegennimmt. Dieser muss immer dann ausgeführt werden, wenn Zugrie
auf die Datenbank erforderlich sind und der Datenbankserver noch nicht gestartet wurde.
Im folgenden Abschnitt 3.3.1.3 wird davon ausgegangen, dass /usr/local/pgsql/bin
entweder in der PATH-Variablen enthalten ist oder der Benutzer sich in diesem Verzeichnis
bendet (in diesem Fall ist allen im folgenden genannten Befehlen ein "./" voranzustellen).
3.3.1.2 Installation unter Windows 2000
Unter WindowsNT und neuer ist eine unmittelbare Installation von PostGreSQL nicht
möglich. Vielmehr wird dazu eine Umgebung für Linuxprogramme benötigt, die unter
dem Namen Cygwin ebenfalls frei erhältlich im Internet ist [CYGWIN]. Zur Installation
dieser Umgebung sollten die Hinweise auf der Internetseite gelesen werden! Zusätzlich
wird ein Paket benötigt, welches Hilfsroutinen für Cygwin zur Verfügung stellt und ebenfalls im Internet frei erhältlich ist [CYIPC]. Zur Installation des zuletzt genannten Pakets
stehen wieder die Quelldateien zur Verfügung oder als Alternative die bereits übersetzten benötigten Dateien. Zur Installation der Software ist in diesem Fall unbedingt die
entsprechende Dokumentation der Entwickler in den beiliegenden Dateien und / oder im
Internet zu lesen.
Nachdem diese Umgebung richtig installiert und konguriert ist, kann man innerhalb der
60
KAPITEL 3. VERWENDUNG DURCH PROGRAMMIERER
Linuxumgebung PostGreSQL mit den Quelldateien (wie im vorangegangenen Abschnitt
beschrieben) selbst installieren und von seinem Windowsrechner aus benutzen.
Hinweis: Beim Testen einer PostGreSQL-Installation (aktuelle Version 7.1.3) in der
Cygwin-Umgebung ist zumindest bei mir ein Problem bei der Ausführung des Befehls
make aufgetreten. Der Compiler meldet ein Problem mit einer unerfüllten Referenz auf
filename_completion_function(). Diese kann man beheben, indem man im Unterverzeichnis ./src/bin/psql/ die Datei tab-complete.c önet und alle Vorkommnisse dieser
Funktion durch rl_filename_completion_function() ersetzt!
3.3.1.3 Konguration des DBMS PostGreSQL
Zur allgemeinen Konguration von PostGreSQL liegt den Quelldateien umfangreiche Dokumentation zur Administration und Benutzung bei (existiert auch als rpm-Datei). Diese
Dokumentation ist ebenso im Internet [PGSQL] lesbar.
Im Folgenden werden daher nur die Schritte beschrieben, die unbedingt notwendig sind,
um das EPoint-Framework mit einer PostGreSQL Datenbank zu betreiben. Dabei werden keine besonderen Sicherheitsvorkehrungen betrieben10 ! Wichtig für eine Benutzung
im Netzwerk ist zunächst, dass der postmaster11 mit der Option -i gestartet wird. Andernfalls ist eine Benutzung von PostGreSQL nur auf dem Rechner möglich, auf dem das
DBMS installiert wurde!
Für die folgenden Ausführungen zur Konguration des DBMS ist wichtig, dass man die
Identität des bei der Installation erstellten DBMS-Systembenutzers postgres annimmt
(als root mit su - postgres) und die entsprechenden Befehle entweder im System-PATH
enthalten sind oder im aktuellen Verzeichnis12 zur Verfügung stehen.
Datenbank : Damit das Framework seine Daten in einer Datenbank speichern kann,
muss zunächst einmal eine solche erzeugt werden. Dazu erstellt man mit dem Befehl createdb epoint eine neue Datenbank mit dem vom Framework verwendeten
Defaultnamen epoint. Selbstverständlich kann auf diese Weise jede anders benannte
Datenbank erzeugt werden, die bei Anpassung des Frameworks genauso verwendet
werden kann. Auf diese Weise ist es möglich, auf einem Rechner mehrere Datenbanken für mehrere Shops bereitzustellen!
Netzwerkzugri : Nach der Installation bendet sich im Verzeichnis /usr/local/pg-
sql/data/ (manchmal auch unter /var/lib/pgsql/data) die Datei pg_hba.conf.
Diese Datei regelt den Zugri auf die von PostGreSQL gehosteten Datenbanken.
Um einen Zugri für den frameworkseitigen Default-Benutzer epoint aus demselben
Subnetz heraus und unter Verwendung eines verschlüsselten Passworts zu erlauben,
muss folgende Zeile an das Ende dieser Datei gestellt werden:
10 Dafür
sollte die den Quellen beiliegende Dokumentation für den Administrator gelesen werden!
das DBMS im System
12 default: /usr/local/pgsql/bin
11 startet
3.3. TECHNISCHE VORAUSSETZUNGEN
61
host epoint 137.193.211.0 255.255.255.0 crypt
Diese Zeile bewirkt, dass jedem Rechner aus demKlasse C Subnetz 137.193.211.0/24
der Zugri auf die Datenbank epoint erlaubt wird, sofern er sich mit einem verschlüsselten (crypt) Passwort anmeldet. Als Alternative kann der Zugri auf alle
Datenbanken (Name: all) gewährt werden oder nur einzelnen Rechnern der Zugri auf bestimmte Datenbanken (Subnetzmaske 255.255.255.255 unter Angabe
der entsprechenden IP-Adresse des Rechners). Das bedeutet, dass z.B. in der Praktikumsverwendung nur ein einziger leistungsstarker Rechner entsprechend konguriert
werden muss, um mehrere Datenbanken für verschiedene Shops zur Verfügung zu
stellen. Diese und alle weiteren Möglichkeiten sind normalerweise in derselben Datei
dokumentiert. Nach Änderungen in dieser Datei muss der postmaster gegebenenfalls beendet und neu gestartet werden!
Datenbanknutzer : Als nächstes muss man einen Datenbankbenutzer erstellen, in des-
sen Namen das Framework später auf die Datenbank zugreift. Dieser Benutzer ist
rein PostGreSQL-spezisch und hat nichts mit dem verwendeten Betriebssystem zu
tun. Man erstellt den Benutzer mit dem Befehl createuser -D -A, dessen Namen
und Passwort im Anschluss abgefragt werden13 . Dieser Benutzer darf keine neuen
Benutzer erstellen und hat auch nicht das Recht neue Datenbanken einzurichten.
Nach diesen Schritten kann sich, gemäÿ den eben angeführten Beispielen bei der Konguration, der Benutzer epoint an die Datenbank epoint anmelden14 und diese mit SQLBefehlen manipulieren. Eine einfache Möglichkeit dazu bietet das mitgelieferte Terminal
für den Textmodus. Mit psql -U epoint -d epoint kann man sich lokal als Benutzer
epoint an die Datenbank epoint anmelden und SQL-Befehle eingeben.
Unabhängig davon besteht auch die Möglichkeit mit Hilfe von ODBC-Treibern und/oder
einem entsprechenden Client auf die Datenbank zuzugreifen. Dazu gibt es im Internet
zahlreiche und frei erhältliche Programme.
3.3.2 Voraussetzungen für die Verwendung von RMI
Verwendung von RMI im Zusammenhang mit dem EPoint Framework bedeutet, dass der
Shop auf einem Rechner Objekte zur Verwendung für den EPoint auf einem anderen Rechner15 zur Verfügung stellt. Der Client kann dann eine Verbindung zum Server aufbauen
und die Objekte des Servers für Methodenaufrufe verwenden so kann der Shop Dienste
und Ressourcen für den EPoint zur Verfügung stellen. Die wichtigsten Objekte, die der
13 Die
vom Framework verwendeten Standardwerte für Name und Passwort bei der Anmeldung sind
epoint und epoint.
14 unter Beachtung der in pg_hba.conf denierten Zugrisrichtlinien
15 die Verwendung eines zweiten Rechners ist dabei nicht obligatorisch, eine zweite virtuelle Maschine
auf demselben Rechner kann ebenfalls benutzt werden RMI kommuniziert dann aber trotzdem über die
Netzwerkebene
62
KAPITEL 3. VERWENDUNG DURCH PROGRAMMIERER
Server zur Verfügung stellt, sind die Datenobjekte, um deren Persistenz sich der Shop
ebenso kümmert, wie um die Konsistenz der bearbeiteten Daten.
Da die zur Verfügung gestellten Objekte von allen EPoints benutzt werden können und
somit mehrere EPoints auf Datenobjekte des Servers gleichzeitig zugreifen, stellen die
EPoints beim Shop eigene Threads dar, gegen die alle von den Serverobjekten erreichbaren Datenobjekte entsprechend geschützt werden müssen.
Dieser Abschnitt geht im Wesentlichen auf die eben beschriebene Verwendung von RMI
durch das EPoint-Framework ein. Dabei werden nur Grundanforderungen erläutert und
im Anschluss daran kurz auf die Verwendung durch den Programmierer eingegangen. Für
detailliertere Ausführungen zum Thema RMI verweise ich an dieser Stelle zum einen auf
die RMI-Homepage [RMI] und zum anderen auf die Studienarbeit von Stephan Moritz
[SASM01].
3.3.2.1 Technische Anforderungen
Voraussetzung für die Verwendung von Remote Method Invocation ist, dass sich alle beteiligten Rechner in einem IP-Netzwerk benden und einander über dieses Netzwerk erreichen können. In der Regel benden sich dazu alle Clients im selben Subnetz wie der
Server.
Zum Betrieb müssen auf der Seite des EPoints die IP-Adresse bzw. der Hostname des
Rechners16 bekannt (und entsprechend eingestellt) sein, auf dem der Shop gestartet wurde. Wichtig ist, dass der Shop als Server stets vor allen EPoints gestartet werden muss,
andernfalls scheitert die Aufnahme einer Verbindung durch den EPoint mit einer Exception auf die der Programmierer entsprechend reagieren kann, indem er z.B. eine erneute
Registrierung beim Shop versucht. Der Shop muss bei seiner Initialisierung in einem eigenen Thread den Registry Server starten. Ein entsprechendes Programm liegt dem JSDK
von Sun bei und bendet sich im Installationspfad im Verzeichnis /bin unter dem Namen
rmiregistry. Dieses Programm ist die Schnittstelle, die für RMI-Anfragen an den Server
benötigt wird. Alternativ kann der RegistryServer bereits zuvor (z.B. automatisch beim
Rechnerstart) gestartet werden und zur Verfügung stehen.
3.3.2.2 Firewall und Proxy
Der Registryserver hört standardmäÿig auf Port 1099 auf Anfragen von Clients. Allerdings ist es möglich ihn so zu kongurieren, dass er auch auf anderen Ports auf Anfragen
wartet. Eine entsprechende Firewall ist so zu kongurieren, dass er den Verbindungsaufbau über diesen Port zuläÿt und weitere Ports für den nachfolgenden Verbindungsaufbau
genutzt werden können.
Der RMI-Mechanismus von Java ist auÿerdem in der Lage, einen Proxy zum Zugri auf
16 z.B.
137.193.211.113 bzw. popeye.studfb.unibw-muenchen.de
3.3. TECHNISCHE VORAUSSETZUNGEN
63
RemoteObjects zu verwenden. Dazu gibt es in Abhängigkeit vom Proxy verschiedene Möglichkeiten zur Konguration von RMI, die auf der RMI-Homepage [RMI] im Bereich der
Frequently Asked Questions sehr genau erläutert werden.
64
KAPITEL 3. VERWENDUNG DURCH PROGRAMMIERER
Kapitel 4
Implementierung des
EPoint-Framework
Dieses Kapitel widmet sich meiner Implementierung der Frameworkklassen und ist vor
allem für Entwickler gedacht, die das Framework ausbauen und weiterentwickeln werden.
Dazu werden einige Besonderheiten und Probleme aufgezeigt und erläutert, wie ich die
Probleme bewältigt habe. Ich habe bereits in vorangegangenen Kapiteln zum Verständnis
und zur Anleitung für den Programmierer mehrere Male erklärt, wie einige Probleme (z.B.
Referenzierung persistenter Objekte, Besonderheiten von RMI u.ä.) von mir gelöst und
implementiert wurden. Daher wird hier die Struktur der Implementierung im Überblick
erläutert. Details ndet man an den relevanten Stellen im Quellcode in Form von ausführlichen Kommentaren. Weiterhin ist die JavaDoc bei den Klassenbeschreibungen und
an den notwendigen Stellen (vor allem in den Interfaces) in den Methodenbeschreibungen
sehr ausführlich.
4.1 Datenbankimplementierung
Die Benutzung einer Datenbank wurde von mir im Wesentlichen durch die Verwendung des
DBPersistenceManager zentralisiert. Implementationen dieses Interfaces stellen den einzigen direkten Kommunikationsweg mit der Datenbank dar. Anstatt also SQL-Connections
direkt zum Ausführen von Statements immer dann zu nutzen, wenn dies nötig ist, werden
diese Aufgaben generalisiert und im Interface speziziert.
Bei der Implementierung des Interfaces in PSQLDBConnection muss man vor allem beachten, wieviele Connections zur Datenbank geönet werden. Önet man beispielsweise für
jeden DataBasket eine eigene Datenbankverbindung und berücksichtigt dabei nicht ein
komplexes Table-/Row-Locking, erzeugt man garantiert Deadlocks. Daher habe ich mich
65
66
KAPITEL 4. IMPLEMENTIERUNG DES EPOINT-FRAMEWORK
entschieden, für den Zugri auf gleiche Tabellen, nur jeweils genau eine Connection zu verwenden. Das heiÿt, dass dem Framework für die Manipulation der Datenstrukturen nur
eine Datenbankverbindung zur Verfügung steht. Jeweils eine weitere Datenbankverbindung wird genutzt, um persistente Properties in der Tabelle Properties zu manipulieren
und um die Tabelle mit den LogEntries zu bearbeiten. Jede weitere Parallelität beim Datenbankzugri würde umfangreiche Deadlockprävention erfordern, die durch den Vorteil
der Parallelität keinesfalls zu rechtfertigen wäre. Allerdings stellt diese Umsetzung damit
auch das Nadelöhr für eine sehr groÿe Anwendung mit vielen EPoints und hohem Parallelitätsgrad dar.
Um den parallelen Zugri durch mehrere Threads auf den Persistenzmanager zu serialisieren, muss eine Datenbankverbindung immer erst explizit angefordert werden. An dieser
Stelle wird, wie in Abb. 3.2 auf Seite 50 gezeigt, der Zugri auf die Datenbankverbindung
serialisiert.
Weiterhin bestimmt die Schnittstelle epoint.data.PersistentObject (Seite 138) die
Funktionalität eines für die Persistenz durch den DBPersistenceManager bestimmten Objekts. Darin enthalten sind vor allem folgende Methoden:
• setOID(String) bzw. getOID(), die vom Persistenzmanager verwendet werden einem gespeicherten Objekt eine explizite Identität zuzuweisen, die es in der Datenbank identizierbar macht.
• loadPersistentData(...) stellt eine Art zweiten Konstruktor für persistente Objekte dar. Der Persistenzmanager ruft diesen immer dann auf, wenn ein Objekt
gerade deserialisiert wurde, um diesem die Möglichkeit zu geben weitere wichtige
Daten zu initialisieren (z.B. transiente Attribute).
• updatePersistentObject(...) ist immer so zu implementieren, dass es die im
Parameter spezizierten Properties in der persistenten Fassung desselben Objekts
aktualisiert. Diese Methode wird normalerweise immer von innerhalb desselben Objekts aufgerufen, wenn dieses seinen Zustand ändert.
• getSerializedForm() liefert die mit dem im Shop aktuell gesetzten Serializer erzeugte serialisierte String-Version dieses Objekts, wie sie in die Datenbank gespeichert und zur Wiederherstellung genutzt wird.
Die konsequente Nutzung der Möglichkeiten zum Speichern, Laden und Aktualisieren
durch den DBPersistenceManager gewährleistet für die PersitentObjects eine Art online Persistenz, bei der der Persistenzmanager zu jedem Zeitpunkt über den zuletzt bestätigten
Zustand der Objekte verfügt. Auf diese Weise sind die von mir implementierten persistenten Frameworkklassen immer mit dem letzten bestätigten Zustand in der Datenbank
gespeichert wie bei einer objektorientierten Datenbank.
Eine weitere wichtige Tatsache ist die folgende: Wird ein Objekt serialisiert und anschlieÿend deserialisiert, so hat man danach nicht die Referenz auf das originale Objekt, sondern
4.2. RMI-IMPLEMENTIERUNG UND DATENKLASSEN
67
eine Referenz auf einen Klon davon. Dies führt dazu, dass der Persistenzmanager in der
Lage sein muss zu erkennen, welche Objekte bei einer Anfrage bereits deserialisiert wurden und sich als Instanz in der virtuellen Maschine benden und welche erst aus der
Datenbank beschat werden müssen.
Dazu wird in meiner Implementierung ein Cache eingesetzt, der alle erstmals deserialisierten Objekte schwach referenziert (durch java.lang.ref.WeakReference). Das ermöglicht zwar eine Garbage-Collection der Objekte, falls diese nicht mehr in Benutzung
sind, aber gleichzeitig hat man immer eine Referenz auf alle die Objekte, die sich noch
in der virtuellen Maschine benden. Der Cache wird also immer dann mit neuen Objekten versorgt, wenn sie zum ersten Mal in die Datenbank geschrieben werden oder, wenn
sie aus der Datenbank gelesen werden. Ein Aktualisieren der Objekte im Cache ist nicht
notwendig, da es sich im Cache um Referenzen auf die Objekte handelt, die somit immer
genau das aktuelle Objekt in seinem aktuellen Zustand bezeichnen. Der Cache wird allerdings nicht(!) benutzt, um Schreibvorgänge zu puern! Das Schreiben von Daten in die
Datenbank ndet immer direkt und sofort statt.
Durchsucht man den Cache vor jedem Zugri auf die Datenbank nach dem gewünschten Objekt, kann es zu keinem Zeitpunkt dazu kommen, dass ein Objekt (mit derselben
semantischen Identität) zweimal in der virtuellen Maschine vorkommt. Das hat ganz nebenbei auch noch den positiven Eekt der Beschleunigung des Gesamtsystems, da ein
Lesen in diesem Cache wesentlich ezienter ist, als das Lesen aus der Datenbank und die
anschlieÿende Deserialisierung.
4.2 RMI-Implementierung und Datenklassen
Die Datenklassen sind ganz allgemein die Klassen, die durch die Interfaces in epoint.data
speziziert und in epoint.data.rmi implementiert werden. Sie denieren die zentralen
Objekte für den Remotezugri auf den Shop. Daher ist besonders bei diesen Klassen sehr
viel Wert auf eine optimale threadsichere Implementierung zu legen. Allerdings ist es an
vielen Stellen sehr schwer zu durchschauen, welche Threads auf die Klassen zugreifen und
wann der Schutz durch interne Monitore zu Deadlocks führt. Dazu folgendes Beispiel:
Bei der Implementierung von CatalogImpl wird beim Entfernen eines CatalogItem mit
remove ein Monitor verwendet, der die Datenstrukturen schützt, die zum Speichern aller
CatalogItems im Katalog notwendig sind. Werden während des aktiven Schutzes Listener
befragt, ob das Entfernen erlaubt ist, bzw. darüber informiert, dass gerade ein CatalogItem
entfernt wurde, so kann dies zu einem Deadlock führen. Der Grund ist, dass der Aufruf
der Methoden der Listener einen RMI-Call darstellt1 und dieser Aufruf wiederum einen
eigenen Thread. Versucht dieser Thread/Listener seinerseits auf denselben Katalog zuzugreifen um Informationen über die Katalogeinträge zu erhalten, wird er blockiert werden,
da sich diese ja momentan in Bearbeitung benden und durch einen Monitor geschützt
sind.
1 auch
wenn sie in derselben virtuellen Maschine exisitieren
68
KAPITEL 4. IMPLEMENTIERUNG DES EPOINT-FRAMEWORK
Dieses Beispiel zeigt deutlich die komplexen Auswirkungen der Nutzung von RMI. Denn
Mengen von Threads, die logisch eigentlich ein einziger sind, stellen in der Praxis bei
der Verwendung von RMI eine Kette mehrerer hintereinandergeschalteter Threads dar.
Einzig aus diesem Grund wurde das Interface RemoteRunnable deniert. Dieses speziziert nur eine Methode run() und wird dazu benutzt anfallende Arbeiten zu sammeln
und zu einem späteren Zeitpunkt auszuführen. Diese Technik wird von mir angewandt,
um z.B. die Nachrichten an die Listener während eines rollback oder commit von Objekten der Klasse DataBasket zu sammeln und erst nach dem kompletten Vorgang zu senden:
while (lDBEntries.size() > 0) { // lDBEntries enthaelt alle Eintraege im DataBasket
// ich entferne den naechsten DBEntry aus der Liste
DBEntry dbe = (DBEntry)lDBEntries.remove(lDBEntries.size()-1);
// ich gebe dem DBEntry die Moeglichkeit einen Cache mit
// Hilfe einer Datenbankverbindung (Passwort) zu initialisieren
dbe.initCache(sPasswd);
// ich benachrichtige den Container, auf den sich der DBEntry bezieht
// ueber den Commit, und erhalte ein Buendel von Aufgaben, in Form
// eines RemoteRunnable, das zu einem spaeteren Zeitpunkt ausgefuehrt wird
RemoteRunnable r = dbe.getTarget().commitTransaction(dbe,sPasswd);
lRunnables.add(r); // lRunnables speichert alle RemoteRunnable fuer die
spaetere Aufuehrung
Shop.getTheShop().getDBPersistenceManager().deleteDBEntry(dbe,con);
}
// die Transaktion der Datenbankverbindung fuer den gesamten Commit wird
// ebenfalls bestaetigt
con.commit();
// und die Datenbankverbindung wird freigegeben
returnConnection(con);
// erst jetzt werden die Aufgaben der Container in Form von
// RemoteRunnables ausgefuehrt, da erst jetzt wieder eine
// Datenbankverbindung zur Verfuegung steht
Iterator itRunnables = lRunnables.iterator();
while (itRunnables.hasNext()) {
RemoteRunnable r = (RemoteRunnable)itRunnables.next();
r.run(null);
itRunnables.remove();
}
Listing 4.1: Codeausschnitt aus einem DataBasket-commit
Im Codeausschnitt wird die Arbeit, die zu erledigen ist, um die Listener zu informieren, von DBEntryTarget.commitTransaction in Form eines RemoteRunnable-Objekts
zurückgegeben. Diese werden erst dann ausgeführt, wenn die Datenbankverbindung (die
für den DataBasket reserviert war) wieder zur Verfügung steht.
Eine weitere Besonderheit bei der Implementierung der Datenklassen stellt die Verwendung eigener Iteratoren dar. Diese sind Implementationen von epoint.data.RemoteIterator (Seite 140) und verhalten sich gemäÿ der Spezikation normaler Iteratoren
4.3. TRANSAKTIONSMECHANISMUS
69
(java.util.Iterator). Ein RemoteIterator ist aber nur als RemoteObject auf dem Server erreichbar. Der Grund dafür ist, dass normale Iteratoren nicht serialisierbar sind und
somit als Rückgabeparameter nicht in Frage kommen. Defacto macht es auch keinen Sinn,
einen Iterator serialisierbar zu machen, da die Kollektion von Objekten bei einer Serialisierung normalerweise nicht mit in die jeweils andere Maschine geklont werden soll.
Ein weiteres Manko, diesmal von Seiten des SDK ist, dass die Kollektionen, die von einigen
Klassen im Package java.util zurückgegeben werden, nicht serialisierbar sind. So kann
z.B. bei der Implementierung der Methode keySet() in Catalog nicht einfach der Keyset
der HashMap zurückgegeben werden, in der die CatalogItems gespeichert werden, da das
zurückgelieferte Set-Objekt nicht serialisierbar ist.
Weitere Details zu den Datenklassen ndet man im nächsten Abschnitt, wo es um Transaktionen zwischen den Datenklassen und die entsprechenden Konsistenzbedingungen geht.
4.3 Transaktionsmechanismus
Streng genommen stellt der Transaktionsmechanismus in EPoint ein eigenes kleines Framework dar. Die Bestandteile dieses Frameworks werden im UML-Diagramm im Anhang
auf Seite 99 zusammenhängend gezeigt. Dabei gibt es DataBaskets, in denen DBEntries
aufgezeichnet bzw. enthalten sind. Ein DBEntry beschreibt dabei immer eine Aktion, die
im Rahmen der durch den DataBasket bezeichneten Transaktion ausgeführt werden soll.
Die vom DBEntry beschriebene Aktion ndet im Rahmen der Transaktion immer in einem DBEntryTarget statt. Ein DBEntryTarget kann dabei ein beliebiger Container2 sein,
der TransactionItems 3 beinhaltet.
Bei einer Aktion in einem DBEntryTarget (Catalog oder Stock) wird ein TransactionItem
(CatalogItem bzw. StockItem) aus dem Container (DBEntryTarget) entfernt, hinzugefügt
oder modiziert. Es ist die Aufgabe des entsprechenden DBEntryTarget-Containers, die
Auswirkungen der Aktion nur für den dazu übergebenen DataBasket (also des TransaktionHandles) sichtbar zu machen und konkurrente Transaktionen, die Dateninkonsistenzen im Container herbeiführen könnten, entsprechend zu verhindern. Fügt also eine
Transaktion ein Element zu einem Container hinzu, so ist dieses Element nur für den
verwendeten DataBasket sichtbar, der es im Rahmen derselben Transaktion auch wieder
entfernen könnte. Allerdings müssen Versuche dasselbe Element durch andere Transaktionen/DataBaskets hinzuzufügen, z.B. beim Katalog, verhindert werden. Es liegt damit
ebenfalls in der Verantwortung des Containers einen DBEntry in den verwendeten DataBasket einzufügen, der die Aktion beschreibt.
Mit Hilfe dieses kleinen Frameworks werden nun Transaktionen in EPoint implementiert
und verwendet. Dabei gibt es für einzelne Elemente in einem Container verschiedene Zustände, in denen bestimmte Operationen (im Rahmen einer Transaktion) erlaubt sind, die
2 also
Catalog und Stock in der Standard-Implementation
CatalogItems und StockItems in meiner Implementation
3 respektive
70
KAPITEL 4. IMPLEMENTIERUNG DES EPOINT-FRAMEWORK
wieder zu neuen Zuständen führen. Dabei unterscheide ich anhand der Zustände folgende
Elemente:
1. Elemente, die aufgrund einer abgeschlossenen Transaktion bereits fester Bestandteil
eines Containers sind (Normal Containerelement )
2. Elemente, die im Rahmen einer noch nicht abgeschlossenen Transaktion zu einem
Container hinzugefügt werden (Temporary Added )
3. Elemente, die einem der beiden vorangegangenen Punkten zugeordnet werden können und gerade im Rahmen einer Transaktion editiert werden (Currently Edited )
4. Elemente, die im Rahmen einer noch nicht abgeschlossenen Transaktion aus einem
Container entfernt werden (Temporary Removed )
5. Elemente, die gerade instanziiert wurden und keinem Container zugeordnet werden
können (New / NotPersistent )
Jedem dieser Zustände können die in Abb. 4.1 dargestellten Operationen zugeordnet werden, die den Zustand der Elemente verändern. In der Abbildung sind jeder möglichen
Operation (auf der linken Seite) die Zustände zugeordnet, in denen diese Operation erlaubt ist.
add()
New / NotPersistent
add(db)
New / NotPersistent
remove()
Normal Container-Element
remove(db)
Normal Container-Element
get()
Normal Container-Element
Temporary Removed (db)
Temporary Added (db)
Currently Edited (db)
get(db)
Normal Container-Element
Temporary Added (db)
Currently Edited (db)
db.commit()
Temporary Added (db)
Temporary Removed (db)
Currently Edited (db)
db.rollback()
Temporary Added (db)
Temporary Removed (db)
Currently Edited (db)
Abbildung 4.1: Erlaubte Operationen bei Transaktionszuständen von TransactionItems
In Abbildung 4.1 ndet man die möglichen Operationen auf TransactionItems zusammen mit den Zuständen dargestellt, in denen diese erlaubt sind. Operationen, die mit einem
(db) gekennzeichnet sind, beschreiben Operationen, die im Rahmen einer gröÿeren Transaktion stattnden und deren Folge-Operationen in derselben Transaktion stattnden müssen
(diese Zustände/Operationen sind zusätzlich rot unterlegt).
Die möglichen Zustandsübergänge eines einzelnen Elements, die durch die Operationen
ausgelöst werden, werden in Abb. 4.2 gezeigt. Wird dort beispielsweise ein neues nicht persistentes Element durch die Methode add, im Rahmen einer Transaktion mit Datenkorb
4.3. TRANSAKTIONSMECHANISMUS
71
db, zu einem Container hinzugefügt, so ändert es seinen Zustand von New/NotPersistent` nach Temporary Added. In diesem Zustand sind genau die Operationen erlaubt,
die in Abb. 4.1 links von diesem Zustand stehen. Wird eine dieser Operationen ausgeführt,
überführen sie das Element, entsprechend der Abb. 4.2, in einen neuen Zustand.
get(db)
Temporary Added
(db)
add(db)
remove(db)
db.commit()
db.rollback()
get()
New / NotPersistent
add()
remove()
get(db)
Normal ContainerElement
get(db)
db.rollback()
Currently Edited (db)
db.commit()
remove(db)
db.commit()
db.rollback()
Temporary Removed
(db)
add(db)
remove(db)
persistent states
Abbildung 4.2: Zustandsübergänge von TransactionItems in DBEntryTargets
Die Abb. 4.2 zeigt die Zustandsübergänge eines einzelnen TransactionItem in einem
DBEntryTarget zusammen mit den erlaubten Operationen in jedem dieser Zustände. Blaue
Zustände sind dabei Zustände die keiner Transaktion zugeordnet sind und rote Zustände
sind transaktionsspezisch. Bei den Operationen angegebene DataBaskets (db) bezeichnen
dabei immer denselben DataBasket, der die vorangegangene Zustandsänderung in einen
transaktionsspezischen Zustand herbeigeführt hat.
Etwas vereinfacht ergibt sich für die Zustandsübergänge in Abbildung 4.2 das Zustandsübergangsdiagramm aus Abbildung 4.3.
Für EPoint gilt genau das eben beschriebene Zustandsmodell, mit den entsprechenden
Operationen, für alle Container (also Catalog, Stock ). Eine Modellierung dieser Zustände
ist einerseits für die im folgenden Abschnitt erklärten Konsistenzbedingungen (für Catalog und Stock) notwendig und andererseits wird es auch verwendet, um zu entscheiden,
welche dieser Zustände persistent sind. Das kann man in Abbildung 4.2 sehr gut sehen, in
der alle persistenten Zustände durch einen groÿen gepunkteten Rahmen gekennzeichnet
sind. Das bedeutet für die Implementierung der Methoden, die in oder aus diesem Rahmen führen, dass diese die Verantwortung für die Persistenz/Löschung der Elemente in
der Datenbank entsprechend übernehmen.
72
KAPITEL 4. IMPLEMENTIERUNG DES EPOINT-FRAMEWORK
Temporary Added (db)
New / NotPersistent
Normal Container-Element
Currently Edited (db)
Temporary Removed (db)
Abbildung 4.3: Zustandsübergänge von TransactionItems
In Abbildung 4.3 sind die Zustandsübergänge aus Abbildung 4.2 nocheinmal vereinfacht
dargestellt. Diesmal zeigt sie nur die möglichen Übergänge zwischen den zuständen, ohne
die Operationen, die sie herbeiführen.
Im Beispiel heiÿt das für die Methode add in allen Containern, dass sie die ihr übergebenen Elemente immer persistent machen muss, es sei denn das Element wurde zuvor in
derselben Transaktion entfernt und bendet sich im Zustand Temporary Removed. In
diesem Fall überführt sie nur den Zustand des bereits persistenten Elements in den neuen
Zustand Currently Edited.
Damit ist auch schon die folgende Besonderheit erwähnt: die beiden Aktionen remove
mit anschlieÿendem add (für dasselbe TransactionItem und innerhalb derselben Transaktion) werden zu einer edit Aktion konvertiert. Dadurch vermeidet man die Speicherung
vieler verbrauchter Zustände eines TransactionItems durch mehrmaliges Entfernen und
Hinzufügen eines als identisch betrachteten TransactionItems.
4.3.1 Konsistenzbedingungen
Die Zustandsübergänge und erlaubten Operationen innerhalb eines einzelnen DBEntryTargets wurden im vorangegangenen Abschnitt zusammen mit ihrer Umsetzung beschrieben. Für eine spezielle Implementation von DBEntryTarget muss jedoch noch deniert
werden, wann Objekte als identisch betrachtet werden und was bei einer Verletzung der
Bedingungen geschieht.
Für die Einträge in Katalogen und Beständen gilt: Alle Katalogeinträge werden dann als
identische Objekte bezeichnet, wenn sie denselben Key besitzen. Die Gleichheit von Bestandseinträgen basiert auf deren Objektidentität. Dazu werden intern vier Datenstrukturen verwaltet, die folgende Daten (entsprechend den Zuständen aus dem vorangegangenen
Abschnitt 4.3) enthält:
1. TransactionItems als normaler Bestandteil des Containers (New/NotPersistent)
4.3. TRANSAKTIONSMECHANISMUS
73
2. in einer Transaktion hinzugefügte TransactionItems (Temporary Added)
3. in einer Transaktion entfernte TransactionItems (Temporary Removed)
4. in einer Transaktion editierte TransactionItems (Currently Edited)
So kann einfach verfolgt werden, welche der Aktionen add, remove und edit von welcher
Transaktion entsprechend Abbildung 4.1 erlaubt sind und entsprechend Abbildung 4.3
umgesetzt werden müssen.
Allerdings gelten für die gesamte Datenstruktur Catalog -Stock weitere Bedingungen, die
an die Zuordnung von mehreren Stocks zu einem Catalog geknüpft sind. Da Bestände
und somit Bestandseinträge auf Katalogen (bzw. Katalogeinträgen) basieren, müssen bei
bestimmten Operationen auf den Elementen des einen Containers weitere Bedingungen
(bezüglich des Zustands) an die Elemente des anderen Containers gestellt werden.
Operation
ohne db
add
remove
edit
mit db1
add
remove
edit
Zustand des CatalogItem (Associated Item)
normal
√
√
√
normal
√
√
√
in Transaktion mit
Datenkorb db1
added removed edited
√
[−]X
[−]X
√
[−]E
[−]E
√
[−]E
[−]E
added removed edited
√
√
[−]X
√
√
[−]E
√
√
[−]E
in Transaktion
mit Datenkorb db2
added removed edited
√
[−]X
[−]X
√
[−]E
[−]E
√
[−]E
[−]E
Tabelle 4.1: Zulässige Zustände des Associated Item für Operationen auf Stocks bzw.
StockItems
Tabelle 4.1 zeigt, wie sich Operationen auf StockItems auswirken, wenn
sich die CatalogItems, auf denen sie basieren, in einem bestimmten Zustand
benden. Dabei wird folgende Symbolik verwendet:
√
• :: erlaubte Operation
• [−]E :: nicht erlaubt und mit Exception quittiert
• [−]X :: Situation kann nicht auftreten (wird ignoriert)
Die gesamten Bedingungen (zusammen mit den Reaktionen) bei Operationen auf Elementen in diesen komplexen Zuständen habe ich in Tabelle 4.1 und 4.2 zusammenfassend
deniert. Tabelle 4.1 zeigt dabei die möglichen Operationen auf StockItems in den Zeilen
74
KAPITEL 4. IMPLEMENTIERUNG DES EPOINT-FRAMEWORK
und die möglichen Zustände des zugehörigen (associated ) CatalogItems in den Spalten. In
der Tabelle wird durch entsprechende Symbolik verdeutlicht, wie die Operation auf dem
StockItem, bei einem gegebenen Zustand des CatalogItems, umgesetzt wird.
In Tabelle 4.2 wird der umgekehrte Sachverhalt dargestellt, nämlich welche Operationen
auf dem Katalog erlaubt sind, während sich die darauf basierenden StockItems in einem
bestimmten Zustand benden.
Operation
ohne db
add
remove
edit
mit db1
add
remove
edit
Zustand jedes beliebigen zugeordneten StockItem
normal
[−]√
→√rm
in Transaktion mit
Datenkorb db1
added removed
edited
√
√
[−]
[−]√
→
→√cm
→rb,rm
√rb
√
normal
[−]√
→rmdb
√
added
[−]√
→rb
√
removed
√
√
√
edited
[−]√
→rb,rm
√
in Transaktion
mit Datenkorb db2
added removed edited
[−]√
[−]√
[−]√
[−]E
[−]E
[−]E
√
√
√
Tabelle 4.2: Operationen auf Katalogeinträgen in Zusammenhang mit Zuständen zugehöriger StockItems
Tabelle 4.2 zeigt die Reaktionen bei möglichen Operationen auf CatalogItems, wenn sich ein darauf basierendes StockItem in einem der beschriebenen
Zustände bendet. Dabei wird die folgende Symbolik verwendet:
√
• :: erlaubte Operation
• [−]√ :: kann nicht auftreten, Operation erlaubt
• [−]E :: Operation wird mit Exception verhindert
• →?? :: kaskadische Fortsetzung der Operation durch:
rb:: Rollback der entsprechenden Operation
cm:: Commit der entsprechenden Operation
rm:: Operation remove (ohne db) auf dem Item
rmdb:: Operation remove (mit db) auf dem Item
Das folgende Beispiel soll zeigen, wie diese Tabellen zu lesen sind: Wird in einem Bestand
gerade ein StockItem, im Rahmen einer noch nicht beendeten Transaktion db1, gelöscht,
dann kann einer Transaktion db2 nicht gestattet werden, das zugehörige CatalogItem aus
dem Basiskatalog zu löschen. Der Grund ist, dass db2 vor db1 beendet werden könnte
und somit das CatalogItem endgültig gelöscht wäre. Würde nun db1 abgebrochen werden,
gäbe es für das StockItem kein CatalogItem im Katalog mehr.
4.4. ERWEITERUNGSMÖGLICHKEITEN
75
Die in den Tabellen denierten Reaktionen stellen (wie im Beispiel mit dem aus dem
Basiskatalog entfernten CatalogItem ) implizit Bedingungen an die Container Catalog und
Stock. Diese Bedingungen sind bereits dadurch deniert, dass in allen Fällen, die in den
Tabellen dargestellt werden, genau so zu verfahren ist, dass eine erlaubte Operation im
gegebenen Zustand entweder durchgeführt wird oder eine unerlaubte Operation durch
eine Exception abgefangen wird. In manchen Fällen ist, wie in der Tabelle vermerkt, eine
kaskadische Fortsetzung der Operation notwendig. Wenn zum Beispiel ein Katalogeintrag
gelöscht wird, werden alle nicht an einer Transaktion beteiligten Bestandseinträge ebenfalls gelöscht.
Meine Implementation setzt die Kontrolle dieser Bedingungen und die Durchführung
(von z.B. kaskadischen Fortsetzungen einer Operation) mit Hilfe des bereits unterstüzten
Listener-Konzepts durch. Zu diesem Zweck wird bei der Erstellung von jedem Stock ein
persistenter CatalogIntegrityListener (Seite 154, Zeile 47) beim Basis-Catalog angemeldet. Dieser Listener leitet alle Aktionen, die auf dem Basis-Katalog ausgeführt werden,
entsprechend der denierten Reaktionen in den Tabellen, weiter. Weiterhin werden dabei
die entsprechenden Operationen auf dem Katalog so im Stock umgesetzt, dass die unmöglichen Situationen aus Tabelle 4.1 gar nicht erst auftreten können. Entsprechend tritt die
in der Tabelle denierte VetoException auf, wenn der Listener aufgrund der Operation
im Catalog versucht, diese kaskadisch auf die StockItems fortzusetzen, welche sich in dem
bei der Exception gekennzeichneten Zustand benden.
Eine ausführlichere Beschreibung der Arbeitsweise des CatalogIntegrityListeners möchte
ich an dieser Stelle nicht geben. Dazu kann man die von mir zu diesem Zweck erstellte
JavaDoc-Dokumentation für diese innere Klasse von AbstractStock konsultieren bzw.
bei einer Weiterentwicklung die Kommentare im Quelltext berücksichtigen.
4.4 Erweiterungsmöglichkeiten
In diesem Abschnitt möchte ich zuletzt einen kurzen Ausblick auf aus meiner Sicht anstehende und teilweise notwendige Erweiterungen für das EPoint Framework geben. Die
angesprochenen Erweiterungsmöglichkeiten sind bereits in meiner Implementierung berücksichtigt worden oder können problemlos darauf aufbauen.
4.4.1 EPoint Unterstützung
Einer der ersten Ansatzpunkte für Erweiterungen ist sicher eine umfassendere Unterstützung für die Implementierung von EPoints. Es wurde bereits in früheren Kapiteln
angedeutet, dass eine Implementierung der EPoints als Prozess angedacht war. Dazu sind
bereits Datenstrukturen in der Datenbank, wie auch Interfaces in epoint.sale enthalten.
Dennoch existiert momentan keine Notwendigkeit dafür, den EPoint anhand irgendeiner
vorgeschriebenen Struktur zu implementieren. Seine einzige Bindung an das Framework
76
KAPITEL 4. IMPLEMENTIERUNG DES EPOINT-FRAMEWORK
ist bereits in der Klasse epoint.sale.EPoint erfasst und implementiert. Sie umfasst im
Wesentlichen die Registrierung beim Shop, den Erhalt einer Referenz auf dessen ShopServices und die Referenzierung des für den EPoint bestimmten InfoStore-Objekts.
Dennoch wäre es wünschenswert, die Prozessstruktur von SalesPoint auch in EPoint zu
implementieren und durch weitere Frameworkklassen und GUI-Elemente zu unterstützen.
Der einzige Unterschied, der dabei auftritt ist, dass ein EPoint im Gegensatz zu einem
SalesPoint ein eigenständiges Programm darstellt, das durch einen Prozessautomaten vollständig beschrieben werden soll.
4.4.2 GUI
Ebenfalls sehr wichtig, aber noch nicht in EPoint enthalten, sind GUI-Elemente zur effektiven Anwendungsentwicklung. Dabei stehen Klassen im Vordergrund, die in der Lage
sind, Datenklassen wie Catalog und Stock darzustellen und in dieser Darstellung editierbar zu machen.
Für den Shop könnten Oberlächenelemente für einen Server bereitgestellt werden. Mit
diesen könnte z.B. ein GUI-Interface für die unterliegende Datenbank bzw. den DBPersistenceManager bereitgestellt werden. Ebenso denkbar wären Steuerelemente zur Einsicht
in alle aktuell angemeldeten EPoints oder ein Messaging-System, mit dem Nachrichten an
angemeldete EPoints übermittelt werden könnten.
Für die Bereitstellung von GUI-Elementen für das Framework gibt es eigentlich keine
Grenzen, wohl aber einige Anforderungen. Wichtig wäre z.B. ein systematischer Aufbau von GUI-Elementen, so dass auf unterschiedlich hohen Abstraktionsebenen angesetzt
werden kann um eigene GUI-Elemente abzuleiten. Für Kataloge sollte z.B. zunächst eine
abstrakte Tabelle implementiert werden, deren Methoden dann verwendet werden können, um grasche Tabelle zu realisieren. Wichtig bei der Erstellung von GUI-Elementen
ist auch die Berücksichtigung der hohen Parallelität des Frameworks durch den Einsatz
mehrerer EPoints. Daher sollten auf jeden Fall Listener verwendet werden, um angezeigte
Daten immer auf dem aktuellen Stand zu halten.
4.4.3 Benutzermanagement
Ein Benutzermanagement für die EPoints wird sicher auch benötigt. Dabei kann man
jedoch problemlos auf bestehende Datenstrukturen im Framework zurückgreifen, um z.B.
persistente Kataloge mit Benutzerdaten anzulegen. Eine derartige Implementierung könnte auch den an InformationStore gebunden sein, um EPoint -spezische Benutzerdaten zu
unterstützen.
4.4. ERWEITERUNGSMÖGLICHKEITEN
77
4.4.4 Views und Filter
Views bzw. Filter für Kataloge und Stocks fehlen in der derzeitigen Implementation des
Frameworks ebenfalls. Deren Implementation ist im Wesentlichen eine Erweiterung der
entsprechenden Interfaces aus epoint.data um eine Methode isContained(Object),
die das Filtern der Items übernimmt. Alle anderen Methoden im Interface würden wieder
den zu lternden Container referenzieren und in Abhängigkeit des Ergebnisses der neuen
Methode den Methodenaufruf entsprechend weiterleiten.
4.4.5 Wrapperklassen für RemoteObjects
In einem ersten Ansatz hatte ich versucht, die Implementationen für die Datenklassen so
zu gestalten, dass sie auch lokal beim EPoint (Client) instanziiert werden können. Damit
würde das umständliche Anfordern der Objekte bei den ShopServices entfallen. Leider
erwies sich die direkte Implementierung in die Datenklassen als zu unübersichtlich, als
dass man meinen Ansatz gleich hätte berücksichtigen können.
Meine Idee ist es daher, nachträglich für die Klassen im Package epoint.data.rmi Wrapperklassen mit den Interfaces aus epoint.datazu implementieren. Diese sollen dann sowohl in der Umgebung des Shops, als auch in der Umgebung des EPoints instanziierbar
sein. Objekte dieser Klassen könnten sich dann zunächst bei ihrer Instanziierung darüber informieren, in welcher Umgebung sie erzeugt wurden. Dazu dienen die Methoden
Shop.isServerApplication() bzw. EPoint.isClientApplication().
Die Wrapperklassen verwalten eine Referenz auf ein entsprechendes Objekt der Klasse,
für die sie implementiert wurden (also normalerweise vom selben Typ des Interfaces). An
diese werden die Methodenaufrufe für das Interface weitergeleitet. Der Trick besteht darin, in Abhängigkeit vom Ergebnis der Methodenaufrufe in EPoint / Shop im Konstruktor
der Wrapperklassen darüber zu entscheiden, ob sie eine lokale Referenz auf ein lokal erzeugtes Objekt der Originalklasse in der Umgebung des Shops verwenden oder ob sie sich
der ShopServices in der Umgebung des EPoints bedienen, um diese Referenz zu erhalten.
Das Ergebnis sind Wrapper-Klassen vom Typ der Interfaces, die sowohl beim EPoint, wie
auch beim Shop instanziiert werden können und trotzdem immer als eigentliches Datenobjekt beim Server existieren.
4.4.6 Server für Shops
Die Möglichkeit, einen Shop als Server für andere Shops auftreten zu lassen, besteht jetzt
schon, indem ein weiterer Shop das ShopServices -Interface verwendet. Da die ShopServices ja für jeden Client frei kongurierbar sind, ist auch eine spezielle Verwendung für
Shops anstatt für EPoints als Client möglich.
78
KAPITEL 4. IMPLEMENTIERUNG DES EPOINT-FRAMEWORK
Seitens des Frameworks könnte dies jedoch besser unterstützt werden, indem weitere Authentisierungsmechanismen und Interaktionsmöglichkeiten angeboten werden. Ein Messaging-System, welches nur dem Nachrichtenaustausch zwischen zwei entfernten Systemen
dient, wäre ebenso denkbar wie eine gemeinsame Datenbasis für zwei Shops.
4.4.7 Sicherheit
Bezüglich der Sicherheit ist bereits mit wenig Aufwand viel getan, indem die ShopIdentServices eine Registrierung aller Clients verlangt, bevor diese Zugri auf die ShopServices
erhalten. Da diese ebenfalls in Abhängigkeit von der Identität des Client kongurierbar
sind, kann man sehr feine Abstufungen bezüglich der Möglichkeiten von Clients bei der
Nutzung des Servers machen.
Dennoch wäre es möglich, diese Mechanismen für eine Nutzung in sicherheitsrelevanten Bereichen durch Passwörter, Verschlüsselungstechniken usw. auszubauen. Weiterhin
denkbar wäre eine Fortsetzung der Sicherheit auf die Schnittstellen der Datenklassen.
So könnten man beispielsweise bestimmten Clients den vollen Zugri auf einen Katalog
erlauben, wohingegen anderen Clients nur lesender Zugri gewährt werden würde. Eine Umsetzung dieses Verfahrens könnte bei einer Neuimplementierung der Datenklassen
ansetzen, aber auch wieder bei Wrapper-Klassen, die entsprechend kongurierbar sind.
4.4.8 Weitere Nutzung der RMI-Möglichkeiten
Zu den vielen weiteren interessanten Möglichkeiten der Nutzung von RMI, die auch von
Hr. Moritz in seiner Studienarbeit erläutert werden [SASM01], gehört die Einrichtung einer Code-Basis. So könnte der Shop zusätzlich die Rolle eines echten Servers für EPoints
übernehmen, indem er auch die Klassen, die zum Instanziieren der vom Shop zur Verfügung gestellten Klassen zum Download anbietet.
In diesem Fall könnten EPoints auch dann unabhängig vom Shop implementiert werden,
wenn die gesamte Code-Basis (also alle class-Dateien) nicht komplett beim EPoint vorhanden ist. Wie diese Technik umgesetzt werden kann, ist in der Studienarbeit von Hr.
Moritz bzw. in der Dokumentation von SUN zum Thema RMI nachzulesen.
Anhang A
Datenbank-Tabellen
Die in diesem Anhang aufgelisteten Tabellen benden sich auch in den Quellen bzw.
Dokumentationen zum Framework, in der Datei epoint/docs/tabelstructure.html.
Die Tabellen beschreiben die Tabellenstruktur, wie sie in der von mir implementierten
Fassung der PSQLDBConnection Klasse in der Datenbank angelegt und für die Persistenz
der Daten benutzt werden.
Für den Leser, der sich mit dem hier verwendeten relationalen Datenbanksystem
nicht auskennt, wird im Folgenden ein sehr kurzer Einblick gegeben, der für das grundlegende Verständnis ausreichen sollte. Dieser ist nur zum Verständnis gedacht und deniert
keinesfalls die dargestellten Prinzipien und Arbeitsweisen. Dazu sei an dieser Stelle auf einschlägige und umfangreich vorhandene Literatur verwiesen1 !
In relationalen Datenbanken werden werden Daten in Form von Datensätzen abgespeichert.
Ein Datensatz besteht dabei aus einer Menge von Attributen, die ihrerseits einen bestimmten
Typ aufweisen. Dadurch ergibt sich für einen Datensatz eine Art Signatur für seinen Typ.
Mit Hilfe dieser Signatur kann man eine Art Tabelle denieren, die viele dieser gleichartigen Datensätze aufnehmen kann. Mehrere dieser Tabellen werden dann zusammen in einer
Datenbank gespeichert. Ein Datenbank Managementsystem wie [PGSQL] verwaltet mehrere solcher Datenbanken und stellt Möglichkeiten für den Zugri und die Manipulation der
Daten zur Verfügung.
Im Normalfall wird für den Zugri auf die Daten eine eigene Sprache verwendet: SQL2 bietet umfangreiche Möglichkeiten zum Denieren von Tabellen, zur Manipulation und zum
Auslesen der Daten sowie viele andere Möglichkeiten zum Umgang mit Datenbanken.
Zu den besonderen Fähigkeiten von relationalen Datenbanken gehört die Möglichkeit Abhängigkeiten zwischen den Datensätzen der einzelnen Tabellen zu denieren, sowie Regeln
anzugeben, mit denen diese Vorgaben durchgesetzt werden können. So können z.B. innerhalb
der Signatur von Tabellen Attributmengen angegeben werden, die in ihrer Wertausprägung
einmalig sein müssen. Solche Attributmengen bezeichnet man als Keys, von denen auch
mehrere in einer Tabelle nebeneinander existieren können in diesem Fall wird einer dieser
Schlüssel als Primärschlüssel ausgezeichnet. Bestehen zwischen den Datensätzen mehrerer
1 dazu:
[1] und [2] in der Literaturliste
Query Language
2 Standard
79
80
ANHANG A. DATENBANK-TABELLEN
Tabellen Abhängigkeitsbeziehungen, so lassen sich z.B. Fremdschlüsselbedingungen angeben,
bei denen die Attribute einer Tabelle nur die Werte enthalten dürfen, die in einer anderen
Tabelle als Key ausgezeichnet sind. In diesem Fall darf die referenzierende Tabelle für diese
Attribute nur Werte annehmen, die bereits in der ersten Tabelle für diese Schlüsselattribute
eingetragen sind. Weiterhin lassen sich bei solchen Beziehungen Regeln angeben, die z.B.
das Löschen von Attributwerten in der Orginaltabelle kaskadisch auf die referenzierenden
Tabellen fortsetzen dasselbe gilt für Manipulationen an diesen Daten.
Relationale Datenbanken haben viele weitere Eigenschaften und bieten viele weitere Möglichkeiten. Dazu gehört z.B. die Unterstützung von Transaktionen bei der Ausführung mehrerer Manipulationen vieler Daten mit den ACID -Eigenschaften3 . Auÿerdem werden im
Normalfall Views unterstützt, die benutzerdenierte Sichten (auch kombiniert über mehrere
Tabellen) gleichzeitig erlauben. Die Möglichkeiten von SQL reichen aber weit über einfache Datenabfragen und Manipulieren von Attributwerten hinaus. Es lassen sich komplexe
Sprachkonstrukte bilden, die mit Hilfe der Relationenalgebra, Filtern, Regeln und DBMS spezischen (hart kodierten) Funktionen umfassende Datenabfragen und -manipulationen
ermöglichen.
Wichtig ist, dass es sich bei den folgenden um PostGreSQL-Tabellen handelt, die pro
Tabelle eine weitere unsichtbare Spalte oid enthalten. Dieses Attribut ist für jeden Datensatz in der gesamten Datenbank eindeutig und wird in meiner Implementation als
Object Identier für persistente Objekte verwendet, der grundsätzlich immer als Referenz
auf ein persistentes Objekt verwendet werden kann.
Wenn bei der Beschreibung der Tabellen von Transaktionen die Rede ist, beziehen sich
diese im Normalfall immer auf Transaktionen auf Frameworkebene, die mit Hilfe der
DataBaskets durchgeführt werden!
Innerhalb der Tabellen sind Schlüssel (Keys ) durch gleichfarbige Attribute hervorgehoben.
Auÿerdem werden in jeder Tabelle (zusätzlich zur tabellarischen Sicht) die entsprechenden
Denitionen in Form von SQL angegeben.
A.1 Kataloge und Katalogeinträge
In diesem Abschnitt werden alle Tabellen aufgeführt, die in der Datenbank für die Persistenzhaltung von Katalogen und deren Einträgen verantwortlich sind. Das sind im wesentlichen die Tabellen Catalogs und CatItems. Beide Tabellen enthalten neben explizit gespeicherten Eigenschaften der jeweiligen Objekte die serialisierte Fassung des gesicherten
Objekts in jedem Datensatz. Zusätzlich zu den genannten Tabellen ist A.6 von Relevanz.
Sie enthält die Identier aller TransactionItems und wird daher auch von CatalogItems
referenziert.
3 Atomicity,
Consistency, Isolation, Durability: laufen entweder ganz oder gar nicht ab, hinterlassen
die Datenbank in einem bezüglich aller Regel konsistenten Zustand, laufen isoliert von anderen parallel
ablaufenden Transaktionen ab und sind in ihren Auswirkungen nach erfolgreicher Ausführung auch nach
einem Systemabsturz persistent
A.1. KATALOGE UND KATALOGEINTRÄGE
81
Tabelle A.1: Datenbanktabelle Catalogs
In dieser Tabelle sind alle persistenten Catalog-Objekte gespeichert. Kataloge werden
grundsätzlich immer über ihren Namen referenziert. Entsprechende Schlüsselbeziehungen
(erkennbar in den SQL-Anweisungen) innerhalb der Datenbank sorgen dafür, dass z.B.
referenzierende CatalogItems automatisch aktualisiert werden. Die Interface-Denition
für Catalog ndet man auf Seite 108.
82
ANHANG A. DATENBANK-TABELLEN
Tabelle A.2: Datenbanktabelle CatalogItems
Diese Tabelle enthält die gespeicherten Versionen der CatalogItems die in irgendeiner
Weise in Verbindung mit einem Catalog stehen. Die Zugehörigkeit zu einem Katalog
wird durch die entsprechende Fremdschlüsselbeziehung dargestellt. Der Status eines Katalogeintrags ist anhand der Added bzw. Removed Attribute abzulesen. Zu diesem Zweck
dienen aber auch die Views A.15 bzw. A.14 oder A.13. Die Interface-Denition für CatalogItems ndet man auf Seite 111.
A.2. BESTÄNDE UND BESTANDSEINTRÄGE
83
A.2 Bestände und Bestandseinträge
Dieser Abschnitt enthält die Tabellen, die in der Datenbank verantwortlich für die Speicherung von Beständen basierend auf den Katalogen verantwortlich sind. Das sind vor allem die Tabellen Stocks und StockItems. Wie auch bei den Katalogeinträgen, referenziert
die Tabelle StockItems die Tabelle A.6, die alle TransactionItems mit deren Identier
speichert. Da die Bestände auf den Katalogen basieren und damit auch jeder Bestandseinträge jeweils einem Katalogeintrag zugeordnet sein muÿ, existieren in den Tabellen
ebenfalls wieder umfangreiche Fremdschlüsselbedingungen.
Da diese Sichtweise für eine direkte Manipulation in der Datenbank relativ schwer zu
durchschauen ist, existieren die Views A.16, A.17 bzw. A.13 zur Verdeutlichung des momentanen Zustandes eines Bestandseintrages.
Tabelle A.3: Datenbanktabelle Stocks
Stocks referenzieren ihren Basis-Katalog immer im Fremdschlüssel über dessen Namen
in der Datenbank. Entsprechende Regeln schalten Updates und gelöschte Einträge auf
die Stocks und deren Einträge weiter. Innerhalb der Datenbank unterscheiden sich
CountingStocks und StoringStocks nur durch das Flag in dieser Tabelle und durch
die Anzahl der StockItems in Tabelle A.4. In der Regel ndet man dort nur maximal
ein StockItems für jede Transaktion relativ zu einem CountingStock. Die InterfaceDenition für Stocks ndet man auf Seite 141.
84
ANHANG A. DATENBANK-TABELLEN
Tabelle A.4: Datenbanktabelle StockItems
Jedes StockItem sein CatalogItem nicht über dessen Primärschlüssel in der Datenbank, sondern verwendet dazu dessen Identier für TransactionItems aus Tabelle A.6.
So werden einerseits umfangreiche Updates bei Änderungen des Transaktionsstatus von
CatalogItem vermieden und weiterhin ist es möglich die Identität des Objekts das den
referenzierten Identier besitzt problemlos auszutauschen4 . Die Interface-Denition für
StockItems ndet man auf Seite 144.
A.3. TRANSAKTIONEN
85
A.3 Transaktionen
Die Tabellen zu den Transaktionen beschreiben Transaktionen, die auf Frameworkebene
zunächst mit Hilfe der DataBaskets aufgezeichnet werden. Erst wenn auf Frameworkebene diese Transaktion bestätigt wird, wird eine Datenbanktransaktion gestartet, die die
entsprechenden Änderungen durchführt. Die Tabellen in diesem Abschnitt enthalten die
persistenten Objekte, die in EPoint benutzt werden, um diese Transaktionen aufzuzeichnen.
Tabelle A.5: Datenbanktabelle TransactionHandles
Diese Tabelle enthält die persistenten DataBaskets, die die Handles für komplexe Manipulationen in der Datenbank darstellen. Die Konsistenzbedingungen für diese Transaktionen
werden jedoch auf Frameworkebene kontrolliert. Die Interface-Denition für DataBaskets
ndet man auf Seite 118.
86
ANHANG A. DATENBANK-TABELLEN
Tabelle A.6: Datenbanktabelle TransactionItems
TransactionItems sind die eigentlichen Gegenstände der Transaktionen. Auf Frameworkebene sind dies defaultmäÿig die CatalogItems (Tabelle A.2) und StockItems (Tabelle
A.4), die diese Tabelle referenzieren. Sie enthält zu jedem existenten TransactionItem
den DataBasket aus Tabelle A.5, in dem es gerade enthalten ist. Die Interface-Denition
für TransactionItems ndet man auf Seite 148.
A.3. TRANSAKTIONEN
87
Tabelle A.7: Datenbanktabelle Transactions
In dieser Tabelle werden die DBEntries gespeichert, die die Aktionen einer Transaktion
beschreiben. Sie sind innerhalb eines DataBasket geordnet und enthalten neben der Beschreibung der Aktion, das eine Referenz auf das TransactionItem und den DataBasket
in dem diese Aktion enthalten ist. Die Interface-Denition für DBEntry ndet man auf
Seite 120.
88
ANHANG A. DATENBANK-TABELLEN
A.4 Weitere Tabellen
Dieser Abschnitt beschreibt zusätzliche Tabellen, die vom Framework für andere Aufgaben
auÿer der Datenhaltung in Katalogen und Beständen, bzw. Transaktionen zwischen diesen,
genutzt werden. Manche dieser Tabellen werden in der von mir implementierten Fassung
von EPoint noch nicht direkt genutzt, sind jedoch für eine entsprechende Nutzung zu
einem späteren Zeitpunkt (nach einer entsprechenden Erweiterung der Funktionalität)
vorgesehen. In diesem Fall existieren auf Frameworkebene wenigstens Interfaces, an denen
die Tabellenstrukturen angelehnt sind.
Tabelle A.8: Datenbanktabelle PersistentObjects
Diese Tabelle speichert die PersistentUserObjects, die die Objekte auf Frameworkebene
darstellen, die durch den Programmierer nachträglich für die Persistenz in der Datenbank
vorgesehen und implementiert sind. Da über diese Objekte nichts bekannt ist, wird auch
keine dierenzierte Speicherung in mehreren Attributen unterstützt, sondern nur die komplette Serialisierung mit Referenz durch einen vom Programmierer denierten Key. Die
Interface-Denition für PersistentUserObject ndet man auf Seite 140.
A.4. WEITERE TABELLEN
89
Tabelle A.9: Datenbanktabelle Properties
In der Properties Tabelle werden in Bezug zu einem Zugrisschlüssel beliebige Daten als String abgespeichert. Im Framework steht dazu z.B. im Shop die Methode
setPersistentProperty(String, String) zur Verfügung. Dabei stehen Keys mit einem bestimmten Präx5 für Werte, die vom Framework intern verwendet werden. Dazu
gehören z.B. auch persistente Listener! Die Methode zum Setzen dieser Properties ndet
man im Shop auf Seite 327, Zeile 571.
90
ANHANG A. DATENBANK-TABELLEN
Tabelle A.10: Datenbanktabelle EPoints
In dieser Tabelle wird die Signatur (EPointInfo) aller jemals erfolgreich beim Shop angemeldeten EPoints gespeichert. Auf diese Weise kann ein EPoint wiedererkannt und mit
persistenten Daten, die nur ihn betreen (InfoStore) assoziiert werden. Anhand des
Primary Key läÿt sich erkennen, dass ein EPoint nur anhand seines Namens und der
IP-Adresse von der aus er sich anmeldet identiziert wird. Die Interface-Denition für
EPointInfo ndet man auf Seite 322.
A.4. WEITERE TABELLEN
91
Tabelle A.11: Datenbanktabelle GateStates
Diese Tabelle ist für zukünftige Erweiterungen vorgesehen, in der EPoints mit persistenten
Zustandsinformationen an den Gates seines Prozesses unterstützt wird. Dies stellt im
Prinzip eine spezielle Alternative zum InfoStore dar, der ja ohnehin in der Lage ist auch
diese Information zu speichern. In meiner Implementierung ist diese Tabelle jedoch noch
ungenutzt.
92
ANHANG A. DATENBANK-TABELLEN
Tabelle A.12: Datenbanktabelle LogEntries
Die LogEntries Tabelle ist dazu vorgesehen, LogEntries mehrerer Logles persistent
zu speichern. Davon wird in meiner Implementierung zwar kein Gebrauch gemacht6
allerdings steht die Möglichkeit eines Logs speziell für die Datenbank mit Hilfe des
DBPersistenceManager zur Verfügung. Die Interface-Denition für LogEntry ndet man
auf Seite 317.
A.5. VIEWS
93
A.5 Views
Die von mir denierten Views dienen ausschlieÿlich (bis auf StockItemsFull7 ) zum besseren Verständnis beim direkten arbeiten mit der Datenbank. Sie verdeutlichen bestimmte
Sachverhalte vor allem bezüglich von Transaktionszuständen und können in aktuellen
PostGreSQL-Datenbanken auch nur zum Lesen verwendet werden!
Tabelle A.13: Datenbank-View TransactionItem States
Diese View zeigt die Identier aller TransactionItems zusammen mit der Information
über ihren momentanen Status in einer Transaktion.
7 diese
View wird auch direkt zur Informationsgewinnung im PSQLDBPersistenceManager genutzt
94
ANHANG A. DATENBANK-TABELLEN
Tabelle A.14: Datenbank-View Extended TransactionItem States
Diese View zeigt ähnlich wie in Tabelle A.17 alle TransactionItems zusammen mit der
Information über ihren momentanen Status in einer Transaktion, der hier allerdings ausgewertet wird und in Klartext angegeben wird (Status ).
Tabelle A.15: Datenbank-View CatalogItem States
Diese View zeigt die Identier aller CatalogItems zusammen mit der Information über
ihren momentanen Status in einer Transaktion.
A.5. VIEWS
95
Tabelle A.16: Datenbank-View StockItems Full
Diese View zeigt alle StockItems mit aufgelöster Schlüsselbeziehung zu ihren CatalogItems. Das Attribut RefTransID aus Tabelle A.4 wurde hier aufgelöst zum eigentlichen
CatalogItem als Fremdschlüssel in CatalogItems (Tabelle A.2).
Tabelle A.17: Datenbank-View StockItem States
Diese View zeigt die Identier aller StockItems zusammen mit der Information über
ihren momentanen Status in einer Transaktion.
96
ANHANG A. DATENBANK-TABELLEN
Anhang B
UML Diagramme
In diesem Anhang sollen einige wesentliche Zusammenhänge und Vererbungsbeziehungen
zwischen Frameworkklassen in Form von UML dargestellt werden. Die einzelnen Diagramme stellen dabei folgendes dar:
B.1 enthält eine Übersicht über die Vererbungsbeziehungen alle elementaren Datenklas-
sen, die in Zusammenhang mit Stocks bzw. Catalogs stehen. Dabei sind im oberen
Teil die Interfaces aus epoint.data angegeben, die in epoint.data.rmi implementiert werden (unterer Teil).
B.2 zeigt Vererbungsbeziehungen und Assoziationen zwischen den Klassen des im Abschnitt 4.3 beschriebenen Transaktionsframework.
B.3 zeigt die Vererbungsbeziehungen zwischen allen Klassen und Interfaces aus dem
Package epoint.data.
B.4 enthält das UML-Diagramm aller Klassen aus epoint.data.rmi, die Interfaces aus
epoint.data implementieren. Das sind alle die Datenklassen, die auch für RMIAufrufe zur Verfügung stehen!
97
-vValue
(from rmi)
(from rmi)
(from rmi)
AbstractStock
-sOwner
(from data)
(from data)
CountingStockImpl
(from rmi)
(from data)
StoringStockImpl
(from rmi)
(from data)
(from data)
<<Interface>>
CountingStock
(from data)
<<Interface>>
Stock
DecimalValue
StringValue
<<Interface>>
StoringStock
<<Interface>>
StockItem
(from data)
AbstractValue
Abbildung B.1: UML - Vererbung und Implementierung der Datenklassen
CatalogImpl
CatalogItemImpl
(from rmi)
StockItemImpl
-cBaseCatalog
(from data)
<<Interface>>
Catalog
-vValue
-cOwnerCatalog
(from data)
<<Interface>>
CatalogItem
(from data)
<<Interface>>
Value
98
ANHANG B. UML DIAGRAMME
(from data)
(from data)
(from rmi)
DBEntryImpl
1
(from data)
(from data)
<<Interface>>
CatalogItem
-tiCache
1
(from data)
<<Interface>>
StockItem
<<Interface>>
TransactionItem
(from rmi)
DataBasketImpl
Abbildung B.2: UML - Vererbung und Implementierung des Transaktionsframework
<<Interface>>
Catalog
<<Interface>>
Stock
-dbetCache
(from data)
1
(from data)
1
<<Interface>>
DBEntry
0..n
1
<<Interface>>
DBEntryTarget
-dbCache
(from data)
<<Interface>>
DataBasket
99
(from data)
(from data)
-catalog
(from data)
(from data)
(from data)
(from data)
<<Interface>>
ContainerChangeEvent
ContainerChangeEventImpl
-db
1
(from data)
-db
(from data)
<<Interface>>
DataBasket
(from data)
<<Interface>>
CatalogItem
InformationStoreImpl
(from data)
<<Interface>>
InformationStore
(from data)
<<Interface>>
StoringStock
(from data)
<<Interface>>
Stock
(from data)
(from data)
0..n
Abbildung B.3: UML - Vererbung im Package epoint.data
<<Interface>>
RemoteIterator
(from data)
(from data)
<<Interface>>
RemoteRunnable
(from data)
<<Interface>>
CountingStock
(from data)
(from data)
<<Interface>>
Catalog
<<Interface>>
ListenableContainer
<<Interface>>
Nameable
<<Interface>>
PContainerListener
(from data)
<<Interface>>
ContainerListener
StringValue
DecimalValue
(from data)
AbstractValue
(from data)
<<Interface>>
Value
<<Interface>>
PersistentUserObject
<<Interface>>
PersistentFrameworkObject
(from data)
<<Interface>>
PersistentObject
-dbet
(from data)
1
1
<<Interface>>
DBEntry
1
(from data)
<<Interface>>
DBEntryTarget
(from data)
<<Interface>>
StockItem
-ti1
(from data)
<<Interface>>
TransactionItem
100
ANHANG B. UML DIAGRAMME
DBEntryImpl
0..n
1
1
DataBasketImpl
1
-s
StoringStockImpl
StockItemImpl
0..n
1
AbstractStock
CountingStockImpl
PersistentContainerListener
ContainerListenerAdapter
RemoteRunnableImpl
ASStockItemIterator
DBEIterator
1
Abbildung B.4: UML - Vererbung im Package epoint.data.rmi
CatalogItemImpl
0..n
1
CatalogImpl
RemoteIteratorImpl
CatalogItemIterator
1
101
102
ANHANG B. UML DIAGRAMME
Anhang C
Quelltexte
In diesem Anhang benden sich sämtliche in meiner Diplomarbeit von mir erstellten Quelltexte für das EPoint -Framework. Eine Beschreibung der verwendeten Package-Struktur
bendet sich auf Seite 27, im Abschnitt über die Äuÿere Struktur des Frameworks. Auÿerdem enthält die JavaDoc umfangreichere Package-Beschreibungen.
Package: epoint.data
epoint.data.AbstractValue.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
epoint.data.Catalog.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
epoint.data.CatalogItem.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111
epoint.data.ContainerChangeEvent.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
epoint.data.ContainerChangeEventImpl.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
epoint.data.ContainerListener.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115
epoint.data.CountingStock.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117
epoint.data.DataBasket.java. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .118
epoint.data.DBEntry.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120
epoint.data.DBEntryTarget.java. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .122
epoint.data.DecimalValue.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123
epoint.data.InformationStore.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126
epoint.data.InformationStoreImpl.java. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .130
epoint.data.ListenableContainer.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136
epoint.data.Nameable.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137
epoint.data.PContainerListener.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138
epoint.data.PersistentFrameworkObject.java. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .138
epoint.data.PersistentObject.java. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .138
epoint.data.PersistentUserObject.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140
epoint.data.RemoteIterator.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140
epoint.data.RemoteRunnable.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141
epoint.data.Stock.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141
epoint.data.StockItem.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144
epoint.data.StoringStock.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146
epoint.data.StringValue.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146
epoint.data.TransactionItem.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148
epoint.data.Value.java. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .149
103
104
ANHANG C. QUELLTEXTE
Package: epoint.data.exception
epoint.data.exception.DataBasketConictException.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 150
epoint.data.exception.DetachListenerException.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151
epoint.data.exception.DuplicateKeyException.java. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .151
epoint.data.exception.IllegalPersistenceTypeException.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151
epoint.data.exception.KeyNotFoundException.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152
epoint.data.exception.NoSuchKeyException.java. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .152
epoint.data.exception.NotEditableException.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153
epoint.data.exception.UnknownTargetException.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153
epoint.data.exception.VetoException.java. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .153
Package: epoint.data.rmi
epoint.data.rmi.AbstractStock.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154
epoint.data.rmi.ASStockItemIterator.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 177
epoint.data.rmi.CatalogImpl.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186
epoint.data.rmi.CatalogItemImpl.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 214
epoint.data.rmi.CatalogItemIterator.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 218
epoint.data.rmi.ContainerListenerAdapter.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 221
epoint.data.rmi.CountingStockImpl.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 223
epoint.data.rmi.DataBasketImpl.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 234
epoint.data.rmi.DBEIterator.java. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .240
epoint.data.rmi.DBEntryImpl.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241
epoint.data.rmi.PersistentContainerListener.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 245
epoint.data.rmi.RemoteIteratorImpl.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 247
epoint.data.rmi.RemoteRunnableImpl.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 247
epoint.data.rmi.StockItemImpl.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 248
epoint.data.rmi.StoringStockImpl.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 253
Package: epoint.db
epoint.db.DBPersistenceManager.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 263
epoint.db.PSQLDBConnection.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 271
epoint.db.ReferenceCache.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 307
Package: epoint.db.exception
epoint.db.exception.DBAccessException.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 312
epoint.db.exception.NoSuchDriverException.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 313
Package: epoint.log
epoint.log.Log.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 313
epoint.log.LogEntry.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 317
epoint.log.LogFilter.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 319
Package: epoint.sale
epoint.sale.EPoint.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 320
105
epoint.sale.EPointInfo.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 322
epoint.sale.EPointProcess.java. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .323
epoint.sale.Gate.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 324
epoint.sale.ParamFile.java. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .325
epoint.sale.Serializer.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 326
epoint.sale.Shop.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 327
epoint.sale.ShopServices.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 335
epoint.sale.ShopServicesIdent.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 337
epoint.sale.Transition.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 337
Package: epoint.sale.exception
epoint.sale.exception.NoRunningShopException.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 338
epoint.sale.exception.PermissionDeniedException.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 338
Package: epoint.sale.rmi
epoint.sale.rmi.EPointInfoImpl.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 339
epoint.sale.rmi.ShopServicesIdentImpl.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 341
epoint.sale.rmi.ShopServicesImpl.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 341
Package: epoint.test.server
epoint.test.server.MyShop.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 344
Package: epoint.client
epoint.test.client.SampleCatalogListener.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 344
epoint.test.client.TestEPoint.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 345
Package: epoint.xml
epoint.xml.XMLConverter.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 353
epoint.xml.XMLConverterImpl.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 355
epoint.xml.XMLObjectClass.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 357
Package: epoint.xml.exception
epoint.xml.exception.DTDNotFoundException.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 358
106
ANHANG C. QUELLTEXTE
C.1 Package epoint.data
epoint.data.AbstractValue.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
epoint.data.Catalog.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
epoint.data.CatalogItem.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111
epoint.data.ContainerChangeEvent.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .113
epoint.data.ContainerChangeEventImpl.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
epoint.data.ContainerListener.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .115
epoint.data.CountingStock.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117
epoint.data.DataBasket.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
epoint.data.DBEntry.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .120
epoint.data.DBEntryTarget.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
epoint.data.DecimalValue.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123
epoint.data.InformationStore.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .126
epoint.data.InformationStoreImpl.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130
epoint.data.ListenableContainer.java. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .136
epoint.data.Nameable.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137
epoint.data.PContainerListener.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138
epoint.data.PersistentFrameworkObject.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138
epoint.data.PersistentObject.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138
epoint.data.PersistentUserObject.java. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .140
epoint.data.RemoteIterator.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140
epoint.data.RemoteRunnable.java. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .141
epoint.data.Stock.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141
epoint.data.StockItem.java. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .144
epoint.data.StoringStock.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146
epoint.data.StringValue.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146
epoint.data.TransactionItem.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148
epoint.data.Value.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149
Listing C.1: epoint.data.AbstractValue
5
10
15
20
/*
* AbstractValue.java
*
* Created on 22. Juli 2001, 14:35
*/
package epoint.data;
import java.rmi.*;
import java.rmi.server.*;
import epoint.data.exception.*;
import epoint.data.*;
import java.io.*;
/**
* This abstract Value defines default-behaviour of a Value that may be attached
* to {@link CatalogItem CatalogItems} or {@link StockItem StockItems}.<br>
* A Value is always local and as such cannot be updated in meanings of its
* persistent state. Manipulating a Value does only affect the local object
* itself and not the Value in a CatalogItem or StockItem, until it is re-set
* at those objects. During the use of RMI a Value looses it object-identity!
*
* @author Danny Poppe
C.1. PACKAGE EPOINT.DATA
25
30
35
40
45
50
55
60
65
70
75
80
85
90
95
100
105
110
* @version 1.0
*/
public abstract class
AbstractValue implements Value, Comparable {
/**
* Stores the information, if this Value is editable or not.
*/
private boolean bEditable = true;
///////////////////////////////////////////////////////////////////////////
//
Constructors
///////////////////////////////////////////////////////////////////////////
/**
* Creates a new Value that defaults to <code>null</code>-data and is
* editable.
*/
public AbstractValue() {
}
/**
* Returns <code>true</code> only if this Value is currently editable.
*
* @return <code>true</code> if this Value may be edited
*/
nal public boolean isEditable() {
return bEditable;
}
/**
* Sets this Value to be editable or not.
*
* @param bEditable if <code>true</code> this item is allowed to be edited
*
otherwise not
*/
nal public void setEditable(boolean bEditable) {
this.bEditable = bEditable;
}
/**
* This implementation returns the same as {@link #getSerializedForm()}
*
* @return a String representation of this Value
*/
public String toString() {
return getSerializedForm();
}
/**
* Creates a deep-clone of this Value while serializing this Object and
* creating new instance using de-serialization.<br>
* Subclasses may need to override this method for creating deep-clones
* from classes with transient fields, special serialization-mechanism or
* non-serializable references.
*
* @param bEditable <code>true</code> if the clone shall be editable, otherwise
*
<code>false</code>
* @return a new Value that is equal to this Value
*/
public Value getDeepClone(boolean bEditable) {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(this);
return(Value)(new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()))).readObject();
} catch (Exception e) {
System.out.println(e.getMessage());
e.printStackTrace();
}
return null;
}
/**
* Returns <code>true</code> only if this Value is a Catalog. Otherwise
* always <code>false</code> is returned! Subclasses normally implement
* this method to return <code>false</code>!
*
* @return <code>true</code> if this Value is a Catalog
*/
abstract public boolean isCatalog();
/**
* Returns
* returns
* default
*
* @return
*/
a String-representation of this Value. This default-implementation
the serialized Value as created by the {@link epoint.sale.Shop Shops}
{@link epoint.sale.Serializer PersistenceSerializer} as retrieved by {@link epoint.sale.Shop#getPersistenceSerializer()}.
a String representing this Value
nal public
String getSerializedForm() {
107
108
}
ANHANG C. QUELLTEXTE
return
epoint.sale.Shop.getPersistenceSerializer().toString(this);
/**
* This method is provided only for compatibility-reasons and returns
* the same as {@link #toString()} would return.
*
* @return the same as {@link #toString()}
*/
nal public String toRemoteString() {
return toString();
}
115
120
}
Listing C.2: epoint.data.Catalog
5
/*
* Catalog.java
*
* Created on 18. Mai 2001, 22:59
*/
10
package epoint.data;
import java.rmi.*;
import epoint.data.exception.*;
import java.util.*;
15
20
25
30
35
40
45
50
55
60
65
/**
* A Catalog is a remotely available container used to store
* {@link CatalogItem CatalogItems}, providing methods for adding, removing
* and editing them within {@link DataBasket DataBasket-Transactions}. This interface
* indirectly inherits from {@link java.rmi.Remote Remote} and therefore is
* implemented to be remotely available only at the Shop.<br>
* <br>
* A Catalog itself may be used as a Value in a CatalogItem. In this case the
* Catalog cannot be deleted until the referencing CatalogItem is no more
* persistent!<br>
* <br>
* CatalogItems within a Catalog are identified, using a key-String that
* must be unique for the entire Catalog. The Catalog itself has a name that
* must also be unique within all other Catalogs in the entire Shop! That means
* the composed name of each CatalogItem with the Catalog it belongs to (plus
* the DataBasket's name - if the item is added within a transaction) is unqiue
* within all such names in a Shop. <br>
* <br>
* <h4>Persistence</h4>
*
* Every Catalog is made persistent upon creation and may be retrieved from
* the Shop by its name. If a Catalog is no more needed, explicit deletion
* via the {@link epoint.sale.Shop#getDBPersistenceManager() Shops PersistenceManager}
* is needed ({@link epoint.db.DBPersistenceManager#deleteCatalog(Catalog,Connection)
* DBPErsistenceManager.deleteCatalog}).<br>
* CatalogItems that are somehow contained in the Catalog (eg. they are added, edited
* or deleted within an uncommitted transaction) are made persistent
* in this Catalog. That means, if the application is shut down and the Catalog
* is retrieved after restart, all CatalogItems may be normally accessed again
* and are in the same state as before shutdown.<br>
* <br>
* The persistent state of a Catalog consists of:
* <ul>
* <li>the catalog's name</li>
* <li>serialized catalog (see {@link epoint.sale.Shop#getPersistenceSerializer() Shop.getPersistenceSerializer()})</li>
* </ul>
*
* @see epoint.data.rmi.CatalogImpl
* @see epoint.data.CatalogItem
* @see epoint.sale.Serializer
* @author Danny Poppe
* @version 1.0
*/
public interface Catalog extends Value, Nameable, PersistentFrameworkObject, DBEntryTarget, ListenableContainer, Remote {
/**
* The programmatical name of the 'name' property, which is explicitly made
* persistent with an instance of this Catalog.
*/
nal public static String PERSISTENT_PROPERTY_CATALOGNAME = "Catalog.Name";
/**
* Returns the number of visible CatalogItems for the given DataBasket
* in this Catalog. These are all normal CatalogItems that are not part
* of a transaction plus all CatalogItems that are added using the given
* DataBasket and those CatalogItems that are currently edied within some
* transaction.
*
C.1. PACKAGE EPOINT.DATA
70
75
80
85
90
95
100
105
110
115
120
125
130
135
140
145
150
155
* @param db the DataBasket that is used to look at the items in this
*
Catalog (may be <code>null</code>)
* @return the number of CatalogItems stored in this Catalog
* @throws RemoteException if an error occurs during the use of RMI
*/
public int size(DataBasket db) throws RemoteException;
/**
* Immediately adds a new CatalogItem to this Catalog. This is done, using
* an anonymous DataBasket that is immediately committed.
*
* @param ci the CatalogItem that shall be added to this catalog
* @throws DuplicateKeyException if the CatalogItem has a key that is
*
already registered with a CatalogItem in thisCatalog
* @throws DataBasketConflictException if the given CatalogItem is
*
already added within an uncommitted transaction
* @throws RemoteException if an error occurs during the use of RMI
*/
public void add(CatalogItem ci) throws RemoteException, DataBasketConflictException;
/**
* Adds a new CatalogItem to this Catalog, registering the with the
* given transaction. The CatalogItem will afterwards belong to this
* Catalog only for the given DataBasket, but may not be added or removed
* (with the same key) again within other transactions.<br>
* If the CatalogItem was previously removed by the same DataBasket, the
* previously registered removing-transaction will be converted into an
* editing transaction of the same key. That means the CatalogItem registered
* with the given key may or may not change its object-identity, depending
* on the added item - althoug the transaction itself is marked as an
* editing one!
*
* @param ci the CatalogItem that shall be added to this catalog
* @param db the DataBasket that shall be used for this transaction
*
(if <code>null</code> is given, {@link #add(CatalogItem)} is
*
subsequently called)
* @throws DuplicateKeyException if the CatalogItem has a key that is
*
already registered with a CatalogItem in thisCatalog
* @throws DataBasketConflictException if the given CatalogItem is
*
already added or removed within another uncommitted transaction
* @throws RemoteException if an error occurs during the use of RMI
*/
public void add(CatalogItem ci, DataBasket db) throws RemoteException, DataBasketConflictException;
/**
* Removes the given CatalogItem from this Catalog immediately. This
* is done, using an anonymous DataBasket.
*
* @param sKey the key of the CatalogItem to remove from this Catalog
* @return the CatalogItem with the given key, which was consequently
*
removed from this Catalog, or <code>null</code> if no such
*
CatalogItem could be found within this Catalog
* @throws DataBasketConflictException if the key is already removed
*
or added within a transaction
* @throws RemoteException if an error occurs during the use of RMI
* @throws VetoException if a registered listener vetoes the removal of the given key
*/
public CatalogItem remove(String sKey) throws RemoteException, VetoException, DataBasketConflictException;
/**
* Removes the given CatalogItem from this Catalog registering this
* removal with the given DataBasket. The CatalogItem will not be visible
* to everyone, but a new item with the same key may only be added by the
* same transaction, or by others if the given DataBasket has committed.
*
* @param sKey the key of the CatalogItem to remove from this Catalog
* @param db the DataBasket that is used to register the removal of the
*
item with the given key
* @return the CatalogItem with the given key, which was temporarily
*
removed from this Catalog, or <code>null</code> if no such
*
CatalogItem could be found within this Catalog
* @throws DataBasketConflictException if the key is already removed
*
or added within another transaction
* @throws RemoteException if an error occurs during the use of RMI
* @throws VetoException if a registered listener vetoes the removal of the given key
*/
public CatalogItem remove(String sKey, DataBasket db) throws RemoteException, VetoException, DataBasketConflictException;
/**
* Returns a remotely available iterator over all CatalogItems in this
* Catalog that are visible at the moment of the call. See the documentation
* of {@link epoint.data.rmi.CatalogItemIterator} for more information
* about the iterators behaviour!
*
* @param db the DataBasket that is used to look at the items in this
*
Catalog
* @param bForEdit if set to <code>true</code> the CatalogItems returned
*
by the iterator may be edited, otherwise not
* @throws RemoteException if an error occurs during the use of RMI
* @return an iterator over all items in this catalog
* @see #get(String,DataBasket,boolean)
* @see epoint.data.rmi.CatalogItemIterator
*/
109
110
public
160
165
170
175
180
185
190
195
200
205
210
215
220
225
230
235
240
ANHANG C. QUELLTEXTE
RemoteIterator iterator(DataBasket db,
boolean
bForEdit)
throws
RemoteException;
/**
* Returns a synchronized set of all keys that are registered
* with a CatalogItem in this Catalog and are visible to the given
* DataBasket.
*
* @param db the DataBasket that is used to look at the items in this
*
Catalog
* @throws RemoteException if an error occurs during the use of RMI
* @return a synchronized set of all keys that are
*
registered with a CatalogItem in this Catalog
*/
public Set keySet(DataBasket db) throws RemoteException;
/**
* Returns a CatalogItem from this Catalog by its key, using the given
* DataBasket to look at the items, that are contained in this Catalog.
*
* @param sKey the key of the CatalogItem to be returned by this method
* @param db the DataBasket that is used to look at the items contained
*
in this Catalog (eg. CatalogItems that are added using a specific
*
uncommitted DataBasket can only be retrieved, using the same
*
DataBasket until it is committed)
* @param bForEdit if <code>true</code> the requested item may be edited
*
within the given transaction. The changes are visible only, if
*
the same DataBasket is used, until it is committed. In this case
*
the Dataasket must not be <code>null</code>!
* @return a synchronized set of all keys that are
*
registered with a CatalogItem in this Catalog
* @throws RemoteException if an error occurs during the use of RMI
* @throws DataBasketConflictException if another DataBasket is currently
*
used to edit the requested item or if the item is added/removed
*
within another transaction
* @throws VetoException if the item was requested for editing, but a registered listener vetoes this
*/
public CatalogItem get(String sKey, DataBasket db, boolean bForEdit) throws RemoteException, VetoException,
DataBasketConflictException;
/**
* Works like {@link #get(String,DataBasket,boolean)}, but assumes the item
* shall be retrieved for reading only (eg. <code>bForEdit = false</code>).
*
* @param sKey the key of the CatalogItem that shall be returned
* @param db the DataBasket used to determine visibility of the CatalogItem
* @return the requested CatalogItem or <code>null</code> if no such item
*
can be seen by the given DataBasket
* @throws RemoteException whenever an error occurs during remote-access
*/
public CatalogItem get(String sKey, DataBasket db) throws RemoteException;
/**
* Works like {@link #get(String,DataBasket,boolean)}, but assumes the item
* shall be retrieved for reading without a DataBasket (eg. <code>db = null,
* bForEdit = false</code>).
*
* @param sKey the key of the CatalogItem that shall be returned
* @return the requested CatalogItem or <code>null</code> if no such item
*
can be seen by the given DataBasket
* @throws RemoteException whenever an error occurs during remote-access
*/
public CatalogItem get(String sKey) throws RemoteException;
/**
* Returns <code>true</code> if the given key is contained in this Catalog,
* and visible for the given DataBasket.
*
* @param sKey the key of the CatalogItem to check for containment in this
*
Catalog.
* @param db the DataBasket that shall be used for searching the CatalogItem
*
in this Catalog (may be <code>null</code> for no DataBasket).
* @return <code>true</code> only if the given key belongs to a CatalogItem
*
that is contained in this Catalog from the viewpoint of the given
*
DataBasket
* @throws RemoteException if an error occurs during the use of RMI
*/
public boolean contains(String sKey, DataBasket db) throws RemoteException;
/**
* If the given CatalogItem contains this Catalog as a Value, a flag
* is set to mark the referenced Catalog to be persistent as a Value.
* This is needed to prevent this Catalog from beeing made transient
* on demand.
*
* @param ci the CatalogItem that uses this Catalog as a Value
* @throws RemoteException if an error occurs during the use of RMI
*/
public void setPersistentValueFlag(CatalogItem ci) throws RemoteException;
/**
* Checks if this Catalog is a persistent Value of some CatalogItem.
*
* @return <code>true</code> only if this Catalog is a persistent
C.1. PACKAGE EPOINT.DATA
245
*
Value of some CatalogItem
* @throws RemoteException if an error occurs during the use of RMI
*/
public boolean isPersistentValue() throws RemoteException;
/**
* Checks if this Catalog may be edited in any way and should be checked
* by all extensions of this Catalog that modify the persistent state of this
* Catalog.
*
* @return <code>true</code> if this Catalog may be freely edited, otherwise
*
editing this Catalog is not supported
* @throws RemoteException if an error occurs during the use of RMI
*/
public boolean isEditable() throws RemoteException;
/**
* This method is used to enable or disable editing this Catalog. This
* method should NEVER directly be used to modify the state! It is
* intended to be only used by the framework itself. Especially don't set
* this Catalog to be editable, if it is not. This may cause severe
* problems with the persistent state of this instance while it is accessed
* from multiple threads (EPoints).
*
* @param bEditable if set <code>true</code> this Catalog may be freely
*
edited and the parts of its persistent state may be modified,
*
otherwise not and in such cases a
*
{@link epoint.data.exception.NotEditableException NotEditableException}
*
is thrown!
* @param sCon the connections password that shall be used to update
*
the persistent state of this Catalog
* @throws RemoteException if an error occurs during the use of RMI
*/
public void setEditable(boolean bEditable, String sCon) throws RemoteException;
250
255
260
265
270
275
}
Listing C.3: epoint.data.CatalogItem
5
/*
* CatalogItem.java
*
* Created on 18. Mai 2001, 23:00
*/
10
package epoint.data;
import java.rmi.*;
import epoint.data.exception.*;
import java.io.Serializable;
15
20
25
30
35
40
45
/**
* CatalogItems are remotely available elements of an {@link Catalog}
* that are uniquely identified by their key within a Catalog.
* CatalogItems are composed of their key for identification in a Catalog and
* a {@link Value} to be stored with them.<br>
* <br>
* <h4>Persistence</h4>
*
* A CatalogItem is accessible via RMI and is always located as one instance at
* the Shop, that is managing the persistence. The CatalogItem is persistent
* only, if it is contained in some Catalog (that is also, if it is is removed
* from a Catalog within a still uncommitted transaction).
* The persistent state of a CatalogItem consists of:
* <ul>
* <li>the CatalogItems key</li>
* <li>serialized Value (see {@link epoint.sale.Shop#getPersistenceSerializer() Shop.getPersistenceSerializer()})</li>
* <li>serialized CatalogItem (see {@link epoint.sale.Shop#getPersistenceSerializer() Shop.getPersistenceSerializer()})</li>
* <li>its current state within a transaction as a {@link TransactionItem}
* <li>(and of course the Catalog it is associated with)</li>
* </ul>
*
* @see epoint.data.rmi.CatalogItemImpl
* @see Catalog
* @author Danny Poppe
* @version 1.0
*/
public interface CatalogItem extends TransactionItem, PersistentFrameworkObject {
/**
* The programmatical name of the 'key' property, which is explicitly made
* persistent with this catalogItem
*/
nal public static String PERSISTENT_PROPERTY_CIKEY = "CatalogItem.Key";
/**
* The programmatical name of the 'catalog' property, which is a reference
* to the persistent Catalog this CatalogItem is associated with.
*/
nal public static String PERSISTENT_PROPERTY_CATALOG = "CatalogItem.Catalog";
111
112
50
55
60
65
70
75
80
85
90
95
100
105
110
115
120
125
130
135
ANHANG C. QUELLTEXTE
/**
* The programmatical name of the 'value' property, which is the Value
* stored with this CatalogItem
*/
nal public static String PERSISTENT_PROPERTY_CIVALUE = "CatalogItem.Value";
/**
* The programmatical name of the 'catalogflag' property, which shows, if the
* Value represented by this CatalogItem is a reference to a persistent
* Catalog - this information is explicitly made persistent with this
* CatalogItem.
*/
nal public static String PERSISTENT_PROPERTY_CICATALOGFLAG = "CatalogItem.CatalogValue";
/**
* Returns the key of this CatalogItem which is used to
* uniquely identify it within a Catalog.
*
* @return the String which is used as the key in the Catalog to uniquely
*
identify this CatalogItem
* @throws RemoteException whenever an error occurs during the use of RMI
*/
public String getKey() throws RemoteException;
/**
* Returns the value that is represented by this CatalogItem
*
* @return the Value represented by this CatalogItem
* @throws RemoteException if an error occurs during the use of RMI
*/
public Value getValue() throws RemoteException;
/**
* Sets a new Value for this CatalogItem, which can only be done,
* if this CatalogItem is editable. If this CatalogItem is persistent
* its persistent state is automatically updated by this action.
*
* @param v the new Value that shall be associated with this CatalogItem
* @throws NotEditableException if this CatalogItem (or the Catalog it is contained in) is
*
currently not editable
* @throws RemoteException if an error occurs during the use of RMI
*/
public void setValue(Value v) throws RemoteException, NotEditableException;
/**
* Returns <CODE>true</CODE> only if this CatalogItem is currently editable
* (which also depends on the Catalog it is contained in).
*
* @throws RemoteException if an error occurs during the use of RMI
* @return <CODE>true</CODE> if this CatalogItem is currently editable
*
or <CODE>false</CODE> otherwise
*/
public boolean isEditable() throws RemoteException;
/**
* Sets this CatalogItem to be editable or not. This method should never
* be called directly, as this may possibly result in serious
* data-inconsistency!
*
* @param bEditable <CODE>true</CODE> if this CatalogItem shall be editable
*
<CODE>false</CODE> otherwise
* @param sCon the password for the connection that shall be used to update
*
the persistent state of this CatalogItem
* @throws RemoteException if an error occurs during the use of RMI
*/
public void setEditable(boolean bEditable, String sCon) throws RemoteException;
/**
* Compares this CatalogItem to another and returns an <CODE>int</CODE>
* as specified in {@link java.lang.Comparable}
*
* @param obj the CatalogItem to be compared to this CatalogItem
* @return an <CODE>int</CODE> as defined in {@link java.lang.Comparable}
* @throws RemoteException if an error occurs during the use of RMI
*/
public int compareTo(CatalogItem obj) throws RemoteException;
/**
* Returns the Catalog, this CatalogItem is currently contained in.
* This may be <code>null</code>, if the CatalogItem is currently added
* or removed within another transaction than the one that retrieved it
* from the Catalog.
*
* @return the Catalog this CatalogItem is consequently contained in
* @throws RemoteException if an error occurs during the use of RMI
*/
public Catalog getCatalog() throws RemoteException;
/**
* This method should NEVER be called directly, as it is to be used
* only internally by the framework. It is used to set the Catalog
* of this CatalogItem. This performs only the setting of the Catalog
* for this item and does not effectivly add it to the given Catalog.
*
C.1. PACKAGE EPOINT.DATA
140
145
150
155
160
}
* @param cCatalog the Catalog, this CatalogItem is already part of
* @param sCon the password for the connection to update the persistent state of this CatalogItem
* @throws RemoteException if an error occurs during the use of RMI
*/
public void setCatalog(Catalog cCatalog, String sCon) throws RemoteException;
/**
* Returns a real clone of this CatalogItem, which is a new Object,
* representing the same data of this item while referencing the original
* data of the original object.<br>
* Never use this method to create an editable clone of an originally
* not-editable CatalogItem, as this would result in data-inconsistency if
* editing is performed afterwards - normally this case is additionally protected
* by an {@link IllegalArgumentException}.
*
* @param bEditable if set to <code>true</code>, the clone will be
*
editable and the changes are effectively made also on a
*
persistent state of the original item
* @return a clone of this CatalogItem, which is only a clone of this
*
object and not a deep clone
* @throws RemoteException if an error occurs during the use of RMI
*/
public CatalogItem getShallowClone(boolean bEditable) throws RemoteException;
Listing C.4: epoint.data.ContainerChangeEvent
5
/*
* ContainerChangeEvent.java
*
* Created on 12. Oktober 2001, 16:26
*/
package
10
15
20
epoint.data;
/**
* This event is delivered to {@link ContainerListener}s and describes an action
* performed with a {@link TransactionItem} relative to a {@link ListenableContainer}.
* The action described by an object of this class is nromally a transaction of
* a TransactionItem relative to a {@link DBEntryTarget} (that must implement {@link
* ListenableContainer}, performed by a DataBasket.
*
* @see ListenableContainer
* @see ContainerListener
* @see TransactionItem
* @author Danny Poppe
* @version 1.0
*/
public interface ContainerChangeEvent extends java.io.Serializable {
25
30
35
40
45
50
55
}
/**
* Returns the manipulated item.
*
* @return the item that was manipulated
*/
public TransactionItem getAffectedItem();
/**
* Returns the DBEntryTarget relative to which the TransactionItem as
* retrieved by {@link #getAffectedItem} was manipulated.
*
* @return the Container that was/is target of this event
*/
public DBEntryTarget getTransactionTarget();
/**
* Returns the DataBasket that performs the action as described by this event
*
* @return the DataBasket that performs the described action
*/
public DataBasket getBasket();
/**
* Used to determine if this is a transaction relative to a Catalog.
* In this case {@link #getTransactionTarget()} returns a {@link Catalog} and
* may be casted to such.
*
* @return <code>true</code> if this is an event that takes place at a Catalog
*/
public boolean isCatalogTransaction();
/**
* Used to determine if this is a transaction relative to a Stock.
* In this case {@link #getTransactionTarget()} returns a {@link Stock} and
* may be casted to such.
*
* @return <code>true</code> if this is an event that takes place at a Stock
*/
public boolean isStockTransaction();
113
114
ANHANG C. QUELLTEXTE
Listing C.5: epoint.data.ContainerChangeEventImpl
5
/*
* ContainerChangeEventImpl.java
*
* Created on 15. Oktober 2001, 23:41
*/
package
10
15
20
25
30
35
40
45
50
55
60
65
70
75
80
85
epoint.data;
/**
* This is a default-implementation for the {@link ContainerChangeEvent}-interface
* and provides default-implementation for use with Catalogs and Stocks.
*
* @see ContainerListener
* @see ListenableContainer
* @see Catalog
* @see Stock
* @see ContainerChangeEvent
* @author Danny Poppe
* @version 1.0
*/
public class ContainerChangeEventImpl implements ContainerChangeEvent {
/**
* The reference to the TransactionItem of this event.
*/
private TransactionItem ti;
/**
* The reference to the DBEntryTarget of this event
*/
private DBEntryTarget dbet;
/**
* The DataBasket that performs the action described by this event.
*/
private DataBasket db;
/**
* This flag is <code>true</code> only if this DBEntryTarget is a Stock.
*/
private boolean bStock = false;
/**
* This flag is <code>true</code> only if this DBEntryTarget is a Catalog.
*/
private boolean bCatalog = false;
/**
* Creates a new ContainerChangeEvent that describes an action performed
* with a Catalog.
*
* @param ci the CatalogItem that was modified
* @param c the Catalog in which the state of the CatalogItem has changed
* @param db the DataBasket relative to which the state of the CatalogItem has changed
*/
public ContainerChangeEventImpl(CatalogItem ci, Catalog c, DataBasket db) {
ti = ci;
dbet = c;
this.db = db;
bCatalog = true;
}
/**
* Creates a new ContainerChangeEvent that describes an action performed
* with a Stock.
*
* @param si the StockItem that was modified
* @param s the Stock in which the state of the StockItem has changed
* @param db the DataBasket relative to which the state of the StockItem has changed
*/
public ContainerChangeEventImpl(StockItem si, Stock s, DataBasket db) {
ti = si;
dbet = s;
this.db = db;
bStock = true;
}
/**
*/
// Description is inherited
public TransactionItem getAffectedItem() {
return ti;
}
/**
*/
// Description is inherited
public DBEntryTarget getTransactionTarget() {
return dbet;
}
C.1. PACKAGE EPOINT.DATA
/**
*/
// Description is inherited
public DataBasket getBasket() {
return db;
}
90
95
/**
*/
// Description is inherited
public boolean isStockTransaction() {
return bStock;
}
100
105
}
/**
*/
// Description is inherited
public boolean isCatalogTransaction() {
return bCatalog;
}
Listing C.6: epoint.data.ContainerListener
5
10
15
20
25
30
35
40
45
50
55
60
/*
* ContainerListener.java
*
* Created on 12. Oktober 2001, 16:23
*/
package epoint.data;
import java.rmi.RemoteException;
import epoint.data.exception.*;
import java.beans.PropertyChangeEvent;
/**
* A ContainerListener can be attached to any {@link ListenableContainer}
* and is listening to all transactions of items in such containers. Additionally
* listening to property-changes is supported. But it is implementation* specific to the ListenableContainer, what property-changes are
* propagated (eg. a name-change or something like that).<br>
* As for this framework there may be two types of listeners supported. The first
* type is a remotely available Listener that may derive from {@link
* epoint.data.rmi.ContainerListenerAdapter ContainerListenerAdapter}. These
* Listeners are instantiated for example at an EPoint and listen to a Catalog
* that is remotely available at the Shop, as long as the EPoint is "on-line".
* Afterwards they unregister automatically (if not explicitly done) from the
* Catalog.<br>
* The other type of Listeners derive from {@link epoint.data.rmi.PersistentContainerListener
* PersistentContainerListener} and are serialized and re-instanciated as remotely
* available Listeners at the Shop! So the creating instance has no more
* access to that Listener, but the Listener belongs to the persistent state of the
* container and may react on actions as described above. They are useful to
* veto some actions and to control referential integrity, as they have access
* to the entire Shop's environment they can even directly access the
* PersistenceManager of the Shop - although they were created at client-side for
* example.<br>
* <br>
* ATTENTION: As to the nature of RMI, which interprets various calls as own Threads
* it may be possible that a listener recieves an information some time after
* the action took place - this may result in apparently diffuse information. For example:
* <ol>
* <li>an item is removed from a catalog
* <li>the Catalog fires a <code>canRemoveItem</code> and a <code>removedItem</code>
* <li>the listener recieves both actions and registers them in its own data-structures
* <li>the transaction is rolled back and the Catalog fires a <code>rolledBackRemoveTransactionItem</code>
* <li>another independent client removes the same item again and step 1 and 2 take place
* <li>the listener recieves the <code>rolledBackRemoveTransactionItem</code>
* </ol>
* Between the last two steps the listener may be confused, as the second item cannot
* remove an item with another DataBasket again, as it was previously removed by
* another DataBasket. But the listener can order the events, by the identity of the
* DataBaskets.<br>
* The only thing that can be guaranteed is, that depending events (after an addedItem always
* follows a rollBack or a commit) always come in the right order!
*
*
* @see epoint.data.ListenableContainer
* @see epoint.data.ContainerChangeEvent
* @see epoint.data.rmi.PersistentContainerListener
* @see epoint.data.rmi.ContainerListenerAdapter
* @author Danny Poppe
* @version 1.0
*/
public interface ContainerListener extends java.rmi.Remote, java.util.EventListener {
115
116
65
70
75
80
85
90
95
100
105
110
115
120
125
130
135
ANHANG C. QUELLTEXTE
/**
* Called whenever a TransactionItem was added to a DBEntryTarget using a
* DataBasket that has not yet committed.
*
* @param e an event object describing the event.
* @throws RemoteException whenever an error occurs during remote-access
*/
public void addedItem(ContainerChangeEvent e) throws RemoteException;
/**
* Called whenever the adding of a TransactionItem was commited.
*
* @param e an event object describing the event.
* @throws RemoteException whenever an error occurs during remote-access
*/
public void committedAddTransactionItem(ContainerChangeEvent e) throws RemoteException;
/**
* Called whenever the adding of a TransactionItem was rolled back.
*
* @param e an event object describing the event.
* @throws RemoteException whenever an error occurs during remote-access
*/
public void rolledbackAddTransactionItem(ContainerChangeEvent e) throws RemoteException;
/**
* Called to ask whether a TransactionItem may be removed. If one of the listeners vetos the removal, all
* listeners that had already been asked will receive a {@link #noRemoveTransactionItem noRemoveTransactionItem}
* event.
*
* @param e an event object describing the event.
* @exception VetoException if the listener wants to veto the removal.
* @throws RemoteException whenever an error occurs during remote-access
*/
public void canRemoveTransactionItem(ContainerChangeEvent e) throws VetoException, RemoteException;
/**
* Called for each listener that already agreed with a removal that was then rejected by another listener.
*
* @param e an event object describing the event.
* @throws RemoteException whenever an error occurs during remote-access
*/
public void noRemoveTransactionItem(ContainerChangeEvent e) throws RemoteException;
/**
* Called whenever a TransactionItem was removed from the DBEntryTarget.
*
* @param e an event object describing the event.
* @throws RemoteException whenever an error occurs during remote-access
*/
public void removedTransactionItem(ContainerChangeEvent e) throws RemoteException;
/**
* Called whenever the removal of a TransactionItem was commited.
*
* @param e an event object describing the event.
* @throws RemoteException whenever an error occurs during remote-access
*/
public void committedRemoveTransactionItem(ContainerChangeEvent e) throws RemoteException;
/**
* Called whenever the removal of a TransactionItem was rolled back.
*
* @param e an event object describing the event.
* @throws RemoteException whenever an error occurs during remote-access
*/
public void rolledbackRemoveTransactionItem(ContainerChangeEvent e) throws RemoteException;
/**
* Called to ask whether a TransactionItem may be edited. If one of the listeners vetos the editing, all
* steners that had already been asked will receive a {@link #noEditTransactionItem noEditTransactionItem} event.
*
* @param e an event object describing the event.
* @exception VetoException if the listener wants to veto the editing.
* @throws RemoteException whenever an error occurs during remote-access
*/
public void canEditTransactionItem(ContainerChangeEvent e) throws VetoException, RemoteException;
145
/**
* Called for each listener that already agreed with an editing that was then rejected by another listener.
*
* @param e an event object describing the event.
* @throws RemoteException whenever an error occurs during remote-access
*/
public void noEditTransactionItem(ContainerChangeEvent e) throws RemoteException;
150
/**
* Called whenever editing a TransactionItem was started. This event may be accompanied by a
* <code>removedTransactionItem</code> and a <code>addedTransactionItem</code> event, but this is implementation
* specific.
140
C.1. PACKAGE EPOINT.DATA
*
* @param e an event object describing the event.
* @throws RemoteException whenever an error occurs during remote-access
*/
public void editingTransactionItem(ContainerChangeEvent e) throws RemoteException;
155
/**
* Called whenever editing a TransactionItem was commited.
*
* @param e an event object describing the event.
* @throws RemoteException whenever an error occurs during remote-access
*/
public void committedEditTransactionItem(ContainerChangeEvent e) throws RemoteException;
160
165
/**
* Called whenever editing a TransactionItem was rolled back.
*
* @param e an event object describing the event.
* @throws RemoteException whenever an error occurs during remote-access
*/
public void rolledbackEditTransactionItem(ContainerChangeEvent e) throws RemoteException;
170
175
180
}
/**
* Called whenever a specific property of the container has changed.
* This is implemented for at least the name of the container
*
* @param event an event object describing the event
* @throws RemoteException whenever an error occurs during remote-access
*/
public void propertyChange(PropertyChangeEvent event) throws RemoteException;
Listing C.7: epoint.data.CountingStock
5
10
15
20
25
30
35
40
45
50
/*
* CountingStock.java
*
* Created on 9. Januar 2002, 12:09
*/
package epoint.data;
import java.rmi.*;
import epoint.data.exception.*;
/**
* A CountingStock is a special Stock, that is only used to count the number
* of CatalogItems available in the Catalog the Stock is based on. That means
* there are no more additional StockItems stored as an identity for a CatalogItem.
* Instead the StockItems are taken as "counting elements" for the CatalogItem
* they are associated with.
*
* @author Danny Poppe
* @version 1.0
*/
public interface CountingStock extends Stock, Remote {
/**
* Adds a number of counting StockItems for the given key in the BaseCatalog
* to this CountingStock.
*
* @param sKey the key of the CatalogItem in the BaseCatalog for which to add
*
a number of counting StockItems
* @param iCount the number of virtually added StockItems, which are counting
*
the associated CatalogItem
* @param db the DataBasket that symbolizes the transaction in which to perform
*
this action
* @throws RemoteException whenever an error occurs during remote-access
*/
public void add(String sKey, int iCount, DataBasket db) throws RemoteException;
/**
* Adds a number of counting StockItems for the given key in the BaseCatalog
* to this CountingStock. Therefore a anonymous Dataasket is used that is
* immediately committed.
*
* @param sKey the key of the CatalogItem in the BaseCatalog for which to add
*
a number of counting StockItems
* @param iCount the number of virtually added StockItems, which are counting
*
the associated CatalogItem
* @throws RemoteException whenever an error occurs during remote-access
*/
public void add(String sKey, int iCount) throws RemoteException;
/**
* Removes a number of counting StockItems for the given key in the BaseCatalog
* from this CountingStock.
*
* @param sKey the key of the CatalogItem in the BaseCatalog for which to remove
*
a number of counting StockItems
117
118
55
60
65
70
75
80
}
ANHANG C. QUELLTEXTE
* @param iCount the number of virtually removed StockItems, which are counting
*
the associated CatalogItem
* @param db the DataBasket that symbolizes the transaction in which to perform
*
this action
* @throws RemoteException whenever an error occurs during remote-access
* @throws VetoException if a listener vetoes the removal
* @throws IllegalArgumentException if the number of StockItems that shall be removed
*
exceeds the number of available StockItems or if the provided number
*
is negative
*/
public void remove(String sKey, int iCount, DataBasket db) throws RemoteException, VetoException;
/**
* Removes a number of counting StockItems for the given key in the BaseCatalog
* from this CountingStock. Therefore an anonymous DataBasket is used, that is
* immediately committed.
*
* @param sKey the key of the CatalogItem in the BaseCatalog for which to remove
*
a number of counting StockItems
* @param iCount the number of virtually removed StockItems, which are counting
*
the associated CatalogItem
* @throws RemoteException whenever an error occurs during remote-access
* @throws VetoException if a listener vetoes the removal
* @throws IllegalArgumentException if the number of StockItems that shall be removed
*
exceeds the number of available StockItems or if the provided number
*
is negative
*/
public void remove(String sKey, int iCount) throws RemoteException, VetoException;
Listing C.8: epoint.data.DataBasket
5
10
15
20
25
30
35
40
45
50
55
/*
* DataBasket.java
*
* Created on 21. August 2001, 20:31
*/
package epoint.data;
import epoint.data.exception.*;
import java.rmi.*;
/**
* The DataBasket describes a logical transaction that is composed of {@link DBEntry DBEntries}
* describing actions on {@link TransactionItem TransactionItems} relative to some
* {@link DBEntryTarget DBEntryTargets}. Therefore it stores several DBEntries in order
* and does a commit or rollback on all of them at once, using a single background-transaction
* for persistence of the actions effects.<br>
* DataBaskets can be seen as the container for the actions that are bound together
* as an atomic action, but it can also be seen as a viewpoint for future-actions. For example
* if a DataBasket was used to add a TransactionItem to a DBEntryTarget, the effects of this
* action can only be seen from the viewpoint of the DataBasket. That means if another DataBasket
* is used to delete the just added item it cannot see the item until the first DataBasket has
* committed (therefore it will fail to delete the item until committ-time).<br>
* But the second basket may also be unable to add the same item again (as implemented in Catalog),
* because this means a concurrent action which may produce forbidden states in the container (eg.
* a duplicate key). And this is although the second basket doesn't know about the first
* DataBasket adding the item. In this case normally a {@link epoint.data.exception.DataBasketConflictException}
* is thrown and the programmer may decide to wait until the first DataBasket has committed to try again
* or react properly.<br>
* The DataBasket is persistent until it is explicitly deleted. But its DBEntries are only
* persistent until the Basket has committed or was rolled back. The name of the DataBasket
* is unique under all DataBaskets and cannot be chosen or changed.<br>
* A DataBasket has several states that signal, if this DataBaskt is currently used,
* has failed to commit or rollback a transaction, or is clean and empty. This state
* is automatically checked when using the DataBasket and can be checked externally,
* with the string-constants defined in this interface.<br>
* <br>
* DBEntries in a DataBasket are always organized in an ordered list, describing the
* several actions as described by the DBEntryTargets. But the user of this framework normally
* uses the DataBasket only as a TransactionHandle and never looks into the DataBasket itself, as
* the content depends on the DBEntryTargets implementation.
*
* @see TransactionItem
* @see DBEntry
* @see DBEntryTarget
*
* @author Danny Poppe
* @version 1.0
*/
public interface DataBasket extends java.rmi.Remote, Nameable, PersistentFrameworkObject {
/**
* This constant defines the name of each DataBasket as beeing part
* of its persistent state.
*/
nal public static String PERSISTENT_PROPERTY_BASKETNAME = "DataBasket.Name";
C.1. PACKAGE EPOINT.DATA
60
65
70
75
80
85
90
95
100
105
110
115
120
125
130
135
140
/**
* This constant reflects the state of the DataBasket as beeing in use
* and usable for more actions.
*/
public nal static String DB_USED = "in use";
/**
* This constant reflects the state of the DataBasket as currently
* committing its actions and unusable for this time.
*/
public nal static String DB_COMMIT = "commit";
/**
* This constant reflects the state of the DataBasket as currently
* rolling back its actions and unusable for this time.
*/
public nal static String DB_ROLLBACK = "rollback";
/**
* This constant reflects the state of the DataBasket as having
* finished previous actions and beeing ready for re-use.
*/
public nal static String DB_READY = "ready";
/**
* This constant reflects the state of the DataBasket as having
* failed its previous actions and beeing unusuable, but ready for
* inspection.
*/
public nal static String DB_FAILED = "failed";
/**
* Adds a new DBEntry to this DataBasket which describes one change
* as part of the whole Transaction of this DataBasket. The given entry
* is enqueued at the and of all other.
*
* @param dbe the DataBasketEntry that shall be part of this DataBaskets
*
Transaction
* @throws RemoteException whenever an error occurs during remote-access
* @param sConPasswd the password for the connection
*/
public void add(DBEntry dbe, String sConPasswd) throws RemoteException;
/**
* Returns an iterator that returns all DBEntries of this DataBasket
*
* @return an iterator returning all DBEntries in this DataBasket
* @throws RemoteException whenever an error occurs during remote-access
*/
public RemoteIterator iterator() throws RemoteException;
/**
* Returns all DBEntries of this DataBasket, that refer to
* the given TransactionItem
*
* @param ti the TransactionItem for which the DBEntries shall be returned
* @return an iterator over all DBEntries describing actions on the given TransactionItem
* @throws RemoteException whenever an error occurs during remote-access
*/
public RemoteIterator iterator(TransactionItem ti) throws RemoteException;
/**
* Makes all changes described by DBEntries in this DataBasket persistent,
* by executing all {@link DBEntryTarget#commitTransaction(DBEntry,String)}
* methods of the DBEntryTargets for each DBEntry in this Container.
*
* @throws RemoteException whenever an error occurs during remote-access
*/
public void commit() throws RemoteException;
/**
* Rolls back all changes described by DBEntries in this DataBasket,
* by executing all {@link DBEntryTarget#rollbackTransaction(DBEntry,String)}
* methods of the DBEntryTargets for each DBEntry in this Container.
*
* @throws RemoteException whenever an error occurs during remote-access
*/
public void rollback() throws RemoteException;
/**
* Returns <code>true</code> only if this DataBasket has finished and may be
* re-used from the scratch.
*
* @return <code>true</code> only if this DataBasket contains no DBEntries
* @throws RemoteException whenever an error occurs during remote-access
*/
public boolean hasFinished() throws RemoteException;
/**
* Returns the next index for a DBEntry that may be used to enqueue it
* to this DataBasket.
*
* @return the next higher index for a DBEntry in this DataBasket
* @throws RemoteException whenever an error occurs during remote-access
*/
public int getNextOrderedIndex() throws RemoteException;
/**
119
120
* Returns the size of this DataBasket as the number of DBEntries
* stored in it.
*
* @return the size of this DataBasket
* @throws RemoteException whenever an error occurs during remote-access
*/
public int size() throws RemoteException;
145
150
/**
* Returns the DBEntry at the given index in this DataBasket.
*
* @param iIdx the index-position from which to return the DBEntry
* @return the DBEntry at the given index
* @throws RemoteException whenever an error occurs during remote-access
*/
public DBEntry get(int iIdx) throws RemoteException;
155
160
/**
* Removes a single action from this DataBasket.
*
* @param dbe the DBEntry to remove from this basket
* @param sConPasswd the password for the connection in which to make thsi removal persistent
* @throws RemoteException whenever an error occurs during remote-access
*/
public void removeTransaction(DBEntry dbe, String sConPasswd) throws RemoteException;
165
170
ANHANG C. QUELLTEXTE
/*******************************************************************************
*
Nameable-Interface
******************************************************************************/
/**
* Returns the unique name of this DataBasket.
*
* @return the name of this DataBasket
* @throws RemoteException whenever an error occurs during remote-access
*/
public String getName() throws RemoteException;
175
180
185
}
/**
* This method cannot be used in a DataBasket and always throws an exception!
*
* @param sName the new name of the DataBasket
* @throws RemoteException whenever an error occurs during remote-access
*/
public void setName(String sName) throws RemoteException;
Listing C.9: epoint.data.DBEntry
5
/*
* DBEntry.java
*
* Created on 21. August 2001, 20:38
*/
package
10
15
20
25
30
35
import
import
epoint.data;
java.rmi.*;
epoint.data.exception.*;
/**
* A DBEntry describes changes made within a transaction to a {@link DBEntryTarget}
* implementing container. This basic definition describes removing, adding and
* modifying transactions. DBEntries are normally stored within a {@link DataBasket}
* in that order they were created (this is important, as most likely dependencies exist
* between severeal actions within a transaction).<br>
* A DBEntry is always created by the DBEntryTarget that must persistify the actions
* described by the DBEntry on demand when the appropriate methdos are called.<br>
* An existing DBEntry always belongs to a DataBasket and describes a transaction
* of a {@link TransactionItem} relative to a {@link DBEntryTarget} and is persistent
* as long as the corresponding DataBasket is persistent and has not yet committed or
* rolled back its registered transactions.
*
* @see DataBasket
* @see DBEntryTarget
* @see TransactionItem
*
* @author Danny Poppe
* @version 1.0
*/
public interface DBEntry extends Remote, PersistentFrameworkObject {
/**
* This constant defines the ordered index of this DBEntry in the DataBasket
* as part of its persistent state.
*/
C.1. PACKAGE EPOINT.DATA
nal public static
40
45
50
55
60
65
70
75
80
85
90
95
100
105
110
115
120
125
String PERSISTENT_PROPERTY_ORDER_INDEX = "DBEntry.OrderIndex";
/**
* This constant defines the transaction-type described by this DBEntry
* as part of its persistent state.
*/
nal public static String PERSISTENT_PROPERTY_TRANSACTION_TYPE = "DBEntry.TransactionType";
/**
* This constant defines additional persistent properties describing this DBEntry
* as part of its persistent state. This is done as an additional key-value
* mapping and may be used be extending implementations of the transaction
* mechanism.
*/
nal public static String PERSISTENT_PROPERTY_PROPERTIES = "DBEntry.Properties";
/**
* This constant defines a removing transaction beeing described by this
* DBEntry.
*/
nal public static String TRANSACTION_REMOVE = "Removing Transaction";
/**
* This constant defines an adding transaction beeing described by this
* DBEntry.
*/
nal public static String TRANSACTION_ADD = "Adding Transaction";
/**
* This constant defines a modifying transaction beeing described by this
* DBEntry.
*/
nal public static String TRANSACTION_MODIFY = "Modifying Transaction";
/**
* Returns the TransactionItem that is manipulated in the way it is described
* by this DBEntry.
*
* @return the TransactionItem of this DBEntry
* @throws RemoteException whenever an error occurs during remote-access
*/
public TransactionItem getTransactionItem() throws RemoteException;
/**
* Returns a String-identifier that describes the change that is to be
* done with the TransactionItem. Each {@link DBEntryTarget} must understand
* how this String is interpreted and how to make the appropriate change
* persistent or how to revert it.
*
* @return one of the constants as defined by this interface or its descendants
* @throws RemoteException whenever an error occurs during remote-access
*/
public String getTransactionType() throws RemoteException;
/**
* Returns the DataBasket which contains this DBEntry.
*
* @return the DataBasket containing this DBEntry
* @throws RemoteException whenever an error occurs during remote-access
*/
public DataBasket getBasket() throws RemoteException;
/**
* Returns additional properties for the action described by this
* DBEntry.
*
* @param sKey the key for the property to be returned
* @return the property that is stored for the given key or <code>null</code> if no
*
such property exists
* @throws RemoteException whenever an error occurs during remote-access
*/
public String getProperty(String sKey) throws RemoteException;
/**
* Returns all additional properties of this DBEntry.
*
* @return a mapping of key-value pairs that describe the additional properties
*
of this DBEntry
* @throws RemoteException whenever an error occurs during remote-access
*/
public java.util.Map getProperties() throws RemoteException;
/**
* Returns the target-container relative to which this DBEntry describes
* an action that was performed within a transaction.
*
* @return the DBEntryTarget the action described by this DBEntry belongs to
* @throws UnknownTargetException if the target cannot be interpreted
* @throws RemoteException whenever an error occurs during remote-access
*/
public DBEntryTarget getTarget() throws RemoteException, UnknownTargetException;
121
122
130
135
140
}
ANHANG C. QUELLTEXTE
/**
* Returns the ordered index in (ascending order) at which this DBEntry
* is stored within its {@link DataBasket}.
*
* @return the relative position of this DBEntry within the DataBasket
* @throws RemoteException whenever an error occurs during remote-access
*/
public int getOrderIndex() throws RemoteException;
/**
* This method is only used for framework-internal reasons.
* It initializes the caches for persistent framework-objects.
*
* @param sConPasswd the connections password
* @throws RemoteException whenever an error occurs during the use of RMI
*/
public void initCache(String sConPasswd) throws RemoteException;
Listing C.10: epoint.data.DBEntryTarget
5
10
15
20
25
30
35
40
45
50
55
60
/*
* DBEntryTarget.java
*
* Created on 12. September 2001, 10:51
*/
package epoint.data;
import java.rmi.*;
import java.io.Serializable;
import java.sql.Connection;
/**
* This interface describes a possible target of transactions. This means
* that any container implementing this interface provides the ability for
* its elements implementing the {@link TransactionItem}-interface to be part
* of a transaction.<br>
* This basic-interface declares methdos for such a container that may be called
* to commit or rollback actions that were performed with the implementing
* container. Therefore such actions must have been described with {@link DBEntry DBEntries}
* and their effects must be saved in the state of the container for detecting
* concurrent actions that cannot be performed until the affected transaction
* has committed or failed. This information is also evaluated for the methods in
* this interface.<br>
* Such DBEntries are normally collected within {@link DataBasket DataBaskets}
* and are rolled back or committed in their entirety.<br>
* This DBEntry is always persistent and therefore transactions may securely extend
* over time. Although this allows transactions to be open for a very long time,
* this should not be used in this way, as open transactions always need a lot of data
* and ressources which are unsusable as long as a transaction is persistent and not
* used. The persistence-feature for transactions is only used for security-reasons!
*
* @see DataBasket
* @see DBEntry
* @see TransactionItem
*
* @author Danny Poppe
* @version 1.0
*/
public interface DBEntryTarget extends Remote, Serializable, PersistentFrameworkObject {
/**
* This method rolls back a single action of an entire transaction that was performed
* with this Container. Therefore it is checked if the given DBEntry describes a
* valid transaction relative to this Container and deletes the data
* that prepared the action within the Container.<br>
* As transactions are normally rolled back in its entirety a password must be
* provided that is unique for all rollbacks that belong together. This password
* identifies the background-transaction that is used for persistence in a database
* for example. This password ensures that more than one action is rolled back in the
* background together with all others - which means that rollback in its persistent state
* can also fail only in its entirety.
*
* @param dbe the databasket-entry that describes an action performed relative to this Catalog
* @param sConPasswd the password to use for the background-transaction that combines more than one rollback of actions within a
transaction
* @return a Runnable-object that contains things to be done after the entire transaction is rolled back
* @throws RemoteException if something goes wrong during the remote-procedure-call
*/
public RemoteRunnable rollbackTransaction(DBEntry dbe, String sConPasswd) throws RemoteException;
/**
* This method commits the given part of a transaction that was performed with this
* container. The same comments as in {@link #rollbackTransaction(DBEntry,String)} do
* apply.
*
* @param dbe the databasket-entry that describes an action performed relative to this Catalog
* @param sConPasswd the password to use for the background-transaction that combines more than one rollback of actions within a
transaction
C.1. PACKAGE EPOINT.DATA
65
70
75
}
* @return a Runnable-object that contains things to be done after the entire transaction is rolled back
* @throws RemoteException if something goes wrong during the remote-procedure-call
*/
public RemoteRunnable commitTransaction(DBEntry dbe, String sConPasswd) throws RemoteException;
/**
* This method checks, if the given DBEntry describes a valid action that was
* already done to this Container within an uncommitted transaction.
*
* @param dbe the databasket-entry that describes an action relative to some container
* @return <code>true</code> only if the given action was really done within a transaction
*
to this container
* @throws RemoteException whenever an error occurs during remote-access
*/
public boolean isValidTransaction(DBEntry dbe) throws RemoteException;
Listing C.11: epoint.data.DecimalValue
package epoint.data;
import java.math.BigDecimal;
5
10
15
20
25
30
35
40
45
50
55
60
65
/**
* A DecialValue is a Value that may be used to represent numbers.
*
* @author Danny Poppe
* @version 1.0
*/
public class DecimalValue extends AbstractValue implements Comparable {
////////////////////////////////////////////////////////////////////////////////
//
NEW CLASS CONSTANTS
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//
NEW CLASS VARIABLES
////////////////////////////////////////////////////////////////////////////////
/**
* Stores the String-representation of the number represented by this Value
*/
private String sNumber;
////////////////////////////////////////////////////////////////////////////////
//
OVERRIDDEN CLASS VARIABLES
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//
NEW INSTANCE VARIABLES
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//
OVERRIDDEN INSTANCE VARIABLES
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//
CONSTRUCTORS
////////////////////////////////////////////////////////////////////////////////
/**
* Creates new undefined DecimalValue, representing no number.
*/
protected DecimalValue() {
}
/**
* Creates a new decimal Value, using an integer.
*
* @param iInteger the decimal that shall be represented by this Value
*/
public DecimalValue(int iInteger) {
sNumber = ""+iInteger;
}
/**
* Creates a new decimal Value, using a long.
*
* @param lLong the long-value to initialize this decimal value
*/
public DecimalValue(long lLong) {
sNumber = ""+lLong;
}
/**
* Creates a new decimal Value, using a float.
*
* @param fFloat the float-value to initialize this decimal value
123
124
70
*/
public
}
75
80
85
90
95
100
105
110
115
120
125
130
135
140
145
150
155
ANHANG C. QUELLTEXTE
DecimalValue(oat fFloat) {
sNumber = ""+fFloat;
/**
* Creates a new decimal Value, using a double.
*
* @param dDouble the double-value to initialize this decimal value
*/
public DecimalValue(double dDouble) {
sNumber = ""+dDouble;
}
/**
* Creates a new decimal Value, using a {@link BigDecimal}.
*
* @param bdBigDecimal the BigDecimal to initialize this decimal value
*/
public DecimalValue(BigDecimal bdBigDecimal) {
sNumber = bdBigDecimal.toString();
}
////////////////////////////////////////////////////////////////////////////////
//
INTERFACE Value
////////////////////////////////////////////////////////////////////////////////
/**
* Compares this DecimalValue to another (in fact the same as {@link #compareTo(Object)}.
*
* @return an <code>int</code> as specified by {@link Comparable#compareTo(Object)}
* @param v the Value that is compared to this DecimalValue
*/
public int compareTo(Value v) {
return (new BigDecimal(sNumber)).compareTo(((DecimalValue)v).decimalValue());
}
/**
* This method returns <code>false</code> to indicate that this Value is not a
* Catalog.
*
* @return <code>true</code>
*/
nal public boolean isCatalog() {
return false;
}
////////////////////////////////////////////////////////////////////////////////
//
INTERFACE Comparable
////////////////////////////////////////////////////////////////////////////////
/**
* Compares this object with the specified object for order. Returns a
* negative integer, zero, or a positive integer as this object is less
* than, equal to, or greater than the specified object.<p>
*
* In the foregoing description, the notation
* <tt>sgn(</tt><i>expression</i><tt>)</tt> designates the mathematical
* <i>signum</i> function, which is defined to return one of <tt>-1</tt>,
* <tt>0</tt>, or <tt>1</tt> according to whether the value of <i>expression</i>
* is negative, zero or positive.
*
* The implementor must ensure <tt>sgn(x.compareTo(y)) ==
* -sgn(y.compareTo(x))</tt> for all <tt>x</tt> and <tt>y</tt>. (This
* implies that <tt>x.compareTo(y)</tt> must throw an exception iff
* <tt>y.compareTo(x)</tt> throws an exception.)<p>
*
* The implementor must also ensure that the relation is transitive:
* <tt>(x.compareTo(y)>0 && y.compareTo(z)>0)</tt> implies
* <tt>x.compareTo(z)>0</tt>.<p>
*
* Finally, the implementer must ensure that <tt>x.compareTo(y)==0</tt>
* implies that <tt>sgn(x.compareTo(z)) == sgn(y.compareTo(z))</tt>, for
* all <tt>z</tt>.<p>
*
* It is strongly recommended, but <i>not</i> strictly required that
* <tt>(x.compareTo(y)==0) == (x.equals(y))</tt>. Generally speaking, any
* class that implements the <tt>Comparable</tt> interface and violates
* this condition should clearly indicate this fact. The recommended
* language is "Note: this class has a natural ordering that is
* inconsistent with equals."
*
* @param o the Object to be compared.
* @return a negative integer, zero, or a positive integer as this object
*
is less than, equal to, or greater than the specified object.
*
* @throws ClassCastException if the specified object's type prevents it
*
from being compared to this Object.
C.1. PACKAGE EPOINT.DATA
*/
160
165
170
175
180
185
190
195
200
205
210
215
220
225
230
235
240
public int compareTo(Object o) {
return compareTo((Value)o);
}
////////////////////////////////////////////////////////////////////////////////
//
OVERRIDDEN CLASS METHODS
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//
OVERRIDDEN INSTANCE METHODS
////////////////////////////////////////////////////////////////////////////////
/**
* Returns <code>true</code> if the given object is an instance of
* DecimalValue and represents the same number.
*
* @param o the Object to compare to this DecimalValue
* @return <code>true</code> if the given Object is a DecimalValue representing the same number
*/
public boolean equals(Object o) {
if (o instanceof DecimalValue) {
return decimalValue().equals(((DecimalValue)o).decimalValue());
}
return false;
}
////////////////////////////////////////////////////////////////////////////////
//
NEW CLASS METHODS
////////////////////////////////////////////////////////////////////////////////
/**
* Returns this DecimalValue as a BigDecimal.
*
* @return this Value as a BigDecimal
*/
public BigDecimal decimalValue() {
return new BigDecimal(sNumber);
}
/**
* Returns this DecimalValue as a float-value.
*
* @return this Value as a float-value
*/
public oat floatValue() {
return (new Float(sNumber)).floatValue();
}
/**
* Returns this DecimalValue as an int-value.
*
* @return this Value as an int-value
*/
public int intValue() {
return (new Integer(sNumber)).intValue();
}
/**
* Returns this DecimalValue as a double-value.
*
* @return this Value as a double-value
*/
public double doubleValue() {
return (new Double(sNumber)).doubleValue();
}
/**
* Returns this DecimalValue as a long-value.
*
* @return this Value as a long-value
*/
public long longValue() {
return (new Long(sNumber)).longValue();
}
/**
* Returns this DecimalValue as a String
*
* @return this Value as a String
*/
public String toString() {
return sNumber;
}
/**
* Takes the given DecimalValue and adds it to this DecimalValue.
*
125
126
245
* @param dvAdd the DecimalValue to be added to this DecimalValue
* @return the result of the adding
*/
public DecimalValue add(DecimalValue dvAdd) {
return new DecimalValue(decimalValue().add(dvAdd.decimalValue()));
}
250
/**
* Returns the absolute DecimalValue of this DecimalValue.
*
* @return the absolute DecimalValue of this Decimalvalue
*/
public DecimalValue abs() {
return new DecimalValue(decimalValue().abs());
}
255
260
/**
* Takes this DecimalValue and divides it by the given DecimalValue, returning
* the result.
*
* @param dvDivisor the divisor for the dividing-operation
* @return the result of the dividing-operation
*/
public DecimalValue divideBy(DecimalValue dvDivisor) {
return new DecimalValue(decimalValue().divide(dvDivisor.decimalValue(),20,decimalValue().ROUND_HALF_UP));
}
265
270
/**
* Returns the maximum of this and the given DecimalValue.
*
* @param dvCompare another DecimalValue
* @return the maximum of this and the given DecimalValue
*/
public DecimalValue max(DecimalValue dvCompare) {
return new DecimalValue(decimalValue().max(dvCompare.decimalValue()));
}
275
280
/**
* Returns the minimum of this and the given DecimalValue.
*
* @param dvCompare another DecimalValue
* @return the minimum of this and the given DecimalValue
*/
public DecimalValue min(DecimalValue dvCompare) {
return new DecimalValue(decimalValue().min(dvCompare.decimalValue()));
}
285
290
/**
* Multiplies this DecimalValue with the given DecimalValue and returns
* the result.
*
* @param dvMult the factor this DecimalValue is multiplied with
* @return the result of the multiplication
*/
public DecimalValue multiply(DecimalValue dvMult) {
return new DecimalValue(decimalValue().multiply(dvMult.decimalValue()));
}
295
300
/**
* Returns the negated DecimalValue of this DecimalValue
*
* @return the negation of this DecimalValue
*/
public DecimalValue negate() {
return new DecimalValue(decimalValue().negate());
}
305
310
/**
* Calculates the difference between this and the given Value and returns
* the result.
*
* @param dvSub the subtrahent for this operation
* @return the result of the calculation
*/
public DecimalValue subtract(DecimalValue dvSub) {
return new DecimalValue(decimalValue().subtract(dvSub.decimalValue()));
}
315
320
325
ANHANG C. QUELLTEXTE
////////////////////////////////////////////////////////////////////////////////
//
NEW INSTANCE METHODS
////////////////////////////////////////////////////////////////////////////////
}
Listing C.12: epoint.data.InformationStore
C.1. PACKAGE EPOINT.DATA
package
5
10
15
20
25
30
35
40
45
50
55
60
65
70
75
80
85
import
import
epoint.data;
java.io.Serializable;
epoint.data.exception.*;
/**
* The Interface describes fundamental requirements for a class providing
* methods for serialization and de-serialization of Non-Remote-Objects
* and primitive datatypes.<br>
* An InfoStore is primarily used for association with an {@link epoint.sale.EPoint}
* and is intended to keep track of the state of that EPoint. A Programmer may use
* the InfoStore to persistify various data that is guaranteed to be available
* if the EPoint re-starts. The programmer decides how to interpret the data to
* restore a previous state of the EPoint. Such manipulations may be committed to
* have a defined state available.<br>
* Using the {@link #getPersistenceState()} the InfoStore is able to inform about
* its last state. If an InfoStore did not previously existed, {@link #NOT_PERSISTENT_STATE}
* is returned. If it previously existed and was normally closed using {@link #closeService(boolean)}
* it is in a {@link #DEFINED_PERSISTENT_STATE} - otherwise it is always in an
* {@link #UNDEFINED_PERSISTENT_STATE} and the programmer may decide to rollback
* previous changes to a defined persistent state or to evaluate the uncommitted state.
*
* @author Danny Poppe, Mirko Pracht
* @version 1.0 27/10/01
* @since EPoint 1.0
*/
public interface
InformationStore {
/**
* Defines the a non-present persistent state
*/
nal public static int NOT_PERSISTENT_STATE = 1;
/**
* Defines a clean persistent state.
*/
nal public static int DEFINED_PERSISTENT_STATE = 2;
/**
* Defines an uncommitted persistent state.
*/
nal public static int UNDEFINED_PERSISTENT_STATE = 3;
/* ***************************** adder method's *******************************/
/** Add an <CODE>Object</CODE>, which is <CODE>Serializable</CODE>,
* to the InformationStore
* @param key A <CODE>String</CODE> as unique identifier
* @param ser the <CODE>Object</CODE>, which is <CODE>Serializable</CODE>
* @throws DuplicateKeyException if the key isn't unique
* @throws IllegalPersistenceTypeException if the Object is an instance of <CODE>Remote</CODE>
*/
public void addObject(String key, Serializable ser) throws DuplicateKeyException, IllegalPersistenceTypeException;
/** Add an <CODE>String</CODE> to the InformationStore
* @param key A <CODE>String</CODE> as unique identifier
* @param s the <CODE>String</CODE>, which should stored in the InformationStore
* @throws DuplicateKeyException if the key isn't unique
* @throws IllegalPersistenceTypeException if the Object is an instance of <CODE>Remote</CODE>
*/
public void addString(String key, String s) throws DuplicateKeyException, IllegalPersistenceTypeException;
/** Add an <CODE>boolean</CODE> to the InformationStore
* @param key A <CODE>String</CODE> as unique identifier
* @param b the <CODE>boolean</CODE>, which should stored in the InformationStore
* @throws DuplicateKeyException if the key isn't unique
* @throws IllegalPersistenceTypeException if the Object is an instance of <CODE>Remote</CODE>
*/
public void addBoolean(String key, boolean b) throws DuplicateKeyException, IllegalPersistenceTypeException;
/** Add an <CODE>byte</CODE> to the InformationStore
* @param key A <CODE>String</CODE> as unique identifier
* @param b the <CODE>byte</CODE>, which should stored in the InformationStore
* @throws DuplicateKeyException if the key isn't unique
* @throws IllegalPersistenceTypeException if the Object is an instance of <CODE>Remote</CODE>
*/
public void addByte(String key, byte b) throws DuplicateKeyException, IllegalPersistenceTypeException;
/** Add an <CODE>char</CODE> to the InformationStore
* @param key A <CODE>String</CODE> as unique identifier
* @param c the <CODE>char</CODE>, which should stored in the InformationStore
* @throws DuplicateKeyException if the key isn't unique
* @throws IllegalPersistenceTypeException if the Object is an instance of <CODE>Remote</CODE>
*/
public void addChar(String key, char c) throws DuplicateKeyException, IllegalPersistenceTypeException;
/** Add an <CODE>double</CODE> to the InformationStore
* @param key A <CODE>String</CODE> as unique identifier
* @param d the <CODE>double</CODE>, which should stored in the InformationStore
* @throws DuplicateKeyException if the key isn't unique
* @throws IllegalPersistenceTypeException if the Object is an instance of <CODE>Remote</CODE>
127
128
90
95
100
105
110
115
120
ANHANG C. QUELLTEXTE
*/
public void
addDouble(String key, double d) throws DuplicateKeyException, IllegalPersistenceTypeException;
/** Add an <CODE>float</CODE> to the InformationStore
* @param key A <CODE>String</CODE> as unique identifier
* @param f the <CODE>float</CODE>, which should stored in the InformationStore
* @throws DuplicateKeyException if the key isn't unique
* @throws IllegalPersistenceTypeException if the Object is an instance of <CODE>Remote</CODE>
*/
public void addFloat(String key, oat f) throws DuplicateKeyException, IllegalPersistenceTypeException;
/** Add an <CODE>int</CODE> to the InformationStore
* @param key A <CODE>String</CODE> as unique identifier
* @param i the <CODE>int</CODE>, which should stored in the InformationStore
* @throws DuplicateKeyException if the key isn't unique
* @throws IllegalPersistenceTypeException if the Object is an instance of <CODE>Remote</CODE>
*/
public void addInt(String key, int i) throws DuplicateKeyException, IllegalPersistenceTypeException;
/** Add an <CODE>short</CODE> to the InformationStore
* @param key A <CODE>String</CODE> as unique identifier
* @param s the <CODE>short</CODE>, which should stored in the InformationStore
* @throws DuplicateKeyException if the key isn't unique
* @throws IllegalPersistenceTypeException if the Object is an instance of <CODE>Remote</CODE>
*/
public void addShort(String key, short s) throws DuplicateKeyException, IllegalPersistenceTypeException;
/** Replace the entry for the given Key in the InformationStore.
* @param key the unique identifier for the entry in the InformationStore
* @param ser the new <code>Object</code> for the given key
* @throws VetoException occurs, if you don't have the permission for this operation
* @throws KeyNotFoundException occurs, if the given key don't exist
* @throws NotEditableException occurs, if you don't have write permissions
* @throws IllegalPersistenceTypeException occurs, if the new entry an instance
* of <code>Remote</code>
*/
/* ******************************** setter method's ************************** */
125
130
135
140
145
150
155
160
165
170
public void
setObject(String key, Serializable ser) throws KeyNotFoundException, VetoException, IllegalPersistenceTypeException;
/** Replace the entry for the given Key in the InformationStore.
* @param key the unique identifier for the entry in the InformationStore
* @param s the new <code>String</code> for the given key
* @throws VetoException occurs, if you don't have the permission for this operation
* @throws KeyNotFoundException occurs, if the given key don't exist
* @throws NotEditableException occurs, if you don't have write permissions
* @throws IllegalPersistenceTypeException occurs, if the new entry an instance
* of <code>Remote</code>
*/
public void setString(String key, String s) throws KeyNotFoundException, VetoException, NotEditableException,
IllegalPersistenceTypeException;
/** Replace the entry for the given Key in the InformationStore.
* @param key the unique identifier for the entry in the InformationStore
* @param b the new <code>boolean</code> for the given key
* @throws VetoException occurs, if you don't have the permission for this operation
* @throws KeyNotFoundException occurs, if the given key don't exist
* @throws NotEditableException occurs, if you don't have write permissions
* @throws IllegalPersistenceTypeException occurs, if the new entry an instance
* of <code>Remote</code>
*/
public void setBoolean(String key, boolean b) throws KeyNotFoundException, VetoException, NotEditableException,
IllegalPersistenceTypeException;
/** Replace the entry for the given Key in the InformationStore.
* @param key the unique identifier for the entry in the InformationStore
* @param b the new <code>byte</code> for the given key
* @throws VetoException occurs, if you don't have the permission for this operation
* @throws KeyNotFoundException occurs, if the given key don't exist
* @throws NotEditableException occurs, if you don't have write permissions
* @throws IllegalPersistenceTypeException occurs, if the new entry an instance
* of <code>Remote</code>
*/
public void setByte(String key, byte b) throws KeyNotFoundException, VetoException, NotEditableException,
IllegalPersistenceTypeException;
/** Replace the entry for the given Key in the InformationStore.
* @param key the unique identifier for the entry in the InformationStore
* @param c the new <code>char</code> for the given key
* @throws VetoException occurs, if you don't have the permission for this operation
* @throws KeyNotFoundException occurs, if the given key don't exist
* @throws NotEditableException occurs, if you don't have write permissions
* @throws IllegalPersistenceTypeException occurs, if the new entry an instance
* of <code>Remote</code>
*/
public void setChar(String key, char c) throws KeyNotFoundException, VetoException, NotEditableException,
IllegalPersistenceTypeException;
/** Replace the entry for the given Key in the InformationStore.
* @param key the unique identifier for the entry in the InformationStore
* @param d the new <code>double</code> for the given key
* @throws VetoException occurs, if you don't have the permission for this operation
* @throws KeyNotFoundException occurs, if the given key don't exist
* @throws NotEditableException occurs, if you don't have write permissions
* @throws IllegalPersistenceTypeException occurs, if the new entry an instance
C.1. PACKAGE EPOINT.DATA
175
180
185
190
195
200
205
210
* of <code>Remote</code>
*/
setDouble(String key, double d) throws KeyNotFoundException, VetoException, NotEditableException,
IllegalPersistenceTypeException;
/** Replace the entry for the given Key in the InformationStore.
* @param key the unique identifier for the entry in the InformationStore
* @param f the new <code>float</code> for the given key
* @throws VetoException occurs, if you don't have the permission for this operation
* @throws KeyNotFoundException occurs, if the given key don't exist
* @throws NotEditableException occurs, if you don't have write permissions
* @throws IllegalPersistenceTypeException occurs, if the new entry an instance
* of <code>Remote</code>
*/
public void setFloat(String key, oat f) throws KeyNotFoundException, VetoException, NotEditableException,
IllegalPersistenceTypeException;
/** Replace the entry for the given Key in the InformationStore.
* @param key the unique identifier for the entry in the InformationStore
* @param i the new <code>int</code> for the given key
* @throws VetoException occurs, if you don't have the permission for this operation
* @throws KeyNotFoundException occurs, if the given key don't exist
* @throws NotEditableException occurs, if you don't have write permissions
* @throws IllegalPersistenceTypeException occurs, if the new entry an instance
* of <code>Remote</code>
*/
public void setInt(String key, int i) throws KeyNotFoundException, VetoException, NotEditableException, IllegalPersistenceTypeException
;
/** Replace the entry for the given Key in the InformationStore.
* @param key the unique identifier for the entry in the InformationStore
* @param s the new <code>short</code> for the given key
* @throws VetoException occurs, if you don't have the permission for this operation
* @throws KeyNotFoundException occurs, if the given key don't exist
* @throws NotEditableException occurs, if you don't have write permissions
* @throws IllegalPersistenceTypeException occurs, if the new entry an instance
* of <code>Remote</code>
*/
public void setShort(String key, short s) throws KeyNotFoundException, VetoException, NotEditableException,
IllegalPersistenceTypeException;
/** Get the <code>Object</code> for the given identifier
* @param key the unique identifier for the entry
* @throws VetoException occurs, if you don't have the permission for this operation
* @throws KeyNotFoundException occurs, if the given key don't exist
* @return the <code>Object</code> for the identifier
*/
public void
/* ******************************** getter method's *****************************/
215
220
225
230
235
240
245
250
255
129
public
Object getObject(String key) throws KeyNotFoundException, VetoException;
/** Get the <code>String</code> for the given identifier
* @param key the unique identifier for the entry
* @throws VetoException occurs, if you don't have the permission for this operation
* @throws KeyNotFoundException occurs, if the given key don't exist
* @throws ClassCastException occurs, if the <code>Object</code> for the key isn't a <code>String</code>
* @return the <code>String</code> for the identifier
*/
public String getString(String key) throws KeyNotFoundException, ClassCastException, VetoException;
/** Get the <code>boolean</code> for the given identifier
* @param key the unique identifier for the entry
* @throws VetoException occurs, if you don't have the permission for this operation
* @throws KeyNotFoundException occurs, if the given key don't exist
* @throws ClassCastException occurs, if the Wrapper-Class for the key isn't a <code>Boolean</code>
* @return the <code>boolean</code> for the identifier
*/
public boolean getBoolean(String key) throws KeyNotFoundException, ClassCastException, VetoException;
/** Get the <code>byte</code> for the given identifier
* @param key the unique identifier for the entry
* @throws VetoException occurs, if you don't have the permission for this operation
* @throws KeyNotFoundException occurs, if the given key don't exist
* @throws ClassCastException occurs, if the Wrapper-Class for the key isn't a <code>Byte</code>
* @return the <code>byte</code> for the identifier
*/
public byte getByte(String key) throws KeyNotFoundException, ClassCastException, VetoException;
/** Get the <code>char</code> for the given identifier
* @param key the unique identifier for the entry
* @throws VetoException occurs, if you don't have the permission for this operation
* @throws KeyNotFoundException occurs, if the given key don't exist
* @throws ClassCastException occurs, if the Wrapper-Class for the key isn't a <code>Character</code>
* @return the <code>char</code> for the identifier
*/
public char getChar(String key) throws KeyNotFoundException, ClassCastException, VetoException;
/** Get the <code>double</code> for the given identifier
* @param key the unique identifier for the entry
* @throws VetoException occurs, if you don't have the permission for this operation
* @throws KeyNotFoundException occurs, if the given key don't exist
* @throws ClassCastException occurs, if the Wrapper-Class for the key isn't a <code>Double</code>
* @return the <code>double</code> for the identifier
*/
public double getDouble(String key) throws KeyNotFoundException, ClassCastException, VetoException;
130
260
265
270
275
280
285
290
295
300
ANHANG C. QUELLTEXTE
/** Get the <code>float</code> for the given identifier
* @param key the unique identifier for the entry
* @throws VetoException occurs, if you don't have the permission for this operation
* @throws KeyNotFoundException occurs, if the given key don't exist
* @throws ClassCastException occurs, if the Wrapper-Class for the key isn't a <code>Float</code>
* @return the <code>float</code> for the identifier
*/
public oat getFloat(String key) throws KeyNotFoundException, ClassCastException, VetoException;
/** Get the <code>int</code> for the given identifier
* @param key the unique identifier for the entry
* @throws VetoException occurs, if you don't have the permission for this operation
* @throws KeyNotFoundException occurs, if the given key don't exist
* @throws ClassCastException occurs, if the Wrapper-Class for the key isn't a <code>Integer</code>
* @return the <code>int</code> for the identifier
*/
public int getInt(String key) throws KeyNotFoundException, ClassCastException, VetoException;
/** Get the <code>short</code> for the given identifier
* @param key the unique identifier for the entry
* @throws VetoException occurs, if you don't have the permission for this operation
* @throws KeyNotFoundException occurs, if the given key don't exist
* @throws ClassCastException occurs, if the Wrapper-Class for the key isn't a <code>Short</code>
* @return the <code>short</code> for the identifier
*/
public short getShort(String key) throws KeyNotFoundException, ClassCastException, VetoException;
/**
* Make all changes persistent (since the last commit())
*/
public void commit();
/**
* Removes all changes previously made (since the last commit())
*/
public void rollback();
/**
* Opens this InfoStores service to be available for storing data.
*/
public void openService();
/** Close the related service for this InformationStore
*/
public void closeService(boolean execCommit);
public int
305
getPersistenceState();
public boolean
containsKey(String sKey);
public void
deleteProperty(String sKey);
}// InformationStore
Listing C.13: epoint.data.InformationStoreImpl
package
5
10
15
20
25
30
import
import
import
import
import
import
import
import
import
import
epoint.data;
epoint.sale.Shop;
epoint.sale.EPoint;
java.rmi.RemoteException;
java.rmi.Remote;
java.io.IOException;
java.io.Serializable;
epoint.data.exception.*;
epoint.xml.*;
epoint.xml.exception.*;
epoint.data.rmi.*;
/**
* InformationStoreImpl is the default implementation of the InformationStore Interface.
*
*
* @see InformationStore InformationStore class-documentation for further documentation
* @author Mirko Pracht, Danny Poppe
* @version 1.0 27/10/01
* @since EPoint 1.0
*/
public class
InformationStoreImpl
implements
InformationStore {
/** Describes the prefix for the internal DataBasket of the InformationStore */
nal public static String sDBNameProperty = "InternalDataBasket for ";
/** Describes the name of property, which show the successful end of the service */
nal public static String PERSISTENT_STATE_PROPERTY = "Persistent State of Service";
C.1. PACKAGE EPOINT.DATA
35
40
45
50
131
/** the Catalog for the InformationStore */
private Catalog catalog;
/** the DataBasket for the InformationStore */
private DataBasket db;
/** the XMLConverter for the InformationStore */
private XMLConverter conv;
/** the DataBasket for internal editing operations */
private DataBasket dbTemp;
/** Creates new <code>InformationStoreImpl</code>.
* @param name the name for the InformationStore
*/
public InformationStoreImpl(epoint.sale.EPointInfo epi){
conv = new XMLConverterImpl();
{
catalog = EPoint.getShopServices().getPersistentCatalog(EPoint.getShopServices().getInfoStoreName());
/* the databasket for this InfoStore is always the same... so its
* name is stored as a property in this infostore and not always
* newly created (Danny Poppe)
*/
try
55
try
{
dbTemp = EPoint.getShopServices().createPersistentDataBasket();
String sDBName = getString(sDBNameProperty + catalog.getName());
db = EPoint.getShopServices().getPersistentDataBasket(sDBName);
db.getName(); //throws an exception if db==null, so a new one is created
} catch (Exception e) {
db = EPoint.getShopServices().createPersistentDataBasket();
try {
addObject(sDBNameProperty + catalog.getName(),db.getName());
commit();
} catch (Exception e2) {
e2.printStackTrace();
}
}
if (!catalog.contains(PERSISTENT_STATE_PROPERTY,null)) setService();
60
65
70
75
80
85
}
catch
(RemoteException e) { e.printStackTrace();}
}
/** A helpful method. It controls, if an Object is an instance of <code>Remote</code>
* @throws IllegalPersistenceTypeException occurs the Runtime-Exception,
* if the Object is an instance of <code>Remtoe</code>
*/
private void remoteControl(Serializable ser) {
if (ser instanceof Remote) { throw new IllegalPersistenceTypeException("Objects of classes implementing the Remote-interface cannot
be stored!");}
}
/* ***************************** adder method's *************************/
90
95
100
105
/** Add an <CODE>Object</CODE>, which is <CODE>Serializable</CODE>,
* to the InformationStore
* @param key A <CODE>String</CODE> as unique identifier
* @param ser the <CODE>Object</CODE>, which is <CODE>Serializable</CODE>
* @throws DuplicateKeyException if the key isn't unique
* @throws IllegalPersistenceTypeException if the Object is an instance of <CODE>Remote</CODE>
*/
public void addObject(String key, Serializable ser) {
remoteControl(ser);
try {
catalog.add(EPoint.getShopServices().createCatalogItem(key,new StringValue(conv.toXMLString(ser))),db);
}
}
catch
catch
catch
(IOException ioe) {ioe.printStackTrace();}
(DataBasketConflictException dbce) {dbce.printStackTrace();}
(ClassNotFoundException ce) {ce.printStackTrace();}// end of try-catch
public void
try {
110
115
}
deleteProperty(String sKey) {
catalog.remove(sKey,db);
} catch (RemoteException rmie) {
rmie.printStackTrace();
} catch (VetoException ve) {
ve.printStackTrace();
} catch (DataBasketConflictException dbce) {
dbce.printStackTrace();
}
/** Add an <CODE>String</CODE> to the InformationStore
132
120
125
130
135
140
145
150
155
160
165
170
175
180
185
190
195
200
205
ANHANG C. QUELLTEXTE
* @param key A <CODE>String</CODE> as unique identifier
* @param s the <CODE>String</CODE>, which should stored in the InformationStore
* @throws DuplicateKeyException if the key isn't unique
* @throws IllegalPersistenceTypeException if the Object is an instance of <CODE>Remote</CODE>
*/
public void addString(String key, String toSerialize) {
addObject(key,toSerialize);
}
/** Add an <CODE>boolean</CODE> to the InformationStore
* @param key A <CODE>String</CODE> as unique identifier
* @param b the <CODE>boolean</CODE>, which should stored in the InformationStore
* @throws DuplicateKeyException if the key isn't unique
* @throws IllegalPersistenceTypeException if the Object is an instance of <CODE>Remote</CODE>
*/
public void addBoolean(String key, boolean b) {
addObject(key,new Boolean(b));
}
/** Add an <CODE>byte</CODE> to the InformationStore
* @param key A <CODE>String</CODE> as unique identifier
* @param b the <CODE>byte</CODE>, which should stored in the InformationStore
* @throws DuplicateKeyException if the key isn't unique
* @throws IllegalPersistenceTypeException if the Object is an instance of <CODE>Remote</CODE>
*/
public void addByte(String key, byte b) {
addObject(key,new Byte(b));
}
/** Add an <CODE>char</CODE> to the InformationStore
* @param key A <CODE>String</CODE> as unique identifier
* @param c the <CODE>char</CODE>, which should stored in the InformationStore
* @throws DuplicateKeyException if the key isn't unique
* @throws IllegalPersistenceTypeException if the Object is an instance of <CODE>Remote</CODE>
*/
public void addChar(String key, char c) {
addObject(key,new Character(c));
}
/** Add an <CODE>double</CODE> to the InformationStore
* @param key A <CODE>String</CODE> as unique identifier
* @param d the <CODE>double</CODE>, which should stored in the InformationStore
* @throws DuplicateKeyException if the key isn't unique
* @throws IllegalPersistenceTypeException if the Object is an instance of <CODE>Remote</CODE>
*/
public void addDouble(String key, double d) {
addObject(key,new Double(d));
}
/** Add an <CODE>float</CODE> to the InformationStore
* @param key A <CODE>String</CODE> as unique identifier
* @param f the <CODE>float</CODE>, which should stored in the InformationStore
* @throws DuplicateKeyException if the key isn't unique
* @throws IllegalPersistenceTypeException if the Object is an instance of <CODE>Remote</CODE>
*/
public void addFloat(String key, oat f) {
addObject(key,new Float(f));
}
/** Add an <CODE>int</CODE> to the InformationStore
* @param key A <CODE>String</CODE> as unique identifier
* @param i the <CODE>int</CODE>, which should stored in the InformationStore
* @throws DuplicateKeyException if the key isn't unique
* @throws IllegalPersistenceTypeException if the Object is an instance of <CODE>Remote</CODE>
*/
public void addInt(String key, int i) {
addObject(key,new Integer(i));
}
/** Add an <CODE>short</CODE> to the InformationStore
* @param key A <CODE>String</CODE> as unique identifier
* @param s the <CODE>short</CODE>, which should stored in the InformationStore
* @throws DuplicateKeyException if the key isn't unique
* @throws IllegalPersistenceTypeException if the Object is an instance of <CODE>Remote</CODE>
*/
public void addShort(String key, short s){
addObject(key,new Short(s));
}
/* ******************************** setter method's ******************************/
/** Replace the entry for the given Key in the InformationStore.
* @param key the unique identifier for the entry in the InformationStore
* @param ser the new <code>Object</code> for the given key
* @throws VetoException occurs, if you don't have the permission for this operation
* @throws KeyNotFoundException occurs, if the given key don't exist
* @throws NotEditableException occurs, if you don't have write permissions
* @throws IllegalPersistenceTypeException occurs, if the new entry an instance
* of <code>Remote</code>
*/
public void setObject(String key, Serializable ser) throws VetoException {
remoteControl(ser);
C.1. PACKAGE EPOINT.DATA
try
210
215
220
225
230
235
240
245
250
255
260
265
270
275
280
285
290
295
{
if
(!(catalog.contains(key,db))) {
throw new KeyNotFoundException("Could not find the key "+key+" in the InformationStore!");
} // end of if (catalog.keyList().contains(key))
try {
catalog.get(key,db,true).setValue(new StringValue(conv.toXMLString(ser)));
}
catch (IOException ioe) {ioe.printStackTrace();}
catch (DataBasketConflictException dbce) {dbce.printStackTrace();}
catch (ClassNotFoundException ce) {ce.printStackTrace();}
} catch (RemoteException re) { re.printStackTrace();}
}
/** Replace the entry for the given Key in the InformationStore.
* @param key the unique identifier for the entry in the InformationStore
* @param s the new <code>String</code> for the given key
* @throws VetoException occurs, if you don't have the permission for this operation
* @throws KeyNotFoundException occurs, if the given key don't exist
* @throws NotEditableException occurs, if you don't have write permissions
* @throws IllegalPersistenceTypeException occurs, if the new entry an instance
* of <code>Remote</code>
*/
public void setString(String key, String s) throws VetoException {
setObject(key,s);
}
/** Replace the entry for the given Key in the InformationStore.
* @param key the unique identifier for the entry in the InformationStore
* @param b the new <code>boolean</code> for the given key
* @throws VetoException occurs, if you don't have the permission for this operation
* @throws KeyNotFoundException occurs, if the given key don't exist
* @throws NotEditableException occurs, if you don't have write permissions
* @throws IllegalPersistenceTypeException occurs, if the new entry an instance
* of <code>Remote</code>
*/
public void setBoolean(String key, boolean b) throws VetoException {
setObject(key, new Boolean(b));
}
/** Replace the entry for the given Key in the InformationStore.
* @param key the unique identifier for the entry in the InformationStore
* @param b the new <code>byte</code> for the given key
* @throws VetoException occurs, if you don't have the permission for this operation
* @throws KeyNotFoundException occurs, if the given key don't exist
* @throws NotEditableException occurs, if you don't have write permissions
* @throws IllegalPersistenceTypeException occurs, if the new entry an instance
* of <code>Remote</code>
*/
public void setByte(String key, byte b) throws VetoException {
setObject(key,new Byte(b));
}
/** Replace the entry for the given Key in the InformationStore.
* @param key the unique identifier for the entry in the InformationStore
* @param c the new <code>char</code> for the given key
* @throws VetoException occurs, if you don't have the permission for this operation
* @throws KeyNotFoundException occurs, if the given key don't exist
* @throws NotEditableException occurs, if you don't have write permissions
* @throws IllegalPersistenceTypeException occurs, if the new entry an instance
* of <code>Remote</code>
*/
public void setChar(String key, char c) throws VetoException {
setObject(key, new Character(c));
}
/** Replace the entry for the given Key in the InformationStore.
* @param key the unique identifier for the entry in the InformationStore
* @param d the new <code>double</code> for the given key
* @throws VetoException occurs, if you don't have the permission for this operation
* @throws KeyNotFoundException occurs, if the given key don't exist
* @throws NotEditableException occurs, if you don't have write permissions
* @throws IllegalPersistenceTypeException occurs, if the new entry an instance
* of <code>Remote</code>
*/
public void setDouble(String key, double d) throws VetoException {
setObject(key,new Double(d));
}
/** Replace the entry for the given Key in the InformationStore.
* @param key the unique identifier for the entry in the InformationStore
* @param f the new <code>float</code> for the given key
* @throws VetoException occurs, if you don't have the permission for this operation
* @throws KeyNotFoundException occurs, if the given key don't exist
* @throws NotEditableException occurs, if you don't have write permissions
* @throws IllegalPersistenceTypeException occurs, if the new entry an instance
* of <code>Remote</code>
*/
public void setFloat(String key, oat f) throws VetoException {
setObject(key,new Float(f));
}
/** Replace the entry for the given Key in the InformationStore.
* @param key the unique identifier for the entry in the InformationStore
* @param i the new <code>int</code> for the given key
133
134
300
305
310
315
320
325
330
335
340
345
350
355
360
365
370
375
380
ANHANG C. QUELLTEXTE
* @throws VetoException occurs, if you don't have the permission for this operation
* @throws KeyNotFoundException occurs, if the given key don't exist
* @throws NotEditableException occurs, if you don't have write permissions
* @throws IllegalPersistenceTypeException occurs, if the new entry an instance
* of <code>Remote</code>
*/
public void setInt(String key, int i) throws VetoException {
setObject(key,new Integer(i));
}
/** Replace the entry for the given Key in the InformationStore.
* @param key the unique identifier for the entry in the InformationStore
* @param s the new <code>short</code> for the given key
* @throws VetoException occurs, if you don't have the permission for this operation
* @throws KeyNotFoundException occurs, if the given key don't exist
* @throws NotEditableException occurs, if you don't have write permissions
* @throws IllegalPersistenceTypeException occurs, if the new entry an instance
* of <code>Remote</code>
*/
public void setShort(String key, short s) throws VetoException {
setObject(key, new Short(s));
}
/* **************************** getter method's ***************************/
/** Get the <code>Object</code> for the given identifier
* @param key the unique identifier for the entry
* @throws VetoException occurs, if you don't have the permission for this operation
* @throws KeyNotFoundException occurs, if the given key don't exist
* @return the <code>Object</code> for the identifier
*/
public Object getObject(String key) throws VetoException {
Object o = null;
try {
o = conv.fromXMLString(
((StringValue)catalog.get(key,db,false).getValue()).getString()
);
}
catch (IOException ioe) {ioe.printStackTrace();}
catch (DataBasketConflictException dbce) {dbce.printStackTrace();}
catch (ClassNotFoundException e) {e.printStackTrace();}
return o;
}
/** Get the <code>String</code> for the given identifier
* @param key the unique identifier for the entry
* @throws VetoException occurs, if you don't have the permission for this operation
* @throws KeyNotFoundException occurs, if the given key don't exist
* @throws ClassCastException occurs, if the <code>Object</code> for the key isn't a <code>String</code>
* @return the <code>String</code> for the identifier
*/
public String getString(String key) throws VetoException {
Object o = getObject(key);
if (!(o instanceof String)) {
throw new ClassCastException();
} // end of if (getObject(key) instanceof Integer)
return (String)o;
}
/** Get the <code>boolean</code> for the given identifier
* @param key the unique identifier for the entry
* @throws VetoException occurs, if you don't have the permission for this operation
* @throws KeyNotFoundException occurs, if the given key don't exist
* @throws ClassCastException occurs, if the Wrapper-Class for the key isn't a <code>Boolean</code>
* @return the <code>boolean</code> for the identifier
*/
public boolean getBoolean(String key) throws VetoException {
Object o = getObject(key);
if (!(o instanceof Boolean)) {
throw new ClassCastException();
} // end of if (getObject(key) instanceof Integer)
return ((Boolean)o).booleanValue();
}
/** Get the <code>byte</code> for the given identifier
* @param key the unique identifier for the entry
* @throws VetoException occurs, if you don't have the permission for this operation
* @throws KeyNotFoundException occurs, if the given key don't exist
* @throws ClassCastException occurs, if the Wrapper-Class for the key isn't a <code>Byte</code>
* @return the <code>byte</code> for the identifier
*/
public byte getByte(String key) throws VetoException {
Object o = getObject(key);
if (!(o instanceof Byte)) {
throw new ClassCastException();
} // end of if (getObject(key) instanceof Integer)
return ((Byte)o).byteValue();
}
C.1. PACKAGE EPOINT.DATA
385
390
395
400
405
410
415
420
425
430
435
440
445
450
455
/** Get the <code>char</code> for the given identifier
* @param key the unique identifier for the entry
* @throws VetoException occurs, if you don't have the permission for this operation
* @throws KeyNotFoundException occurs, if the given key don't exist
* @throws ClassCastException occurs, if the Wrapper-Class for the key isn't a <code>Character</code>
* @return the <code>char</code> for the identifier
*/
public char getChar(String key) throws VetoException{
Object o = getObject(key);
if (!(o instanceof Character)) {
throw new ClassCastException();
} // end of if (getObject(key) instanceof Integer)
return ((Character)o).charValue();
}
/** Get the <code>double</code> for the given identifier
* @param key the unique identifier for the entry
* @throws VetoException occurs, if you don't have the permission for this operation
* @throws KeyNotFoundException occurs, if the given key don't exist
* @throws ClassCastException occurs, if the Wrapper-Class for the key isn't a <code>Double</code>
* @return the <code>double</code> for the identifier
*/
public double getDouble(String key) throws VetoException {
Object o = getObject(key);
if (!(o instanceof Double)) {
throw new ClassCastException();
} // end of if (getObject(key) instanceof Integer)
return ((Double)o).doubleValue();
}
/** Get the <code>float</code> for the given identifier
* @param key the unique identifier for the entry
* @throws VetoException occurs, if you don't have the permission for this operation
* @throws KeyNotFoundException occurs, if the given key don't exist
* @throws ClassCastException occurs, if the Wrapper-Class for the key isn't a <code>Float</code>
* @return the <code>float</code> for the identifier
*/
public oat getFloat(String key) throws VetoException {
Object o = getObject(key);
if (!(o instanceof Float)) {
throw new ClassCastException();
} // end of if (getObject(key) instanceof Integer)
return ((Float)o).floatValue();
}
/** Get the <code>int</code> for the given identifier
* @param key the unique identifier for the entry
* @throws VetoException occurs, if you don't have the permission for this operation
* @throws KeyNotFoundException occurs, if the given key don't exist
* @throws ClassCastException occurs, if the Wrapper-Class for the key isn't a <code>Integer</code>
* @return the <code>int</code> for the identifier
*/
public int getInt(String key) throws VetoException {
Object o = getObject(key);
if (!(o instanceof Integer)) {
throw new ClassCastException();
} // end of if (getObject(key) instanceof Integer)
return ((Integer)o).intValue();
}
/** Get the <code>short</code> for the given identifier
* @param key the unique identifier for the entry
* @throws VetoException occurs, if you don't have the permission for this operation
* @throws KeyNotFoundException occurs, if the given key don't exist
* @throws ClassCastException occurs, if the Wrapper-Class for the key isn't a <code>Short</code>
* @return the <code>short</code> for the identifier
*/
public short getShort(String key) throws VetoException {
Object o = getObject(key);
if (!(o instanceof Short)) {
throw new ClassCastException();
} // end of if (getObject(key) instanceof Integer)
return ((Short)o).shortValue();
}
/************************ rollback and commit **************************/
460
465
470
/** Make all changes persistent (since the last commit())
*/
public void commit() {
try {
db.commit();
}
catch(RemoteException e) {e.printStackTrace();}
}
/** Removes alle changes previously made (since the last commit())
*/
public void rollback() {
try {
db.rollback();
135
136
ANHANG C. QUELLTEXTE
}
475
480
}
catch(RemoteException
e) {e.printStackTrace();}
/** Close the related service for this InformationStore
*/
public void closeService(boolean execCommit) {
if
}
485
(execCommit) {
commit();
try
}
{
catalog.get(PERSISTENT_STATE_PROPERTY,dbTemp,true).setValue(new StringValue(conv.toXMLString(new Integer(InformationStore.
DEFINED_PERSISTENT_STATE))));
dbTemp.commit();
catch
490
(Exception e) { e.printStackTrace();}
}
495
private void
try {
500
}
}
CatalogItem ci = EPoint.getShopServices().createCatalogItem(PERSISTENT_STATE_PROPERTY,
Integer(InformationStore.NOT_PERSISTENT_STATE))));
catalog.add(ci,null);
catch
}
}
510
new
StringValue(conv.toXMLString(new
(Exception e) { e.printStackTrace();}
public void
try {
505
setService() {
openService() {
catalog.get(PERSISTENT_STATE_PROPERTY,dbTemp,true).setValue(new StringValue(conv.toXMLString(new Integer(InformationStore.
UNDEFINED_PERSISTENT_STATE))));
dbTemp.commit();
catch
(Exception e) { e.printStackTrace();}
public int getPersistenceState() {
try {
return ((Integer)conv.fromXMLString(catalog.get(PERSISTENT_STATE_PROPERTY,null,false).getValue().toString())).intValue();
}
515
520
525
}
catch (Exception e) { e.printStackTrace();}
return InformationStore.UNDEFINED_PERSISTENT_STATE;
public boolean containsKey(String sKey)
try {
return catalog.contains(sKey,db);
}
}
catch (RemoteException
return false;
{
e) {e.printStackTrace();}
} // InformationStoreImpl
Listing C.14: epoint.data.ListenableContainer
5
/*
* ListenableContainer.java
*
* Created on 15. Oktober 2001, 18:14
*/
package epoint.data;
import java.rmi.*;
10
15
20
25
/**
* This interface should be implemented by all containers that allow Listeners
* to be informed about operations against them. As normal Listeners are remotely
* available, the container must be failsafe and check, if the RemoteListener
* is still available (the host may shut down after registering the Listener).
* If such a case is detected, the Listener should silently be removed from
* the container!<br>
* Persistent listeners may be added, but they need to be independent from
* remotely available data on client-side environment (for the same reason
* as the case described above).
*
* @see ContainerListener
* @see ContainerChangeEvent
*
* @author Danny Poppe
* @version 1.0
C.1. PACKAGE EPOINT.DATA
*/
public interface
30
35
40
45
50
55
60
65
70
75
ListenableContainer
extends
java.rmi.Remote {
/**
* Registers a remotely available listener to this container, which
* calls the appropriate methods of the listener if one of the
* specified actions are carried out. They are valid, as long as the
* registering host is up and the listener is remotely available.
*
* @param cl the remotely available ContainerListener, that is automatically
*
detached, if it throws a {@link epoint.data.exception.DetachListenerException}
*
or if a RemoteException occurs while informing the listener
* @return a key-identifier that may be used to remove the listener from this
*
ListenableContainer
* @throws RemoteException whenever an error occurs during remote-access
*/
public String addContainerListener(ContainerListener cl) throws RemoteException;
/**
* Registers a persistent listener at this container. That means the listener
* is serialized and recreated at the server-side. Such a listener will not be
* accessible anymore, but can be removed, using the ID that is returned
* after adding the listener.
*
* @param pcl the ContainerListener, that is automatically
*
detached, if it throws a {@link epoint.data.exception.DetachListenerException}
* @return a key-identifier that may be used to remove the listener from this
*
ListenableContainer
* @throws RemoteException whenever an error occurs during remote-access
*/
public String addPersistentContainerListener(PContainerListener pcl) throws RemoteException;
/**
* Removes the given listener from this container by identifying it, using
* the given ID that was returned, when the listener was added to this ListenableContainer.
*
* @param sIdentifier the identifier of the listener that shall be detached
* @return the removed ContainerListener or <code>null</code> if no such listener existed
* @throws RemoteException whenever an error occurs during remote-access
*/
public ContainerListener removeContainerListener(String sIdentifier) throws RemoteException;
/**
* This method may be called by a PersistentListener to enforce throws update of
* its own persistent state at this Container. That is useful, if local data
* exists at the listener that also needs to be persistent.
*
* @param sListenerID the id of the persistent listener that needs an update of
*
its persistent state
* @throws RemoteException whenever an error occurs during remote-access
*/
public void updatePersistentListener(String sListenerID) throws RemoteException;
}
Listing C.15: epoint.data.Nameable
5
10
15
20
25
30
/*
* Nameable.java
*
* Created on 19. Mai 2001, 21:58
*/
package epoint.data;
import epoint.data.exception.*;
import java.rmi.*;
/**
* The Namable-interface may be implemented by objects that support setting
* and getting their name. Namable objects are per default remotely available
* and as such implement the Remote-interface.
*
* @author Danny Poppe
* @version 1.0
*/
public interface Nameable extends java.io.Serializable, Remote {
/**
* The programmatical name of the name-property tat is supported by
* classes implementing this interface.
*/
public static String PROPERTY_NAME = "epoint.data.Nameable.name";
/**
* Sets a new name for this Namable.
*
* @param sName the new name
* @throws RemoteException whenever an error occurs during remote-access
*/
public void setName(String sName) throws RemoteException;
/**
137
138
35
}
ANHANG C. QUELLTEXTE
* Returns the name of this Namable.
*
* @return the name of this object
* @throws RemoteException whenever an error occurs during remote-access
*/
public String getName() throws RemoteException;
Listing C.16: epoint.data.PContainerListener
5
/*
* PContainerListener.java
*
* Created on 16. Januar 2002, 19:28
*/
package epoint.data;
import java.rmi.*;
10
15
20
/**
* This ContainerListener is intended to be persistent together with the container
* it is listening to. As such it is normally serialized and re-created at the
* containers location. As such it is normally no more accessible by the creator
* of this listener, but has access on ressources that are available only
* in the containers environment (eg. Shop-ressources).
*
* @see ListenableContainer
* @see ContainerListener
* @see ContainerChangeEvent
* @author Danny Poppe
* @version 1.0
*/
public interface PContainerListener extends ContainerListener {
25
/**
* This method is used by the container to inform the listener about its own
* ID at the container. So the listener is able to call the
* {@link ListenableContainer#updatePersistentListener(String)}-method to
* enforce an update of its own persistent version at the container.
*
* @param sID the ID of this listener at the container
* @throws RemoteException whenever an error occurs during remote-access
*/
public void setListenerID(String sID) throws RemoteException;
30
35
40
}
/**
* Returns the serialized version of this listener for making it persistent
* or for re-creation at the container-side-environment.
*
* @return this serialized listener
* @throws RemoteException whenever an error occurs during remote-access
*/
public String getSerializedForm() throws RemoteException;
Listing C.17: epoint.data.PersistentFrameworkObject
5
/*
* PersistentFrameworkObject.java
*
* Created on 24. September 2001, 10:58
*/
package
10
15
epoint.data;
/**
* PersistentFrameworkObjects are all PersistentObjects that directly belong to the
* framework-classes or inherit from them. As such they have a special identity
* and normally another way of being made persistent as other persistent objects.
*
* @see PersistentUserObject
* @author Danny Poppe
* @version 1.0
*/
public interface PersistentFrameworkObject extends PersistentObject {
}
Listing C.18: epoint.data.PersistentObject
/*
* PersistentObject.java
*
C.1. PACKAGE EPOINT.DATA
5
* Created on 11. September 2001, 10:08
*/
package
10
15
20
25
30
35
40
45
50
55
60
65
70
75
80
85
90
import
import
import
epoint.data;
java.rmi.*;
java.io.Serializable;
java.sql.Connection;
/**
* A PersistentObject in meanings of this framework is always a remotely
* available object with a persistent state. It defines methods that allow
* to manage its persistent state, but it must ensure, that it uses these methods
* itself to update its persistent state.<br>
* PersistentObjects have a unqiue ID that allows other objects to reference the
* persistent state of those objects. For example may user-defined objects
* refer to a persistent object using this objects ID rather than a reference that
* will be serialized (and in the case of most framework-persistent-objects the
* serialized version does not contain its full state). If the persistent
* object itself is needed, the PersistenceManager may be used to retrieve
* the object by its oid rather than by a serialized reference. So it is up to
* the PersistenceManager to deliver an up to date version of the object and
* handle multiple references to a persistent object as one reference to an object.<br>
* Because of this, a PersistentObject should be referenced only by transient fields
* of class. If a PersistentObject belongs to the persistent state of a {@link
* PersistentUserObject} it should be referenced only by its OID (a transient cache
* could be initialized using this persistent information).<br>
* But one must be aware, that persistent versions of such objects may disappear
* if its usage is shared among many clients. If this can occur, but has to be avoided,
* one must ensure to have a persistent copy available in this case.
*
* @author Danny Poppe
* @version 1.0
*/
public interface PersistentObject extends Remote, Serializable {
/**
* Persistent objects have the serialized-property that
* describes their serialized state.
*/
public nal static String PERSISTENT_PROPERTY_SERIALIZED = "PersistentObject.Serialized";
/**
* Persistent objects have a unique access-key that may
* be used to get them from the persistence-manager. This is for example
* the unique ObjectID.
*/
public nal static String PERSISTENT_PROPERTY_ACCESSKEY = "PersistentObject.AccessKey";
/**
* Returns the serialized version of this object as created with the
* Shop PersistenceSerializer ({@link epoint.sale.Shop#getPersistenceSerializer()}).
*
* @return the serialized version of this persistent object
* @throws RemoteException whenever an error occurs during remote-access
*/
public String getSerializedForm() throws RemoteException;
/**
* Returns the object identifier of this persistent object. This may be
* <code>null</code> only if this object is currently not persistent!
* The OID must be unique under all persistent objects and is normally generated
* by the {@link epoint.db.DBPersistenceManager} while persistifying this
* object.
*
* @return a unique identifier, that may be used to get this object from
*
the PersistenceManager, or <code>null</code> if it is not persistent
* @throws RemoteException whenever an error occurs during remote-access
*/
public String getOID() throws RemoteException;
/**
* Return <code>true</code> only if this object is currently persistent.
*
* @return <code>true</code> if this object is currently persistent
* @throws RemoteException whenever an error occurs during remote-access
*/
public boolean isPersistent() throws RemoteException;
/**
* This method should only be called by the PersistenceManager to set this
* object persistent and available under the given ID.
*
* @param sOID the object identifier under which this object is available
*
at the PersistenceManager or <code>null</code> if this object
*
is no longer persistent
* @throws RemoteException whenever an error occurs during remote-access
*/
public void setPersistent(String sOID) throws RemoteException;
/**
* This method can be used to load additionally state-information into
* this de-serialized object's instance. This may be for example other
* persistent objects that were not part of the serialized version of
* this object (eg. objects available as OID of other persistent objects).<br>
139
140
95
100
105
110
115
120
125
130
}
ANHANG C. QUELLTEXTE
* In fact this is some kind of a general constructor for de-serialized
* persistent objects.<br>
* Additional available information is passed by a {@link java.util.Map} of
* key-value mappings that are understood by this object. There are no default
* keys for PersistentUserObjects. PersistentFrameworkObjects define their own
* keys in their interfaces.<br>
* This method should only be called ONCE and DIRECTLY after de-serialization
* by the DBPersistenceManager!
*
* @param mProperties key-value mappings of additional initializing data
* @throws RemoteException whenever an error occurs during remote-access
*/
public void loadPersistentData(java.util.Map mProperties, String sConPasswd) throws RemoteException;
/**
* This method should be implemented to use the PersistenceManager for updating
* its persistent state. The method itself should always be called by methods
* of the implementing class that have manipulated the persistent state of the
* object.<br>
* For efficency-reasons a list of property keys may be passed to select parts
* of a complex persistent state that need an update instead of updating
* the entire complex data.
*
* @param lPropertyKeys a list of keys that signal the properties that need an update
*
in their persistent state (in fact the available keys are normally the same
*
as understood by {@link #loadPersistentData(java.util.Map, String)})
* @param sConPasswd a password for the connection that shall be used to
*
update the persistent state
* @throws RemoteException whenever an error occurs during remote-access
*/
public void updatePersistentObject(java.util.List lPropertyKeys, String sConPasswd) throws RemoteException;
/**
* Returns the same as <code>toString()</code> would return, but may be used by remote-connections,
* as {@link java.rmi.server.RemoteObject#toString()} returns a description of the
* stub-class at client-side.
*
* @return the String describing this object
* @throws RemoteException whenever an error occurs during remote-access
*/
public String toRemoteString() throws RemoteException;
Listing C.19: epoint.data.PersistentUserObject
5
/*
* PersistentUserObject.java
*
* Created on 24. September 2001, 11:13
*/
package
10
15
20
epoint.data;
/**
* Newly created classes that do not derive from persistent framework-classes,
* must implement PersistentUserObject, if they have a persistent state.<br>
* Their persistence is handled in another way than PersistentFrameworkObjects.
*
* @see PersistentFrameworkObject
* @author Danny Poppe
* @version 1.0
*/
public interface PersistentUserObject extends PersistentObject {
}
Listing C.20: epoint.data.RemoteIterator
5
10
15
/*
* RemoteIterator.java
*
* Created on 8. September 2001, 18:53
*/
package epoint.data;
import java.util.*;
import java.rmi.*;
import java.io.Serializable;
/**
* A RemoteIterator implements the same functionality as a {@link java.util.Iterator}.
* The only difference is, that this iterator is available only by remote-reference
* and not locally. All methods behave the same way as in the normal Iterator.<br>
*
* @see java.util.Iterator
* @author Danny Poppe
C.1. PACKAGE EPOINT.DATA
20
* @version 1.0
*/
public interface
25
30
35
40
}
RemoteIterator extends Remote, Serializable {
/**
* See {@link java.util.Iterator#hasNext()} for documentation!
*
* @throws RemoteException whenever an error occurs during remote-access
*/
public boolean hasNext() throws RemoteException;
/**
* See {@link java.util.Iterator#next()} for documentation!
*
* @throws RemoteException whenever an error occurs during remote-access
*/
public Object next() throws RemoteException;
/**
* See {@link java.util.Iterator#remove()} for documentation!
*
* @throws RemoteException whenever an error occurs during remote-access
*/
public void remove() throws RemoteException;
Listing C.21: epoint.data.RemoteRunnable
5
/*
* RemoteRunnable.java
*
* Created on 25. Januar 2002, 23:02
*/
package epoint.data;
import java.rmi.*;
10
15
20
25
/**
* A RemoteRunnable is a handle for operations that need to be performed
* at some other location. It may be used to collect such work and do it
* for example at some later time, even within an own Thread.<br>
* This RemoteRunnable does not derive from {@link Runnable}! And as such may
* not be used directly as an argument for a {@link Thread}.
*
* @author Danny Poppe
* @version 1.0
*/
public interface RemoteRunnable extends Remote {
/**
* Starts the work that has to be done.
*
* @param oParams may contain various parameters
* @throws RemoteException whenever an error occurs during remote-access
*/
public void run(Object[] oParams) throws RemoteException;
}
Listing C.22: epoint.data.Stock
5
/*
* Stock.java
*
* Created on 6. August 2001, 20:57
*/
package
10
15
20
25
import
import
import
import
epoint.data;
java.rmi.*;
java.util.List;
java.util.Iterator;
epoint.data.exception.*;
/**
* A Stock is a Container that stores elements in a 1:n relation relative to
* CatalogItems in a Catalog. As such a Stock is always based upon a single
* Catalog and may contain many non-equal elements for a single CatalogItem
* in the Catalog.<br>
* A Stock must ensure its own referential integrity. That means if a CatalogItem
* is removed from a Catalog, all associated StockItems in the Stock(s) are also
* removed from the Stock. If a CatalogItem is added to a Catalog, StockItems may
* only be added to the Stock, if this is done by the same DataBasket, and so on...<br>
* StockItems can be of any type, but their name must be equal to a visible
* key in the Catalog. Per default a StockItem has its name and also a Value as
* a CatalogItem.<br>
* There are two possible instances of a Stock: {@link CountingStock}s which
* only count the items of a Catalog and Stocks that contain individual (but also
* counting) instances of StockItems, so called {@link StoringStock}s.<br>
141
142
30
35
40
45
50
55
60
65
70
75
80
85
90
95
100
105
110
115
ANHANG C. QUELLTEXTE
* StockItems must be viewed from the viewpoint of a maximum of two transactions.
* The first transaction that is related to their key in the base-Catalog and the
* second transaction that handles the actions performed with StockItems relative to this
* Stock. This may result in several conflicts for example, if a StockItem is edited
* by DataBasket#1 and the associated CatalogItem shall be removed by DataBasket#2.
* These conflicts always result in a {@link DataBasketConflictException} for
* actions against StockItems and {@link VetoException}s for actions against
* CatalogItems.
*
*
* @see Catalog
* @see StockItem
* @see CountingStock
* @see StoringStock
* @author Danny Poppe
* @version 1.0
*/
public interface Stock extends Remote, Nameable, DBEntryTarget, PersistentFrameworkObject, ListenableContainer {
/**
* The name of the persistent property that describes this Stocks name.
*/
nal public static String PERSISTENT_PROPERTY_STOCKNAME = "Stock.Name";
/**
* The name of the persistent property that describes this Stocks base-Catalog.
*/
nal public static String PERSISTENT_PROPERTY_BASECATALOG = "Stock.Catalog";
/**
* The name of the persistent property that describes if this Stock is a special {@link CountingStock}.
*/
nal public static String PERSISTENT_PROPERTY_COUNTINGFLAG = "Stock.IsCountingStock";
/**
* Adds a new StockItem to this Stock, using an anonymous DataBasket that
* immediately committs.
*
* @param si a StockItem to be added to this Stock
* @throws RemoteException whenever an error occurs during remote-access
* @throws DataBasketConflictException if
*/
public void add(StockItem si) throws RemoteException, DataBasketConflictException;
/**
* Adds a new StockItem to this Stock, using the given DataBasket.
*
* @param si the new StockItem to be added
* @param db the DataBasket to use for this adding (may be <code>null</code>)
* @throws RemoteException whenever an error occurs during remote-access
* @throws DataBasketConflictException if this is an impossible concurrent transaction
*/
public void add(StockItem si, DataBasket db) throws RemoteException, DataBasketConflictException;
/**
* Returns an Iterator over all elements in this Stock that are associated
* with the given key in the base-Catalog and visible to a <code>null</code>-DataBasket.
* The items that are returned are not editable!
*
* @param sKey the key of the CatalogItem for which to return the StockItems
* @throws RemoteException whenever an error occurs during remote-access
* @return an Iterator over the requested StockItems, that are not editable
*/
public RemoteIterator get(String sKey) throws RemoteException;
/**
* Returns an Iterator over all elements in this Stock that are associated
* with the given key in the base-Catalog and visible to the given DataBasket.
* if <code>bForEdit</code> is <code>true</code>, the DataBasket must not be
* <code>null</code> and the items are returned for editing by the given DataBasket.<br>
* <br>
* ATTENTION: any {@link VetoException} of a listener that vetoes the editing
* will be converted into a RuntimeException!
*
* @param sKey the key in the base-Catalog for which to return the StockItems
* @param bForEdit if <code>true</code>, <code>db</code> must not be <code>null</code> and
*
the items are returned ready for being edited by the given DataBasket *
otherwise they are not editable
* @param db the DataBasket used to determine visibility of StockItems
* @throws RemoteException whenever an error occurs during remote-access
* @return an Iterator that returns the requested items whether for editing or not
*/
public RemoteIterator get(String sKey, boolean bForEdit, DataBasket db) throws RemoteException;
/**
* Removes the given StockItem from this Stock, using an anonymous DataBasket,
* that immediately commits.
*
* @param si the StockItem to remove
* @throws RemoteException whenever an error occurs during remote-access
* @throws VetoException if a listener vetoes the removal of the given item
* @throws DataBasketConflictException if this is an impossible concurrent transaction
C.1. PACKAGE EPOINT.DATA
120
125
130
135
140
145
150
155
160
165
170
175
180
185
190
195
200
* @return the StockItem that was removed, or <code>null</code> if no such StockItem existed
*/
StockItem remove(StockItem si) throws RemoteException, VetoException, DataBasketConflictException;
public
/**
* Removes the given StockItem from this Stock, using the given DataBasket
*
* @param si the StockItem to remove
* @param db the DataBasket that shall be used to perform the removal
* @throws RemoteException whenever an error occurs during remote-access
* @throws VetoException if a listener vetoes the removal of the given item
* @throws DataBasketConflictException if this is an impossible concurrent transaction
* @return the StockItem that was removed, or <code>null</code> if no such StockItem existed
*/
public StockItem remove(StockItem si, DataBasket db) throws RemoteException, VetoException, DataBasketConflictException;
/**
* Returns an Iterator over all StockItems in this Stock, visible to
* the given DataBasket.
*
* @param db the DataBasket that determines visibility of the StockItems to be returned
*
and relative to which a possible editing is performed
* @param bForEdit if <code>true</code> the DataBasket must not be <code>null</code>
*
and the items returned by the iterator are returned for editing<br>
*
possible VetoExceptions are converted into RuntimeExceptions!
* @throws RemoteException whenever an error occurs during remote-access
* @return an iterator over the requested StockItems
*/
public RemoteIterator iterator(DataBasket db, boolean bForEdit) throws RemoteException;
/**
* Returns an Iterator over all StockItems in this Stock associated with the given key
* in the base-Catalog, visible to the given DataBasket.
*
* @param sKey the key in the base-Catalog for which StockItems shall be returned from this Stock
* @param db the DataBasket that determines visibility of the StockItems and relative to which a
*
possible editing is performed
* @param bForEdit if <code>true</code>, <code>db</code> must not be <code>null</code>
*
and the items as returned by the iterator are returned for editing relative to the given DataBasket<br>
*
possible VetoExceptions are converted into RuntimeExceptions!
* @throws RemoteException whenever an error occurs during remote-access
* @return an iterator over the requested StockItems
*/
public RemoteIterator iterator(String sKey, DataBasket db, boolean bForEdit) throws RemoteException;
/**
* Returns the base-Catalog, this Stock is associated with.
*
* @return the Catalog that is the base-Catalog for this Stock
* @throws RemoteException whenever an error occurs during remote-access
*/
public Catalog getCatalog() throws RemoteException;
/**
* Returns <code>true</code> if the implementing class is a CountingStock,
* otherwise <code>false</code>.
*
* @return <code>true</code> only if this is a CountingStock
* @throws RemoteException whenever an error occurs during remote-access
*/
public boolean isCountingStock() throws RemoteException;
/**
* Returns <code>true</code> if this Stock is currently editable.
*
* @throws RemoteException whenever an error occurs during remote-access
* @return <code>true</code> if this Stock is currently editable, otherwise <code>false</code>
*/
public boolean isEditable() throws RemoteException;
/**
* This method is only for internal use by the framework! Never call it directly,
* as this may result in data-inconsistency!<br>
* This method sets this Stock to be editable or not.
*
* @param bEditable <code>true</code> if this Stock shall be editable, otherwise <code>false</code>
* @param sCon the password that protects the connection, which is used to update the
*
persistent state of this Stock
* @throws RemoteException whenever an error occurs during remote-access
*/
public void setEditable(boolean bEditable, String sCon) throws RemoteException;
/**
* Returns the number of visible StockItems to the given DataBasket in this
* Stock.
*
* @param db the Databasket that determines visibility of the StockItems (may be <code>null</code>)
* @throws RemoteException whenever an error occurs during remote-access
143
144
205
ANHANG C. QUELLTEXTE
* @return the size of this Stock
*/
size(DataBasket db) throws RemoteException;
public int
/**
* Returns the number of visibile StockItems associated with the given key of
* the base-Catalog, that are visibile, using the given DataBasket.
*
* @param sKey the key for which to return the number of available StockItems
* @param db the DataBasket that determines visibility of the StockItems
* @throws RemoteException whenever an error occurs during remote-access
* @return the number of StockItems for this given key
*/
public int size(String sKey, DataBasket db) throws RemoteException;
210
215
220
/**
* Returns a set of keys for which StockItems are visible for the given DataBasket
* in this Stock.
*
* @param db the DataBasket for which the returned keys are visible
* @throws RemoteException whenever an error occurs during remote-access
* @return a set of keys, that are visible to the given DataBasket
*/
public java.util.Set keySet(DataBasket db) throws RemoteException;
225
230
235
240
}
/**
* Returns a StockItem by its internal ID from this Stock. This method is
* only for use by the framework! Never use it directly!
*
* @param lID the internal ID of the StockItem that shall be returned
* @throws RemoteException whenever an error occurs during remote-access
* @return the StockItem with the given ID, or <code>null</code> if no
*
such item exists in this Stock
*/
public StockItem getItemByInternalID(long lID) throws RemoteException;
Listing C.23: epoint.data.StockItem
5
/*
* StockItem.java
*
* Created on 9. August 2001, 21:52
*/
package
10
15
20
25
30
35
40
45
import
import
epoint.data;
java.rmi.*;
epoint.data.exception.VetoException;
/**
* StockItems are remotely available items that may be added to a {@link Stock}.
* They consist of a name and {@link Value}. The name of a StockItem that
* shall be added to a Stock, must match a visible key in the base-Catalog of the
* Stock.
*
* @author Danny Poppe
* @version 1.0
*/
public interface StockItem extends java.rmi.Remote, java.io.Serializable, TransactionItem, PersistentFrameworkObject {
/**
* This is the name for the
*/
nal public static String
/**
* This is the name for the
*/
nal public static String
/**
* This is the name for the
*/
nal public static String
/**
* This is the name for the
*/
nal public static String
persistent property describing this StockItems Stock.
PERSISTENT_PROPERTY_STOCK = "StockItem.Stock";
persistent property describing this StockItems name.
PERSISTENT_PROPERTY_CATALOGITEMKEY = "StockItem.CatalogItem.Key";
persistent property describing this StockItems internal identifier.
PERSISTENT_PROPERTY_IDENTIFIER = "StockItem.ID";
persistent property describing this StockItems Value.
PERSISTENT_PROPERTY_SIVALUE = "StockItem.Value";
/**
* Returns the Stock, this StockItem is currently contained in.
*
* @throws RemoteException whenever an error occurs during remote-access
* @return the Stock of this StockItem
*/
public Stock getStock() throws RemoteException;
/**
C.1. PACKAGE EPOINT.DATA
50
55
60
65
70
75
80
85
90
95
100
105
110
115
120
125
130
135
* This method should never be called directly, as this may result in
* data-inconsistency - it is only for framework-internal use.<br>
* This method sets the new Stock of this StockItem, but does not
* effectively add this item to the Stock!
*
* @param sStock the new Stock, this item shall be contained in
* @param sCon the password that protects the connection in which
*
to update the persistent state of this StockItem
* @throws RemoteException whenever an error occurs during remote-access
*/
public void setStock(Stock sStock, String sCon) throws RemoteException;
/**
* Returns the associated item of this StockItem, if this StockItem is
* currently contained in a Stock.
*
* @param db the DataBasket which is used to access the associated item
*
of this StockItem
* @param bEditable if <code>true</code> the associated item will be returned
*
for being edited by the given DataBasket
* @throws RemoteException whenever an error occurs during remote-access
* @throws VetoException if a listener vetoes the editing
* @return the requested associated CatalogItem of this StockItem
*/
public CatalogItem getAssociatedItem(DataBasket db, boolean bEditable) throws RemoteException, VetoException;
/**
* Does the same as {@link #getAssociatedItem(DataBasket,boolean)}, but assumes,
* that <code>bEditable = false</code>.
*
* @param db the DataBasket that is used to get the associated item from the
*
Catalog
* @throws RemoteException whenever an error occurs during remote-access
* @return the associated item of this StockItem
*/
public CatalogItem getAssociatedItem(DataBasket db) throws RemoteException;
/**
* Returns the value that is stored with this StockItem.
*
* @throws RemoteException whenever an error occurs during remote-access
* @return the Value of this StockItem
*/
public Value getValue() throws RemoteException;
/**
* Sets a new value for this StockItem, which can only be done, if this
* StockItem is currently editable. If tis StockItem is persistent in a
* Stock, the persistent state is automatically updated.
*
* @param vNes the new Value for this StockItem
* @throws RemoteException whenever an error occurs during remote-access
*/
public void setValue(Value vNes) throws RemoteException;
/**
* Does the same as {@link #setValue(Value)} but uses a given password,
* that protects the update of the persistent state of the StockItem.
* This is normally only needed for framework-internal reasons.
*
* @param vValue the new Value for this StockItem
* @param sConPasswd the password that protects the connection in which
*
this StockItems persistent state is updated.
* @throws RemoteException whenever an error occurs during remote-access
*/
void setValue(Value vValue, String sConPasswd) throws RemoteException;
/**
* Returns an internal ID, which is unique under all StockItems. This ID is
* only needed for framework-internal reasons.
*
* @throws RemoteException whenever an error occurs during remote-access
* @return a unique ID under all StockItems
*/
public long getInternalID() throws RemoteException;
/**
* Returns <code>true</code> only, if this StockItem is allowed to be edited.
*
* @throws RemoteException whenever an error occurs during remote-access
* @return <code>true</code> if this StockItem may be edited
*/
public boolean isEditable() throws RemoteException;
/**
* This method is only for framework-internal use. Never call it directly,
* as this may result in data-inconsistency!<br>
* This method sets this StockItem to be editable or not.
*
* @param bEditable if <code>true</code> this StockItem will be editable
* @param sCon the password for the connection in which to update the persistent
*
state of this StockItem
* @throws RemoteException whenever an error occurs during remote-access
*/
public void setEditable(boolean bEditable, String sCon) throws RemoteException;
/**
145
146
140
145
150
155
}
ANHANG C. QUELLTEXTE
* Returns a shallow-clone of this StockItem, which means that a fully new
* object is returned, that represents the same data as the original, while
* referencing the same data.<br>
* NEVER use this method to create an editable clone of an item, that is not
* editable. This could cause data-inconsistency and is normally protected
* by an IllegalArgumentException!
*
* @param bEditable if <code>true</code> the returned StockItem will be editable,
*
otherwise not
* @throws RemoteException whenever an error occurs during remote-access
* @return a shallow-clone of this StockItem
*/
public StockItem getShallowClone(boolean bEditable) throws RemoteException;
/**
* Returns the name of this StockItem, that must match a visible key
* in the base-Catalog of the Stock, this StockItem is contained in.
*
* @throws RemoteException whenever an error occurs during remote-access
* @return the name of this StockItem
*/
public String getName() throws RemoteException;
Listing C.24: epoint.data.StoringStock
5
/*
* StoringStock.java
*
* Created on 9. Januar 2002, 13:12
*/
package
10
15
epoint.data;
/**
* A StoringStock is a "normal" Stock, that stores StockItem-objects for
* CatalogItems in its base-Catalog, as described in {@link Stock}
*
* @see Stock
* @author Danny Poppe
* @version 1.0
*/
public interface StoringStock extends Stock, java.rmi.Remote {
}
Listing C.25: epoint.data.StringValue
5
/*
* StringValue.java
*
* Created on 7. September 2001, 21:25
*/
package epoint.data;
import java.rmi.RemoteException;
10
15
20
25
30
35
/**
* A StringValue is a Value that may be used in StockItems or CatalogItems to
* represent a normal String.
*
* @author Danny Poppe
* @version 1.0
*/
public class StringValue extends AbstractValue implements Comparable {
/**
* The String represented by this Value.
*/
private String sValue = null;
/**
* Creates a new StringValue, that is editable and represents a null-String.
*/
protected StringValue() {
}
/**
* Creates a new StringValue, that is editable and represents the given
* String.
*
* @param sValue the String that initializes this Value
*/
public StringValue(String sValue) {
this.sValue = sValue;
}
C.1. PACKAGE EPOINT.DATA
40
45
50
55
60
65
70
75
80
85
90
95
100
105
110
115
120
125
/**
* Creates a new StringValue.
*
* @param sValue the String-value to be represented by this Value
* @param bEditable defines if this Value shall be editable or not
*/
public StringValue(String sValue, boolean bEditable) {
this(sValue);
setEditable(bEditable);
}
/**
* Returns the String that is represented by this Value.
*
* @return the String of this StringValue
*/
nal public String getString() {
return sValue;
}
/**
* Compares the given String of the given Value, which must be a StringValue to
* the String represented by this Value, as defined in {@link Comparable#compareTo(Object)}
*
* @param v must be StringValue, that is compared to this one
* @return an int as defined in {@link Comparable#compareTo(Object)}
*/
public int compareTo(Value v) {
StringValue sv = (StringValue) v;
return sValue.compareTo(sv.getString());
}
/**
* Returns always <code>false</code> for this implementation.
*
* @return <code>false</code>
*/
nal public boolean isCatalog() {
return false;
}
/**
* Returns the String of this StringValue
*
* @return the String of this StringValue
*/
public String toString() {
return sValue;
}
/**
* Compares this object with the specified object for order. Returns a
* negative integer, zero, or a positive integer as this object is less
* than, equal to, or greater than the specified object.<p>
*
* In the foregoing description, the notation
* <tt>sgn(</tt><i>expression</i><tt>)</tt> designates the mathematical
* <i>signum</i> function, which is defined to return one of <tt>-1</tt>,
* <tt>0</tt>, or <tt>1</tt> according to whether the value of <i>expression</i>
* is negative, zero or positive.
*
* The implementor must ensure <tt>sgn(x.compareTo(y)) ==
* -sgn(y.compareTo(x))</tt> for all <tt>x</tt> and <tt>y</tt>. (This
* implies that <tt>x.compareTo(y)</tt> must throw an exception iff
* <tt>y.compareTo(x)</tt> throws an exception.)<p>
*
* The implementor must also ensure that the relation is transitive:
* <tt>(x.compareTo(y)>0 && y.compareTo(z)>0)</tt> implies
* <tt>x.compareTo(z)>0</tt>.<p>
*
* Finally, the implementer must ensure that <tt>x.compareTo(y)==0</tt>
* implies that <tt>sgn(x.compareTo(z)) == sgn(y.compareTo(z))</tt>, for
* all <tt>z</tt>.<p>
*
* It is strongly recommended, but <i>not</i> strictly required that
* <tt>(x.compareTo(y)==0) == (x.equals(y))</tt>. Generally speaking, any
* class that implements the <tt>Comparable</tt> interface and violates
* this condition should clearly indicate this fact. The recommended
* language is "Note: this class has a natural ordering that is
* inconsistent with equals."
*
* @param o the Object to be compared.
* @return a negative integer, zero, or a positive integer as this object
*
is less than, equal to, or greater than the specified object.
*
* @throws ClassCastException if the specified object's type prevents it
*
from being compared to this Object.
*/
147
148
ANHANG C. QUELLTEXTE
public int compareTo(Object o) {
return this.getString().compareTo(((StringValue)o).getString());
}
130
135
140
145
}
/**
* Returns <code>true</code> only if the given Object is a StringValue,
* representing the same String as this StringValue.
*
* @param o another object that is compared to this StringValue
* @return <code>true</code> if the given Object is a StringValue representing
*
the same String as this StringValue
*/
public boolean equals(Object o) {
if (o instanceof StringValue) {
return getString().equals(((StringValue)o).getString());
}
return false;
}
Listing C.26: epoint.data.TransactionItem
5
/*
* TransactionItem.java
*
* Created on 21. August 2001, 21:06
*/
package
10
15
20
25
30
35
40
45
50
55
60
import
import
epoint.data;
java.rmi.*;
java.io.Serializable;
/**
* A TransactionItem is the unique type of an item that may be modified within
* a transaction. TransactionItems are stored within {@link DBEntryTarget} implementing
* containers and may be modified according to the containers ability. All those
* manipulations may be collected within a single transaction and hence this
* transaction describes a single atomic action.<br>
* TransactionItems, as all other parts of a transaction, are persistent since
* they are in use by a transaction and may be uniquely identified under
* all other TransactionItems by their identifier, which is duplicate only
* if this item is edited in a transaction. In this case the original and
* the edited item have the same identifier.
*
* @see DBEntryTarget
* @see DBEntry
* @see DataBasket
*
* @author Danny Poppe
* @version 1.0
*/
public interface TransactionItem extends Remote, Serializable {
/**
* This constant defines the identifier of each TransactionItem in its
* persistent state.
*/
nal public static String PERSISTENT_PROPERTY_TRANSACTION_IDENTIFIER = "TransactionItem.Identifier";
/**
* This constant defines the state of this TransactionItem itsself
* (as a part of a transaction) as beeing an element of its persistent state.
*/
nal public static String PERSISTENT_PROPERTY_TRANSACTION_STATE = "TransactionItem.State";
/**
* This constant defines the DataBasket in which a modification of the state of
* this TransactionItem may be described (using a DBEntry) beeing an element
* of its persistent state.
*/
nal public static String PERSISTENT_PROPERTY_TRANSACTION_BASKET = "TransactionItem.Basket";
/**
* Returns a String that uniquely identifies this TransactionItem
* within all others.
*
* @return a String that is unique under all TransactionItems
* @throws RemoteException whenever an error occurs during remote-access
*/
public String getTransactionItemIdentifier() throws RemoteException;
/**
* If this Item is currently involved in a Transaction using a DataBasket
* this method returns the DataBasket of the Transaction - otherwise
* it returns <code>null</code>
*
* @return the DataBasket that describes an action on this TransactionItem
* @throws RemoteException whenever an error occurs during remote-access
*/
C.1. PACKAGE EPOINT.DATA
public
65
70
75
80
}
DataBasket getBasket() throws RemoteException;
/**
* This method is used as a flag-setter for framework-internal calls ONLY!
* It should never be called directly by a programmer, otherwise data-inconsistency
* may occur.<br>
* This method sets the DataBasket that currently contains this TransactionItem
* and defines the state of the TransactionItem in meanings of the
* transaction-state.
*
* @param db the DataBasket that will contain this TransactionItem
* @param sCon the password that protects the connection that is used to
*
update the persistent state of this TransactionItem
* @throws RemoteException whenever an error occurs during remote-access
*/
public void setBasket(DataBasket db, String sCon) throws RemoteException;
Listing C.27: epoint.data.Value
package
5
10
15
20
25
30
35
40
45
50
55
60
import
import
import
epoint.data;
java.rmi.*;
epoint.data.exception.*;
java.io.Serializable;
/**
* A Value represents the data that is stored with a CatalogItem or a StockItem.
*
*
* @see CatalogItem
* @see StockItem
* @author Danny Poppe
* @version 1.0
*/
public interface Value extends Serializable {
/**
* Returns <code>true</code> only if this Value is currently editable.
*
* @return <code>true</code> if this Value is editable
* @throws RemoteException whenever an error occurs during remote-access
*/
public boolean isEditable() throws RemoteException;
/**
* Sets this Value to be editable or not.
*
* @param bEditable if <code>true</code> this Value will be editable
* @throws RemoteException whenever an error occurs during remote-access
*/
public void setEditable(boolean bEditable) throws RemoteException;
/**
* Returns a deep clone of this Value which may be editable or not
*
* @param bEditable if set to <code>true</code> the returned clone
*
is editable, otherwise not
* @return a clone of this Value
* @throws RemoteException whenever an error occurs during remote-access
*/
public Value getDeepClone(boolean bEditable) throws RemoteException;
/**
* Compares this Value to another Value of the same type.
*
* @param v the Value that shall be compared to this Value
* @return an int as defined in {@link Comparable#compareTo(Object)}
* @throws RemoteException whenever an error occurs during remote-access
*/
public int compareTo(Value v) throws RemoteException;
/**
* Returns <code>true</code> only if this Value is a Catalog.
*
* @return <code>true</code> if this Value is Catalog
* @throws RemoteException whenever an error occurs during remote-access
*/
public boolean isCatalog() throws RemoteException;
/**
* Returns a serialized version of this Value, as created with the
* Shops default PersistenceSerializer ({@link epoint.sale.Shop#getPersistenceSerializer()}).
*
* @return this serialized Value
* @throws RemoteException whenever an error occurs during remote-access
*/
public String getSerializedForm() throws RemoteException;
}
149
150
ANHANG C. QUELLTEXTE
C.1.1 Package epoint.data.exception
epoint.data.exception.DataBasketConictException.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 150
epoint.data.exception.DetachListenerException.java. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .151
epoint.data.exception.DuplicateKeyException.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151
epoint.data.exception.IllegalPersistenceTypeException.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151
epoint.data.exception.KeyNotFoundException.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .152
epoint.data.exception.NoSuchKeyException.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152
epoint.data.exception.NotEditableException.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153
epoint.data.exception.UnknownTargetException.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .153
epoint.data.exception.VetoException.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153
Listing C.28: epoint.data.exception.DataBasketConictException
5
/*
* DataBasketConflictException.java
*
* Created on 1. Oktober 2001, 11:41
*/
package
10
15
20
25
30
35
40
45
50
55
60
epoint.data.exception;
/**
* The DataBasketConflictException is thrown, whenever transactions shall be carried
* out, that conflict with other transactions. A Catalog for example can only have
* one valid transaction adding a CatalogItem with a specific key. A second transaction
* trying to add a CatalogItem with the same key, will result in a DataBasketConflictException.
*
* @author Danny Poppe
* @version 1.0
*/
public class DataBasketConflictException extends Exception {
/**
* Defines a concurrent removal of another transaction.
*/
nal public static int CONCURRENT_REMOVAL = 1;
/**
* Defines a concurrent adding of another transaction.
*/
nal public static int CONCURRENT_ADDITION = 2;
/**
* Defines a concurrent editing of another transaction.
*/
nal public static int CONCURRENT_EDITING = 4;
/**
* Defines an unknown problem with an earlier concurrent transaction.
*/
nal public static int CONCURRENT_UNDEFINED = 128;
/**
* Contains one of the constants in this class to indicate the problem that
* caused this exception.
*/
private int iType = 0;
/**
* Creates new <code>DataBasketConflictException</code> without detail message.
*/
protected DataBasketConflictException() {
}
/**
* Constructs an <code>DataBasketConflictException</code> with the specified detail message.
* and the reason, why this exception is thrown, as one of the constants defined in this class.
*
* @param msg the detail message.
* @param iType one of the constants in this class to define the reason for
*
this exception to be thrown
*/
public DataBasketConflictException(String msg, int iType) {
super(msg);
this.iType = iType;
}
C.1. PACKAGE EPOINT.DATA
65
70
}
/**
* Returns the <code>int</code> indicating the reason, why this exception is thrown.
*
* @return the <code>int</code> indicating the reason for this exception
*/
nal public int getType() {
return iType;
}
Listing C.29: epoint.data.exception.DetachListenerException
5
/*
* DetachListenerException.java
*
* Created on 10. Januar 2002, 10:15
*/
package
10
15
20
epoint.data.exception;
/**
* This Exception may be thrown by a ContainerListener to signal that it wants
* to be removed from the container. The implementing Container should silently
* recognize the signal and remove the listener.
*
* @author Danny Poppe
* @version 1.0
*/
public class DetachListenerException extends RuntimeException {
/**
* Creates new <code>DetachListenerException</code> without detail message.
*/
public DetachListenerException() {
}
}
Listing C.30: epoint.data.exception.DuplicateKeyException
5
/*
* DKException.java
*
* Created on 19. Mai 2001, 21:49
*/
package
10
15
epoint.data.exception;
/**
* DuplicateKeyException is thrown whenever there it is tried break with the demand
* for a unique key or unique name.
*
* @author Danny Poppe
* @version 1.0
*/
public class DuplicateKeyException extends java.lang.RuntimeException {
/**
* Creates new <code>DKException</code> without detail message.
*/
protected DuplicateKeyException() {
}
20
25
30
}
/**
* Constructs an <code>DKException</code> with the specified detail message.
*
* @param msg the detail message.
*/
public DuplicateKeyException(String msg) {
super(msg);
}
Listing C.31: epoint.data.exception.IllegalPersistenceTypeException
package
5
epoint.data.exception;
/**
* IllegalPersistenceTypeException is thrown by adding a remote-object
* to an InformationStore
*
* @author Mirko Pracht
151
152
10
ANHANG C. QUELLTEXTE
* @version 1.0 27/10/01
* @since EPoint 1.0
*/
public class
15
extends
java.lang.RuntimeException {
/**
* Constructs an <code>IllegalPersistenceTypeException</code> with the specified detail message.
* @param msg the detail message.
*/
public IllegalPersistenceTypeException(String msg) { super(msg);}
20
25
IllegalPersistenceTypeException
/**
* Creates new <code>IllegalPersistenceTypeException</code> without detail message.
*/
protected IllegalPersistenceTypeException() {}
}
Listing C.32: epoint.data.exception.KeyNotFoundException
package
5
10
/**
* Exception is thrown, if a given Key can't be found in the InformationStore.
*
* @author Mirko Pracht
* @version 1.0 27/10/01
* @since EPoint 1.0
*/
public class
15
20
25
epoint.data.exception;
}
KeyNotFoundException
extends
java.lang.RuntimeException {
/**
* Creates new <code>KeyNotFoundException</code> without detail message.
*/
protected KeyNotFoundException() {
}
/**
* Constructs an <code>KeyNotFoundException</code> with the specified detail message.
* @param msg the detail message.
*/
public KeyNotFoundException(String msg) {
super(msg);
}
Listing C.33: epoint.data.exception.NoSuchKeyException
5
/*
* NoSuchKeyException.java
*
* Created on 10. Januar 2002, 09:11
*/
package
10
15
20
25
30
epoint.data.exception;
/**
* This exception is thrown whenever a required key does not exist. This is
* the case, if one adds a StockItem to a Stock, but the base-catalog of the
* Stock does not contain a CatalogItem with the same key as the StockItems
* name (that must be visible to the given DataBasket!).
*
* @author Danny Poppe
* @version 1.0
*/
public class NoSuchKeyException extends RuntimeException {
/**
* Creates a new <code>NoSuchKeyException</code> without detail message.
* This constructor should not be used, as we always want a detail-message.
*/
protected NoSuchKeyException() {
}
/**
* Constructs a <code>NoSuchKeyException</code> with the specified detail message.
*
* @param msg the detail message that describes the reason for this exception
*/
public NoSuchKeyException(String msg) {
super(msg);
C.1. PACKAGE EPOINT.DATA
35
}
}
Listing C.34: epoint.data.exception.NotEditableException
5
/*
* NotEditableException.java
*
* Created on 15. Juli 2001, 14:56
*/
package
10
15
epoint.data.exception;
/**
* This exception is thrown, whenever it is tried to edit e currently
* not editable element or container. This can be prevented, if <code>isEditable()</code>
* is checked before editing!
*
* @author Danny Poppe
* @version 1.0
*/
public class NotEditableException extends java.lang.RuntimeException {
/**
* Creates new <code>NotEditableException</code> without detail message.
*/
protected NotEditableException() {
}
20
25
30
}
/**
* Constructs an <code>NotEditableException</code> with the specified detail message.
* @param msg the detail message.
*/
public NotEditableException(String msg) {
super(msg);
}
Listing C.35: epoint.data.exception.UnknownTargetException
5
/*
* UnknownTargetException.java
*
* Created on 13. September 2001, 16:58
*/
package
10
15
epoint.data.exception;
/**
* This exception is thrown by a DBEntry, if the DBEntryTarget could not be
* identified in its implementation.
*
* @author Danny Poppe
* @version 1.0
*/
public class UnknownTargetException extends RuntimeException {
/**
* Creates new <code>UnknownTargetException</code> without detail message.
*/
protected UnknownTargetException() {
}
20
25
30
}
/**
* Constructs an <code>UnknownTargetException</code> with the specified detail message.
* @param msg the detail message.
*/
public UnknownTargetException(String msg) {
super(msg);
}
Listing C.36: epoint.data.exception.VetoException
5
/*
* VetoException.java
*
* Created on 11. September 2001, 10:40
*/
153
154
ANHANG C. QUELLTEXTE
package epoint.data.exception;
import epoint.data.*;
10
15
20
/**
* VetoExceptions may be thrown by ContainerListeners that are asked to
* agree with a transaction to signal, that they don't want the transaction
* to take place.
*
* @see ContainerListener
* @see ListenableContainer
* @author Danny Poppe
* @version 1.0
*/
public class VetoException extends java.lang.Exception {
/**
* Creates new <code>VetoException</code> without detail message.
*/
protected VetoException() {
}
25
30
35
}
/**
* Constructs an <code>VetoException</code> with the specified detail message.
* @param msg the detail message.
*/
public VetoException(String msg) {
super(msg);
}
C.1.2 Package epoint.data.rmi
epoint.data.rmi.AbstractStock.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154
epoint.data.rmi.ASStockItemIterator.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 177
epoint.data.rmi.CatalogImpl.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186
epoint.data.rmi.CatalogItemImpl.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .214
epoint.data.rmi.CatalogItemIterator.java. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .218
epoint.data.rmi.ContainerListenerAdapter.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .221
epoint.data.rmi.CountingStockImpl.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 223
epoint.data.rmi.DataBasketImpl.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 234
epoint.data.rmi.DBEIterator.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 240
epoint.data.rmi.DBEntryImpl.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241
epoint.data.rmi.PersistentContainerListener.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 245
epoint.data.rmi.RemoteIteratorImpl.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .247
epoint.data.rmi.RemoteRunnableImpl.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 247
epoint.data.rmi.StockItemImpl.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 248
epoint.data.rmi.StoringStockImpl.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 253
Listing C.37: epoint.data.rmi.AbstractStock
5
10
/*
* AbstractStock.java
*
* Created on 12. Oktober 2001, 10:50
*/
package epoint.data.rmi;
import epoint.data.*;
import epoint.data.exception.*;
import java.rmi.RemoteException;
import java.util.*;
C.1. PACKAGE EPOINT.DATA
15
20
25
30
35
40
45
50
55
60
65
70
75
80
85
90
95
import
import
import
epoint.sale.Shop;
java.beans.PropertyChangeEvent;
java.sql.Connection;
/**
* A Stock is an identifying collection of objects for the {@link epoint.data.CatalogItem}s
* in a specific {@link epoint.data.Catalog}. Each CatalogItem in a Catalog may have
* zero or more {@link epoint.data.StockItem}s associated to it. They are accessible
* with the CatalogItems key in the Stock and represent more specifying or shaping
* objects for the single CatalogItem. See {@link Stock} for more documentation!<br>
* <br>
* This default abstract implementation provides default-behaviour for StoringStocks
* and CountingStocks. It provides the persistent {@link CatalogIntegrityListener} that attaches
* itself to the base-Catalog of this Stock and performs data-integrity operation
* depending on the operation against the Catalog or vetoes actions against the
* Catalog, that may result in integrity-loss.
*
* @author Danny Poppe
* @version 1.0
*/
abstract public class AbstractStock extends java.rmi.server.UnicastRemoteObject implements Stock {
/**
* This Listener is attached to any Catalog that is referenced by a Stock
* and makes changes to affected StockItems by manipulations of the
* CatalogItems.<br>
* In detail this means, if a CatalogItem is removed from the base-catalog
* this listener removes the StockItems from the referencing Stock. This
* must only be done for the objects, as the database will automatically
* delete StockItems of CatalogItems that have been deleted, eg. updates
* their states.
*
* @author Danny Poppe
* @version 1.0
*/
protected class CatalogIntegrityListener extends PersistentContainerListener {
/**
* This ParamThread is a normal Thread that is extended to be used
* with a parameter for its run.
*
* @author Danny Poppe
* @version 1.0
*/
private abstract class ParamThread extends Thread {
/**
* The parameter that was passed to this Thread.
*/
private Object oParam;
/**
* Sets the parameter for this Thread.
*
* @param o the parameter for this Thread
*/
public void setParam(Object o) {
this.oParam = o;
}
/**
* Returns the current parameter of this Thread.
*
* @return the current parametr of this Thread.
*/
public Object getParam() {
return oParam;
}
/**
* The run-method that is used to start this Thread. Subclasses
* must implement this method.
*/
public abstract void run();
}
/**
* This is the cache for the reference to the Stock this Listener is
* attached to. After
* deserialization it is re-initialized by using the OID of the Stock.
*/
transient private Stock sReferencingStock = null;
/**
* This is the OID of the referencing Stock which is part of the persistent
* state of this listener.
*/
private String sStockOID = null;
/**
* This Map maps TransactionIDs of currently removed CatalogItems to the names
* of DataBaskets that are used to remove the corresponding normal StockItems.
*/
private Map mRemovingTransIDs = Collections.synchronizedMap(new HashMap());
/**
* This Map maps TransactionIDs of currently removed CatalogItems to a list of
155
156
100
105
110
115
120
125
130
135
140
145
150
155
160
165
170
175
180
185
ANHANG C. QUELLTEXTE
* OIDs of the StockItems that are currently edited or added by the same
* Transaction and need to be removed when the CatalogItems are finally
* removed.
*/
private Map mExternalRemovedTransIDs = Collections.synchronizedMap(new HashMap());
/**
* This Map maps TransactionIDs of currently edited CatalogItems to their keys.
* This is used to detect key-changes, which is normally not allowed and
* also (normally) impossible.
*/
private Map mEditedTransIDs = Collections.synchronizedMap(new HashMap());
/**
* Creates a new IntegrityListener for the given Stock.
*
* @param sReferencingStock the Stock this listener shall be created for,
*
and added to its base-catalog as a persistent listener
* @throws RemoteException whenever an error occurs during the use of RMI
*/
public CatalogIntegrityListener(Stock sReferencingStock) throws RemoteException {
this.sReferencingStock = sReferencingStock;
this.sStockOID = sReferencingStock.getOID();
}
/**
* Returns the Stock this listener has been created for, if this is
* impossible due to some error or non-existence, this listener
* fires a {@link DetachListenerException} to remove itself from the
* containers it is attached to. This method throws a {@link DetachListenerException}
* if the Stock cannot be reached from this listener.
*
* @return the Stock this listener was created for
* @throws RemoteException whenever an error occurs during the use of RMI
*/
public AbstractStock getStock() throws RemoteException {
if (sReferencingStock == null) {
try {
sReferencingStock = Shop.getTheShop().getDBPersistenceManager().getStockByOID(sStockOID,getDefaultConnection());
} catch (epoint.db.exception.DBAccessException dbae) {
Shop.getTheShop().logException(dbae);
} nally {
returnConnection(getDefaultConnection());
}
}
if (sReferencingStock == null) throw new DetachListenerException();
return (AbstractStock)sReferencingStock;
}
/**
* Called to ask whether a TransactionItem may be edited. If one of the listeners vetos the editing, all
* steners that had already been asked will receive a {@link #noEditTransactionItem noEditTransactionItem} event.
*
* @param e an event object describing the event.
* @exception VetoException if the listener wants to veto the editing.
* @throws RemoteException whenever an error occurs during the use of RMI
*/
public void canEditTransactionItem(ContainerChangeEvent e) throws VetoException, RemoteException {
// A CatalogItem may always be edited, equal to the state of the StockItem
}
/**
* Called whenever a specific property of the container has changed.
* This is implemented for at least the name of the container.
* This sets this Stock being no longer persistent, if the Catalog is no
* longer persistent.
*
* @param event an event object describing the event
* @throws RemoteException whenever an error occurs during the use of RMI
*/
public void propertyChange(PropertyChangeEvent event) throws RemoteException {
if (!((Catalog)event.getSource()).isPersistent()) getStock().setPersistent(null);
}
/**
* Called whenever the removal of a TransactionItem was committed. In this
* case the internal DataBasket that removed the corresponding normal
* StockItems has to commit, too.
*
* @param e an event object describing the event.
* @throws RemoteException whenever an error occurs during the use of RMI
*/
public void committedRemoveTransactionItem(ContainerChangeEvent e) throws RemoteException {
// the removal of a CatalogItem has committed and so has to be the removal
// of our internal DataBasket (that removed the StockItems)
String sTransID = e.getAffectedItem().getTransactionItemIdentifier();
String sDBName = (String)mRemovingTransIDs.remove(sTransID);
DataBasket dbInternal = null;
try {
dbInternal = Shop.getTheShop().getDBPersistenceManager().getDataBasket(sDBName,getDefaultConnection());
returnConnection(getDefaultConnection());
C.1. PACKAGE EPOINT.DATA
190
195
200
205
210
215
220
225
230
235
240
245
250
255
260
265
270
}
dbInternal.commit();
Shop.getTheShop().getDBPersistenceManager().deleteDataBasket(dbInternal, getDefaultConnection());
returnConnection(getDefaultConnection());
} catch (Exception exc) {
Shop.getTheShop().logException(exc);
} nally {
returnConnection(getDefaultConnection());
}
/**
* Called whenever a TransactionItem was removed from the DBEntryTarget.
* In this case the StockItems that are currently involved into a
* transaction using the same Basket are also removed by the same Basket.
*
* @param e an event object describing the event.
* @throws RemoteException whenever an error occurs during the use of RMI
*/
public void removedTransactionItem(ContainerChangeEvent e) throws RemoteException {
// ok, so we can remove the externally removable items too
String sTransID = e.getAffectedItem().getTransactionItemIdentifier();
AbstractStock as = getStock();
List lOIDs = (List)mExternalRemovedTransIDs.get(sTransID);
DataBasket dbExternal = e.getBasket();
Iterator itOIDs = lOIDs.iterator();
while (itOIDs.hasNext()) {
String sOID = (String)itOIDs.next();
StockItem si = Shop.getTheShop().getDBPersistenceManager().getStockItemByOID(sOID,getDefaultConnection());
returnConnection(getDefaultConnection());
try {
as.remove(si,dbExternal);
} catch (Exception exc) {
throw new RuntimeException(exc.getMessage());
}
}
}
/**
* Called whenever the adding of a TransactionItem was rolled back.
*
* @param e an event object describing the event.
* @throws RemoteException whenever an error occurs during the use of RMI
*/
public void rolledbackAddTransactionItem(ContainerChangeEvent e) throws RemoteException {
// nothing to be done
}
/**
* Called whenever a TransactionItem was added to a DBEntryTarget.
*
* @param e an event object describing the event.
* @throws RemoteException whenever an error occurs during the use of RMI
*/
public void addedItem(ContainerChangeEvent e) throws RemoteException {
// nothing to be done
}
/**
* Called to ask whether a TransactionItem may be removed. If one of the listeners vetos the removal, all
* listeners that had already been asked will receive a {@link #noRemoveTransactionItem noRemoveTransactionItem}
* event.<br>
* In this case all Stock-listeners are informed about the removal of the
* corresponding StockItems, which may result in a {@link VetoException}
* that is forwarded to the Catalog. Afterwards it is checked, if there
* are StockItems involved into transactions that cannot be ignored
* (results in a VetoException) and the remaining StockItems that are
* not part of any transaction are removed by an internal DataBasket.
*
* @param e an event object describing the event.
* @exception VetoException if the listener wants to veto the removal.
* @throws RemoteException whenever an error occurs during the use of RMI
*/
public void canRemoveTransactionItem(ContainerChangeEvent e) throws VetoException, RemoteException {
// we need to remove all corresponding StockItems, too
String sTransID = e.getAffectedItem().getTransactionItemIdentifier();
if (mRemovingTransIDs.containsKey(sTransID)) return; // this removal has already been checked!
String sKey = ((CatalogItem)e.getAffectedItem()).getKey();
AbstractStock as = (AbstractStock)getStock();
DataBasket dbInternal = new DataBasketImpl();
mRemovingTransIDs.put(sTransID,dbInternal.getName());
getStock().getCatalog().updatePersistentListener(sListenerID);
DataBasket dbExternal = e.getBasket();
Map mRemoveByExternal = new HashMap(); // this maps TransIDs of StockItems to those who need to be removed by the external
DataBasket (edited)
if (getStock().isCountingStock()) {
CountingStock cs = (CountingStock)getStock();
boolean bRemoved = false;
// the number of accessible items without the DataBasket
157
158
275
280
285
290
295
300
305
310
315
320
325
330
335
340
345
350
355
ANHANG C. QUELLTEXTE
int
iNormalSize = 0;
// we count 5 IllegalArgumentExceptions to reduce the chance
// of Exceptions because of concurrent removals
int iExceptionCount = 5;
String sReason = "";
while ((!bRemoved) && (iExceptionCount != 0)) {
try {
iNormalSize = cs.size(sKey,null);
cs.remove(sKey,iNormalSize,dbInternal);
bRemoved = true;
} catch (IllegalArgumentException iae) {
sReason = iae.getMessage();
iExceptionCount--;
}
}
if (!bRemoved) throw new VetoException("Too much concurrent transactions! Try again later! ("+sReason+")");
} else {
// first we must ask the listeners about the removal of the stockitems
// that are currently edited by the same basket
Iterator itEdited = as.getEditedItems(sKey).iterator();
while (itEdited.hasNext()) {
StockItem siEdited = (StockItem)itEdited.next();
if (!siEdited.getBasket().equals(dbExternal)) {
Iterator itNoRemove = mRemoveByExternal.values().iterator();
while (itNoRemove.hasNext()){
StockItem siNoRemove = (StockItem)itNoRemove.next();
as.fireNoRemoveTransactionItem(new ContainerChangeEventImpl(siNoRemove,as,dbExternal));
}
dbInternal.rollback();
mRemovingTransIDs.remove(sTransID);
try {
Shop.getTheShop().getDBPersistenceManager().deleteDataBasket(dbInternal,getDefaultConnection());
returnConnection(getDefaultConnection());
} catch (Exception exc) {
returnConnection(getDefaultConnection());
Shop.getTheShop().logException(exc);
}
throw new VetoException("You cannot remove a CatalogItem that has StockItems currently edited by another transaction!"
);
} else {
try {
as.fireCanRemoveTransactionItem(new ContainerChangeEventImpl(siEdited,as,dbExternal));
mRemoveByExternal.put(siEdited.getTransactionItemIdentifier(),siEdited);
} catch (VetoException ve) {
Iterator itNoRemove = mRemoveByExternal.values().iterator();
while (itNoRemove.hasNext()){
StockItem siNoRemove = (StockItem)itNoRemove.next();
as.fireNoRemoveTransactionItem(new ContainerChangeEventImpl(siNoRemove,as,dbExternal));
}
dbInternal.rollback();
mRemovingTransIDs.remove(sTransID);
try {
Shop.getTheShop().getDBPersistenceManager().deleteDataBasket(dbInternal,getDefaultConnection());
} catch (Exception exc) {
returnConnection(getDefaultConnection());
Shop.getTheShop().logException(exc);
}
throw new VetoException("A Listener prevents the removal of a corresponding StockItem ("+siEdited.toRemoteString()
+") that is currently edited by the given DataBasket: "+ve);
}
}
}
// now we inform the listeners about the removal of the items that are currently added by
// the same transaction
Iterator itAdded = as.getAddedItems(sKey).iterator();
while (itAdded.hasNext()) {
StockItem siAdded = (StockItem)itAdded.next();
if (!siAdded.getBasket().equals(dbExternal)) {
dbInternal.rollback();
mRemovingTransIDs.remove(sTransID);
try {
Shop.getTheShop().getDBPersistenceManager().deleteDataBasket(dbInternal,getDefaultConnection());
returnConnection(getDefaultConnection());
} catch (Exception exc) {
returnConnection(getDefaultConnection());
Shop.getTheShop().logException(exc);
}
throw new VetoException("You cannot remove a CatalogItem that has StockItems currently added by another transaction!")
;
} else {
try {
as.fireCanRemoveTransactionItem(new ContainerChangeEventImpl(siAdded,as,dbExternal));
mRemoveByExternal.put(siAdded.getTransactionItemIdentifier(),siAdded);
} catch (VetoException ve) {
Iterator itNoRemove = mRemoveByExternal.values().iterator();
while (itNoRemove.hasNext()){
StockItem siNoRemove = (StockItem)itNoRemove.next();
as.fireNoRemoveTransactionItem(new ContainerChangeEventImpl(siNoRemove,as,dbExternal));
C.1. PACKAGE EPOINT.DATA
360
365
370
375
380
385
390
395
400
405
410
415
420
425
430
435
440
445
159
}
dbInternal.rollback();
mRemovingTransIDs.remove(sTransID);
try {
Shop.getTheShop().getDBPersistenceManager().deleteDataBasket(dbInternal,getDefaultConnection());
returnConnection(getDefaultConnection());
} catch (Exception exc) {
returnConnection(getDefaultConnection());
Shop.getTheShop().logException(exc);
}
throw new VetoException("A Listener prevents the removal of a corresponding StockItem ("+siAdded.toRemoteString()+
") that is currently added by the given DataBasket: "+ve);
}
}
}
// finally we can remove all the other items
Iterator it = as.getStockItems(sKey).iterator(); // edited and added Items should now be registered
List lToRemove = new LinkedList(); // this list will contain all items that must be removed
// as this cannot be done within the iterator to avoid concurrent modifications
while (it.hasNext()) {
StockItem si = (StockItem)it.next();
// we remove only items with different transactionids than
// added or edited items of the same external basket
// (that excludes edited items, which are removed separately)
if (!mRemoveByExternal.containsKey(si.getTransactionItemIdentifier())) lToRemove.add(si);
}
Iterator itRemove = lToRemove.iterator();
while (itRemove.hasNext()) {
StockItem si = (StockItem)itRemove.next();
try {
as.remove(si,dbInternal);
} catch (VetoException ve) {
Iterator itNoRemove = mRemoveByExternal.values().iterator();
while (itNoRemove.hasNext()){
StockItem siNoRemove = (StockItem)itNoRemove.next();
as.fireNoRemoveTransactionItem(new ContainerChangeEventImpl(siNoRemove,as,dbExternal));
}
dbInternal.rollback();
mRemovingTransIDs.remove(sTransID);
try {
Shop.getTheShop().getDBPersistenceManager().deleteDataBasket(dbInternal,getDefaultConnection());
returnConnection(getDefaultConnection());
} catch (Exception exc) {
returnConnection(getDefaultConnection());
Shop.getTheShop().logException(exc);
}
throw ve;
} catch (DataBasketConflictException dbce) {
Iterator itNoRemove = mRemoveByExternal.values().iterator();
while (itNoRemove.hasNext()){
StockItem siNoRemove = (StockItem)itNoRemove.next();
as.fireNoRemoveTransactionItem(new ContainerChangeEventImpl(siNoRemove,as,dbExternal));
}
dbInternal.rollback();
mRemovingTransIDs.remove(sTransID);
try {
Shop.getTheShop().getDBPersistenceManager().deleteDataBasket(dbInternal,getDefaultConnection());
returnConnection(getDefaultConnection());
} catch (Exception exc) {
returnConnection(getDefaultConnection());
Shop.getTheShop().logException(exc);
}
throw new VetoException("Operation conflicts with another DataBasket that does not allow the StockItems to be removed
! ("+dbce+")");
}
}
}
// items that are added or edited by the same transaction are alread removed
// above
if (as.getAddedItems(sKey).size() != 0) {
// there are added items of other transactions
// we must prevent the CatalogItem-removal
Iterator it = as.getAddedItems(sKey).iterator();
while (it.hasNext()) {
StockItem si = (StockItem)it.next();
if (!(si.getBasket().equals(dbExternal) || si.getBasket().equals(dbInternal))) {
Iterator itNoRemove = mRemoveByExternal.values().iterator();
while (itNoRemove.hasNext()){
StockItem siNoRemove = (StockItem)itNoRemove.next();
as.fireNoRemoveTransactionItem(new ContainerChangeEventImpl(siNoRemove,as,dbExternal));
}
dbInternal.rollback();
mRemovingTransIDs.remove(sTransID);
try {
Shop.getTheShop().getDBPersistenceManager().deleteDataBasket(dbInternal,getDefaultConnection());
returnConnection(getDefaultConnection());
} catch (Exception exc) {
160
ANHANG C. QUELLTEXTE
}
returnConnection(getDefaultConnection());
Shop.getTheShop().logException(exc);
throw new
450
}
if
455
460
465
470
475
}
if
480
485
490
495
500
505
510
}
515
520
525
530
}
}
VetoException("You cannot remove a CatalogItem that has StockItems currently added by another transaction! (
It might rollback!)");
(as.getEditedItems(sKey).size() != 0) {
Iterator it = as.getEditedItems(sKey).iterator();
while (it.hasNext()) {
StockItem si = (StockItem)it.next();
if (!(si.getBasket().equals(dbExternal) || si.getBasket().equals(dbInternal))) {
Iterator itNoRemove = mRemoveByExternal.values().iterator();
while (itNoRemove.hasNext()){
StockItem siNoRemove = (StockItem)itNoRemove.next();
as.fireNoRemoveTransactionItem(new ContainerChangeEventImpl(siNoRemove,as,dbExternal));
}
dbInternal.rollback();
mRemovingTransIDs.remove(sTransID);
try {
Shop.getTheShop().getDBPersistenceManager().deleteDataBasket(dbInternal,getDefaultConnection());
returnConnection(getDefaultConnection());
} catch (Exception exc) {
returnConnection(getDefaultConnection());
Shop.getTheShop().logException(exc);
}
throw new VetoException("You cannot remove a CatalogItem that has StockItems currently edited by another transaction
! (It might rollback!)");
}
}
(as.getRemovedItems(sKey).size() != 0) {
// if there are some more transactions currently removing items
// we must prevent the removal, as they may be rolled back after the removal
// of the CatalogItem
Iterator it = as.getRemovedItems(sKey).iterator();
while (it.hasNext()) {
StockItem si = (StockItem)it.next();
if (!(si.getBasket().equals(dbExternal) || si.getBasket().equals(dbInternal))) {
Iterator itNoRemove = mRemoveByExternal.values().iterator();
while (itNoRemove.hasNext()){
StockItem siNoRemove = (StockItem)itNoRemove.next();
as.fireNoRemoveTransactionItem(new ContainerChangeEventImpl(siNoRemove,as,dbExternal));
}
dbInternal.rollback();
mRemovingTransIDs.remove(sTransID);
try {
Shop.getTheShop().getDBPersistenceManager().deleteDataBasket(dbInternal,getDefaultConnection());
returnConnection(getDefaultConnection());
} catch (Exception exc) {
returnConnection(getDefaultConnection());
Shop.getTheShop().logException(exc);
}
throw new VetoException("You cannot remove a CatalogItem that has StockItems currently removed by another transaction
! (It might rollback!)");
}
}
}
// now, as no exception occured, we can remember the OIDs of
// StockItems that need to be removed by the the external DataBasket
// (added and edited)
List lOIDs = new LinkedList();
Iterator itRemove = mRemoveByExternal.values().iterator();
while (itRemove.hasNext()) {
StockItem siTemp = (StockItem)itRemove.next();
lOIDs.add(siTemp.getOID());
}
mExternalRemovedTransIDs.put(sTransID,lOIDs);
getStock().getCatalog().updatePersistentListener(sListenerID);
/**
* Called whenever editing a TransactionItem was started. This event may be accompanied by a
* <code>removedTransactionItem</code> and a <code>addedTransactionItem</code> event, but this is implementation
* specific. We only remember the original key of the StockItem to detect illegal
* key-name changes.
*
* @param e an event object describing the event.
* @throws RemoteException whenever an error occurs during the use of RMI
*/
public void editingTransactionItem(ContainerChangeEvent e) throws RemoteException {
// the only thing that is of interest if a CatalogItem is edited,
// is that we need to update the keys in the stock, if the key of
// the CatalogItem changes, so lets remember the old key!
mEditedTransIDs.put(e.getAffectedItem().getTransactionItemIdentifier(),
((CatalogItem)e.getAffectedItem()).getKey());
getStock().getCatalog().updatePersistentListener(sListenerID);
C.1. PACKAGE EPOINT.DATA
}
535
540
545
550
555
560
565
570
575
580
585
590
595
600
605
610
615
/**
* Called whenever editing a TransactionItem was rolled back.
*
* @param e an event object describing the event.
* @throws RemoteException whenever an error occurs during the use of RMI
*/
public void rolledbackEditTransactionItem(ContainerChangeEvent e) throws RemoteException {
mEditedTransIDs.remove(e.getAffectedItem().getTransactionItemIdentifier());
}
/**
* Called for each listener that already agreed with an editing that was then rejected by another listener.
*
* @param e an event object describing the event.
* @throws RemoteException whenever an error occurs during the use of RMI
*/
public void noEditTransactionItem(ContainerChangeEvent e) throws RemoteException {
}
/**
* Called for each listener that already agreed with a removal that was then rejected by another listener.
* If the removal of a CatalogItem is rolled back, we also need to rollback
* the internal DataBasket that removed the normal StockItems and we need to
* delete the information about the other StockItems edited or added by the
* same Basket - which would have been deleted after a succesful removal
* of the CatalogItem.
*
* @param e an event object describing the event.
* @throws RemoteException whenever an error occurs during the use of RMI
*/
public void noRemoveTransactionItem(ContainerChangeEvent e) throws RemoteException {
// we must rollback our internal DataBasket that prepared the removal of the normal
// items
String sTransID = e.getAffectedItem().getTransactionItemIdentifier();
String sDBName = (String)mRemovingTransIDs.remove(sTransID);
DataBasket dbExternal = e.getBasket();
List lOIDs = (List)mExternalRemovedTransIDs.get(sTransID);
DataBasket dbInternal = null;
List lNoRemove = new LinkedList();
try {
dbInternal = Shop.getTheShop().getDBPersistenceManager().getDataBasket(sDBName,getDefaultConnection());
returnConnection(getDefaultConnection());
Iterator itNoRemove = lOIDs.iterator();
while (itNoRemove.hasNext()) {
String sOID = (String)itNoRemove.next();
StockItem siNoRemove = Shop.getTheShop().getDBPersistenceManager().getStockItemByOID(sOID,getDefaultConnection());
returnConnection(getDefaultConnection());
getStock().fireNoRemoveTransactionItem(new ContainerChangeEventImpl(siNoRemove,getStock(),dbExternal));
}
dbInternal.rollback();
Shop.getTheShop().getDBPersistenceManager().deleteDataBasket(dbInternal, getDefaultConnection());
returnConnection(getDefaultConnection());
} catch (Exception exc) {
returnConnection(getDefaultConnection());
Shop.getTheShop().logException(exc);
throw new RuntimeException("Could not create defined persistent state! ("+exc+")");
}
mExternalRemovedTransIDs.remove(sTransID);
getStock().getCatalog().updatePersistentListener(sListenerID);
}
/**
* Called whenever the adding of a TransactionItem was commited.
*
* @param e an event object describing the event.
* @throws RemoteException whenever an error occurs during the use of RMI
*/
public void committedAddTransactionItem(ContainerChangeEvent e) throws RemoteException {
}
/**
* Called whenever editing a TransactionItem was commited.
* We only check, if the name has changed and in this case we
* throw a RuntimeException, as this is illegal!
*
* @param e an event object describing the event.
* @throws RemoteException whenever an error occurs during the use of RMI
*/
public void committedEditTransactionItem(ContainerChangeEvent e) throws RemoteException {
// lets check, if the item has changed its key
String sOldKey = (String)mEditedTransIDs.get(e.getAffectedItem().getTransactionItemIdentifier());
String sNewKey = ((CatalogItem)e.getAffectedItem()).getKey();
if (!sOldKey.equals(sNewKey)) {
throw new RuntimeException("Detected a key-change of a CatalogItem! This is not allowed!");
}
}
161
162
620
625
630
635
640
645
650
655
660
665
670
675
680
685
690
695
700
705
}
ANHANG C. QUELLTEXTE
/**
* Called whenever the removal of a TransactionItem was rolled back.
* If the removal of CatalogItem is rolled back, we also need to
* rollback the internal DataBasket that removed the corresponding StockItems.
*
* @param e an event object describing the event.
* @throws RemoteException whenever an error occurs during the use of RMI
*/
public void rolledbackRemoveTransactionItem(ContainerChangeEvent e) throws RemoteException {
// we must rollback our internal DataBasket that prepared the removal of the normal
// items
String sTransID = e.getAffectedItem().getTransactionItemIdentifier();
String sDBName = (String)mRemovingTransIDs.remove(sTransID);
DataBasket dbInternal = null;
try {
dbInternal = Shop.getTheShop().getDBPersistenceManager().getDataBasket(sDBName,getDefaultConnection());
returnConnection(getDefaultConnection());
dbInternal.rollback();
Shop.getTheShop().getDBPersistenceManager().deleteDataBasket(dbInternal, getDefaultConnection());
returnConnection(getDefaultConnection());
} catch (Exception exc) {
returnConnection(getDefaultConnection());
Shop.getTheShop().logException(exc);
}
}
/**
* The default HashMap-size for mappings of keys to elements.
*/
protected nal static int iDefaultMapSize = 20;
/**
* The default List-size for lists of keys or items.
*/
protected nal static int iDefaultListSize = 10;
/**
* The Catalog this Stock is based on
*/
transient private Catalog cBaseCatalog;
/**
* The name of this Stock
*/
transient private String sName;
/**
* The ObjectID of this persistent Stock
*/
transient private String sOID;
/**
* All remotely available listeners are mapped to their ID in this HashMap.
* They get lost together with the objects instance.
*/
transient private Map mListener = Collections.synchronizedMap(new HashMap());
/**
* This monitor protects manipulations of items within this stock
*/
transient private Object m_oItemsLock = new Object();
/**
* This monitor protects manipulations of properties of this stock,
* such as its name and editability.
*/
transient private Object m_oPropertyLock = new Object();
/**
* This Map maps keys of the BaseCatalog to a list of StockItems in this Stock
*/
transient private Map mItems = Collections.synchronizedMap(new HashMap(iDefaultMapSize));
/**
* This Map maps keys of CatalogItems to a list of StockItems which are currently added
* within a still uncommitted transaction
*/
transient private Map mAddedItems = Collections.synchronizedMap(new HashMap(iDefaultMapSize));
/**
* This Map maps keys of CatalogItems to a list of StockItems which are currently
* edited within a transaction
*/
transient private Map mEditedItems = Collections.synchronizedMap(new HashMap(iDefaultMapSize));
/**
* This Map maps keys of CatalogItems to a list of StockItems which are currently
* removed within a still uncommitted transaction
*/
transient private Map mRemovedItems = Collections.synchronizedMap(new HashMap(iDefaultMapSize));
/**
* All locally available persistent listeners are mapped to their ID in this HashMap.
* As they are not directly part of the persistent state of this Stock, this Map
* needs to be re-initialized using the listener-ids which are properties of the
* Shop. These keys are persistent in the serialized version of this Stock.
C.1. PACKAGE EPOINT.DATA
*/
Map mPersistentListener = Collections.synchronizedMap(new HashMap());
/**
* This List is part of the serialized persistent state of this Stock and
* contains the property-names which contain the serialized versions of the
* persistent listeners attached to this Stock.
*/
private List lPersistentListenerIDs = Collections.synchronizedList(new LinkedList());
/**
* Stores the flag whether this Stock is currently editable or not
*/
private Boolean bEditable = new Boolean(true);
transient private
710
715
720
725
730
735
740
745
750
755
760
765
770
775
780
785
790
/**
* Creates a new Stock and initializes basic data, such as its name and
* its base-catalog. The Stock is automatically made persistent upon creation.
*
* @param sName the name of this Stock, which must be unique under all Stocks,
*
if <code>null</code> is given, a unique name is automatically choosen
* @param cBaseCatalog the Catalog on which this Stock is based on
* @throws RemoteException whenever an error occurs during remote-access
*/
public AbstractStock(String sName, Catalog cBaseCatalog) throws RemoteException {
if (cBaseCatalog == null) throw new NullPointerException("The BaseCatalog for a Stock may not be null!");
this.cBaseCatalog = cBaseCatalog;
//if no name is given, we set a default name
if (sName == null) {
long lUniqueLong = Shop.getTheShop().getGlobalUniqueNumber();
sName = "DefaultStockName #"+lUniqueLong;
}
this.sName = sName;
try {
Shop.getTheShop()
.getDBPersistenceManager()
.createStock(this,getDefaultConnection());
returnConnection(getDefaultConnection());
} catch (Exception e) {
returnConnection(getDefaultConnection());
Shop.getTheShop().logException(e);
}
CatalogIntegrityListener pcl = new CatalogIntegrityListener(this); // the Listener attaches itself to the Catalog of this Stock
cBaseCatalog.addPersistentContainerListener(pcl);
}
/**
* This method is called by the DBPersistenceManager, if this object
* was made persistent before and needs to be re-created. It uses the
* given data to enter the previous state and initializes some
* variables.
*
* @param mProperties the data used to re-enter a previous state is
*
mapped to defined keys in this hashmap
* @throws RemoteException whenever an error occurs during remote-access
*/
public void loadPersistentData(java.util.Map mProperties, String sConPasswd) throws RemoteException {
try {
synchronized (getItemsLock()) {
synchronized (getPropertyLock()) {
mListener = Collections.synchronizedMap(new HashMap());
// we re-set the name of the stock
this.sName = (String)mProperties.get(PERSISTENT_PROPERTY_STOCKNAME);
// and we need to set the base-catalog
this.cBaseCatalog = (Catalog)mProperties.get(PERSISTENT_PROPERTY_BASECATALOG);
// now we can initialize and re-create the items-containers
mItems = Collections.synchronizedMap(new HashMap(iDefaultMapSize));
mRemovedItems = Collections.synchronizedMap(new HashMap(iDefaultMapSize));
mAddedItems = Collections.synchronizedMap(new HashMap(iDefaultMapSize));
mEditedItems = Collections.synchronizedMap(new HashMap(iDefaultMapSize));
try {
// first load all normal StockItems
List lPersistent = Shop.getTheShop()
.getDBPersistenceManager()
.getStockItems(this, false, false,getReservedConnection(sConPasswd));
Iterator it = lPersistent.iterator();
while (it.hasNext()) {
StockItem si = (StockItem)it.next();
Set s = getStockItems(si.getName());
s.add(si);
}
// now we are getting all temporarily added items
lPersistent = Shop.getTheShop()
.getDBPersistenceManager()
.getStockItems(this, true, false,getReservedConnection(sConPasswd));
it = lPersistent.iterator();
while (it.hasNext()) {
StockItem si = (StockItem)it.next();
Set s = getAddedItems(si.getName());
s.add(si);
163
164
ANHANG C. QUELLTEXTE
795
800
805
810
815
820
825
830
835
840
845
850
855
860
865
870
875
880
}
}
}
}
}
}
// now all the temporarily removed items
lPersistent = Shop.getTheShop()
.getDBPersistenceManager()
.getStockItems(this, false, true,getReservedConnection(sConPasswd));
it = lPersistent.iterator();
while (it.hasNext()) {
StockItem si = (StockItem)it.next();
Set s = getRemovedItems(si.getName());
s.add(si);
}
// and finally all items that are modified using a databasket
lPersistent = Shop.getTheShop()
.getDBPersistenceManager()
.getStockItems(this, true, true,getReservedConnection(sConPasswd));
it = lPersistent.iterator();
while (it.hasNext()) {
StockItem si = (StockItem)it.next();
Set s = getEditedItems(si.getName());
s.add(si);
}
mPersistentListener = java.util.Collections.synchronizedMap(new HashMap());
mListener = java.util.Collections.synchronizedMap(new HashMap());
it = lPersistentListenerIDs.iterator();
while (it.hasNext()) {
String sKey = (String)it.next();
String sSerialized = Shop.getTheShop().getPersistentProperty(sKey);
PersistentContainerListener pcl = (PersistentContainerListener)Shop.getTheShop().getPersistenceSerializer().
fromString(sSerialized);
mPersistentListener.put(sKey,pcl);
}
} catch (Exception e) {
Shop.getTheShop().logException(e);
}
catch
(RuntimeException e) {
Shop.getTheShop().logException(e);
throw e;
/**
* Returns the Catalog on which this Stock is based on.
*
* @return the Catalog for this Stock
* @throws RemoteException whenever an error occurs during remote-access
*/
nal public Catalog getCatalog() throws RemoteException {
return cBaseCatalog;
}
/**
* Returns the keys for which StockItems are contained in this
* Stock and which are visible to the given DataBasket
*
* @param db the DataBasket that determines visibility of the StockItems
* @throws RemoteException whenever an error occurs during the use of RMI
* @return a set that contains all keys for which StockItems are visible
*
to the given DataBasket
*/
public nal java.util.Set keySet(DataBasket db) throws RemoteException {
Set sRetVal = Collections.synchronizedSet(new TreeSet());
// we need to convert the HashMaps keyset into our own set,
// as the HashMaps keyset is not serializable - we need
// serializability for remote-transport
// The following Sets provides Dead-Lock safety, as we don't use a monitor
// but only a SnapShot of the current keys
Set sItemKeys = new HashSet(mItems.keySet());
Set sAddedItemKeys = new HashSet(mAddedItems.keySet());
Iterator it = sItemKeys.iterator();
while (it.hasNext()) {
String sKey = (String)it.next();
if (getStockItems(sKey).size() == 0) continue;
if (getCatalog().contains(sKey,db)) sRetVal.add(sKey);
}
if (db != null) {
it = sAddedItemKeys.iterator();
while (it.hasNext()) {
String sKey = (String)it.next();
if (getAddedItems(sKey).size() == 0) continue;
if (!getCatalog().contains(sKey,db)) continue;
RemoteIterator itr = get(sKey,false,db);
while (itr.hasNext()) {
StockItem si = (StockItem)itr.next();
if (db.equals(si.getBasket())) {
sRetVal.add(sKey);
break;
C.1. PACKAGE EPOINT.DATA
}
885
890
895
900
905
910
915
920
925
930
935
940
945
950
955
960
965
}
}
165
}
}
// edited items are also contained as normal item
// removed items are never visible
return sRetVal;
/**
* Adds a new StockItem to this Stock, using the given DataBasket.
*
* @param si the StockItem that shall be added to this Stock
* @param db the DataBasket to use for integrating the action into a transaction, maybe
*
<code>null</code> if no transaction shall be used
* @throws DataBasketConflictException if this transaction conflicts with another
* @throws RemoteException whenever an error occurs during remote-access
*/
public abstract void add(StockItem si, DataBasket db) throws RemoteException, DataBasketConflictException;
// documentation is inherited
public RemoteIterator get(String sKey, boolean bForEdit, DataBasket db)
if (!getCatalog().contains(sKey,db)) {
return new RemoteIteratorImpl();
} else
return new ASStockItemIterator(this,bForEdit,db,sKey);
}
throws
RemoteException {
// documentation is inherited
public RemoteIterator get(String sKey) throws RemoteException {
return get(sKey,false,null);
}
/**
* Returns <code>true</code> only if this is a CountingStock, that counts the number of
* CatalogItems rather than specifying them in ways of details about them.
*
* @return <code>true</code> only if this object is a CountingStock
* @throws RemoteException whenever an error occurs during the use of RMI
*/
abstract public boolean isCountingStock() throws RemoteException;
// documentation is inherited
public abstract StockItem remove(StockItem si, DataBasket db)
throws
// documentation is inherited
public RemoteIterator iterator(DataBasket db, boolean bForEdit)
return new ASStockItemIterator(this,bForEdit,db);
}
RemoteException, VetoException, DataBasketConflictException;
throws
RemoteException {
// documentation is inherited
public RemoteIterator iterator(String sKey, DataBasket db, boolean bForEdit)
return new ASStockItemIterator(this,bForEdit,db,sKey);
}
// documentation is inherited
nal public boolean isPersistent()
return sOID != null;
}
// documentation is inherited
nal public String getOID()
return sOID;
}
throws
throws
throws
RemoteException {
RemoteException {
// documentation is inherited
nal public void setPersistent(String sOID)
this.sOID = sOID;
}
throws
RemoteException {
/**
* Uses the Shops {@link epoint.db.DBPersistenceManager} to update this
* objects persistent data. Therefore a list of properties may be given,
* that selects the data, that needs an update. This method must always be
* called after changes in the persistent state of this object. Methods of
* this class, changing relevant data, call this method implicitly. Extensions
* of this class may need to call it for updates of additional local data, using
* {@link epoint.data.PersistentObject#PERSISTENT_PROPERTY_SERIALIZED} in the
* list of parameters.
*
* @param lPropertyKeys a list of Strings, that must contain a valid constant
*
describing the change
*
<ul>
*
<li>{@link epoint.data.Stock#PERSISTENT_PROPERTY_BASECATALOG}</li>
*
<li>{@link epoint.data.Stock#PERSISTENT_PROPERTY_COUNTINGFLAG}</li>
*
<li>{@link epoint.data.Stock#PERSISTENT_PROPERTY_STOCKNAME}</li>
*
<li>{@link epoint.data.PersistentObject#PERSISTENT_PROPERTY_SERIALIZED}</li>
*
</ul>
* @param sCon the password that is used to get the protected connection in which this
RemoteException {
166
970
975
980
985
990
995
1000
1005
1010
1015
1020
1025
1030
1035
1040
1045
1050
1055
ANHANG C. QUELLTEXTE
*
object is updated
* @throws RemoteException if an error occurs during remote-access
*/
public void updatePersistentObject(java.util.List lPropertyKeys, String sCon) throws RemoteException {
if (!isPersistent()) return;
if (!Arrays.asList(new Object[]{PERSISTENT_PROPERTY_BASECATALOG,
PERSISTENT_PROPERTY_COUNTINGFLAG,
PERSISTENT_PROPERTY_STOCKNAME,
PERSISTENT_PROPERTY_SERIALIZED})
.containsAll(lPropertyKeys))
throw new IllegalArgumentException("Could not understand at least one of the properties that shall be made persistent!");
try {
Shop.getTheShop().getDBPersistenceManager().updateStock(this,lPropertyKeys,getReservedConnection(sCon));
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Uses the Shops {@link epoint.sale.Serializer PersistenceSerializer} to
* return a String-representation of this Stock, that may be used to persistify
* this Stock.
*
* @return a String-representation of this Stocks state
* @throws RemoteException if an error occurs during remote-access
*/
public String getSerializedForm() throws RemoteException {
return epoint.sale.Shop.getPersistenceSerializer().toString(this);
}
/**
* Returns the name of this Stock.
*
* @return the name of this Stock
* @throws RemoteException whenever an error occurs during remote-access
*/
nal public String getName() throws RemoteException {
return sName;
}
/**
* Sets a new name for this Stock, which must be unique under all Stocks.
*
* @param sName the new name for this Stock
* @throws RemoteException whenever an error occurs during remote-access
*/
nal public void setName(String sName) throws RemoteException {
synchronized (getPropertyLock()) {
if (!isEditable()) throw new NotEditableException("The stock "+getName()+" is currently not editable!");
try {
Stock s = Shop.getTheShop()
.getDBPersistenceManager()
.getStock(getName(),getDefaultConnection());
returnConnection(getDefaultConnection());
if (s != null) throw new DuplicateKeyException("A stock with the given name already exists!");
} catch (Exception e) {
returnConnection(getDefaultConnection());
e.printStackTrace();
}
String sOld = getName();
this.sName = sName;
String sConPasswd = getClass()+".setName()";
updatePersistentObject(Arrays.asList(new Object[]{PERSISTENT_PROPERTY_STOCKNAME}),sConPasswd);
returnConnection(getReservedConnection(sConPasswd));
nal AbstractStock thisStock = this;
nal String sOldName = sOld;
nal String sNewName = sName;
Thread t = new Thread("Fire PropertyChange in AbstractStock (setName)") {
public void run() {
try {
thisStock.firePropertyChange(new java.beans.PropertyChangeEvent(thisStock,Stock.PERSISTENT_PROPERTY_STOCKNAME,sOldName,
sNewName));
} catch (RemoteException rmie) {
Shop.getTheShop().logException(rmie);
}
}
};
t.run();
}
}
/**
* Checks if the given DBEntry belongs to this Stock and
* describes a valid transaction that was already registered.
*
* @param dbe the DBEntry to check for beeing valid
* @return <code>true</code> if the given DBEntry describes a valid transaction
*
that was registered with this Stock
C.1. PACKAGE EPOINT.DATA
1060
1065
1070
1075
1080
1085
1090
1095
1100
1105
1110
1115
1120
* @throws RemoteException whenever an error occurs during remote-access
*/
isValidTransaction(DBEntry dbe) throws RemoteException;
abstract public boolean
/**
* Commits the change described in the given DBEntry that was previously
* made to this Stock by using a DataBasket containing the given
* DBEntry.
*
* @param dbe the DBEntry, that describes the change made to this Stock
* @param sConPasswd the password for the connection that is used to
*
commit the changes in the persistent state of the item / Stock
* @return a RemoteRunnable that must be executed to inform the listeners about
*
this commit
* @throws RemoteException whenever an error occurs during remote-access
*/
abstract public RemoteRunnable commitTransaction(DBEntry dbe, String sConPasswd)
1130
1135
1140
throws
/**
* Reverts the change described by the given DBEntry for this Stock.
*
* @param dbe the DBEntry, that describes the change made to this Stock
* @param sConPasswd the password for the connection that is used to
*
commit the changes in the persistent state of the item / Stock
* @return a RemoteRunnable that must be executed to inform the listeners about this
*
rollback
* @throws RemoteException whenever an error occurs during remote-access
*/
abstract public RemoteRunnable rollbackTransaction(DBEntry dbe, String sConPasswd)
RemoteException;
throws
RemoteException;
/**
* Marks this Stock as beeing editable or not. This method should never be called
* by the programmer. It is intended only for internal use by the framework!
*
* @param bEditable if set to <code>true</code> this Stock will be editable
*
otherwise not
* @param sCon the password that protects the connection in which the persistent state
*
of this Stock is updated
* @throws RemoteException whenever an error occurs during remote-access
*/
public void setEditable(boolean bEditable, String sCon) throws RemoteException {
if (this.bEditable.booleanValue() == bEditable) return;
synchronized (getPropertyLock()) {
Object oMonitor = new Object();
synchronized (this.bEditable) {
oMonitor = this.bEditable;
this.bEditable = new Boolean(bEditable);
oMonitor.notifyAll();
}
}
updatePersistentObject(Arrays.asList(new Object[]{PERSISTENT_PROPERTY_SERIALIZED}),sCon);
}
/**
* Returns a boolean that says if this Stock is editable or not.
*
* @return <code>true</code> only if this STock is currently editable
* @throws RemoteException whenever an error occurs during remote-access
*/
public boolean isEditable() throws RemoteException {
synchronized (bEditable) {
if (!bEditable.booleanValue()) try {
bEditable.wait(750);
} catch (InterruptedException ie) {
Shop.getTheShop().logException(ie);
}
1125
167
}
}
return
bEditable.booleanValue();
/*******************************************************************************
*
Events
******************************************************************************/
/**
* Removes a listener from this container by identifying it, using
* the given ID.
*
* @param sIdentifier the ID of the listener that shall be removed from this Stock
* @return the removed listener or <code>null</code> if no such listener existed
* @throws RemoteException if an error occurs during remote-access
*/
public ContainerListener removeContainerListener(String sIdentifier) throws RemoteException {
ContainerListener cl1 = (ContainerListener)mListener.remove(sIdentifier);
ContainerListener cl2 = (ContainerListener)mPersistentListener.remove(sIdentifier);
Shop.getTheShop().deletePersistentProperty(sIdentifier);
168
1145
lPersistentListenerIDs.remove(sIdentifier);
cl1;
}
1150
1155
1160
1165
1170
1175
1180
1185
1190
1195
1200
1205
1210
1215
1220
1225
1230
ANHANG C. QUELLTEXTE
if (cl1 != null) return
return cl2;
/**
* Registers a remotely available listener to this Stock, which
* calls the appropriate methods of the listener if one of the
* specified actions are carried out.<br>
* The given listener is valid, as long as the registering host is up and
* the listener is remotely available. If an error occurs during accessing
* the listener remotely, the listener is silently removed and not
* used any longer.
*
* @param cl the listener that shall be registered with this Stock
* @return a String-identifier that may be used to remove the listener
*
later from this Stock
* @throws RemoteException if an error occurs during remote-access
*/
public String addContainerListener(ContainerListener cl) throws RemoteException {
String sKey = "ContainerListener at Stock "+getOID()+" #"+Shop.getTheShop().getGlobalUniqueNumber();
mListener.put(sKey,cl);
return sKey;
}
/**
* Registers a persistent listener with this Stock. That means the listener
* is serialized and recreated at the server-side. Such a listener will not be
* accessible anymore, but can be removed, using the ID that is returned
* after adding the listener.<br>
* Such a listener may be used to ensure user-defined constraints for a Stock,
* even if the registering EPoint is not available at a later time. Such a listener
* may not contact the registering EPoint again, but may veto some manipulations.
*
* @param pcl the listener that shall be registered with this Stock
* @return a String-identifier that may later be used to remove the listener again
* @throws RemoteException if an error occurs during remote-access
*/
public String addPersistentContainerListener(PContainerListener pcl) throws RemoteException {
String sSerialized = pcl.getSerializedForm();
pcl = (PContainerListener)Shop.getTheShop().getPersistenceSerializer().fromString(sSerialized);
String sKey = "ContainerListener at Stock "+getOID()+" #"+Shop.getTheShop().getGlobalUniqueNumber();
mPersistentListener.put(sKey,pcl);
lPersistentListenerIDs.add(sKey);
pcl.setListenerID(sKey);
String sConPasswd = getClass()+".setName()";
updatePersistentObject(Arrays.asList(new Object[]{PERSISTENT_PROPERTY_SERIALIZED}),sConPasswd); Shop.getTheShop().
setPersistentProperty(sKey,sSerialized);
returnConnection(getReservedConnection(sConPasswd));
return sKey;
}
/**
* Called whenever a StockItem is added within a transaction.
*
* @param e an event object describing the event.
* @throws RemoteException whenever an error occurs during remote-access
*/
protected void fireAddedItem(ContainerChangeEvent e) throws RemoteException {
Iterator it = mListener.keySet().iterator();
synchronized (getItemsLock()) {
while (it.hasNext()) {
ContainerListener cl = (ContainerListener)mListener.get((String)it.next());
try {
cl.addedItem(e);
} catch (RemoteException rmie) {
it.remove();
} catch (DetachListenerException dle) {
it.remove();
}
}
it = mPersistentListener.keySet().iterator();
while (it.hasNext()) {
String sKey = (String)it.next();
ContainerListener cl = (ContainerListener)mPersistentListener.get(sKey);
try {
cl.addedItem(e);
} catch (DetachListenerException dle) {
it.remove();
lPersistentListenerIDs.remove(sKey);
String sConPasswd = getClass()+".setName()";
updatePersistentObject(Arrays.asList(new Object[]{PERSISTENT_PROPERTY_SERIALIZED}),sConPasswd);
returnConnection(getReservedConnection(sConPasswd));
Shop.getTheShop().deletePersistentProperty(sKey);
}
}
}
}
C.1. PACKAGE EPOINT.DATA
1235
1240
1245
1250
1255
1260
1265
1270
1275
1280
1285
1290
1295
1300
1305
1310
1315
/**
* Called whenever the adding of a StockItem was commited.
*
* @param e an event object describing the event.
* @throws RemoteException whenever an error occurs during remote-access
*/
protected void fireCommittedAddTransactionItem(ContainerChangeEvent e) throws RemoteException {
Iterator it = mListener.keySet().iterator();
synchronized (getItemsLock()) {
while (it.hasNext()) {
ContainerListener cl = (ContainerListener)mListener.get((String)it.next());
try {
cl.committedAddTransactionItem(e);
} catch (RemoteException rmie) {
it.remove();
} catch (DetachListenerException dle) {
it.remove();
}
}
it = mPersistentListener.keySet().iterator();
while (it.hasNext()) {
String sKey = (String)it.next();
ContainerListener cl = (ContainerListener)mPersistentListener.get(sKey);
try {
cl.committedAddTransactionItem(e);
} catch (DetachListenerException dle) {
it.remove();
lPersistentListenerIDs.remove(sKey);
String sUpdatePasswd = getClass()+".updatePersistentObject";
updatePersistentObject(Arrays.asList(new Object[]{PERSISTENT_PROPERTY_SERIALIZED}),sUpdatePasswd);
returnConnection(getReservedConnection(sUpdatePasswd));
Shop.getTheShop().deletePersistentProperty(sKey);
}
}
}
}
/**
* Called whenever the adding of a StockItem was rolled back.
*
* @param e an event object describing the event.
* @throws RemoteException whenever an error occurs during remote-access
*/
protected void fireRolledbackAddTransactionItem(ContainerChangeEvent e) throws RemoteException {
Iterator it = mListener.keySet().iterator();
synchronized (getItemsLock()) {
while (it.hasNext()) {
ContainerListener cl = (ContainerListener)mListener.get((String)it.next());
try {
cl.rolledbackAddTransactionItem(e);
} catch (RemoteException rmie) {
it.remove();
} catch (DetachListenerException dle) {
it.remove();
}
}
it = mPersistentListener.keySet().iterator();
while (it.hasNext()) {
String sKey = (String)it.next();
ContainerListener cl = (ContainerListener)mPersistentListener.get(sKey);
try {
cl.rolledbackAddTransactionItem(e);
} catch (DetachListenerException dle) {
it.remove();
lPersistentListenerIDs.remove(sKey);
String sUpdatePasswd = getClass()+".updatePersistentObject";
updatePersistentObject(Arrays.asList(new Object[]{PERSISTENT_PROPERTY_SERIALIZED}),sUpdatePasswd);
returnConnection(getReservedConnection(sUpdatePasswd));
Shop.getTheShop().deletePersistentProperty(sKey);
}
}
}
}
/**
* Called to ask if a StockItem may be removed. If one of the registered listeners vetos the removal, all
* listeners that had already been asked will receive a {@link ContainerListener#noRemoveTransactionItem(ContainerChangeEvent)
noRemoveTransactionItem}
* event.
*
* @param e an event object describing the event.
*
* @exception VetoException if the listener wants to veto the removal.
* @throws RemoteException whenever an error occurs during remote-access
*/
protected void fireCanRemoveTransactionItem(ContainerChangeEvent e) throws RemoteException, VetoException {
169
170
1320
1325
1330
1335
1340
1345
1350
1355
1360
1365
1370
1375
1380
1385
1390
1395
1400
1405
ANHANG C. QUELLTEXTE
//protected void noRemoveTransactionItem(ContainerChangeEvent e) throws RemoteException;
Iterator it = mListener.keySet().iterator();
boolean bVeto = false;
List lInformedKeys = new LinkedList();
VetoException veThrown = null;
synchronized (getItemsLock()) {
while (it.hasNext()) {
String sKey = (String)it.next();
ContainerListener cl = (ContainerListener)mListener.get(sKey);
try {
cl.canRemoveTransactionItem(e);
lInformedKeys.add(sKey);
} catch (RemoteException rmie) {
it.remove();
lInformedKeys.remove(sKey);
} catch (VetoException ve) {
veThrown = ve;
bVeto = true;
break;
} catch (DetachListenerException dle) {
it.remove();
lInformedKeys.remove(sKey);
}
}
if (bVeto) {
it = lInformedKeys.iterator();
while (it.hasNext()) {
String sKey = (String)it.next();
ContainerListener cl = (ContainerListener)mListener.get(sKey);
cl.noRemoveTransactionItem(e);
}
throw veThrown;
} else {
it = mPersistentListener.keySet().iterator();
while (it.hasNext()) {
String sKey = (String)it.next();
ContainerListener cl = (ContainerListener)mPersistentListener.get(sKey);
try {
cl.canRemoveTransactionItem(e);
lInformedKeys.add(sKey);
} catch (DetachListenerException dle) {
it.remove();
lInformedKeys.remove(sKey);
lPersistentListenerIDs.remove(sKey);
String sUpdatePasswd = getClass()+".updatePersistentObject";
updatePersistentObject(Arrays.asList(new Object[]{PERSISTENT_PROPERTY_SERIALIZED}),sUpdatePasswd);
returnConnection(getReservedConnection(sUpdatePasswd));
Shop.getTheShop().deletePersistentProperty(sKey);
} catch (VetoException ve) {
veThrown = ve;
bVeto = true;
break;
}
}
if (bVeto) {
it = lInformedKeys.iterator();
while (it.hasNext()) {
String sKey = (String)it.next();
ContainerListener cl = (ContainerListener)mPersistentListener.get(sKey);
if (cl == null) cl = (ContainerListener)mListener.get(sKey);
cl.noRemoveTransactionItem(e);
}
throw veThrown;
}
}
}
}
/**
* This method is called, if a previous call to {@link #fireCanRemoveTransactionItem(ContainerChangeEvent)}
* has been successful but the removal will not take place.
*
* @param e an object describing the event
* @throws RemoteException if an error occurs during remote-access
*/
protected void fireNoRemoveTransactionItem(ContainerChangeEvent e) throws RemoteException {
Iterator it = mListener.keySet().iterator();
synchronized (getItemsLock()) { // we dont want to have more actions meanwhile!
while (it.hasNext()) {
ContainerListener cl = (ContainerListener)mListener.get((String)it.next());
try {
cl.noRemoveTransactionItem(e);
} catch (RemoteException rmie) {
it.remove();
} catch (DetachListenerException dle) {
it.remove();
}
}
it = mPersistentListener.keySet().iterator();
C.1. PACKAGE EPOINT.DATA
while
1410
1415
1420
}
1425
1430
1435
1440
1445
1450
1455
1460
1465
1470
1475
1480
1485
1490
}
}
(it.hasNext()) {
String sKey = (String)it.next();
ContainerListener cl = (ContainerListener)mPersistentListener.get(sKey);
try {
cl.noRemoveTransactionItem(e);
} catch (DetachListenerException dle) {
it.remove();
lPersistentListenerIDs.remove(sKey);
String sUpdatePasswd = getClass()+".updatePersistentObject";
updatePersistentObject(Arrays.asList(new Object[]{PERSISTENT_PROPERTY_SERIALIZED}),sUpdatePasswd);
returnConnection(getReservedConnection(sUpdatePasswd));
Shop.getTheShop().deletePersistentProperty(sKey);
}
/**
* Works like {@link #fireNoRemoveTransactionItem(ContainerChangeEvent)}, but is
* called in the case of editing instead.
*
* @param e an object describing the event
* @throws RemoteException whenever an error occurs during remote-access
*/
void fireNoEditTransactionItem(ContainerChangeEvent e) throws RemoteException {
Iterator it = mListener.keySet().iterator();
synchronized (getItemsLock()) { // we dont want to have more actions meanwhile!
while (it.hasNext()) {
ContainerListener cl = (ContainerListener)mListener.get((String)it.next());
try {
cl.noEditTransactionItem(e);
} catch (RemoteException rmie) {
it.remove();
} catch (DetachListenerException dle) {
it.remove();
}
}
it = mPersistentListener.keySet().iterator();
while (it.hasNext()) {
String sKey = (String)it.next();
ContainerListener cl = (ContainerListener)mPersistentListener.get(sKey);
try {
cl.noEditTransactionItem(e);
} catch (DetachListenerException dle) {
it.remove();
lPersistentListenerIDs.remove(sKey);
String sUpdatePasswd = getClass()+".updatePersistentObject";
updatePersistentObject(Arrays.asList(new Object[]{PERSISTENT_PROPERTY_SERIALIZED}),sUpdatePasswd);
returnConnection(getReservedConnection(sUpdatePasswd));
Shop.getTheShop().deletePersistentProperty(sKey);
}
}
}
}
/**
* Called whenever a StockItem was removed from this Stock.
*
* @param e an event object describing the event.
* @throws RemoteException whenever an error occurs during remote-access
*/
protected void fireRemovedTransactionItem(ContainerChangeEvent e) throws RemoteException {
Iterator it = mListener.keySet().iterator();
synchronized (getItemsLock()) {
while (it.hasNext()) {
ContainerListener cl = (ContainerListener)mListener.get((String)it.next());
try {
cl.removedTransactionItem(e);
} catch (RemoteException rmie) {
it.remove();
} catch (DetachListenerException dle) {
it.remove();
}
}
it = mPersistentListener.keySet().iterator();
while (it.hasNext()) {
String sKey = (String)it.next();
ContainerListener cl = (ContainerListener)mPersistentListener.get(sKey);
try {
cl.removedTransactionItem(e);
} catch (DetachListenerException dle) {
it.remove();
lPersistentListenerIDs.remove(sKey);
String sUpdatePasswd = getClass()+".updatePersistentObject";
updatePersistentObject(Arrays.asList(new Object[]{PERSISTENT_PROPERTY_SERIALIZED}),sUpdatePasswd);
returnConnection(getReservedConnection(sUpdatePasswd));
Shop.getTheShop().deletePersistentProperty(sKey);
}
171
172
1495
}
1500
1505
1510
1515
1520
1525
1530
1535
1540
1545
1550
1555
1560
1565
1570
1575
1580
ANHANG C. QUELLTEXTE
}
}
/**
* Called whenever the removal of a StockItem was commited.
*
* @param e an event object describing the event.
* @throws RemoteException whenever an error occurs during remote-access
*/
protected void fireCommittedRemoveTransactionItem(ContainerChangeEvent e) throws RemoteException {
Iterator it = mListener.keySet().iterator();
synchronized (getItemsLock()) {
while (it.hasNext()) {
ContainerListener cl = (ContainerListener)mListener.get((String)it.next());
try {
cl.committedRemoveTransactionItem(e);
} catch (RemoteException rmie) {
it.remove();
} catch (DetachListenerException dle) {
it.remove();
}
}
it = mPersistentListener.keySet().iterator();
while (it.hasNext()) {
String sKey = (String)it.next();
ContainerListener cl = (ContainerListener)mPersistentListener.get(sKey);
try {
cl.committedRemoveTransactionItem(e);
} catch (DetachListenerException dle) {
it.remove();
lPersistentListenerIDs.remove(sKey);
String sUpdatePasswd = getClass()+".updatePersistentObject";
updatePersistentObject(Arrays.asList(new Object[]{PERSISTENT_PROPERTY_SERIALIZED}),sUpdatePasswd);
returnConnection(getReservedConnection(sUpdatePasswd));
Shop.getTheShop().deletePersistentProperty(sKey);
}
}
}
}
/**
* Called whenever the removal of a StockItem was rolled back.
*
* @param e an event object describing the event.
* @throws RemoteException whenever an error occurs during remote-access
*/
protected void fireRolledbackRemoveTransactionItem(ContainerChangeEvent e) throws RemoteException {
Iterator it = mListener.keySet().iterator();
synchronized (getItemsLock()) {
while (it.hasNext()) {
ContainerListener cl = (ContainerListener)mListener.get((String)it.next());
try {
cl.rolledbackRemoveTransactionItem(e);
} catch (RemoteException rmie) {
it.remove();
} catch (DetachListenerException dle) {
it.remove();
}
}
it = mPersistentListener.keySet().iterator();
while (it.hasNext()) {
String sKey = (String)it.next();
ContainerListener cl = (ContainerListener)mPersistentListener.get(sKey);
try {
cl.rolledbackRemoveTransactionItem(e);
} catch (DetachListenerException dle) {
it.remove();
lPersistentListenerIDs.remove(sKey);
String sUpdatePasswd = getClass()+".updatePersistentObject";
updatePersistentObject(Arrays.asList(new Object[]{PERSISTENT_PROPERTY_SERIALIZED}),sUpdatePasswd);
returnConnection(getReservedConnection(sUpdatePasswd));
Shop.getTheShop().deletePersistentProperty(sKey);
}
}
}
}
/**
* Called to ask whether a StockItem may be edited. If one of the listeners vetos the editing, all
* steners that had already been asked will receive a {@link ContainerListener#noEditTransactionItem(ContainerChangeEvent)
noEditTransactionItem} event.
*
* @param e an event object describing the event.
*
* @exception VetoException if the listener wants to veto the editing.
* @throws RemoteException whenever an error occurs during remote-access
*/
C.1. PACKAGE EPOINT.DATA
void
1585
1590
1595
1600
1605
1610
1615
1620
1625
1630
1635
1640
1645
1650
1655
1660
1665
}
fireCanEditTransactionItem(ContainerChangeEvent e) throws RemoteException, VetoException {
//public void noEditTransactionItem(ContainerChangeEvent e) throws RemoteException;
Iterator it = mListener.keySet().iterator();
boolean bVeto = false;
List lInformedKeys = new LinkedList();
VetoException veThrown = null;
synchronized (getItemsLock()) {
while (it.hasNext()) {
String sKey = (String)it.next();
ContainerListener cl = (ContainerListener)mListener.get(sKey);
try {
cl.canEditTransactionItem(e);
lInformedKeys.add(sKey);
} catch (RemoteException rmie) {
it.remove();
lInformedKeys.remove(sKey);
} catch (VetoException ve) {
veThrown = ve;
bVeto = true;
break;
} catch (DetachListenerException dle) {
it.remove();
lInformedKeys.remove(sKey);
}
}
if (bVeto) {
it = lInformedKeys.iterator();
while (it.hasNext()) {
String sKey = (String)it.next();
ContainerListener cl = (ContainerListener)mListener.get(sKey);
cl.noEditTransactionItem(e);
}
throw veThrown;
} else {
it = mPersistentListener.keySet().iterator();
while (it.hasNext()) {
String sKey = (String)it.next();
ContainerListener cl = (ContainerListener)mPersistentListener.get(sKey);
try {
cl.canEditTransactionItem(e);
lInformedKeys.add(sKey);
} catch (DetachListenerException dle) {
it.remove();
lInformedKeys.remove(sKey);
lPersistentListenerIDs.remove(sKey);
String sUpdatePasswd = getClass()+".updatePersistentObject";
updatePersistentObject(Arrays.asList(new Object[]{PERSISTENT_PROPERTY_SERIALIZED}),sUpdatePasswd);
returnConnection(getReservedConnection(sUpdatePasswd));
Shop.getTheShop().deletePersistentProperty(sKey);
} catch (VetoException ve) {
veThrown = ve;
bVeto = true;
break;
}
}
if (bVeto) {
it = lInformedKeys.iterator();
while (it.hasNext()) {
String sKey = (String)it.next();
ContainerListener cl = (ContainerListener)mPersistentListener.get(sKey);
if (cl == null) cl = (ContainerListener)mListener.get(sKey);
cl.noEditTransactionItem(e);
}
throw veThrown;
}
}
}
/**
* Called whenever editing a StockItem was started. This event may be accompanied by a
* <code>removedTransactionItem</code> and a <code>addedTransactionItem</code> event, but this is implementation
* specific.
*
* @param e an event object describing the event.
* @throws RemoteException whenever an error occurs during remote-access
*/
void fireEditingTransactionItem(ContainerChangeEvent e) throws RemoteException {
Iterator it = mListener.keySet().iterator();
synchronized (getItemsLock()) {
while (it.hasNext()) {
ContainerListener cl = (ContainerListener)mListener.get((String)it.next());
try {
cl.editingTransactionItem(e);
} catch (RemoteException rmie) {
it.remove();
} catch (DetachListenerException dle) {
it.remove();
173
174
ANHANG C. QUELLTEXTE
1670
1675
1680
1685
}
1690
1695
1700
1705
1710
1715
1720
1725
1730
1735
1740
1745
1750
1755
}
}
}
it = mPersistentListener.keySet().iterator();
while (it.hasNext()) {
String sKey = (String)it.next();
ContainerListener cl = (ContainerListener)mPersistentListener.get(sKey);
try {
cl.editingTransactionItem(e);
} catch (DetachListenerException dle) {
it.remove();
lPersistentListenerIDs.remove(sKey);
String sUpdatePasswd = getClass()+".updatePersistentObject";
updatePersistentObject(Arrays.asList(new Object[]{PERSISTENT_PROPERTY_SERIALIZED}),sUpdatePasswd);
returnConnection(getReservedConnection(sUpdatePasswd));
Shop.getTheShop().deletePersistentProperty(sKey);
}
}
/**
* Called whenever editing a StockItem was commited.
*
* @param e an event object describing the event.
* @throws RemoteException whenever an error occurs during remote-access
*/
protected void fireCommittedEditTransactionItem(ContainerChangeEvent e) throws RemoteException {
Iterator it = mListener.keySet().iterator();
synchronized (getItemsLock()) {
while (it.hasNext()) {
ContainerListener cl = (ContainerListener)mListener.get((String)it.next());
try {
cl.committedEditTransactionItem(e);
} catch (RemoteException rmie) {
it.remove();
} catch (DetachListenerException dle) {
it.remove();
}
}
it = mPersistentListener.keySet().iterator();
while (it.hasNext()) {
String sKey = (String)it.next();
ContainerListener cl = (ContainerListener)mPersistentListener.get(sKey);
try {
cl.committedEditTransactionItem(e);
} catch (DetachListenerException dle) {
it.remove();
lPersistentListenerIDs.remove(sKey);
String sUpdatePasswd = getClass()+".updatePersistentObject";
updatePersistentObject(Arrays.asList(new Object[]{PERSISTENT_PROPERTY_SERIALIZED}),sUpdatePasswd);
returnConnection(getReservedConnection(sUpdatePasswd));
Shop.getTheShop().deletePersistentProperty(sKey);
}
}
}
}
/**
* Called whenever editing a StockItem was rolled back.
*
* @param e an event object describing the event.
* @throws RemoteException whenever an error occurs during remote-access
*/
protected void fireRolledbackEditTransactionItem(ContainerChangeEvent e) throws RemoteException {
Iterator it = mListener.keySet().iterator();
synchronized (getItemsLock()) {
while (it.hasNext()) {
ContainerListener cl = (ContainerListener)mListener.get((String)it.next());
try {
cl.rolledbackEditTransactionItem(e);
} catch (RemoteException rmie) {
it.remove();
} catch (DetachListenerException dle) {
it.remove();
}
}
it = mPersistentListener.keySet().iterator();
while (it.hasNext()) {
String sKey = (String)it.next();
ContainerListener cl = (ContainerListener)mPersistentListener.get(sKey);
try {
cl.rolledbackEditTransactionItem(e);
} catch (DetachListenerException dle) {
it.remove();
lPersistentListenerIDs.remove(sKey);
String sUpdatePasswd = getClass()+".updatePersistentObject";
updatePersistentObject(Arrays.asList(new Object[]{PERSISTENT_PROPERTY_SERIALIZED}),sUpdatePasswd);
returnConnection(getReservedConnection(sUpdatePasswd));
C.1. PACKAGE EPOINT.DATA
1760
}
1765
1770
1775
1780
1785
1790
1795
1800
1805
1810
1815
1820
1825
1830
1835
1840
1845
}
}
}
Shop.getTheShop().deletePersistentProperty(sKey);
/**
* Called whenever a specific property of this Stock has changed.
* This is implemented for at least the name of this Stock.
*
* @param event an event object describing the event
* @throws RemoteException whenever an error occurs during remote-access
*/
protected void firePropertyChange(java.beans.PropertyChangeEvent event) throws RemoteException {
Iterator it = mListener.keySet().iterator();
synchronized (getItemsLock()) {
while (it.hasNext()) {
ContainerListener cl = (ContainerListener)mListener.get((String)it.next());
try {
cl.propertyChange(event);
} catch (RemoteException rmie) {
it.remove();
} catch (DetachListenerException dle) {
it.remove();
}
}
it = mPersistentListener.keySet().iterator();
while (it.hasNext()) {
String sKey = (String)it.next();
ContainerListener cl = (ContainerListener)mPersistentListener.get(sKey);
try {
cl.propertyChange(event);
} catch (DetachListenerException dle) {
it.remove();
lPersistentListenerIDs.remove(sKey);
String sUpdatePasswd = getClass()+".updatePersistentObject";
updatePersistentObject(Arrays.asList(new Object[]{PERSISTENT_PROPERTY_SERIALIZED}),sUpdatePasswd);
returnConnection(getReservedConnection(sUpdatePasswd));
Shop.getTheShop().deletePersistentProperty(sKey);
}
}
}
}
/**
* Returns the monitor used to protect internal transactions manipulating
* states of StockItems relative to this Stock.
*
* @return the Object that monitors item-manipulations
*/
nal synchronized protected Object getItemsLock() {
if (m_oItemsLock == null) m_oItemsLock = new Object();
return m_oItemsLock;
}
/**
* Returns the monitor used to protect internal transactions manipulating
* the state of this object.
*
* @return the Object that monitors item-manipulations
*/
nal synchronized protected Object getPropertyLock() {
if (m_oPropertyLock == null) m_oPropertyLock = new Object();
return m_oPropertyLock;
}
/**
* Returns a set of StockItems for a given key in this Stock.
*
* @param sKey the key for which to return a set of StockItems
* @return the set of StockItems for the given key
*/
nal Set getStockItems(String sKey) {
synchronized (getItemsLock()) {
Set s = (Set)mItems.get(sKey);
if (s == null) {
s = Collections.synchronizedSet(new HashSet());
mItems.put(sKey,s);
}
return s;
}
}
/**
* Returns a set of StockItems for a given key in this Stock, that are
* added within a transaction.
*
* @param sKey the key for which to return a set of StockItems
* @return the set of StockItems for the given key
175
176
ANHANG C. QUELLTEXTE
*/
nal Set getAddedItems(String sKey) {
synchronized (getItemsLock()) {
1850
1855
1860
1865
1870
1875
1880
1885
1890
1895
1900
1905
1910
1915
1920
1925
1930
Set s = (Set)mAddedItems.get(sKey);
if (s == null) {
s = Collections.synchronizedSet(new HashSet());
mAddedItems.put(sKey,s);
}
return s;
}
}
/**
* Returns a set of StockItems for a given key in this Stock, that are
* edited within a transaction.
*
* @param sKey the key for which to return a set of StockItems
* @return the set of StockItems for the given key
*/
nal Set getEditedItems(String sKey) {
synchronized (getItemsLock()) {
Set s = (Set)mEditedItems.get(sKey);
if (s == null) {
s = Collections.synchronizedSet(new HashSet());
mEditedItems.put(sKey,s);
}
return s;
}
}
/**
* Returns a set of StockItems for a given key in this Stock, that are
* removed within a transaction.
*
* @param sKey the key for which to return a set of StockItems
* @return the set of StockItems for the given key
*/
nal Set getRemovedItems(String sKey) {
synchronized (getItemsLock()) {
Set s = (Set)mRemovedItems.get(sKey);
if (s == null) {
s = Collections.synchronizedSet(new HashSet());
mRemovedItems.put(sKey,s);
}
return s;
}
}
/**
* Two Stocks are decided to be equal, if they have the same name.
*
* @param o the other object that is compared to this Stock
* @return <code>true</code> if the given object is a Stock with the same name as this one
*/
public boolean equals(Object o) {
if (!(o instanceof Stock)) return false;
try {
return sName.equals(((Stock)o).getName());
} catch (RemoteException rmie) {
Shop.getTheShop().logException(rmie);
throw new RuntimeException("Could not complete equality-check, as a RemoteException occured!");
}
}
/**
* Returns a String-representation of this Stock containing information about the base-Catalog,
* the name of this Stock and if this is a CountingStock or a StoringStock.
*
* @return a String-representation of this Stock
*/
public String toString() {
try {
return ((isCountingStock())?("Counting"):(""))+"Stock \""+sName+"\" based on Catalog \""+getCatalog().getName()+"\"";
} catch (RemoteException rmie) {
Shop.getTheShop().logException(rmie);
return "Stock \""+sName+"\" [Exception occured during BaseCatalog-access: "+rmie.getClass().getName()+"]";
}
}
// documentation is inherited
public StockItem getItemByInternalID(long lID) throws RemoteException {
synchronized(getPropertyLock()) {
Collection c = mItems.values();
c.addAll(mAddedItems.values());
c.addAll(mEditedItems.values());
c.addAll(mRemovedItems.values());
Iterator it = c.iterator();
while (it.hasNext()) {
StockItem si = (StockItem)it.next();
if (si.getInternalID() == lID) return si;
C.1. PACKAGE EPOINT.DATA
177
}
1935
}
}
return null;
// documentation is inherited
public void updatePersistentListener(String sID) throws RemoteException {
String sSerialized = ((PersistentContainerListener)mPersistentListener.get(sID)).getSerializedForm();
Shop.getTheShop().setPersistentProperty(sID,sSerialized);
}
1940
1945
/**
* Returns a Connection to the database, that is protected by a class-wide
* default-password.
*
* @return the default-connection of this class
*/
protected Connection getDefaultConnection() {
return Shop.getTheShop().getDBPersistenceManager().getPrivateConnection("Default Connection for class "+getClass().getName(),getClass
());
}
1950
1955
/**
* Returns a Connection to the database, that is protected by the given
* password.
*
* @param sConPasswd the password the protects the requested connection
* @return the requested Connection
*/
protected Connection getReservedConnection(String sConPasswd) {
return Shop.getTheShop().getDBPersistenceManager().getPrivateConnection(sConPasswd,getClass());
}
1960
1965
/**
* Returns the given Connection that was previously requested to the DBPersistenceManager
* for further use by others.
*
* @param con the connection that shall be returned
*/
protected void returnConnection(Connection con) {
Shop.getTheShop().getDBPersistenceManager().returnPrivateConnection(con);
}
1970
1975
/**
* Returns the same as {@link #toString()}, but may be used by remote-connections,
* as {@link java.rmi.server.RemoteObject#toString()} returns a description of the
* Stub at client-side
*
* @return the String describing this object
* @throws RemoteException whenever an error occurs during remote-access
*/
public String toRemoteString() throws RemoteException {
return toString();
}
1980
1985
/*
1990
void addStockItem(StockItem si) {
getStockItems(si.getName()).add(si);
}
void removeStockItem(StockItem si) {
getStockItems(si.getName()).remove(si);
}
1995
void addAddedItem(StockItem si) {
getAddedItems(si.getName()).add(si);
}
2000
void removeAddedItem(StockItem si) {
getAddedItems(si.getName()).remove(si);
}
2005
void addEditedItem(StockItem si) {
getEditedItems(si.getName()).add(si);
}
2010
*/
}
void removeEditedItem(StockItem si) {
getEditedItems(si.getName()).remove(si);
}
Listing C.38: epoint.data.rmi.ASStockItemIterator
package epoint.data.rmi;
import java.rmi.RemoteException;
178
5
10
15
20
25
30
35
40
45
50
55
60
65
70
75
80
85
90
import
import
import
import
import
import
ANHANG C. QUELLTEXTE
epoint.data.*;
epoint.data.exception.*;
java.util.*;
epoint.sale.Shop;
epoint.db.exception.DBAccessException;
java.sql.Connection;
/**
* This implementation of a {@link RemoteIterator} is intended to be used only
* for the {@link AbstractStock}-class and its subclasses to return StockItems of
* those classes.<br>
* In all other meanings this RemoteIterator behaves exactly as the normal
* {@link java.util.Iterator}. An ASStockItemIterator cannot be made persistent.
* It uses a remotely available (not persistent) {@link ContainerListener} to
* detect concurrent modifications of the Stock it is based on.
*
* @see AbstractStock
* @see java.util.Iterator
* @see StockItem
* @author Danny Poppe
* @version 1.0
*/
public class ASStockItemIterator extends RemoteIteratorImpl implements RemoteIterator {
////////////////////////////////////////////////////////////////////////////////
//
NEW CLASS CONSTANTS
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//
NEW CLASS VARIABLES
////////////////////////////////////////////////////////////////////////////////
/**
* This iterator is constructed in the methods of this class and then used
* to return the StockItems.
*/
transient private Iterator itStock = null;
/**
* This iterator is valid only as long as this boolean is <code>true</code>.
* Otherwise a call to {@link #next()} throws a {@link ConcurrentModificationException}.
*/
transient private boolean bValid = true;
/**
* This variable references the AbstractStock, this iterator was created for.
*/
transient private AbstractStock s = null;
/**
* This variable references the DataBasket that was provided to create this iterator.
*/
transient private DataBasket db = null;
/**
* This variable contains <code>true</code>, if the items returned by this
* iterator shall be returned for editing.
*/
transient private boolean bForEdit = false;
/**
* A Listener derived from this class is attached to every Stock for which
* this listener was created. It listens to any visible structural modification
* of the Stock and informs this iterator if it is still valid (using
* {@link ASStockItemIterator#setInvalid(TransactionItem)}.
*
* @author Danny Poppe
* @version 1.0
*/
private class ModifyListener extends ContainerListenerAdapter {
/**
* The databasket for which the operation must be visible to
* set the iterator invalid.
*/
private DataBasket db;
/**
* The iterator, that shall be set invalid.
*/
private ASStockItemIterator it;
/**
* Creates a new Listener to be attached to any Stock that generates
* an iterator.
*
* @param it the iterator that shall be informed about structural modifications
*
of the Stock
* @param db the DataBasket for which the operation must be visible to have
*
an effect on the Stock
* @throws RemoteException whenever an error occurs during remote-access
*/
public ModifyListener(ASStockItemIterator it, DataBasket db) throws RemoteException {
this.it = it;
this.db = db;
C.1. PACKAGE EPOINT.DATA
95
100
105
110
115
120
125
130
135
140
145
150
155
160
165
170
175
}
}
/**
* Every committed add-action of another DataBasket sets the iterator to be invalid!
*
* @param e the events description
* @throws RemoteException whenever an error occurs during remote-access
*/
public void committedAddTransactionItem(ContainerChangeEvent e) throws RemoteException {
if (!e.getBasket().equals(db)) it.setInvalid(e.getAffectedItem());
}
/**
* Committed edit-actions do not affect the Iterator
*
* @param e the events description
* @throws RemoteException whenever an error occurs during remote-access
*/
public void committedEditTransactionItem(ContainerChangeEvent e) throws RemoteException {
}
/**
* Committed remove-actions do not effect the iterator!
*
* @param e the events description
* @throws RemoteException whenever an error occurs during remote-access
*/
public void committedRemoveTransactionItem(ContainerChangeEvent e) throws RemoteException {
}
/**
* Add-actions only affect the Iterator, if the DataBaskets are equal!
*
* @param e the events description
* @throws RemoteException whenever an error occurs during remote-access
*/
public void addedItem(ContainerChangeEvent e) throws RemoteException {
if (e.getBasket().equals(db)) it.setInvalid(e.getAffectedItem());
}
/**
* Remove-actions always affect the iterator.
*
* @param e the events description
* @throws RemoteException whenever an error occurs during remote-access
*/
public void removedTransactionItem(ContainerChangeEvent e) throws RemoteException {
it.setInvalid(e.getAffectedItem());
}
/**
* Rolled-back add-actions only affect the iterator, if they have the
* same DataBasket.
*
* @param e the events description
* @throws RemoteException whenever an error occurs during remote-access
*/
public void rolledbackAddTransactionItem(ContainerChangeEvent e) throws RemoteException {
if (e.getBasket().equals(db)) it.setInvalid(e.getAffectedItem());
}
/**
* Rolled-back remove-actions always affect the iterator.
*
* @param e the events description
* @throws RemoteException whenever an error occurs during remote-access
*/
public void rolledbackRemoveTransactionItem(ContainerChangeEvent e) throws RemoteException {
it.setInvalid(e.getAffectedItem());
}
/**
* Rolled back edit-actions never affect the Iterator.
*
* @param e the events description
* @throws RemoteException whenever an error occurs during remote-access
*/
public void rolledbackEditTransactionItem(ContainerChangeEvent e) throws RemoteException {
}
/**
* Editing-actions never affect the iterator.
*
* @param e the events description
* @throws RemoteException whenever an error occurs during remote-access
*/
public void editingTransactionItem(ContainerChangeEvent e) throws RemoteException {
}
////////////////////////////////////////////////////////////////////////////////
//
OVERRIDDEN CLASS VARIABLES
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//
NEW INSTANCE VARIABLES
////////////////////////////////////////////////////////////////////////////////
179
180
180
185
190
195
200
205
210
215
220
225
230
235
240
245
250
255
260
265
ANHANG C. QUELLTEXTE
/**
* This is a reference to the currently returned item
* of this RemoteIterator.
*/
private StockItem siCurrent = null;
////////////////////////////////////////////////////////////////////////////////
//
OVERRIDDEN INSTANCE VARIABLES
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//
CONSTRUCTORS
////////////////////////////////////////////////////////////////////////////////
/**
* Creates a new StockItemIterator over items with the given name in the
* given Stock.
*
* @param s the Stock for which the StockItems shall be returned
* @param bForEdit if <code>true</code> the DataBasket must not <code>null</code>
*
and the items are returned for editing relative to the given DataBasket
* @param db the DataBasket that determines the visibility of the StockItems in
*
the given Stock
* @param sKey the key in the BaseCatalog for which the StockItems shall be returned
*
by this iterator
* @throws RemoteException whenever an error occurs during the use of RMI
*/
public ASStockItemIterator(AbstractStock s, boolean bForEdit, DataBasket db, String sKey) throws RemoteException {
if (bForEdit && (db == null)) throw new NullPointerException("You must provide a DataBasket for editing a StockItem!");
this.s = s;
this.db = db;
this.bForEdit = bForEdit;
s.addContainerListener(new ModifyListener(this,db));
if (s.isCountingStock()) {
if (sKey != null) {
prepareCounting(sKey);
} else {
prepareCounting();
}
} else {
if (sKey != null) {
prepareStoring(sKey);
} else {
prepareStoring();
}
}
}
/**
* This creates a new RemoteIterator over all visible items in the given
* Stock.
*
* @param s the Stock for which this iterator will return StockItems
* @param bForEdit if <code>true</code> the DataBasket must not be <code>null</code>
*
and the items returned by this iterator are returned for editing
* @param db the DataBasket that determines visibility of the StockItems
*
and relative to which the items may be edited or removed by the iterator
* @throws RemoteException whenever an error occurs during the use of RMI
*/
public ASStockItemIterator(AbstractStock s, boolean bForEdit, DataBasket db) throws RemoteException {
this(s,bForEdit,db,null);
}
/**
* Prepares this RemoteIterator for returning StockItems of a CountingStock
* for the given key.
*
* @param sKey the key for which this iterator will return StockItems of
*
a CountingStock
* @throws RemoteException whenever an error occurs during the use of RMI
*/
private void prepareCounting(nal String sKey) throws RemoteException {
class CountUp {
private int iMax = 0;
private int iCurrent = 0;
public CountUp(int iStart, int iMax) { this.iMax = iMax; this.iCurrent = iStart-1; }
public boolean hasNext() { return iCurrent < iMax; }
public int next() { iCurrent++; return iCurrent; }
}
nal int iNormalSize = s.size(sKey,null);
nal int iAddedSize = s.size(sKey,db);
nal CountUp cNormal = new CountUp(1,iNormalSize);
nal CountUp cAdded = new CountUp(iNormalSize+1,iAddedSize);
nal String sStockName = s.getName();
itStock = new Iterator() {
public boolean hasNext() {
return (cNormal.hasNext() || cAdded.hasNext());
C.1. PACKAGE EPOINT.DATA
181
}
public Object next() {
if (!cNormal.hasNext() && !cAdded.hasNext()) throw new NoSuchElementException("This iterator has no more elements!");
StockItem si = null;
try {
if (cNormal.hasNext()) {
si = new StockItemImpl(sKey,new StringValue(sStockName+" :: CountingStockItem #"+cNormal.next()));
si.setStock(s,null);
} else {
si = new StockItemImpl(sKey,new StringValue(sStockName+" :: CountingStockItem #"+cAdded.next()));
si.setStock(s,null);
si.setBasket(db,null);
270
275
280
}
}
}
return
}
295
300
305
310
315
320
325
330
335
340
345
350
}
//
}
};
}
occured: "+re);
si;
public void
try {
285
290
}
catch (RemoteException re) {
throw new RuntimeException("RemoteException
remove() {
((CountingStock)s).remove(sKey,1,db);
catch (Exception e) {
throw new RuntimeException(e.getClass().getName()+"
occured: "+e.getMessage()+"\n"+e.getStackTrace());
throw new UnsupportedOperationException("Removing StockItems using the Iterator is not implemented!");
/**
* Prepares this Iterator for returning all StockItems of a CountingStock.
*
* @throws RemoteException whenever an error occurs during the use of RMI
*/
private void prepareCounting() throws RemoteException {
nal Set sKeys = s.keySet(db);
nal List lItems = new LinkedList();
nal String sStockName = s.getName();
itStock = new Iterator() {
Iterator itKeys = sKeys.iterator();
RemoteIterator itItems = new RemoteIteratorImpl();
public boolean hasNext() {
try {
if (itItems.hasNext()) return true;
if (!itKeys.hasNext()) {
return false;
} else {
String sKey = (String)itKeys.next();
itItems = new ASStockItemIterator(s,bForEdit,db,sKey);
return hasNext(); // recursive call
}
} catch (RemoteException rmie) {
throw new RuntimeException(rmie);
}
}
public Object next() {
try {
if (itItems.hasNext()) {
return itItems.next();
} else {
if (!itKeys.hasNext()) {
throw new NoSuchElementException("This iterator has no more elements!");
} else {
String sKey = (String)itKeys.next();
itItems = new ASStockItemIterator(s,bForEdit,db,sKey);
return next(); // recursive call
}
}
} catch (RemoteException ve) {
throw new RuntimeException(ve.getClass().getName()+" occured "+ve.getMessage());
}
}
public void remove() {
try {
itItems.remove();
} catch (Exception ve) {
throw new RuntimeException(ve.getClass().getName()+" occured "+ve.getMessage());
}
}
};
/**
while (it.hasNext()) {
String sKey = (String)it.next();
int iNormalSize = s.size(sKey,null);
int iAddedSize = s.size(sKey,db);
for (int iNormalCount = 1; iNormalCount <= iNormalSize; iNormalCount++) {
StockItem si = new StockItemImpl(sKey,new StringValue(sStockName+" :: CountingStockItem #"+iNormalCount));
si.setStock(s,null);
182
355
lItems.add(si);
}
for (int iAddedCount = iNormalSize+1; iAddedCount <= iAddedSize; iAddedCount++) {
StockItem si = new StockItemImpl(sKey,new StringValue(sStockName+" :: CountingStockItem #"+iAddedCount));
si.setStock(s,null);
si.setBasket(db,null);
lItems.add(si);
}
360
365
370
375
380
385
390
395
400
405
410
ANHANG C. QUELLTEXTE
}
}
itStock = lItems.iterator();
*/
/**
* Prepares this Iterator for returning all StockItems of a StoringStock
* that belong to the given key in the base-Catalog.
*
* @param sKey the key in the base-Catalog for which all StockItems in the
*
StoringStock shall be returned
* @throws RemoteException whenever an error occurs during the use of RMI
*/
private void prepareStoring(String sKey) throws RemoteException {
nal String sStockName = s.getName();
List lEdited = new LinkedList();
List lAdded = new LinkedList();
List lNormal = new LinkedList();
List lEditedIDs = new LinkedList();
lEdited.addAll(s.getEditedItems(sKey));
// lets sort aus edited items, that are edited by other baskets!
Iterator it = lEdited.iterator();
while (it.hasNext()) {
StockItem si = (StockItem)it.next();
if (!si.getBasket().equals(db)) {
it.remove();
} else lEditedIDs.add(si.getTransactionItemIdentifier());
}
lAdded.addAll(s.getAddedItems(sKey));
// lets sort out items that are added by other baskets!
it = lAdded.iterator();
while (it.hasNext()) {
StockItem si = (StockItem)it.next();
if (!si.getBasket().equals(db)) it.remove();
}
lNormal.addAll(s.getStockItems(sKey));
// lets sort out items that are edited by the same basket!
it = lNormal.iterator();
while (it.hasNext()) {
StockItem si = (StockItem)it.next();
if (lEditedIDs.contains(si.getTransactionItemIdentifier())) it.remove();
}
nal
nal
nal
Iterator itNormal = lNormal.iterator();
Iterator itEdited = lEdited.iterator();
Iterator itAdded = lAdded.iterator();
nal long
415
420
425
430
435
440
iNumber = Shop.getTheShop().getGlobalUniqueNumber();
new Iterator() {
private int iCount = 0;
itStock =
public boolean hasNext() {
return (itNormal.hasNext()
|| itEdited.hasNext() || itAdded.hasNext());
}
/**
* VetoExceptions will be converted into RuntimExceptions
*/
public Object next() {
if (!hasNext()) throw new NoSuchElementException("This iterator has no more elements!");
iCount++;
StockItem si = null;
try {
if (itNormal.hasNext()) {
si = (StockItem)itNormal.next();
if (bForEdit) {
// this item is a normal item and will be returned for editing in a transaction
// first we need to inform the listeners
try {
s.fireCanEditTransactionItem(new ContainerChangeEventImpl(si,s,db));
} catch (VetoException ve) {
throw new RuntimeException("VetoException occured (was converted into this RuntimeException): "+ve);
}
si = si.getShallowClone(true);
s.getEditedItems(si.getName()).add(si);
String sConPasswd = "Private Connection-editing StockItem in Iterator #"+iNumber+"-"+iCount; // stays in auto-commit
try {
C.1. PACKAGE EPOINT.DATA
445
450
455
460
465
470
475
}
Shop.getTheShop().getDBPersistenceManager().createStockItem(si,true,true,getReservedConnection(sConPasswd));
DBEntry dbe = new DBEntryImpl(db,DBEntry.TRANSACTION_MODIFY,s,si,null);
db.add(dbe,sConPasswd);
si.setBasket(db,sConPasswd);
returnConnection(getReservedConnection(sConPasswd));
s.fireEditingTransactionItem(new ContainerChangeEventImpl(si,s,db));
} catch (DBAccessException e) {
epoint.sale.Shop.getTheShop().logException(e);
try {
getReservedConnection(sConPasswd).rollback();
} catch (Exception e2) {
Shop.getTheShop().logException(e2);
} nally {
returnConnection(getReservedConnection(sConPasswd));
}
throw new RuntimeException(e);
}
} else si = si.getShallowClone(false);
} else if (itEdited.hasNext()) {
si = (StockItem)itEdited.next();
if (bForEdit) {
// this item is already edited by the given basket and so is simply returned
} else si = si.getShallowClone(false);
} else {
si = (StockItem)itAdded.next();
if (bForEdit) {
// this item is currently added by the given basket and so it is simply returned
} else si = si.getShallowClone(false);
}
} catch (RemoteException re) {
throw new RuntimeException("RemoteException occured: "+re);
}
return si;
public void
try {
480
485
490
495
500
}
}
};
}
remove() {
s.remove(siCurrent,db);
catch (Exception e) {
throw new RuntimeException(e.getClass().getName()+"
occured: "+e.getMessage()+"\n"+e.getStackTrace());
}
//throw new UnsupportedOperationException("Removing StockItems using the Iterator is not implemented!");
/**
* Prepares this Iterator for returning all StockItems of a StoringStock.
*
* @throws RemoteException whenever an error occurs during the use of RMI
*/
private void prepareStoring() throws RemoteException {
nal String sStockName = s.getName();
List lEdited = new LinkedList();
List lAdded = new LinkedList();
List lNormal = new LinkedList();
List lEditedIDs = new LinkedList();
Set sKeySet = s.keySet(db);
Iterator itKeySet = sKeySet.iterator();
while
505
510
515
520
525
530
183
(itKeySet.hasNext()) {
String sKey = (String)itKeySet.next();
lEdited.addAll(s.getEditedItems(sKey));
// lets sort aus edited items, that are edited by other baskets!
Iterator it = lEdited.iterator();
while (it.hasNext()) {
StockItem si = (StockItem)it.next();
if (!si.getBasket().equals(db)) {
it.remove();
} else lEditedIDs.add(si.getTransactionItemIdentifier());
}
lAdded.addAll(s.getAddedItems(sKey));
// lets sort out items that are added by other baskets!
it = lAdded.iterator();
while (it.hasNext()) {
StockItem si = (StockItem)it.next();
if (!si.getBasket().equals(db)) it.remove();
}
lNormal.addAll(s.getStockItems(sKey));
// lets sort out items that are edited by the same basket!
it = lNormal.iterator();
while (it.hasNext()) {
StockItem si = (StockItem)it.next();
if (lEditedIDs.contains(si.getTransactionItemIdentifier())) it.remove();
}
184
ANHANG C. QUELLTEXTE
}
nal
nal
nal
535
Iterator itNormal = lNormal.iterator();
Iterator itEdited = lEdited.iterator();
Iterator itAdded = lAdded.iterator();
nal long
new Iterator() {
private int iCount = 0;
public boolean hasNext() {
return (itNormal.hasNext()
itStock =
540
545
550
555
560
565
570
575
580
585
590
595
600
605
610
615
iNumber = Shop.getTheShop().getGlobalUniqueNumber();
}
};
|| itEdited.hasNext() || itAdded.hasNext());
}
/**
* VetoExceptions will be converted into RuntimExceptions
*/
public Object next() {
if (!hasNext()) throw new NoSuchElementException("This iterator has no more elements!");
iCount++;
StockItem si = null;
try {
if (itNormal.hasNext()) {
si = (StockItem)itNormal.next();
if (bForEdit) {
// this item is a normal item and will be returned for editing in a transaction
// first we need to inform the listeners
try {
s.fireCanEditTransactionItem(new ContainerChangeEventImpl(si,s,db));
} catch (VetoException ve) {
throw new RuntimeException("VetoException occured (was converted into this RuntimeException): "+ve);
}
si = si.getShallowClone(true);
s.getEditedItems(si.getName()).add(si);
String sConPasswd = "Private Connection-editing StockItem in Iterator #"+iNumber+"-"+iCount;
try {
Shop.getTheShop().getDBPersistenceManager().createStockItem(si,true,true,getReservedConnection(sConPasswd));
DBEntry dbe = new DBEntryImpl(db,DBEntry.TRANSACTION_MODIFY,s,si,null);
db.add(dbe,sConPasswd);
si.setBasket(db,sConPasswd);
returnConnection(getReservedConnection(sConPasswd));
s.fireEditingTransactionItem(new ContainerChangeEventImpl(si,s,db));
} catch (DBAccessException e) {
epoint.sale.Shop.getTheShop().logException(e);
try {
getReservedConnection(sConPasswd).rollback();
} catch (Exception e2) {
Shop.getTheShop().logException(e2);
} nally {
returnConnection(getReservedConnection(sConPasswd));
}
throw new RuntimeException(e);
}
}
} else if (itEdited.hasNext()) {
si = (StockItem)itEdited.next();
if (bForEdit) {
// this item is already edited by the given basket and so is simply returned
}
} else {
si = (StockItem)itAdded.next();
if (bForEdit) {
// this item is currently added by the given basket and so it is simply returned
}
}
} catch (RemoteException re) {
throw new RuntimeException("RemoteException occured: "+re);
}
return si;
}
public void remove() {
try {
s.remove(siCurrent,db);
} catch (Exception e) {
throw new RuntimeException(e.getClass().getName()+" occured: "+e.getMessage()+"\n"+e.getStackTrace());
}
//throw new UnsupportedOperationException("Removing StockItems using the Iterator is not implemented!");
}
////////////////////////////////////////////////////////////////////////////////
//
INTERFACE ...
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//
OVERRIDDEN CLASS METHODS
////////////////////////////////////////////////////////////////////////////////
C.1. PACKAGE EPOINT.DATA
620
625
630
635
640
645
650
655
660
665
670
675
680
685
690
695
700
705
185
////////////////////////////////////////////////////////////////////////////////
//
OVERRIDDEN INSTANCE METHODS
////////////////////////////////////////////////////////////////////////////////
/**
* Returns <code>true</code> if this iterator has more elements. Otherwise
* <code>false</code> is returned to signal that subsequent calls to
* {@link #next()} will result in an Exception to be thrown.
*
* @throws RemoteException whenever an error occurs during the use of RMI
* @return <code>true</code> if this iterator has more elements
*/
nal public boolean hasNext() throws RemoteException {
return itStock.hasNext();
}
/**
* Returns the next StockItem of this Iterator or throws a {@link NoSuchElementException}
* This method may also throw a RuntimeException that was previously a
* VetoException of a listener that has vetoed the editing of a StockItem.
* And finally a {@link ConcurrentModificationException} is thrown, if the
* underlying Stock is modified by other actions than by removals using
* {@link #remove()}.
*
* @throws RemoteException whenever an error occurs during the use of RMI
* @return the next StockItem of this Iterator
*/
nal public Object next() throws RemoteException {
if (!bValid) throw new ConcurrentModificationException("The Stock this iterator is based on, has been concurrently modified!");
siCurrent = (StockItem)itStock.next();
return siCurrent;
}
/**
* Removes the current item from the Stock, using the given DataBasket, or
* this DataBasket was <code>null</code> an anonymous DataBasket.
* This may result in a VetoException, which is converted into a
* RuntimeException.
*
* @throws RemoteException whenever an error occurs during the use of RMI
*/
nal public void remove() throws RemoteException {
if (!bValid) throw new ConcurrentModificationException("The Stock this iterator is based on, has been concurrently modified!");
itStock.remove();
siCurrent = null;
}
////////////////////////////////////////////////////////////////////////////////
//
NEW CLASS METHODS
////////////////////////////////////////////////////////////////////////////////
/**
* This method is called by the ModificationListener, if it detects a
* a modification of the underlying Stock. In this case the Listener is
* detached from the Stock and this Iterator is set to be invalid.
* Subsequent calls to {@link #next()} or {@link #remove()} will result
* in a {@link ConcurrentModificationException}.
*
* @param ti the StockItem that has caused the structural modification
*
of the Stock
* @throws RemoteException whenever an error occurs during the use of RMI
*/
nal public void setInvalid(TransactionItem ti) throws RemoteException {
StockItem si = (StockItem)ti;
if (s.isCountingStock()) {
if (si.getName().equals(siCurrent.getName())) return;
} else {
if (si.equals(siCurrent)) return;
}
bValid = false;
throw new DetachListenerException();
}
////////////////////////////////////////////////////////////////////////////////
//
NEW INSTANCE METHODS
////////////////////////////////////////////////////////////////////////////////
/**
* Returns a database-connection from the DBPersistenceManager, that
* is protected by a default-password used only by this class.
*
* @return the requested Connection to the database
*/
private Connection getDefaultConnection() {
return Shop.getTheShop().getDBPersistenceManager().getPrivateConnection("Default Connection for class "+getClass().getName(),getClass
());
}
186
ANHANG C. QUELLTEXTE
/**
* Returns a connection that was previously reserved using a password.
*
* @param sConPasswd the password for which the connection shall be returned
* @return the requested connection to the database
*/
private Connection getReservedConnection(String sConPasswd) {
return Shop.getTheShop().getDBPersistenceManager().getPrivateConnection(sConPasswd,getClass());
}
710
715
/**
* Returns a previously requested Connection to the DBPersistenceManager
* for being used by others.
*
* @param con the Connection that has been requested before
*/
private void returnConnection(Connection con) {
Shop.getTheShop().getDBPersistenceManager().returnPrivateConnection(con);
}
720
725
}
Listing C.39: epoint.data.rmi.CatalogImpl
5
10
15
20
25
30
35
40
45
50
55
60
/*
* CatalogImpl.java
*
* Created on 31. Juli 2001, 19:41
*/
package epoint.data.rmi;
import epoint.data.*;
import java.util.*;
import java.rmi.*;
import epoint.data.exception.*;
import epoint.sale.Shop;
import epoint.sale.EPoint;
import epoint.db.DBPersistenceManager;
import java.sql.Connection;
import java.sql.SQLException;
import epoint.db.exception.DBAccessException;
/**
* This is the default implementation of the {@link Catalog Catalog interface}.
* For more information about Catalogs read {@link Catalog here}.<br>
* <br>
* A Catalog is always made persistent at the Shop, when it is created. As such
* you cannot create instances of this class in a client-(/EPoint-) environment.
* Instead the {@link epoint.sale.ShopServices} must be used to get an instance
* of this class. The ShopServices are available using the {@link EPoint#getShopServices()}
* method.
*
* @see Catalog
* @see CatalogItem
* @author Danny Poppe
* @version 1.0
*/
public class CatalogImpl extends java.rmi.server.UnicastRemoteObject implements Catalog {
/*******************************************************************************
*
settings
*******************************************************************************/
/**
* contains the default-hashmap-size for the hashmap storing the catalogitems
* relative to their keys
*/
private nal static int iDefaultHashMapSize = 20;
/*******************************************************************************
*
properties
*******************************************************************************/
/**
* This set holds references to those CatalogItems that refernce this Catalog
* as a Value -- this must be checked, before a Catalog can be deleted
*/
transient private Set sciUsedAsValue = new TreeSet();
/**
* This contains the object-id of this catalog in the DBPersistenceManager.
* As the objects identity remains the same, this objects ID in the manager
* does also, as long this object is persistent there.
*/
transient private String sOID = null;
/**
* This is the name of the catalog, which also acts as a reference for
* CatalogItems!
C.1. PACKAGE EPOINT.DATA
*/
String sName = null;
/**
* Stores the information, if this Catalog is currently editable or not.
*/
private Boolean bEditable = new Boolean(true);
transient private
65
70
75
80
85
90
95
100
105
110
115
120
125
130
/*******************************************************************************
*
items
*******************************************************************************/
/**
* This map maps all the keys to their CatalogItems, that are contained in
* this Catalog and not part of any transaction.
*/
transient private Map mItems = Collections.synchronizedMap(new HashMap(iDefaultHashMapSize));
/**
* This list explicitly contains all keys of the elements in {@link #mItems}
*/
transient private List lKeys = Collections.synchronizedList(new LinkedList());
/**
* This map maps all the keys to their CatalogItems, that are contained in
* this catalog, but are currently removed within an uncommitted transaction.
*/
transient private Map mRemovedItems = Collections.synchronizedMap(new HashMap(iDefaultHashMapSize));
/**
* This list explicitly contains all keys of the elements in {@link #mRemovedItems}
*/
transient private List lRemovedKeys = Collections.synchronizedList(new LinkedList());
/**
* This map maps all the keys to their CatalogItems, that are contained in
* this catalog, but are still added to this catalog within an uncommitted transaction.
*/
transient private Map mAddedItems = Collections.synchronizedMap(new HashMap(iDefaultHashMapSize));
/**
* This list explicitly contains all keys of the elements in {@link #mAddedItems}
*/
transient private List lAddedKeys = Collections.synchronizedList(new LinkedList());
/**
* This map maps all the keys to their CatalogItems, that are contained in
* this catalog, but are currently edited as a copy of the original item
* in another HashMap. This hashmap contains the currently edited copies and
* the original items keep in place until the edites item is committed.
*/
transient private Map mEditedItems = Collections.synchronizedMap(new HashMap(iDefaultHashMapSize));
/**
* This list explicitly contains all keys of the elements in {@link #mEditedItems}
*/
transient private List lEditedKeys = Collections.synchronizedList(new LinkedList());
/*******************************************************************************
*
listener
*******************************************************************************/
/**
* This map maps remotely available Listeners to their keys.
*/
transient private Map mListener = Collections.synchronizedMap(new HashMap());
/**
* This map maps locally available Listeners to their keys.
*/
transient private Map mPersistentListener = Collections.synchronizedMap(new HashMap());
/**
* This list contains the property-IDs of locally available persistified listeners.
* The property contains the serialized version of the listener!
*/
private List lPersistentListenerIDs = Collections.synchronizedList(new LinkedList());
/*******************************************************************************
*
monitors
*******************************************************************************/
140
/**
* This monitor protects item-manipulations on behalf of the hashmaps and
* key-lists
*/
transient private Object m_oItemsLock = new Object();
/**
* This monitor protects property-manipulation of this object
*/
transient private Object m_oPropertyLock = new Object();
145
/*******************************************************************************
*
constructors
*******************************************************************************/
135
/**
* Creates a new persistent Catalog with a unique default-name.
*
187
188
150
ANHANG C. QUELLTEXTE
* @throws RemoteException whenever an error occurs during the use of RMI
*/
RemoteException {
public CatalogImpl() throws
this(null);
155
160
165
170
175
180
185
190
195
200
205
210
215
220
225
230
}
/**
* Creates a new Catalog that is automatically made persistent with the
* given name.
*
* @param sName the name of the Catlog which must be unique
* @throws RemoteException whenever an error occurs during remote-access
* @throws DuplicateKeyException if a Catalog of the same name already exists
*/
public CatalogImpl(String sName) throws RemoteException, DuplicateKeyException {
try {
//if no name is given, we set a default name
if (sName == null) {
long lUniqueLong = Shop.getTheShop().getGlobalUniqueNumber();
sName = "DefaultCatalogName #"+lUniqueLong;
}
synchronized(getPropertyLock()) { synchronized (getItemsLock()) {
this.sName = sName;
try {
Shop.getTheShop()
.getDBPersistenceManager()
.createCatalog(this,
getDefaultConnection());
} catch (Exception e) {
returnConnection(getDefaultConnection());
e.printStackTrace();
}
returnConnection(getDefaultConnection());
}}
} catch (RuntimeException e) {
Shop.getTheShop().logException(e);
throw e;
}
}
// documentation is inherited
public void loadPersistentData(Map mProperties, String sConPasswd) throws RemoteException{
try {
synchronized (this) {
mListener = Collections.synchronizedMap(new HashMap());
this.sName = (String)mProperties.get(PERSISTENT_PROPERTY_CATALOGNAME);
Shop.getTheShop().logInfo("CatalogImpl:"+sName,"Loading persistent data from Catalog "+sName,epoint.log.LogEntry.LOG_LEVEL_DEBUG
);
sciUsedAsValue = new TreeSet();
mItems = Collections.synchronizedMap(new HashMap(iDefaultHashMapSize));
lKeys = Collections.synchronizedList(new LinkedList());
mRemovedItems = Collections.synchronizedMap(new HashMap(iDefaultHashMapSize));
lRemovedKeys = Collections.synchronizedList(new LinkedList());
mAddedItems = Collections.synchronizedMap(new HashMap(iDefaultHashMapSize));
lAddedKeys = Collections.synchronizedList(new LinkedList());
mEditedItems = Collections.synchronizedMap(new HashMap(iDefaultHashMapSize));
lEditedKeys = Collections.synchronizedList(new LinkedList());
try {
// first load all normal CatalogItems
List lPersistent = Shop.getTheShop()
.getDBPersistenceManager()
.getCatalogItems(this, false, false,getReservedConnection(sConPasswd));
Iterator it = lPersistent.iterator();
while (it.hasNext()) {
CatalogItem ci = (CatalogItem)it.next();
Shop.getTheShop().logInfo("CatalogImpl:"+sName,"Adding normal item "+ci.getKey()+"::"+ci,epoint.log.LogEntry.
LOG_LEVEL_DEBUG);
mItems.put(ci.getKey(),ci);
lKeys.add(ci.getKey());
}
// now we are getting all temporarily added items
lPersistent = Shop.getTheShop()
.getDBPersistenceManager()
.getCatalogItems(this, true, false,getReservedConnection(sConPasswd));
it = lPersistent.iterator();
while (it.hasNext()) {
CatalogItem ci = (CatalogItem)it.next();
Shop.getTheShop().logInfo("CatalogImpl:"+sName,"Adding added item "+ci.getKey()+"::"+ci,epoint.log.LogEntry.
LOG_LEVEL_DEBUG);
mAddedItems.put(ci.getKey(),ci);
lAddedKeys.add(ci.getKey());
}
// now all the temporarily removed items
lPersistent = Shop.getTheShop()
.getDBPersistenceManager()
.getCatalogItems(this, false, true,getReservedConnection(sConPasswd));
it = lPersistent.iterator();
while (it.hasNext()) {
C.1. PACKAGE EPOINT.DATA
235
CatalogItem ci = (CatalogItem)it.next();
Shop.getTheShop().logInfo("CatalogImpl:"+sName,"Adding removed item "+ci.getKey()+"::"+ci,epoint.log.LogEntry.
LOG_LEVEL_DEBUG);
mRemovedItems.put(ci.getKey(),ci);
lRemovedKeys.add(ci.getKey());
240
245
250
255
260
}
265
}
270
275
280
285
290
295
300
305
310
315
}
}
}
// and finally all items that are modified using a databasket
lPersistent = Shop.getTheShop()
.getDBPersistenceManager()
.getCatalogItems(this, true, true,getReservedConnection(sConPasswd));
it = lPersistent.iterator();
while (it.hasNext()) {
CatalogItem ci = (CatalogItem)it.next();
Shop.getTheShop().logInfo("CatalogImpl:"+sName,"Adding edited item "+ci.getKey()+"::"+ci,epoint.log.LogEntry.
LOG_LEVEL_DEBUG);
mEditedItems.put(ci.getKey(),ci);
lEditedKeys.add(ci.getKey());
}
} catch (Exception e) {
Shop.getTheShop().logException(e);
}
mPersistentListener = java.util.Collections.synchronizedMap(new HashMap());
mListener = java.util.Collections.synchronizedMap(new HashMap());
Iterator it = lPersistentListenerIDs.iterator();
while (it.hasNext()) {
String sKey = (String)it.next();
String sSerializedListener = Shop.getTheShop().getPersistentProperty(sKey);
PersistentContainerListener pcl = (PersistentContainerListener)Shop.getTheShop().getPersistenceSerializer().fromString(
sSerializedListener);
mPersistentListener.put(sKey,pcl);
}
catch
(RuntimeException e) {
Shop.getTheShop().logException(e);
throw e;
/*******************************************************************************
*
interface methods from Catalog
*******************************************************************************/
/**
* Returns a synchronized set of all keys that are registered
* with a CatalogItem in this Catalog visible to the given DataBasket.
*
* @param db the Databasket that determines visibility of the keys
* @throws RemoteException if an error occurs during the use of RMI while
*
communicating with the Shop
* @return a synchronized set of all keys that are
*
registered with a CatalogItem in this Catalog
*/
public Set keySet(DataBasket db) throws RemoteException {
try {
Set s;
// all normal elements are contained (fully accessible)
s = Collections.synchronizedSet(new TreeSet(lKeys));
// all edited elements are contained (readable)
// s.addAll(lEditedKeys); // this is not needed, as those items are also contained in the normal items
if (db != null) {
// now lets add all keys of items that are added
// by the given databasket
// therefore we create a new set of the items
// to prevent concurrent modifications (a SnapShot)
// the usage of the monitor is unsafe here, as listeners may
// request the keyset during removals of CatalogItems
// => this way we prevent DeadLocks!
Set sAdded = new HashSet(mAddedItems.values());
Iterator it = sAdded.iterator();
while (it.hasNext()) {
CatalogItem ci = (CatalogItem)it.next();
if (ci.getBasket().equals(db)) s.add(ci.getKey());
}
}
return s;
} catch (RuntimeException e) {
Shop.getTheShop().logException(e);
throw e;
}
}
/**
* Adds a new CatalogItem to this Catalog while this item is automatically
* registered as an element of this Catalog and therefore made persistent
* as such.<br>
* The operation itself is performed, using an anonymous DataBasket, that
* automatically commits.<br>
* The item is cloned before beeing added to this catalog and thats
189
190
320
325
330
335
340
345
350
355
360
365
370
375
380
385
390
395
ANHANG C. QUELLTEXTE
* why changes made on the given object afterwards do not reflect in the
* catalog!
*
* @param ci the CatalogItem that shall be added to this catalog
* @throws DuplicateKeyException if the CatalogItem has a key that is
*
already registered with a CatalogItem in thisCatalog
* @throws DataBasketConflictException if this operation conflicts with another transaction
* @throws RemoteException if an error occurs during the use of RMI while
*
communicating with the Shop
*/
public void add(CatalogItem ci) throws RemoteException, DataBasketConflictException, DuplicateKeyException {
DataBasket db = new DataBasketImpl();
add(ci,db);
db.commit();
try {
Shop.getTheShop().getDBPersistenceManager().deleteDataBasket(db,getDefaultConnection());
returnConnection(getDefaultConnection());
} catch (Exception e) {
returnConnection(getDefaultConnection());
Shop.getTheShop().logException(e);
}
/**
try {
synchronized (getPropertyLock()) {
if (!isEditable()) throw new NotEditableException("The catalog "+getName()+" is currently not editable!");
if (ci == null) throw new NullPointerException("No CatalogItem given!");
synchronized (getItemsLock()) {
// we use clone to add it to the catalog
// but the original item is also marked to be not editable
ci.setEditable(false);
ci = ci.getShallowClone(true);
String sKey = ci.getKey();
// currently edited cannot be added
if (lEditedKeys.contains(sKey)) throw new DataBasketConflictException("The item you want to add has a key that is
already contained in this catalog and edited within a transaction!",DataBasketConflictException.
CONCURRENT_EDITING);
// we can finally add items only, if they are not already contained in this catalog
if (lKeys.contains(sKey)) throw new DuplicateKeyException("The item you want to add, has a key, that is already
registered in this catalog!");
// we also cannot have a simultaneous adding transaction!
if (lAddedKeys.contains(sKey)) throw new DataBasketConflictException("The item you want to add has the same key like
an item, that is currently added within an uncomitted transaction!",DataBasketConflictException.
CONCURRENT_ADDITION);
// and think of a removing transaction that wants to rollback!
if (lRemovedKeys.contains(sKey)) throw new DataBasketConflictException("The item you want to add has the same key
like an item, that is currently removed! But the transaction is still uncomitted!",
DataBasketConflictException.CONCURRENT_REMOVAL);
// ok lets check if the value of the given item is a catalog... we must inform the affected catalog in this case
if (ci.getValue().isCatalog()) {
Catalog c = (Catalog) ci.getValue();
c.setPersistentValueFlag(ci);
}
// now we can be sure that the given item is fully new for this catalog
// so we register it with this catalog
lKeys.add(ci.getKey());
mItems.put(ci.getKey(),ci);
// we are the new owner of the catalogitem
ci.setCatalog(this);
// finally we can make the item persistent for this catalog
try {
Shop.getTheShop()
.getDBPersistenceManager()
.createCatalogItem(ci, false, false,
null);
} catch (Exception e) {
// if something went wrong, we reverse the above changes
lKeys.remove(ci.getKey());
mItems.remove(ci.getKey());
ci.setCatalog(null);
Shop.getTheShop().logException(e);
}
fireAddedItem(new ContainerChangeEventImpl(ci,this,null));
}
}
} catch (RuntimeException e) {
StackTraceElement ste = e.getStackTrace()[0];
Shop.getTheShop().logException(e);("A RuntimeException occured in Catalog "+sName+" within "+ste.getClassName()+"."+ste.
getMethodName()+" in line"+ste.getLineNumber()+"\n"+e));
throw e;
}
*/
}
/**
* This method adds a new CatalogItem to
* transaction. The new CatalogItem will
* Catalog only for the transaction, but
* to be added to this catalog, that has
this Catalog as part of a
be visible as part of this
it will also prevent other items
the same key!<br>
C.1. PACKAGE EPOINT.DATA
400
405
410
415
420
425
430
435
440
445
450
455
460
465
470
475
480
485
191
* The added item is automatically registered with the DataBasket and
* becomes a final state as part of this catalog, when the databasket
* will be committed or rolled back!
*
* @param ci the CatalogItem that shall be added to this catalog
* @param db the transaction-handle to use for addition (if <code>null</code> {@link #add(CatalogItem)} is indirectly called)
* @throws RemoteException whenever an error occurs during remote-access
* @throws DataBasketConflictException if this transaction conflicts with another
*/
public void add(CatalogItem ci, DataBasket db) throws RemoteException, DataBasketConflictException {
try {
synchronized (getPropertyLock()) {
if (!isEditable()) throw new NotEditableException("The catalog "+getName()+" is currently not editable!");
if (ci == null) throw new NullPointerException("No CatalogItem given!");
if (db == null) {
add(ci);
} else {
synchronized (getItemsLock()) {
String sKey = ci.getKey();
// currently edited items cannot be added again
if (lEditedKeys.contains(sKey)) throw new DataBasketConflictException(
"The item you want to add has the same key like an item that is already registered in this catalog "+
"but edited within another transaction!",DataBasketConflictException.CONCURRENT_EDITING);
// if this item already belongs to this catalog, we cannot add it again!
if (lKeys.contains(sKey)) throw new DuplicateKeyException(
"The item you want to add has a key, that is already registered in this catalog!");
// currently added items cannot be added within a concurrent transaction
if (lAddedKeys.contains(sKey)) throw new DataBasketConflictException(
"The item you want to add has the same key like an item that was added, "+
"within another (currently uncomitted) transaction!",DataBasketConflictException.CONCURRENT_ADDITION);
// now we want to check, if the given key is currently removed!
// if this is the case, we can add the new item only, if
// this is done within the same transaction
if (lRemovedKeys.contains(sKey)) {
// we have the given key currently removed
if (!db.equals(((CatalogItem)mRemovedItems.get(sKey)).getBasket())) {
// but not in the given transaction => exception
throw new DataBasketConflictException(
"The item you want to add has the same key like an item"+
" that is still removed within another uncomitted transaction!",DataBasketConflictException.CONCURRENT_REMOVAL);
}
}
// now we add the new item
if (ci.getValue().isCatalog()) {
Catalog c = (Catalog) ci.getValue();
c.setPersistentValueFlag(ci);
}
DBEntry dbe;
if (!lRemovedKeys.contains(sKey)) {
String sConPasswd = "Private Catalog-Add Transaction #"+Shop.getTheShop().getGlobalUniqueNumber();
try {
getReservedConnection(sConPasswd).setAutoCommit(false);
} catch (Exception e) {
Shop.getTheShop().logException(e);
}
ci.setEditable(false,sConPasswd);
ci = ci.getShallowClone(true);
// here we are to add a fully new item within a transaction
// and we register it with the catalog first
lAddedKeys.add(sKey);
mAddedItems.put(sKey,ci);
ci.setCatalog(this,sConPasswd);
try {
Shop.getTheShop()
.getDBPersistenceManager()
.createCatalogItem(ci, true, false,getReservedConnection(sConPasswd));
} catch (DBAccessException e) {
Shop.getTheShop().logException(e);
try {
getReservedConnection(sConPasswd).rollback();
} catch (Exception e2) {
Shop.getTheShop().logException(e2);
} nally {
returnConnection(getReservedConnection(sConPasswd));
}
throw new RuntimeException("An error occured while accessing the database: "+e);
}
// now we register the new transaction in the databasket
dbe = new DBEntryImpl(db,DBEntry.TRANSACTION_ADD,this,ci,null);
db.add(dbe,sConPasswd);
ci.setBasket(db,sConPasswd);
// now we can inform the listeners
try {
getReservedConnection(sConPasswd).commit();
returnConnection(getReservedConnection(sConPasswd));
} catch (SQLException sqle) {
Shop.getTheShop().logException(sqle);
throw new RuntimeException("An error occured while accessing the database: "+sqle);
192
ANHANG C. QUELLTEXTE
}
490
495
500
505
510
515
520
525
530
535
540
545
550
555
560
565
570
nally
try {
{
returnConnection(getReservedConnection(sConPasswd));
} catch (Exception e) {
Shop.getTheShop().logException(e);
}
}
fireAddedItem(new ContainerChangeEventImpl(ci,this,db));
return;
} else {
// here we hava a transaction that first removes a key and then
// wants to add it again within the same transaction...
// (we already checked for different
// transactions above!) we change this into a
// modifying transaction of the same key, as it would have
// been requested for editing in a transaction
RemoteIterator it = null;
try {
// first we need the DBEntry we want to change...
// at the moment it describes the removal of the given key
it = db.iterator((CatalogItem)mRemovedItems.get(sKey));
} catch (Exception e) {
Shop.getTheShop().logException(e);
return;
}
String sConPasswd = "Private Catalog-Add Transaction #"+Shop.getTheShop().getGlobalUniqueNumber();
try {
getReservedConnection(sConPasswd).setAutoCommit(false);
} catch (Exception e) {
Shop.getTheShop().logException(e);
}
while (it.hasNext()) {
dbe = (DBEntry)it.next();
if (dbe.getTransactionType().equals(dbe.TRANSACTION_REMOVE) && (dbe.getTarget() instanceof Catalog)) {
// we now have the DBEntry that describes the removal
// of the given CatalogItem relative to some Catalog
Catalog c = (Catalog)dbe.getTarget();
if (!c.getName().equals(this.getName())) continue;
// Now we have the DBEntry we want to change...
// we will roll it back first, so the removed
// item becomes a normal item again
RemoteRunnable r = null;
try {
r = rollbackTransaction(dbe,sConPasswd);
// the transaction is rolled back now and the old DBEntry is now removed from the basket
db.removeTransaction(dbe,sConPasswd);
} catch (RuntimeException rte) {
Shop.getTheShop().logException(rte);
try {
getReservedConnection(sConPasswd).rollback();
} catch (SQLException sqle) {
Shop.getTheShop().logException(sqle);
} nally {
returnConnection(getReservedConnection(sConPasswd));
}
throw rte;
}
// we close this transaction, so we avoid database-deadlocks, according to
// listener-access to the database
try {
getReservedConnection(sConPasswd).commit();
returnConnection(getReservedConnection(sConPasswd));
} catch (SQLException sqle) {
Shop.getTheShop().logException(sqle);
returnConnection(getReservedConnection(sConPasswd));
throw new RuntimeException("Could not complete the database-transaction!");
}
r.run(null);
try {
fireCanEditTransactionItem(new ContainerChangeEventImpl((CatalogItem)mItems.get(sKey),this,db));
Map mProperties = new HashMap();
mProperties.put(ci.PERSISTENT_PROPERTY_CATALOG,ci.getCatalog());
mProperties.put(ci.PERSISTENT_PROPERTY_CIKEY,ci.getKey());
mProperties.put(ci.PERSISTENT_PROPERTY_TRANSACTION_IDENTIFIER,((CatalogItem)mItems.get(sKey)).
getTransactionItemIdentifier());
mProperties.put(ci.PERSISTENT_PROPERTY_CIVALUE,ci.getValue());
mProperties.put(ci.PERSISTENT_PROPERTY_TRANSACTION_BASKET,ci.getBasket());
ci.loadPersistentData(mProperties,null);
} catch (VetoException ve) {
//ok one does not want to edit the item we just rolled back from its
//removal
//we will try to have the old state again while removing
//the item again
try {
remove(sKey,db);
} catch (VetoException ve2) {
//ok, another one does not want to have the old state again
C.1. PACKAGE EPOINT.DATA
throw new
IllegalStateException("Could not complete the request to add an item within the
transaction, it was removed with! "+
"This results in a modifying transaction that could not be completed, because some listener "+
"prevents the item from being edited and another one or the same listener prevents us from "+
"recreating the original state of this transaction, where the item was removed! "+
"The result is that the item is no more part of the transaction!\nVeto was: "+ve2.getMessage());
} nally {
returnConnection(getReservedConnection(sConPasswd));
}
throw new IllegalStateException("Could not add the item because it was previously removed by the given
transaction and a listener "+
"prevents us to change the transaction from a removing transaction to a modifying transaction: "+ve.
getMessage());
575
580
}
sConPasswd = "Private Catalog-Add Transaction #"+Shop.getTheShop().getGlobalUniqueNumber();
try {
getReservedConnection(sConPasswd).setAutoCommit(false);
} catch (Exception e) {
Shop.getTheShop().logException(e);
}
// now we create the modifying transaction with the
// new given item (the original item is rolled back and
// normal part of the catalog again)
ci.setEditable(true,sConPasswd);
lEditedKeys.add(sKey);
mEditedItems.put(sKey,ci);
ci.setCatalog(this,sConPasswd);
try {
Shop.getTheShop()
.getDBPersistenceManager()
.createCatalogItem(ci,true,true,getReservedConnection(sConPasswd));
} catch (Exception e) {
Shop.getTheShop().logException(e);
}
DBEntry dbeNew = new DBEntryImpl(db,dbe.TRANSACTION_MODIFY,this,ci,null);
db.add(dbeNew,sConPasswd);
ci.setBasket(db,sConPasswd);
ci.setEditable(true,sConPasswd);
try {
getReservedConnection(sConPasswd).commit();
} catch (SQLException sqle) {
Shop.getTheShop().logException(sqle);
throw new RuntimeException("An error occured while accessing the database: "+sqle);
} nally {
try {
returnConnection(getReservedConnection(sConPasswd));
} catch (Exception e) {
Shop.getTheShop().logException(e);
}
}
fireEditingTransactionItem(new ContainerChangeEventImpl(ci,this,db));
return;
585
590
595
600
605
610
615
620
}
}// while
// if we are here, something must be wrong:
// we first checked, if the basket that removed the item
// was the same as the given to add the item, but we found no
// DBEntry in the given basket that describes the removal
throw new IllegalStateException("It seems, as we have an internal framework-error!\n"+
"We have a DataBasket that removed an item from this Catalog, but does not contain\n"+
"the appropriate entry describing the removal!");
625
630
635
640
645
650
655
193
}
}
}}// end of synchronization
}// the CatalogItem is added
} catch (RuntimeException e) {
Shop.getTheShop().logException(e);
throw e;
}
/**
* Removes the given CatalogItem from this Catalog. This is performed,
* using an anonymous DataBasket, that automatically commits.
*
* @param sKey of the CatalogItem that shall be removed from this Catalog
* @return the removed CatalogItem or <code>null</code> if no CatalogItem with the given key existed
* @throws VetoException if any Listener vetoes the removal of the given key (CatalogItem)
* @throws DataBasketConflictException if this transaction conflicts with another
* @throws RemoteException if an error occurs during the use of RMI while communicating with the Shop
*/
public CatalogItem remove(String sKey) throws RemoteException, VetoException, DataBasketConflictException {
DataBasket db = new DataBasketImpl();
CatalogItem ci = remove(sKey,db);
db.commit();
try {
Shop.getTheShop().getDBPersistenceManager().deleteDataBasket(db,getDefaultConnection());
returnConnection(getDefaultConnection());
194
ANHANG C. QUELLTEXTE
}
660
}
catch
(Exception e) {
Shop.getTheShop().logException(e);
return
665
670
675
680
685
690
695
700
705
710
}
715
720
725
730
735
740
ci;
/**
try {
synchronized(getPropertyLock()) {
if (!isEditable()) throw new NotEditableException("The catalog "+getName()+" is currently not editable!");
synchronized (getItemsLock()) {
// only edited items can also be seen, but are not allowed to be removed by another transaction
if (lEditedKeys.contains(sKey)) throw new DataBasketConflictException("The item you want to remove is currently edited
within an uncomitted transaction!",DataBasketConflictException.CONCURRENT_EDITING);
if (!lKeys.contains(sKey)) {
return null;
} else {
// a normal element is to be removed, lets check the listeners!
CatalogItem ci = (CatalogItem)mItems.get(sKey);
fireCanRemoveTransactionItem(new ContainerChangeEventImpl(ci,this,null));
// ok, we can remove the item now
ci = (CatalogItem)mItems.remove(sKey);
lKeys.remove(sKey);
// and we must delete its persistent state
try {
Shop.getTheShop()
.getDBPersistenceManager()
.deleteCatalogItem(ci,
null);
} catch (Exception e) {
e.printStackTrace();
mItems.put(sKey,ci);
lKeys.add(sKey);
fireNoRemoveTransactionItem(new ContainerChangeEventImpl(ci,this,null));
return null;
}
// if the value of the removed item is a catalog, we must inform the
// catalog, that it is not part of this persistent catalogitem anymore
if (ci.getValue().isCatalog()) {
Catalog c = (Catalog) ci.getValue();
c.setPersistentValueFlag(ci);
}
// the catalogitem has no Catalog anymore, but is editable
ci.setCatalog(null);
ci.setEditable(true);
// and finally we must inform the listeners about the removal
fireRemovedTransactionItem(new ContainerChangeEventImpl(ci,this,null));
return ci;
}
}} //synchronization
} catch (RuntimeException e) {
StackTraceElement ste = e.getStackTrace()[0];
Shop.getTheShop().logException(e);("A RuntimeException occured in Catalog "+sName+" within "+ste.getClassName()+"."+ste.
getMethodName()+" in line"+ste.getLineNumber()+"\n"+e));
throw e;
}
*/
/**
* Removes the item with the given key from this Catalog, using the given
* DataBasket, in which this transaction is registered.
*
* @param sKey the name of the item that shall be removed from the Catalog
* @param db the DataBasket that identifies the transaction within the removal takes place
* @return the removed CatalogItem if it existed
* @throws VetoException if some listener prevents the removal
* @throws DataBasketConflictException if a concurrent transaction prevents the removal
* @throws RemoteException whenever an error occurs during remote-access
*/
public CatalogItem remove(String sKey, DataBasket db) throws RemoteException, VetoException, DataBasketConflictException {
try {
synchronized (getPropertyLock()) {
if (!isEditable()) throw new NotEditableException("The catalog "+getName()+" is currently not editable!");
if (db == null) {
return remove(sKey);
}
// we can remove items that are normal elements,
// edited elements within the same transaction
// or added elements within the same transaction
synchronized (getItemsLock()) {
if (lEditedKeys.contains(sKey)) {
if (!((CatalogItem)mEditedItems.get(sKey)).getBasket().getName().equals(db.getName()))
throw new DataBasketConflictException("The item you want to remove is currently edited "+
"by a concurrent transaction!",DataBasketConflictException.CONCURRENT_EDITING);
// we have the case that the requested key is currently
// part of an editing transaction
// the item is edited by the same transaction, so we
// rollback the editing transaction and create a removing
// transaction instead
C.1. PACKAGE EPOINT.DATA
745
750
755
760
765
770
775
780
785
790
795
800
805
810
815
820
825
830
CatalogItem ciEdited = (CatalogItem)mEditedItems.get(sKey);
CatalogItem ciNormal = (CatalogItem)mItems.get(sKey);
fireCanRemoveTransactionItem(new ContainerChangeEventImpl(ciEdited,this,db));
fireCanRemoveTransactionItem(new ContainerChangeEventImpl(ciNormal,this,db));
// first we need the DBEntry we want to change...
// at the moment it describes the modification of the given key
RemoteIterator it = it = db.iterator((CatalogItem)mRemovedItems.get(sKey));
String sConPasswd = "Private Catalog-Remove Transaction #"+Shop.getTheShop().getGlobalUniqueNumber();
try {
getReservedConnection(sConPasswd).setAutoCommit(false);
} catch (Exception e) {
Shop.getTheShop().logException(e);
}
while (it.hasNext()) {
DBEntry dbe = (DBEntry)it.next();
if (dbe.getTransactionType().equals(dbe.TRANSACTION_MODIFY) && (dbe.getTarget() instanceof Catalog)) {
// we now have a DBEntry that describes the modifying
// of the CatalogItem relative to some Catalog
Catalog c = (Catalog)dbe.getTarget();
if (!c.getName().equals(this.getName())) continue;
// Now we have the DBEntry we want to change...
// we will rollback this transaction first
// as if the entire transaction is rolled back, we
// want to have the original item again
RemoteRunnable r = null;
try {
r = dbe.getTarget().rollbackTransaction(dbe,sConPasswd);
// the previous modifying transaction is now deleted
db.removeTransaction(dbe, sConPasswd);
} catch (RuntimeException rte) {
try {
returnConnection(getReservedConnection(sConPasswd));
} catch (Exception e) {
Shop.getTheShop().logException(e);
}
throw rte;
}
try {
getReservedConnection(sConPasswd).commit();
} catch (SQLException sqle) {
Shop.getTheShop().logException(sqle);
throw new RuntimeException("An error occured while accessing the database: "+sqle);
} nally {
try {
returnConnection(getReservedConnection(sConPasswd));
} catch (Exception e) {
Shop.getTheShop().logException(e);
}
}
r.run(null);
// Now we make a recursive call to this method to remove the item
// which is now a "normal" item again (after rollback)
return remove(sKey,db);
}// we converted an editing transaction into a removing transaction
}// iteration through all DBEntries while looking for the DBEntry to change
// something is wrong here
throw new IllegalStateException("It seems, as we have an internal framework-error!\n"+
"We have a DataBasket that modified an item in this Catalog, but does not contain\n"+
"the appropriate entry describing this procedure!");
} else if (lKeys.contains(sKey)) {
// a normal CatalogItem has to be removed within a transaction
// (as there are no edited keys)
fireCanRemoveTransactionItem(new ContainerChangeEventImpl((CatalogItem)mItems.get(sKey),this,db));
lKeys.remove(sKey);
lRemovedKeys.add(sKey);
CatalogItem ci = (CatalogItem)mItems.remove(sKey);
mRemovedItems.put(sKey,ci);
DBEntry dbe = new DBEntryImpl(db,DBEntry.TRANSACTION_REMOVE,this,ci,null);
String sConPasswd = "Private Catalog-Remove Transaction #"+Shop.getTheShop().getGlobalUniqueNumber();
try {
getReservedConnection(sConPasswd).setAutoCommit(false);
} catch (Exception e) {
Shop.getTheShop().logException(e);
}
db.add(dbe,sConPasswd);
ci.setBasket(db,sConPasswd);
ci = ci.getShallowClone(true);
ci.setCatalog(null,sConPasswd);
try {
getReservedConnection(sConPasswd).commit();
} catch (SQLException sqle) {
Shop.getTheShop().logException(sqle);
throw new RuntimeException("An error occured while accessing the database: "+sqle);
} nally {
try {
returnConnection(getReservedConnection(sConPasswd));
} catch (Exception e) {
Shop.getTheShop().logException(e);
195
196
835
840
845
850
855
860
865
870
875
880
885
890
895
900
}
905
910
915
920
ANHANG C. QUELLTEXTE
}
}
fireRemovedTransactionItem(new ContainerChangeEventImpl(ci,this,db));
return ci;
} else if (lAddedKeys.contains(sKey)) {
// we have the case, that the requested key is currently added
// within a transaction
if (!(db.equals(((CatalogItem)mAddedItems.get(sKey)).getBasket())))
throw new DataBasketConflictException("The item you want to remove is currently added "+
"by a concurrent transaction!",DataBasketConflictException.CONCURRENT_ADDITION);
fireCanRemoveTransactionItem(new ContainerChangeEventImpl((CatalogItem)mAddedItems.get(sKey),this,db));
// this is the given transaction, we rollback the adding
// transaction
RemoteIterator it = null;
try {
it = db.iterator((CatalogItem)mAddedItems.get(sKey));
} catch (Exception e) {
Shop.getTheShop().logException(e);
return null;
}
String sConPasswd = "Private Catalog-Remove Transaction #"+Shop.getTheShop().getGlobalUniqueNumber();
try {
getReservedConnection(sConPasswd).setAutoCommit(false);
} catch (Exception e) {
Shop.getTheShop().logException(e);
}
while (it.hasNext()) {
DBEntry dbe = (DBEntry)it.next();
DBEntryTarget dbet = dbe.getTarget();
if (dbe.getTransactionType().equals(dbe.TRANSACTION_ADD) && (dbet instanceof Catalog)) {
Catalog c = (Catalog)dbet;
if (c.getName().equals(this.getName())) {
CatalogItem ci = (CatalogItem)mAddedItems.get(sKey);
RemoteRunnable r = rollbackTransaction(dbe,sConPasswd);
db.removeTransaction(dbe,sConPasswd);
ci.setEditable(true,sConPasswd);
try {
getReservedConnection(sConPasswd).commit();
} catch (SQLException sqle) {
Shop.getTheShop().logException(sqle);
throw new RuntimeException("An error occured while accessing the database: "+sqle);
} nally {
try {
returnConnection(getReservedConnection(sConPasswd));
} catch (Exception e) {
Shop.getTheShop().logException(e);
}
}
r.run(null);
fireRemovedTransactionItem(new ContainerChangeEventImpl(ci,this,db));
return ci;
}
}
}
// if we get here, something must be wrong!
throw new IllegalStateException("It seems, as we have an internal framework-error!\n"+
"We have a DataBasket that added an item to this Catalog, but does not contain\n"+
"the appropriate entry describing this procedure!");
} else if (lRemovedKeys.contains(sKey)) {
// the key is already removed by a transaction
throw new DataBasketConflictException("The item you want to remove is already removed from this "+
"Catalog by an uncomitted transaction!",DataBasketConflictException.CONCURRENT_REMOVAL);
}
return null;
}}// end of synchronization
} catch (RuntimeException e) {
Shop.getTheShop().logException(e);
throw e;
}
/**
* Returns the number of CatalogItems in this Catalog, that are visible to the
* given DataBasket.
*
* @param db the DataBasket that determines visibility of CatalogItems in this
*
Catalog (may be <code>null</code>)
* @throws RemoteException if an error occurs during the use of RMI while
*
communicating with the Shop
* @return the number of CatalogItems stored in this Catalog
*/
public int size(DataBasket db) throws RemoteException {
return keySet(db).size();
}
/**
* Returns a CatalogItem from this Catalog by its Key. The item may be retrieved
C.1. PACKAGE EPOINT.DATA
925
930
935
940
945
950
955
960
965
970
975
980
985
990
995
1000
197
* for editing or not, but if it is retrieved for editing a DataBasket must be given
* that allows to rollback or commit the editing.<br>
* If the item is currently edited by another transaction, but not requested for editing
* the original item is returned instead of the currently edited item. If an item
* is currently edited by the same DataBasket, always the edited item
* is returned.
*
* @param sKey the key of the CatalogItem to be returned by this method
* @param db the DataBasket that determines visibility of the requested
*
CatalogItem and relative to which the editing is performed
* @param bForEdit if <code>true</code> the item will be returned for
*
being edited by the given DataBasket
* @return the CatalogItem that is registered with the given key
* @throws RemoteException if an error occurs during the use of RMI while
*
communicating with the Shop
* @throws VetoException only if the item is requested for editing and a listener
*
vetoes this editing
* @throws DataBasketConflictException if a concurrent transaction prevents
*
the requested retrieval of the item
*/
public CatalogItem get(String sKey, DataBasket db, boolean bForEdit) throws RemoteException, VetoException,
DataBasketConflictException {
try {
synchronized (getPropertyLock()) {
if (!isEditable() && bForEdit) throw new NotEditableException("The catalog "+getName()+" is currently not editable!");
synchronized (getItemsLock()) {
CatalogItem ciRetVal = null;
if (lRemovedKeys.contains(sKey)) {
// the requested item is currently removed and therefore cannot be returned
throw new DataBasketConflictException("The item you requested cannot be returned, as it is currently removed within an
uncomitted transaction!",DataBasketConflictException.CONCURRENT_REMOVAL);
} else if (lEditedKeys.contains(sKey)) {
ciRetVal = (CatalogItem)mEditedItems.get(sKey);
if (bForEdit) {
//ok someone wants to edit an item that is already edited
if (db == null) {
throw new DataBasketConflictException("The item you want to edit is currently edited within a concurrent
transaction!",DataBasketConflictException.CONCURRENT_EDITING);
}
if (!db.getName().equals(ciRetVal.getBasket().getName())) {
throw new DataBasketConflictException("The item you want to edit is currently edited within a concurrent
transaction!",DataBasketConflictException.CONCURRENT_EDITING);
}
fireCanEditTransactionItem(new ContainerChangeEventImpl(ciRetVal,this,db));
// ok, lets return the currently edited item again, as it is requested by
// the same editing basket again
fireEditingTransactionItem(new ContainerChangeEventImpl(ciRetVal,this,db));
return ciRetVal;
} else {
// the item to return is currently edited, but not requested for editing
// if no db given, we return a not editable clone of the original item
// otherwise the bEditable modified item
if ((db == null) || (!db.getName().equals(ciRetVal.getBasket().getName()))) {
// we return a not-editable clone of the original item
return ((CatalogItem)mItems.get(sKey)).getShallowClone(false);
} else
// we return a a not editable clone of the currently edited item
return ciRetVal.getShallowClone(false);
}
} else if (lKeys.contains(sKey)) {
ciRetVal = (CatalogItem)mItems.get(sKey);
// the requested item is a normal CatalogItem and can be returned
if (db != null) {
if (bForEdit) {
// we have a databasket and a request for editing an item
// so we create a modifying transaction with a new item
// as a clone of the old item
fireCanEditTransactionItem(new ContainerChangeEventImpl(ciRetVal,this,db));
String sConPasswd = "Private Catalog-Remove Transaction #"+Shop.getTheShop().getGlobalUniqueNumber();
try {
getReservedConnection(sConPasswd).setAutoCommit(false);
} catch (Exception e) {
Shop.getTheShop().logException(e);
}
ciRetVal = ciRetVal.getShallowClone(true);
lEditedKeys.add(sKey);
mEditedItems.put(sKey,ciRetVal);
try {
Shop.getTheShop().getDBPersistenceManager().createCatalogItem(ciRetVal,true,true,getReservedConnection(
sConPasswd));
} catch (Exception e) {
Shop.getTheShop().logException(e);
}
DBEntry dbeNew = new DBEntryImpl(db,DBEntry.TRANSACTION_MODIFY,this,ciRetVal,null);
db.add(dbeNew,sConPasswd);
ciRetVal.setBasket(db,sConPasswd);
try {
getReservedConnection(sConPasswd).commit();
198
ANHANG C. QUELLTEXTE
}
1010
1015
1020
1025
1030
1035
1040
1045
}
1050
1055
1060
1065
1070
1075
1080
1085
catch
(Exception e) {
Shop.getTheShop().logException(e);
throw new RuntimeException("An error occured while accessing the database: "+e);
} nally {
try {
returnConnection(getReservedConnection(sConPasswd));
} catch (Exception e) {
Shop.getTheShop().logException(e);
}
}
fireEditingTransactionItem(new ContainerChangeEventImpl(ciRetVal,this,db));
return ciRetVal;
} else { // requested with DataBasket, but not for editing
return ciRetVal.getShallowClone(false);
}
} else { // no DataBasket
if (bForEdit) {
throw new NullPointerException("You must provide a DataBasket for editing a CatalogItem!");
// fireCanEditTransactionItem(new ContainerChangeEventImpl(ciRetVal,this,null));
// return ciRetVal;
} else
return ciRetVal.getShallowClone(false);
}
} else if (lAddedKeys.contains(sKey)) {
// the requested item is already added within a transaction - before it
// can be returned, we must ensure, that it is the same as the given transaction
ciRetVal = (CatalogItem)mAddedItems.get(sKey);
if (db == null) {
throw new DataBasketConflictException("The item you requested is currently added within a transaction!",
DataBasketConflictException.CONCURRENT_ADDITION);
} else {
if (!db.getName().equals(ciRetVal.getBasket().getName())) throw new
DataBasketConflictException("The item you requested is currently added by another concurrent transaction!",
DataBasketConflictException.CONCURRENT_ADDITION);
if (bForEdit) {
fireCanEditTransactionItem(new ContainerChangeEventImpl(ciRetVal,this,db));
fireEditingTransactionItem(new ContainerChangeEventImpl(ciRetVal,this,db));
return ciRetVal;
} else
return ciRetVal.getShallowClone(false);
}
}
return null;
1005
}
}
}
}// end of synchronization
(RuntimeException e) {
Shop.getTheShop().logException(e);
throw e;
catch
/**
* Works like {@link #get(String,DataBasket,boolean)}, but assumes the item
* shall be retrieved for reading only, without a DataBasket.
*
* @param sKey {@inheritDoc}
* @return {@inheritDoc}
* @throws RemoteException whenever an error occurs during remote-access
*/
public CatalogItem get(String sKey) throws RemoteException {
try {
return get(sKey,null,false);
} catch (VetoException ve) {
// this exception should be logged, as this implies an error in
// the framework
Shop.getTheShop().logException(ve);
return null;
} catch (DataBasketConflictException dbce) {
// we wanted the item for reading only, there should be no conflict
// and if so, the item can simply not be retrieved :o)
return null;
}
}
/**
* Works like {@link #get(String,DataBasket,boolean)}, but assumes the item
* shall be retrieved for reading only.
*
* @param sKey {@inheritDoc}
* @param db {@inheritDoc}
* @return {@inheritDoc}
* @throws RemoteException whenever an error occurs during remote-access
*/
public CatalogItem get(String sKey, DataBasket db) throws RemoteException {
try {
return get(sKey,db,false);
} catch (VetoException ve) {
Shop.getTheShop().logException(ve);
C.1. PACKAGE EPOINT.DATA
1090
1095
1100
1105
1110
1115
1120
1125
1130
1135
1140
1145
1150
1155
1160
1165
1170
1175
}
}
}
return null;
catch (DataBasketConflictException
dbce) {
// we wanted the item for reading only, there should be no conflict
// and if so, the item can simply not be retrieved :o)
return null;
/**
* Returns an iterator over all CatalogItems in this Catalog that are visible
* to the given DataBasket. See {@link CatalogItemIterator} for more details
* about the returned RemoteIterator!
*
* @param db the DataBasket that determines visibility of the CatalogItems
*
and relative to which the CatalogItems are edited
* @param bForEdit if set to <code>true</code> the CatalogItems returned
*
by the iterator may be edited, otherwise not
* @throws RemoteException if an error occurs during the use of RMI while
*
communicating with the Shop
* @return an iterator over all items in this catalog
*/
public RemoteIterator iterator(DataBasket db, boolean bForEdit) throws RemoteException {
return new CatalogItemIterator(this,db,bForEdit);
}
/**
* This method sets a flag for the given CatalogItem using this Catalog
* as a {@link epoint.data.Value Value}. This is important, because as
* long as some CatalogItem in another Catalog references this Catalog
* as a Value, this Catalog is not allowed to be deleted!<br>
* First the given CatalogItem is checked for referencing this Catalog
* and beeing persistent. If so it is added to a set, otherwise it is
* removed from the set. If the set has a size of zero, this Catalog is
* allowed to be deleted.
*
* @param ci a CatalogItem that may or may not reference this Catalog as a Value
* @throws RemoteException if problems occur during remote communications
*/
public void setPersistentValueFlag(CatalogItem ci) throws RemoteException {
synchronized (sciUsedAsValue) {
if (ci.isPersistent() && ci.getValue().isCatalog()) {
sciUsedAsValue.add(ci);
} else {
sciUsedAsValue.remove(ci);
}
}
}
/**
* Returns <code>true</code> if this catalog is used as a Value in some
* persistent CatalogItem.
*
* @return <code>true</code> if the list as described in {@link #setPersistentValueFlag(CatalogItem)} has zero-length
* @throws RemoteException whenever an error occurs during remote-access
*/
public boolean isPersistentValue() throws RemoteException {
synchronized (sciUsedAsValue) {
Iterator it = sciUsedAsValue.iterator();
while (it.hasNext()) {
CatalogItem ci = (CatalogItem)it.next();
if (ci.isPersistent() && ci.getValue().isCatalog()) {
return true;
} else it.remove();
}
}
return false;
}
/*******************************************************************************
*
interface methods from Nameable
*******************************************************************************/
/**
* Returns the name of this Catalog.
*
* @return the name of this Catalog
* @throws RemoteException is thrown whenever an error occurs during the use of RMI
*
while communicating with the Shop
*/
public String getName() throws RemoteException {
return sName;
}
/**
* Sets a new name for this Catalog. Registered {@link epoint.data.ContainerListener ContainerListeners}
* recieve a {@link java.beans.PropertyChangeEvent} from this Catalog, where the change of
* {@link epoint.data.Catalog#PERSISTENT_PROPERTY_CATALOGNAME} is described.
199
200
1180
1185
1190
1195
1200
1205
1210
1215
1220
1225
1230
1235
*
* @param sName the new name for this Catalog
* @throws RemoteException is thrown whenever an error occurs during the use of RMI
*
while communicating with the Shop
*/
public void setName(String sName) throws RemoteException {
synchronized (getPropertyLock()) {
if (!isEditable()) throw new NotEditableException("The catalog "+getName()+" is currently not editable!");
try {
Catalog c = Shop.getTheShop()
.getDBPersistenceManager()
.getCatalog(getName(), getDefaultConnection());
returnConnection(getDefaultConnection());
if (c != null) throw new DuplicateKeyException("A catalog with the given name already exists!");
} catch (Exception e) {
returnConnection(getDefaultConnection());
Shop.getTheShop().logException(e);
}
String sOld = getName();
this.sName = sName;
String sUpdatePasswd = getClass()+".updatePersistentObject";
updatePersistentObject(Arrays.asList(new Object[]{PERSISTENT_PROPERTY_CATALOGNAME}),sUpdatePasswd);
returnConnection(getReservedConnection(sUpdatePasswd));
nal CatalogImpl thisCatalog = this;
nal String sOldName = sOld;
nal String sNewName = sName;
Thread t = new Thread("firePropertyChange in Catalog") {
public void run() {
try {
thisCatalog.firePropertyChange(new java.beans.PropertyChangeEvent(thisCatalog,Catalog.PERSISTENT_PROPERTY_CATALOGNAME,
sOldName,sNewName));
} catch (RemoteException rmie) {
Shop.getTheShop().logException(rmie);
}
}
};
t.run();
}
}
/*******************************************************************************
*
abstract methods from AbstractValue
*******************************************************************************/
/**
* This compares another Value to this Catalog. If the given value is
* a Catalog {@link #compareTo(Catalog)} is called instead. Otherwise
* This Catalog is considered to be "greater" than a normal Value.
*
* @param v another Value this Catalog is compared to
* @return an int as defined by {@link java.lang.Comparable}
* @throws RemoteException whenever an error occurs during remote-access
*/
public int compareTo(Value v) throws RemoteException {
if (v instanceof Catalog) return compareTo((Catalog)v);
return Integer.MAX_VALUE;
}
1245
/**
* For this class this method always returns <code>true</code>. This makes
* sense, as a Catalog may be used as a Value and one does not know if it
* is Catalog or not.
*
* @return always <code>true</code> in this class
* @throws RemoteException whenever an error occurs during remote-access
*/
nal public boolean isCatalog() throws RemoteException {
return true;
}
1250
////////////////////////////////////////////////////////////////////////////////
//
DBEntryTarget
////////////////////////////////////////////////////////////////////////////////
1240
1255
1260
ANHANG C. QUELLTEXTE
// documentation is inherited
public RemoteRunnable rollbackTransaction(DBEntry dbe, String sConPasswd) throws RemoteException {
try {
Connection con = null;
try {
con = getReservedConnection(sConPasswd);
} catch (Exception e) {
Shop.getTheShop().logException(e);
}
synchronized (getItemsLock()) {
synchronized (getPropertyLock()) {
if (!isValidTransaction(dbe)) throw new RuntimeException("The given DBEntry does not conatin a valid description of a
registered transaction related to this catalog!");
C.1. PACKAGE EPOINT.DATA
1265
1270
1275
1280
1285
1290
1295
1300
1305
1310
1315
1320
1325
1330
1335
1340
1345
1350
201
String sType = dbe.getTransactionType();
if (dbe.TRANSACTION_ADD.equals(sType)) {
// Now lets revert a previous temporary adding of an item
CatalogItem ci = (CatalogItem)dbe.getTransactionItem();
String sKey = ci.getKey();
// ok, we remove the date from this catalog
ci = (CatalogItem)mAddedItems.remove(sKey);
lAddedKeys.remove(sKey);
// just for security-reasons, we check neccessary data
if (!ci.getCatalog().getName().equals(this.getName())) ci.setCatalog(this,sConPasswd);
if (!ci.getBasket().getName().equals(dbe.getBasket())) ci.setBasket(dbe.getBasket(),sConPasswd);
// and we delete the data in the database
try {
Shop.getTheShop().getDBPersistenceManager().deleteCatalogItem(ci,con);
} catch (Exception e) {
// puh, an error occured, we need to redo all the action, to preserve
// a persistent state
mAddedItems.put(sKey,ci);
lAddedKeys.add(sKey);
Shop.getTheShop().logException(e);
throw new RuntimeException(e.getMessage());
}
ci.setCatalog(null,sConPasswd);
ci.setBasket(null,sConPasswd);
ci.setEditable(true,sConPasswd);
nal CatalogImpl thisCatalog = this;
nal CatalogItem ciThread = ci;
nal DataBasket dbThread = dbe.getBasket();
RemoteRunnable r = new RemoteRunnableImpl() {
public void run(Object[] o) throws RemoteException {
try {
thisCatalog.fireRolledbackAddTransactionItem(new ContainerChangeEventImpl(ciThread,thisCatalog,dbThread));
} catch (RemoteException rmie) {
Shop.getTheShop().logException(rmie);
}
}
};
return r;
} else if (dbe.TRANSACTION_REMOVE.equals(sType)) {
// Now lets revert a previous temporary removing of an item
CatalogItem ci = (CatalogItem)dbe.getTransactionItem();
String sKey = ci.getKey();
ci = (CatalogItem)mRemovedItems.remove(sKey);
lRemovedKeys.remove(sKey);
lKeys.add(sKey);
mItems.put(sKey,ci);
// in the following cases the state of the manipulated CatalogItem
// is implicitly updated, as it doesnt need to be removed or added
// to the database again - there is no chance where, we can catch
// a failure to have a consistent persistent state in the case
// where a complete rollback fails... so we can only check this
// single manipulation
try {
ci.setEditable(false,sConPasswd);
ci.setCatalog(this,sConPasswd);
ci.setBasket(null,sConPasswd);
} catch (RuntimeException e) {
// ok, an error occured, we take back all changes now
mItems.remove(sKey);
mRemovedItems.put(sKey,ci);
lKeys.remove(sKey);
lRemovedKeys.add(sKey);
Shop.getTheShop().logException(e);
throw e;
}
nal CatalogImpl thisCatalog = this;
nal CatalogItem ciThread = ci;
nal DataBasket dbThread = dbe.getBasket();
RemoteRunnable r = new RemoteRunnableImpl() {
public void run(Object[] o) throws RemoteException {
try {
thisCatalog.fireRolledbackRemoveTransactionItem(new ContainerChangeEventImpl(ciThread,thisCatalog,dbThread))
;
} catch (RemoteException rmie) {
Shop.getTheShop().logException(rmie);
}
}
};
return r;
} else if (dbe.TRANSACTION_MODIFY.equals(sType)) {
// lets revert the editing of an item
CatalogItem ci = (CatalogItem)dbe.getTransactionItem();
String sKey = ci.getKey();
ci = (CatalogItem) mEditedItems.remove(sKey);
lEditedKeys.remove(sKey);
try {
Shop.getTheShop().getDBPersistenceManager().deleteCatalogItem(ci,con);
} catch (Exception e) {
202
// same as above, we take back all changes now (database changes
// are rolled back with the connection)
Shop.getTheShop().logException(e);
mEditedItems.put(sKey,ci);
lEditedKeys.add(sKey);
throw new RuntimeException(e.getMessage());
1355
}
ci.setCatalog(null,sConPasswd);
ci.setBasket(null,sConPasswd);
ci.setEditable(true,sConPasswd);
ci.updatePersistentObject(Arrays.asList(new Object[]{ci.PERSISTENT_PROPERTY_TRANSACTION_STATE}),sConPasswd);
nal CatalogImpl thisCatalog = this;
nal CatalogItem ciThread = (CatalogItem)mItems.get(ci.getKey());
nal DataBasket dbThread = dbe.getBasket();
RemoteRunnable r = new RemoteRunnableImpl() {
public void run(Object[] o) throws RemoteException {
try {
thisCatalog.fireRolledbackEditTransactionItem(new ContainerChangeEventImpl(ciThread,thisCatalog,dbThread));
} catch (RemoteException rmie) {
Shop.getTheShop().logException(rmie);
}
}
};
return r;
1360
1365
1370
1375
1380
}
1385
1390
1395
1400
1405
1410
1415
1420
1425
1430
1435
ANHANG C. QUELLTEXTE
}
}
}// synchronization
} catch (RuntimeException e) {
Shop.getTheShop().logException(e);
throw e;
}
return null;
// documentation is inherited
public RemoteRunnable commitTransaction(DBEntry dbe, String sConPasswd) throws RemoteException {
try {
Connection con = null;
try {
con = getReservedConnection(sConPasswd);
} catch (Exception e) {
Shop.getTheShop().logException(e);
}
synchronized (getItemsLock()) {
synchronized (getPropertyLock()) {
if (!isValidTransaction(dbe)) throw new RuntimeException("The given DBEntry does not conatin a valid description of a
registered transaction related to this catalog!");
String sType = dbe.getTransactionType();
if (dbe.TRANSACTION_ADD.equals(sType)) {
// Now lets make a previous temporary adding of an item persistent
CatalogItem ci = (CatalogItem)dbe.getTransactionItem();
String sKey = ci.getKey();
mAddedItems.remove(sKey);
lAddedKeys.remove(sKey);
mItems.put(sKey,ci);
lKeys.add(sKey);
ci.setBasket(null,sConPasswd);
ci.setCatalog(this,sConPasswd);
ci.updatePersistentObject(Arrays.asList(new Object[]{ci.PERSISTENT_PROPERTY_TRANSACTION_STATE}),sConPasswd);
if (ci.isEditable()) ci.setEditable(false,sConPasswd);
nal CatalogImpl thisCatalog = this;
nal CatalogItem ciThread = ci;
nal DataBasket dbThread = dbe.getBasket();
RemoteRunnable r = new RemoteRunnableImpl() {
public void run(Object[] o) throws RemoteException {
try {
thisCatalog.fireCommittedAddTransactionItem(new ContainerChangeEventImpl(ciThread,thisCatalog,dbThread));
} catch (RemoteException rmie) {
Shop.getTheShop().logException(rmie);
}
}
};
return r;
} else if (dbe.TRANSACTION_REMOVE.equals(sType)) {
// Now lets make a previous temporary removing of an item persistent
CatalogItem ci = (CatalogItem)dbe.getTransactionItem();
String sKey = ci.getKey();
mRemovedItems.remove(sKey);
lRemovedKeys.remove(sKey);
if (!this.equals(ci.getCatalog())) ci.setCatalog(this,sConPasswd);
try {
Shop.getTheShop().getDBPersistenceManager().deleteCatalogItem(ci,con);
} catch (Exception e) {
Shop.getTheShop().logException(e);
mRemovedItems.put(sKey,ci);
lRemovedKeys.add(sKey);
throw new RuntimeException(e.getMessage());
}
C.1. PACKAGE EPOINT.DATA
ci.setCatalog(null,sConPasswd);
ci.setBasket(null,sConPasswd);
if (!ci.isEditable()) ci.setEditable(true,sConPasswd);
nal CatalogImpl thisCatalog = this;
nal CatalogItem ciThread = ci;
nal DataBasket dbThread = dbe.getBasket();
RemoteRunnable r = new RemoteRunnableImpl() {
public void run(Object[] o) throws RemoteException {
try {
thisCatalog.fireCommittedRemoveTransactionItem(new ContainerChangeEventImpl(ciThread,thisCatalog,dbThread));
} catch (RemoteException rmie) {
Shop.getTheShop().logException(rmie);
}
}
};
return r;
} else if (dbe.TRANSACTION_MODIFY.equals(sType)) {
// lets persistify a modification of an item
CatalogItem ciNew = (CatalogItem)dbe.getTransactionItem();
String sKey = ciNew.getKey();
CatalogItem ciOld = (CatalogItem)mItems.get(sKey);
// first delete the original old item
mItems.remove(sKey);
lKeys.remove(sKey);
mEditedItems.remove(sKey);
lEditedKeys.remove(sKey);
ciOld.setBasket(null,sConPasswd);
try {
Shop.getTheShop().getDBPersistenceManager().deleteCatalogItem(ciOld,con);
} catch (Exception e) {
Shop.getTheShop().logException(e);
mEditedItems.put(sKey,ciNew);
lEditedKeys.add(sKey);
mItems.put(sKey,ciOld);
lKeys.add(sKey);
throw new RuntimeException(e.getMessage());
}
ciOld.setCatalog(null,sConPasswd);
mItems.put(sKey,ciNew);
lKeys.add(sKey);
ciNew.setBasket(null,sConPasswd);
ciNew.setCatalog(this,sConPasswd);
if (ciNew.isEditable()) ciNew.setEditable(false,sConPasswd);
if (!ciOld.isEditable()) ciOld.setEditable(true,sConPasswd);
ciNew.updatePersistentObject(Arrays.asList(new Object[]{ciNew.PERSISTENT_PROPERTY_TRANSACTION_STATE}),sConPasswd);
nal CatalogImpl thisCatalog = this;
nal CatalogItem ciThread = ciNew;
nal DataBasket dbThread = dbe.getBasket();
RemoteRunnable r = new RemoteRunnableImpl() {
public void run(Object[] o) throws RemoteException {
try {
thisCatalog.fireCommittedEditTransactionItem(new ContainerChangeEventImpl(ciThread,thisCatalog,dbThread));
} catch (RemoteException rmie) {
Shop.getTheShop().logException(rmie);
}
}
};
return r;
}
1440
1445
1450
1455
1460
1465
1470
1475
1480
1485
1490
1495
}
1500
}
1505
1510
1515
1520
1525
203
}
}
}// synchronization
(RuntimeException e) {
Shop.getTheShop().logException(e);
throw e;
catch
return null;
// documentation is inherited
public boolean isValidTransaction(DBEntry dbe) throws RemoteException {
try {
synchronized (getItemsLock()) {
synchronized (getPropertyLock()) {
try {
CatalogItem ci = (CatalogItem)dbe.getTransactionItem();
String sKey = ci.getKey();
if (dbe.getTransactionType().equals(dbe.TRANSACTION_ADD)) {
return lAddedKeys.contains(sKey);
} else if (dbe.getTransactionType().equals(dbe.TRANSACTION_REMOVE)) {
return lRemovedKeys.contains(sKey);
} else if (dbe.getTransactionType().equals(dbe.TRANSACTION_MODIFY)) {
return lEditedKeys.contains(sKey);
}
} catch (Exception e) {
return false;
}
return false;
}
204
ANHANG C. QUELLTEXTE
}
1530
1535
1540
1545
1550
1555
1560
1565
1570
1575
1580
1585
1590
1595
1600
1605
1610
}
}
}
(RuntimeException e) {
Shop.getTheShop().logException(e);
throw e;
catch
/*******************************************************************************
*
other methods
*******************************************************************************/
/**
* Returns <code>true</code> only if this Catalog is currently editable.
*
* @return <code>true</code> if this Catalog is editable
* @throws RemoteException whenever an error occurs during remote-access
*/
public boolean isEditable() throws RemoteException {
synchronized (bEditable) {
if (!bEditable.booleanValue()) try {
bEditable.wait(1500);
} catch (InterruptedException ie) {
Shop.getTheShop().logException(ie);
}
return bEditable.booleanValue();
}
}
// documentation is inherited
public void setEditable(boolean bEditable, String sCon) throws RemoteException {
if (this.bEditable.booleanValue() == bEditable) return;
synchronized (getPropertyLock()) {
Object oMonitor = new Object();
synchronized (this.bEditable) {
oMonitor = this.bEditable;
this.bEditable = new Boolean(bEditable);
oMonitor.notifyAll();
}
}
updatePersistentObject(Arrays.asList(new Object[]{PERSISTENT_PROPERTY_SERIALIZED}),sCon);
}
// documentation is inherited
public void setEditable(boolean bEditable)
setEditable(bEditable,null);
}
throws
RemoteException {
/**
* Compares this Catalog to another, while comparing their sizes.
*
* @param c another Catalog that shall be compared to this
* @return an <code>int</code> that describes the difference <code>this.size - other.size</code>
* @throws RemoteException whenever an error occurs during remote-access
*/
public int compareTo(Catalog c) throws RemoteException {
return this.size(null)-c.size(null);
}
// documentation is inherited
public String getOID() throws RemoteException {
return sOID;
}
// documentation is inherited
public boolean contains(String sKey, DataBasket db)
return keySet(db).contains(sKey);
}
// documentation is inherited
public boolean isPersistent()
return sOID != null;
}
throws
// documentation is inherited
public void setPersistent(String sOID)
this.sOID = sOID;
}
throws
RemoteException {
RemoteException {
throws
RemoteException {
/**
* Uses the Shops {@link epoint.db.DBPersistenceManager} to update this
* objects persistent data. Therefore a list of properties may be given,
* that selects the data, that needs an update. This method must always be
* called after changes in the persistent state of this object. Methods of
* this class, changing relevant data, call this method implicitly. Extensions
* of this class may need to call it for updates of additional local data, using
* {@link epoint.data.PersistentObject#PERSISTENT_PROPERTY_SERIALIZED} in the
* list of parameters.
*
C.1. PACKAGE EPOINT.DATA
1615
1620
1625
1630
1635
1640
1645
1650
1655
1660
1665
1670
1675
1680
1685
1690
1695
1700
205
* @param lPropertyKeys a list of Strings, that must contain a valid constant
*
describing the change
*
<ul>
*
<li>{@link epoint.data.Catalog#PERSISTENT_PROPERTY_CATALOGNAME}</li>
*
<li>{@link epoint.data.PersistentObject#PERSISTENT_PROPERTY_SERIALIZED}</li>
*
</ul>
* @param sConPasswd the password the protects the connection used to update
*
this PersistentObject
* @throws RemoteException if an error occurs during remote-access
*/
public void updatePersistentObject(List lPropertyKeys, String sConPasswd) throws RemoteException {
try {
if (!isPersistent()) return;
if (!isEditable()) throw new NotEditableException("This Catalog is currently not editable!");
if (!Arrays.asList(new Object[]{PERSISTENT_PROPERTY_CATALOGNAME,PERSISTENT_PROPERTY_SERIALIZED}).containsAll(lPropertyKeys))
throw new IllegalArgumentException("Could not understand at least one of the properties that shall be made persistent!");
try {
synchronized (getItemsLock()) {
synchronized (getPropertyLock()) {
Shop.getTheShop().getDBPersistenceManager().updateCatalog(this,lPropertyKeys,getReservedConnection(sConPasswd));
}
}
} catch (Exception e) {
Shop.getTheShop().logException(e);
}
} catch (RuntimeException e) {
Shop.getTheShop().logException(e);
throw e;
}
}
/**
* Returns the monitor used to protect internal transactions manipulating
* states of CatalogItems relative to this Catalog.
*
* @return the Object that monitors item-manipulations
*/
nal synchronized protected Object getItemsLock() {
if (m_oItemsLock == null) m_oItemsLock = new Object();
return m_oItemsLock;
}
/**
* Returns the monitor used to protect internal transactions manipulating
* the state of this object.
*
* @return the Object that monitors item-manipulations
*/
nal synchronized protected Object getPropertyLock() {
if (m_oPropertyLock == null) m_oPropertyLock = new Object();
return m_oPropertyLock;
}
/**
* Returns a description of this Catalog, containing its name.
*
* @return this Catalogs String-description
*/
public String toString() {
return "Catalog: "+sName;
}
/**
* Returns <code>true</code> if the given object represents the same
* Catalog as this. This is the case, if both Catlogs has the same name.
*
* @param o another object
* @return <code>true</code> if the other object is a Catalog with the same
*
name as this Catalog
*/
public boolean equals(Object o) {
if (!(o instanceof Catalog)) return false;
try {
return ((Catalog)o).getName().equals(getName());
} catch (RemoteException rmie) {
Shop.getTheShop().logException(rmie);
throw new RuntimeException("Could not complete equality-check, as a RemoteException occured!");
}
}
/**
* Only in the case of a Catalog this method does not return a deep-clone.
* Instead <code>this</code> object itself is returned! (This is implemented for the
* Value-interface, where deep-cloning should be supported. But Catalogs
* with the same properties must always exist only once!)
*
* @param bEditable is not used in the method of this object
* @return this object itself
206
1705
1710
1715
1720
1725
1730
1735
1740
1745
1750
1755
1760
1765
1770
1775
1780
1785
ANHANG C. QUELLTEXTE
* @throws RemoteException if an error occurs during remote-access
*/
bEditable) throws RemoteException {
public Value getDeepClone(boolean
return this;
}
/**
* Uses the Shops {@link epoint.sale.Serializer PersistenceSerializer} to
* return a String-representation of this Catalog, that may be used to persistify
* this Catalog.
*
* @return a String-representation of this Catalogs state
* @throws RemoteException if an error occurs during remote-access
*/
public String getSerializedForm() throws RemoteException {
return epoint.sale.Shop.getPersistenceSerializer().toString(this);
}
/*******************************************************************************
*
Events
******************************************************************************/
/**
* Removes a listener from this container by identifying it, using
* the given ID.
*
* @param sIdentifier the ID of the listener that shall be removed from this Catalog
* @return the removed listener or <code>null</code> if no such listener existed
* @throws RemoteException if an error occurs during remote-access
*/
public ContainerListener removeContainerListener(String sIdentifier) throws RemoteException {
ContainerListener cl1 = (ContainerListener)mListener.remove(sIdentifier);
ContainerListener cl2 = (ContainerListener)mPersistentListener.remove(sIdentifier);
Shop.getTheShop().getDBPersistenceManager().deleteProperty(sIdentifier);
lPersistentListenerIDs.remove(sIdentifier);
String sUpdatePasswd = getClass()+".updatePersistentObject";
updatePersistentObject(Arrays.asList(new Object[]{Catalog.PERSISTENT_PROPERTY_SERIALIZED}),sUpdatePasswd);
returnConnection(getReservedConnection(sUpdatePasswd));
if (cl1 != null) return cl1;
return cl2;
}
/**
* Registers a remotely available listener to this Catalog, which
* calls the appropriate methods of the listener if one of the
* specified actions are carried out.<br>
* The given listener is valid, as long as the registering host is up and
* the listener is remotely available. If an error occurs during accessing
* the listener remotely, the listener is silently removed and not
* used any longer.
*
* @param cl the listener that shall be registered with this Catalog
* @return a String-identifier that may be used to remove the listener
*
later from this Catalog
* @throws RemoteException if an error occurs during remote-access
*/
public String addContainerListener(ContainerListener cl) throws RemoteException {
String sKey = "Remotely available ContainerListener at Catalog "+getOID()+" #"+Shop.getTheShop().getGlobalUniqueNumber();
mListener.put(sKey,cl);
return sKey;
}
/**
* Registers a persistent listener with this Catalog. That means the listener
* is serialized and recreated at the server-side. Such a listener will not be
* accessible anymore, but can be removed, using the ID that is returned
* after adding the listener.<br>
* Such a listener may be used to ensure user-defined constraints for a Catalog,
* even if the registering EPoint is not available at a later time. Such a listener
* may not contact the registering EPoint again, but may veto some manipulations.<br>
* Although such a listener will always be locally available at the Catalog,
* it may force to remove itself silently by throwing a {@link DetachListenerException}.
*
* @param pcl the listener that shall be registered with this catalog
* @return a String-identifier that may later be used to remove the listener again
* @throws RemoteException if an error occurs during remote-access
*/
public String addPersistentContainerListener(PContainerListener pcl) throws RemoteException {
String sSerialized = pcl.getSerializedForm();
pcl = (PersistentContainerListener)Shop.getTheShop().getPersistenceSerializer().fromString(sSerialized);
String sPropertyKey = Shop.SYSTEM_PROPERTY_PREFIX+" persistent ContainerListener at Catalog "+getOID()+" #"+Shop.getTheShop().
getGlobalUniqueNumber();
Shop.getTheShop().setPersistentProperty(sPropertyKey,sSerialized);
lPersistentListenerIDs.add(sPropertyKey);
mPersistentListener.put(sPropertyKey,pcl);
pcl.setListenerID(sPropertyKey);
String sUpdatePasswd = getClass()+".updatePersistentObject";
C.1. PACKAGE EPOINT.DATA
1790
}
1795
1800
1805
1810
1815
1820
1825
1830
1835
1840
1845
1850
1855
1860
1865
1870
1875
207
updatePersistentObject(Arrays.asList(new Object[]{Catalog.PERSISTENT_PROPERTY_SERIALIZED}),sUpdatePasswd);
returnConnection(getReservedConnection(sUpdatePasswd));
return sPropertyKey;
/**
* Called whenever a CatalogItem is added within a transaction.
*
* @param e an event object describing the event.
* @throws RemoteException whenever an error occurs during remote-access
*/
protected void fireAddedItem(ContainerChangeEvent e) throws RemoteException {
Iterator it = mListener.keySet().iterator();
synchronized (getItemsLock()) { // we dont want to have more actions meanwhile!
while (it.hasNext()) {
ContainerListener cl = (ContainerListener)mListener.get((String)it.next());
try {
cl.addedItem(e);
} catch (RemoteException rmie) {
it.remove();
} catch (DetachListenerException dle) {
it.remove();
}
}
it = mPersistentListener.keySet().iterator();
while (it.hasNext()) {
String sKey = (String)it.next();
ContainerListener cl = (ContainerListener)mPersistentListener.get(sKey);
try {
cl.addedItem(e);
} catch (DetachListenerException dle) {
it.remove();
lPersistentListenerIDs.remove(sKey);
String sUpdatePasswd = getClass()+".updatePersistentObject";
updatePersistentObject(Arrays.asList(new Object[]{PERSISTENT_PROPERTY_SERIALIZED}),sUpdatePasswd);
returnConnection(getReservedConnection(sUpdatePasswd));
Shop.getTheShop().deletePersistentProperty(sKey);
}
}
}
}
/**
* Called whenever the adding of a CatalogItem was commited.
*
* @param e an event object describing the event.
* @throws RemoteException whenever an error occurs during remote-access
*/
protected void fireCommittedAddTransactionItem(ContainerChangeEvent e) throws RemoteException {
Iterator it = mListener.keySet().iterator();
synchronized (getItemsLock()) { // we dont want to have more actions meanwhile!
while (it.hasNext()) {
ContainerListener cl = (ContainerListener)mListener.get((String)it.next());
try {
cl.committedAddTransactionItem(e);
} catch (RemoteException rmie) {
it.remove();
} catch (DetachListenerException dle) {
it.remove();
}
}
it = mPersistentListener.keySet().iterator();
while (it.hasNext()) {
String sKey = (String)it.next();
ContainerListener cl = (ContainerListener)mPersistentListener.get(sKey);
try {
cl.committedAddTransactionItem(e);
} catch (DetachListenerException dle) {
it.remove();
lPersistentListenerIDs.remove(sKey);
String sUpdatePasswd = getClass()+".updatePersistentObject";
updatePersistentObject(Arrays.asList(new Object[]{PERSISTENT_PROPERTY_SERIALIZED}),sUpdatePasswd);
returnConnection(getReservedConnection(sUpdatePasswd));
Shop.getTheShop().deletePersistentProperty(sKey);
}
}
}
}
/**
* Called whenever the adding of a CatalogItem was rolled back.
*
* @param e an event object describing the event.
* @throws RemoteException whenever an error occurs during remote-access
*/
protected void fireRolledbackAddTransactionItem(ContainerChangeEvent e)
Iterator it = mListener.keySet().iterator();
throws
RemoteException {
208
ANHANG C. QUELLTEXTE
synchronized (getItemsLock())
while (it.hasNext()) {
1880
1885
1890
1895
1900
1905
1910
1915
1920
1925
1930
1935
1940
1945
1950
1955
1960
{ // we dont want to have more actions meanwhile!
ContainerListener cl = (ContainerListener)mListener.get((String)it.next());
try {
cl.rolledbackAddTransactionItem(e);
} catch (RemoteException rmie) {
it.remove();
} catch (DetachListenerException dle) {
it.remove();
}
}
}
}
it = mPersistentListener.keySet().iterator();
while (it.hasNext()) {
String sKey = (String)it.next();
ContainerListener cl = (ContainerListener)mPersistentListener.get(sKey);
try {
cl.rolledbackAddTransactionItem(e);
} catch (DetachListenerException dle) {
it.remove();
lPersistentListenerIDs.remove(sKey);
String sUpdatePasswd = getClass()+".updatePersistentObject";
updatePersistentObject(Arrays.asList(new Object[]{PERSISTENT_PROPERTY_SERIALIZED}),sUpdatePasswd);
returnConnection(getReservedConnection(sUpdatePasswd));
Shop.getTheShop().deletePersistentProperty(sKey);
}
}
/**
* Called to ask if a CatalogItem may be removed. If one of the registered listeners vetos the removal, all
* listeners that had already been asked will receive a {@link ContainerListener#noRemoveTransactionItem(ContainerChangeEvent)
noRemoveTransactionItem}
* event.
*
* @param e an event object describing the event.
*
* @exception VetoException if the listener wants to veto the removal.
* @throws RemoteException whenever an error occurs during remote-access
*/
protected void fireCanRemoveTransactionItem(ContainerChangeEvent e) throws RemoteException, VetoException {
//protected void noRemoveTransactionItem(ContainerChangeEvent e) throws RemoteException;
Iterator it = mListener.keySet().iterator();
boolean bVeto = false;
List lInformedKeys = new LinkedList();
VetoException veThrown = null;
synchronized (getItemsLock()) { // we dont want to have more actions meanwhile!
while (it.hasNext()) {
String sKey = (String)it.next();
ContainerListener cl = (ContainerListener)mListener.get(sKey);
try {
cl.canRemoveTransactionItem(e);
lInformedKeys.add(sKey);
} catch (RemoteException rmie) {
it.remove();
lInformedKeys.remove(sKey);
} catch (VetoException ve) {
veThrown = ve;
bVeto = true;
break;
} catch (DetachListenerException dle) {
it.remove();
lInformedKeys.remove(sKey);
}
}
if (bVeto) {
it = lInformedKeys.iterator();
while (it.hasNext()) {
String sKey = (String)it.next();
ContainerListener cl = (ContainerListener)mListener.get(sKey);
cl.noRemoveTransactionItem(e);
}
throw veThrown;
} else {
it = mPersistentListener.keySet().iterator();
while (it.hasNext()) {
String sKey = (String)it.next();
ContainerListener cl = (ContainerListener)mPersistentListener.get(sKey);
try {
cl.canRemoveTransactionItem(e);
lInformedKeys.add(sKey);
} catch (DetachListenerException dle) {
it.remove();
lPersistentListenerIDs.remove(sKey);
String sUpdatePasswd = getClass()+".updatePersistentObject";
updatePersistentObject(Arrays.asList(new Object[]{PERSISTENT_PROPERTY_SERIALIZED}),sUpdatePasswd);
returnConnection(getReservedConnection(sUpdatePasswd));
Shop.getTheShop().deletePersistentProperty(sKey);
C.1. PACKAGE EPOINT.DATA
1965
}
1970
}
if
1975
1980
}
1985
1990
1995
2000
2005
2010
2015
2020
2025
2030
2035
2040
2045
2050
}
}
}
}
lInformedKeys.remove(sKey);
(VetoException ve) {
veThrown = ve;
bVeto = true;
break;
catch
(bVeto) {
it = lInformedKeys.iterator();
while (it.hasNext()) {
String sKey = (String)it.next();
ContainerListener cl = (ContainerListener)mPersistentListener.get(sKey);
if (cl == null) cl = (ContainerListener)mListener.get(sKey);
cl.noRemoveTransactionItem(e);
}
throw veThrown;
/**
* This method is called, if a previous call to {@link #fireCanRemoveTransactionItem(ContainerChangeEvent)}
* has been successful but the removal will not take place.
*
* @param e an object describing the event
* @throws RemoteException if an error occurs during remote-access
*/
protected void fireNoRemoveTransactionItem(ContainerChangeEvent e) throws RemoteException {
Iterator it = mListener.keySet().iterator();
synchronized (getItemsLock()) { // we dont want to have more actions meanwhile!
while (it.hasNext()) {
ContainerListener cl = (ContainerListener)mListener.get((String)it.next());
try {
cl.noRemoveTransactionItem(e);
} catch (RemoteException rmie) {
it.remove();
} catch (DetachListenerException dle) {
it.remove();
}
}
it = mPersistentListener.keySet().iterator();
while (it.hasNext()) {
String sKey = (String)it.next();
ContainerListener cl = (ContainerListener)mPersistentListener.get(sKey);
try {
cl.noRemoveTransactionItem(e);
} catch (DetachListenerException dle) {
it.remove();
lPersistentListenerIDs.remove(sKey);
String sUpdatePasswd = getClass()+".updatePersistentObject";
updatePersistentObject(Arrays.asList(new Object[]{PERSISTENT_PROPERTY_SERIALIZED}),sUpdatePasswd);
returnConnection(getReservedConnection(sUpdatePasswd));
Shop.getTheShop().deletePersistentProperty(sKey);
}
}
}
}
/**
* Works like {@link #fireNoRemoveTransactionItem(ContainerChangeEvent)}, but is
* called in the case of editing instead.
*
* @param e an object describing the event
* @throws RemoteException whenever an error occurs during remote-access
*/
protected void fireNoEditTransactionItem(ContainerChangeEvent e) throws RemoteException {
Iterator it = mListener.keySet().iterator();
synchronized (getItemsLock()) { // we dont want to have more actions meanwhile!
while (it.hasNext()) {
ContainerListener cl = (ContainerListener)mListener.get((String)it.next());
try {
cl.noEditTransactionItem(e);
} catch (RemoteException rmie) {
it.remove();
} catch (DetachListenerException dle) {
it.remove();
}
}
it = mPersistentListener.keySet().iterator();
while (it.hasNext()) {
String sKey = (String)it.next();
ContainerListener cl = (ContainerListener)mPersistentListener.get(sKey);
try {
cl.noEditTransactionItem(e);
} catch (DetachListenerException dle) {
it.remove();
lPersistentListenerIDs.remove(sKey);
209
210
ANHANG C. QUELLTEXTE
2055
}
2060
2065
2070
2075
2080
2085
2090
2095
2100
2105
2110
2115
2120
2125
2130
2135
}
}
String sUpdatePasswd = getClass()+".updatePersistentObject";
updatePersistentObject(Arrays.asList(new Object[]{PERSISTENT_PROPERTY_SERIALIZED}),sUpdatePasswd);
returnConnection(getReservedConnection(sUpdatePasswd));
Shop.getTheShop().deletePersistentProperty(sKey);
}
/**
* Called whenever a CatalogItem was removed from this Catalog.
*
* @param e an event object describing the event.
* @throws RemoteException whenever an error occurs during remote-access
*/
protected void fireRemovedTransactionItem(ContainerChangeEvent e) throws RemoteException {
Iterator it = mListener.keySet().iterator();
synchronized (getItemsLock()) { // we dont want to have more actions meanwhile!
while (it.hasNext()) {
ContainerListener cl = (ContainerListener)mListener.get((String)it.next());
try {
cl.removedTransactionItem(e);
} catch (RemoteException rmie) {
it.remove();
} catch (DetachListenerException dle) {
it.remove();
}
}
it = mPersistentListener.keySet().iterator();
while (it.hasNext()) {
String sKey = (String)it.next();
ContainerListener cl = (ContainerListener)mPersistentListener.get(sKey);
try {
cl.removedTransactionItem(e);
} catch (DetachListenerException dle) {
it.remove();
lPersistentListenerIDs.remove(sKey);
String sUpdatePasswd = getClass()+".updatePersistentObject";
updatePersistentObject(Arrays.asList(new Object[]{PERSISTENT_PROPERTY_SERIALIZED}),sUpdatePasswd);
returnConnection(getReservedConnection(sUpdatePasswd));
Shop.getTheShop().deletePersistentProperty(sKey);
}
}
}
}
/**
* Called whenever the removal of a CatalogItem was commited.
*
* @param e an event object describing the event.
* @throws RemoteException whenever an error occurs during remote-access
*/
protected void fireCommittedRemoveTransactionItem(ContainerChangeEvent e) throws RemoteException {
Iterator it = mListener.keySet().iterator();
synchronized (getItemsLock()) { // we dont want to have more actions meanwhile!
while (it.hasNext()) {
ContainerListener cl = (ContainerListener)mListener.get((String)it.next());
try {
cl.committedRemoveTransactionItem(e);
} catch (RemoteException rmie) {
it.remove();
} catch (DetachListenerException dle) {
it.remove();
}
}
it = mPersistentListener.keySet().iterator();
while (it.hasNext()) {
String sKey = (String)it.next();
ContainerListener cl = (ContainerListener)mPersistentListener.get(sKey);
try {
cl.committedRemoveTransactionItem(e);
} catch (DetachListenerException dle) {
it.remove();
lPersistentListenerIDs.remove(sKey);
String sUpdatePasswd = getClass()+".updatePersistentObject";
updatePersistentObject(Arrays.asList(new Object[]{PERSISTENT_PROPERTY_SERIALIZED}),sUpdatePasswd);
returnConnection(getReservedConnection(sUpdatePasswd));
Shop.getTheShop().deletePersistentProperty(sKey);
}
}
}
}
/**
* Called whenever the removal of a CatalogItem was rolled back.
*
* @param e an event object describing the event.
* @throws RemoteException whenever an error occurs during remote-access
*/
C.1. PACKAGE EPOINT.DATA
2140
protected void
2145
2150
2155
2160
2165
2170
2175
2180
2185
2190
2195
2200
2205
2210
2215
2220
2225
}
fireRolledbackRemoveTransactionItem(ContainerChangeEvent e) throws RemoteException {
Iterator it = mListener.keySet().iterator();
synchronized (getItemsLock()) { // we dont want to have more actions meanwhile!
while (it.hasNext()) {
ContainerListener cl = (ContainerListener)mListener.get((String)it.next());
try {
cl.rolledbackRemoveTransactionItem(e);
} catch (RemoteException rmie) {
it.remove();
} catch (DetachListenerException dle) {
it.remove();
}
}
it = mPersistentListener.keySet().iterator();
while (it.hasNext()) {
String sKey = (String)it.next();
ContainerListener cl = (ContainerListener)mPersistentListener.get(sKey);
try {
cl.rolledbackRemoveTransactionItem(e);
} catch (DetachListenerException dle) {
it.remove();
lPersistentListenerIDs.remove(sKey);
String sUpdatePasswd = getClass()+".updatePersistentObject";
updatePersistentObject(Arrays.asList(new Object[]{PERSISTENT_PROPERTY_SERIALIZED}),sUpdatePasswd);
returnConnection(getReservedConnection(sUpdatePasswd));
Shop.getTheShop().deletePersistentProperty(sKey);
}
}
}
/**
* Called to ask whether a CatalogItem may be edited. If one of the listeners vetos the editing, all
* steners that had already been asked will receive a {@link ContainerListener#noEditTransactionItem(ContainerChangeEvent)
noEditTransactionItem} event.
*
* @param e an event object describing the event.
*
* @exception VetoException if the listener wants to veto the editing.
* @throws RemoteException whenever an error occurs during remote-access
*/
protected void fireCanEditTransactionItem(ContainerChangeEvent e) throws RemoteException, VetoException {
//public void noEditTransactionItem(ContainerChangeEvent e) throws RemoteException;
Iterator it = mListener.keySet().iterator();
boolean bVeto = false;
List lInformedKeys = new LinkedList();
VetoException veThrown = null;
synchronized (getItemsLock()) { // we dont want to have more actions meanwhile!
while (it.hasNext()) {
String sKey = (String)it.next();
ContainerListener cl = (ContainerListener)mListener.get(sKey);
try {
cl.canEditTransactionItem(e);
lInformedKeys.add(sKey);
} catch (RemoteException rmie) {
it.remove();
lInformedKeys.remove(sKey);
} catch (VetoException ve) {
veThrown = ve;
bVeto = true;
break;
} catch (DetachListenerException dle) {
it.remove();
lInformedKeys.remove(sKey);
}
}
if (bVeto) {
it = lInformedKeys.iterator();
while (it.hasNext()) {
String sKey = (String)it.next();
ContainerListener cl = (ContainerListener)mListener.get(sKey);
cl.noEditTransactionItem(e);
}
throw veThrown;
} else {
it = mPersistentListener.keySet().iterator();
while (it.hasNext()) {
String sKey = (String)it.next();
ContainerListener cl = (ContainerListener)mPersistentListener.get(sKey);
try {
cl.canEditTransactionItem(e);
lInformedKeys.add(sKey);
} catch (DetachListenerException dle) {
it.remove();
lInformedKeys.remove(sKey);
lPersistentListenerIDs.remove(sKey);
String sUpdatePasswd = getClass()+".updatePersistentObject";
updatePersistentObject(Arrays.asList(new Object[]{PERSISTENT_PROPERTY_SERIALIZED}),sUpdatePasswd);
211
212
ANHANG C. QUELLTEXTE
}
2230
}
if
2235
2240
2245
}
2250
2255
2260
2265
2270
2275
2280
2285
2290
2295
2300
2305
2310
}
}
}
}
returnConnection(getReservedConnection(sUpdatePasswd));
Shop.getTheShop().deletePersistentProperty(sKey);
(VetoException ve) {
veThrown = ve;
bVeto = true;
break;
catch
(bVeto) {
it = lInformedKeys.iterator();
while (it.hasNext()) {
String sKey = (String)it.next();
ContainerListener cl = (ContainerListener)mPersistentListener.get(sKey);
if (cl == null) cl = (ContainerListener)mListener.get(sKey);
cl.noEditTransactionItem(e);
}
throw veThrown;
/**
* Called whenever editing a CatalogItem was started. This event may be accompanied by a
* <code>removedTransactionItem</code> and a <code>addedTransactionItem</code> event, but this is implementation
* specific.
*
* @param e an event object describing the event.
* @throws RemoteException whenever an error occurs during remote-access
*/
protected void fireEditingTransactionItem(ContainerChangeEvent e) throws RemoteException {
Iterator it = mListener.keySet().iterator();
synchronized (getItemsLock()) { // we dont want to have more actions meanwhile!
while (it.hasNext()) {
ContainerListener cl = (ContainerListener)mListener.get((String)it.next());
try {
cl.editingTransactionItem(e);
} catch (RemoteException rmie) {
it.remove();
} catch (DetachListenerException dle) {
it.remove();
}
}
it = mPersistentListener.keySet().iterator();
while (it.hasNext()) {
String sKey = (String)it.next();
ContainerListener cl = (ContainerListener)mPersistentListener.get(sKey);
try {
cl.editingTransactionItem(e);
} catch (DetachListenerException dle) {
it.remove();
lPersistentListenerIDs.remove(sKey);
String sUpdatePasswd = getClass()+".updatePersistentObject";
updatePersistentObject(Arrays.asList(new Object[]{PERSISTENT_PROPERTY_SERIALIZED}),sUpdatePasswd);
returnConnection(getReservedConnection(sUpdatePasswd));
Shop.getTheShop().deletePersistentProperty(sKey);
}
}
}
}
/**
* Called whenever editing a CatalogItem was commited.
*
* @param e an event object describing the event.
* @throws RemoteException whenever an error occurs during remote-access
*/
protected void fireCommittedEditTransactionItem(ContainerChangeEvent e) throws RemoteException {
Iterator it = mListener.keySet().iterator();
synchronized (getItemsLock()) { // we dont want to have more actions meanwhile!
while (it.hasNext()) {
ContainerListener cl = (ContainerListener)mListener.get((String)it.next());
try {
cl.committedEditTransactionItem(e);
} catch (RemoteException rmie) {
it.remove();
} catch (DetachListenerException dle) {
it.remove();
}
}
it = mPersistentListener.keySet().iterator();
while (it.hasNext()) {
String sKey = (String)it.next();
ContainerListener cl = (ContainerListener)mPersistentListener.get(sKey);
try {
cl.committedEditTransactionItem(e);
} catch (DetachListenerException dle) {
it.remove();
C.1. PACKAGE EPOINT.DATA
2315
2320
}
2325
2330
2335
2340
2345
2350
2355
2360
2365
2370
2375
2380
2385
2390
2395
2400
}
}
}
lPersistentListenerIDs.remove(sKey);
String sUpdatePasswd = getClass()+".updatePersistentObject";
updatePersistentObject(Arrays.asList(new Object[]{PERSISTENT_PROPERTY_SERIALIZED}),sUpdatePasswd);
returnConnection(getReservedConnection(sUpdatePasswd));
Shop.getTheShop().deletePersistentProperty(sKey);
/**
* Called whenever editing a CatalogItem was rolled back.
*
* @param e an event object describing the event.
* @throws RemoteException whenever an error occurs during remote-access
*/
protected void fireRolledbackEditTransactionItem(ContainerChangeEvent e) throws RemoteException {
Iterator it = mListener.keySet().iterator();
synchronized (getItemsLock()) { // we dont want to have more actions meanwhile!
while (it.hasNext()) {
ContainerListener cl = (ContainerListener)mListener.get((String)it.next());
try {
cl.rolledbackEditTransactionItem(e);
} catch (RemoteException rmie) {
it.remove();
} catch (DetachListenerException dle) {
it.remove();
}
}
it = mPersistentListener.keySet().iterator();
while (it.hasNext()) {
String sKey = (String)it.next();
ContainerListener cl = (ContainerListener)mPersistentListener.get(sKey);
try {
cl.rolledbackEditTransactionItem(e);
} catch (DetachListenerException dle) {
it.remove();
lPersistentListenerIDs.remove(sKey);
String sUpdatePasswd = getClass()+".updatePersistentObject";
updatePersistentObject(Arrays.asList(new Object[]{PERSISTENT_PROPERTY_SERIALIZED}),sUpdatePasswd);
returnConnection(getReservedConnection(sUpdatePasswd));
Shop.getTheShop().deletePersistentProperty(sKey);
}
}
}
}
/**
* Called whenever a specific property of this Catalog has changed.
* This is implemented for at least the name of this Catalog.
*
* @param event an event object describing the event
* @throws RemoteException whenever an error occurs during remote-access
*/
protected void firePropertyChange(java.beans.PropertyChangeEvent event) throws RemoteException {
Iterator it = mListener.keySet().iterator();
synchronized (getItemsLock()) { // we dont want to have more actions meanwhile!
while (it.hasNext()) {
ContainerListener cl = (ContainerListener)mListener.get((String)it.next());
try {
cl.propertyChange(event);
} catch (RemoteException rmie) {
it.remove();
} catch (DetachListenerException dle) {
it.remove();
}
}
it = mPersistentListener.keySet().iterator();
while (it.hasNext()) {
String sKey = (String)it.next();
ContainerListener cl = (ContainerListener)mPersistentListener.get(sKey);
try {
cl.propertyChange(event);
} catch (DetachListenerException dle) {
it.remove();
lPersistentListenerIDs.remove(sKey);
String sUpdatePasswd = getClass()+".updatePersistentObject";
updatePersistentObject(Arrays.asList(new Object[]{PERSISTENT_PROPERTY_SERIALIZED}),sUpdatePasswd);
returnConnection(getReservedConnection(sUpdatePasswd));
Shop.getTheShop().deletePersistentProperty(sKey);
}
}
}
}
/**
* Returns a Connection to the database, that is protected by a class-wide
* default-password.
213
214
ANHANG C. QUELLTEXTE
*
* @return the default-connection of this class
*/
protected Connection getDefaultConnection() {
return Shop.getTheShop().getDBPersistenceManager().getPrivateConnection("Default Connection for class "+getClass().getName(),getClass
());
}
2405
2410
/**
* Returns a Connection to the database, that is protected by the given
* password.
*
* @param sConPasswd the password the protects the requested connection
* @return the requested Connection
*/
protected Connection getReservedConnection(String sConPasswd) {
return Shop.getTheShop().getDBPersistenceManager().getPrivateConnection(sConPasswd,getClass());
}
2415
2420
/**
* Returns the given Connection that was previously requested to the DBPersistenceManager
* for further use by others.
*
* @param con the connection that shall be returned
*/
protected void returnConnection(Connection con) {
Shop.getTheShop().getDBPersistenceManager().returnPrivateConnection(con);
}
2425
2430
// documentation is inherited
public void updatePersistentListener(String sID) throws RemoteException {
String sSerialized = ((PersistentContainerListener)mPersistentListener.get(sID)).getSerializedForm();
Shop.getTheShop().setPersistentProperty(sID,sSerialized);
}
2435
/**
* Returns the same as {@link #toString()}, but may be used by remote-connections,
* as {@link java.rmi.server.RemoteObject#toString()} returns a description of the
* Stub at client-side
*
* @return the String describing this object
* @throws RemoteException whenever an error occurs during remote-access
*/
public String toRemoteString() throws RemoteException {
return toString();
}
2440
2445
}
Listing C.40: epoint.data.rmi.CatalogItemImpl
5
10
15
20
25
30
35
/*
* CatalogItemImpl.java
*
* Created on 30. Juli 2001, 13:38
*/
package epoint.data.rmi;
import epoint.data.*;
import java.rmi.*;
import epoint.data.exception.*;
import epoint.sale.Shop;
import epoint.sale.EPoint;
import java.util.*;
import java.io.*;
import java.sql.Connection;
/**
* This is the default-implementation for the {@link CatalogItem CatalogItem interface}. For
* more documentation about CatalogItems read {@link CatalogItem here}<br>
* <br>
* This CatalogItem is persistent, if it is associated with a Catalog in any
* meanings of being part of it or being involved within a running transaction
* relative to it.<br>
* As a remotely available Object it cannot be instanciated at client-side (EPoint),
* but must requested using the {@link epoint.sale.ShopServices ShopServices}
* ({@link epoint.sale.EPoint#getShopServices()}) instead.
*
* @author Danny Poppe
* @version 1.0
*/
public class CatalogItemImpl extends java.rmi.server.UnicastRemoteObject implements CatalogItem {
/**
* persistent in the serialized version
*/
C.1. PACKAGE EPOINT.DATA
private boolean
40
45
50
55
60
65
70
75
80
85
90
95
100
105
bEditable =
true;
/**
* persistent in the database
*/
transient private String sKey = null;
/**
* name persistent in the database
*/
transient private Catalog cOwnerCatalog = null;
/**
* persistent in the database
*/
transient private String sTransIdent = null;
/**
* persistent in the database
*/
transient private Value vValue = null;
/**
* is retrieved by getting the databasket of the transaction items identifier
*/
transient private DataBasket dbOwner = null;
/**
* set by the persistence-manager
*/
transient private String sOID =
null;
/**
* Creates a new remotely-available CatalogItem without a key and a Value.
*
* @throws RemoteException whenever an error occurs during remote-access
*/
protected CatalogItemImpl() throws RemoteException {
this(null,null);
}
/**
* Creates a new remotely available CatalogItem with the given key, but
* no Value.
*
* @param sKey the name of this CatalogItem
* @throws RemoteException whenever an error occurs during remote-access
*/
public CatalogItemImpl(String sKey) throws RemoteException {
this(sKey,null);
}
/**
* Creates a new remotely available CatalogItem with the given key and Value.
*
* @param sKey the name of this CatalogItem
* @param vValue the initial Value of this CatalogItem
* @throws RemoteException whenever an error occurs during remote-access
*/
public CatalogItemImpl(String sKey, Value vValue) throws RemoteException {
this.sKey = sKey;
this.vValue = vValue;
this.sTransIdent = "CatalogItem TransactionItem #"+epoint.sale.Shop.getTheShop().getGlobalUniqueNumber();
}
// documentation is inherited
public void loadPersistentData(Map mProperties, String sConPasswd) throws RemoteException {
try {
this.cOwnerCatalog = (Catalog)mProperties.get(PERSISTENT_PROPERTY_CATALOG);
this.sKey = (String)mProperties.get(PERSISTENT_PROPERTY_CIKEY);
this.sTransIdent = (String)mProperties.get(PERSISTENT_PROPERTY_TRANSACTION_IDENTIFIER);
this.vValue = (Value)mProperties.get(PERSISTENT_PROPERTY_CIVALUE);
this.dbOwner = (DataBasket)mProperties.get(PERSISTENT_PROPERTY_TRANSACTION_BASKET);
} catch (Exception e) {
Shop.getTheShop().logException(e);
}
}
110
// documentation is inherited
nal public String getKey()
return sKey;
}
115
// documentation is inherited
public void setValue(Value v) throws RemoteException, NotEditableException {
if (!isEditable()) throw new NotEditableException("This CatalogItem is currently not allowed to be edited!");
this.vValue = v;
String sUpdatePasswd = getClass()+".updatePersistentObject";
updatePersistentObject(Arrays.asList(new Object[]{CatalogItem.PERSISTENT_PROPERTY_CIVALUE,CatalogItem.
PERSISTENT_PROPERTY_CICATALOGFLAG}),sUpdatePasswd);
returnConnection(getReservedConnection(sUpdatePasswd));
}
120
throws
RemoteException {
215
216
125
130
135
140
145
150
155
ANHANG C. QUELLTEXTE
// documentation is inherited
nal public void setEditable(boolean bEditable, String sCon) throws RemoteException {
this.bEditable = bEditable;
updatePersistentObject(Arrays.asList(new Object[]{PersistentObject.PERSISTENT_PROPERTY_SERIALIZED}),sCon);
}
// documentation is inherited
nal public boolean isEditable() throws RemoteException {
if (getCatalog() != null) return bEditable && getCatalog().isEditable();
return bEditable;
}
// documentation is inherited
nal public Value getValue() throws RemoteException {
return vValue.getDeepClone(vValue.isEditable());
}
/**
* Compares this CatalogItem to another, by only comparing their Values as
* specified in {@link Value#compareTo(Value)}.
*
* @param ci the CatalogItem that shall be compared to this CatalogItem
* @return an <code>int</code> as specified in {@link Value#compareTo(Value)}
* @throws RemoteException whenever an error occurs during remote-access
*/
public int compareTo(CatalogItem ci) throws RemoteException {
return ci.getValue().compareTo(getValue());
}
// documentation is inherited
nal public Catalog getCatalog()
return cOwnerCatalog;
}
throws
RemoteException {
160
// documentation is inherited
nal public void setCatalog(Catalog c, String sCon) throws RemoteException {
cOwnerCatalog = c;
if (!(c == null)) updatePersistentObject(Arrays.asList(new Object[]{this.PERSISTENT_PROPERTY_CATALOG}),sCon);
}
165
////////////////////////////////////////////////////////////////////////////
//
Methods implemented by TransactionItem
////////////////////////////////////////////////////////////////////////////
170
175
180
185
190
195
// documentation is inherited
nal public DataBasket getBasket()
return dbOwner;
}
throws
RemoteException {
// documentation is inherited
nal public String getTransactionItemIdentifier()
return sTransIdent;
}
throws
RemoteException {
// documentation is inherited
nal public String getSerializedForm() throws RemoteException {
return Shop.getPersistenceSerializer().toString(this);
}
// documentation is inherited
nal public String getOID()
return sOID;
}
throws
RemoteException {
// documentation is inherited
nal public void setBasket(DataBasket db, String sCon) throws RemoteException {
if (db != null) {
if (dbOwner != null) {
if (!dbOwner.equals(db) && dbOwner.iterator(this).hasNext() && !dbOwner.hasFinished())
throw new RuntimeException("You cannot set a new DataBasket, as this item already belongs to a databasket that has not
yet committed!");
}
}
dbOwner = db;
updatePersistentObject(Arrays.asList(new Object[]{PERSISTENT_PROPERTY_TRANSACTION_STATE,PERSISTENT_PROPERTY_TRANSACTION_BASKET}),sCon)
;
}
200
// documentation is inherited
nal public boolean isPersistent()
return sOID != null;
}
205
// documentation is inherited
nal public void setPersistent(String sOID)
this.sOID = sOID;
}
throws
RemoteException {
throws
RemoteException {
C.1. PACKAGE EPOINT.DATA
210
215
220
225
230
235
240
245
250
255
260
265
270
275
280
285
290
295
217
// documentation is inherited
public void updatePersistentObject(List lPropertyKeys, String sCon) throws RemoteException {
if (!isPersistent() || (getCatalog() == null)) return;
List lAllowed = Arrays.asList(new Object[]{PersistentObject.PERSISTENT_PROPERTY_SERIALIZED,
CatalogItem.PERSISTENT_PROPERTY_CATALOG,
CatalogItem.PERSISTENT_PROPERTY_CIKEY,
CatalogItem.PERSISTENT_PROPERTY_TRANSACTION_IDENTIFIER,
CatalogItem.PERSISTENT_PROPERTY_CIVALUE,
CatalogItem.PERSISTENT_PROPERTY_CICATALOGFLAG,
CatalogItem.PERSISTENT_PROPERTY_TRANSACTION_STATE,
CatalogItem.PERSISTENT_PROPERTY_TRANSACTION_BASKET
});
if (!lAllowed.containsAll(lPropertyKeys)) throw new IllegalArgumentException(
"At least one of the properties given for updating the persistent object could not be identified!");
try {
Shop.getTheShop().getDBPersistenceManager().updateCatalogItem(this,lPropertyKeys,getReservedConnection(sCon));
} catch (Exception e) {
Shop.getTheShop().logException(e);
}
}
// documentation is inherited
public CatalogItem getShallowClone(boolean bEditable) throws RemoteException {
if ((!isEditable()) && bEditable && EPoint.isClientApplication()) throw new IllegalArgumentException("You cannot create an editable
clone of a not-editable original!");
CatalogItem ciClone = null;
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(this);
ciClone = (CatalogItem)(new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()))).readObject();
} catch (Exception e) {
Shop.getTheShop().logException(e);
return null;
}
Map m = new HashMap();
m.put(this.PERSISTENT_PROPERTY_CATALOG,getCatalog());
m.put(this.PERSISTENT_PROPERTY_CIKEY,getKey());
m.put(this.PERSISTENT_PROPERTY_TRANSACTION_IDENTIFIER,getTransactionItemIdentifier());
m.put(this.PERSISTENT_PROPERTY_CIVALUE,getValue());
m.put(this.PERSISTENT_PROPERTY_TRANSACTION_BASKET,getBasket());
ciClone.loadPersistentData(m,null);
ciClone.setEditable(bEditable,null);
ciClone.setPersistent(this.getOID());
return ciClone;
}
/**
* Returns <code>true</code> only, if the key and the value of this and
* the given CatalogItem are equal and therefore the given object is
* a CatalogItem.
*
* @param o the CatalogItem that shall be checked for equality to this
* @return <code>true</code> if key and value of this and the given item are equal
*/
public boolean equals(Object o) {
if (!(o instanceof CatalogItem)) return false;
CatalogItem ciOther = (CatalogItem)o;
try {
boolean bValue = (getValue()!=null)?(getValue().equals(ciOther.getValue())):(ciOther.getValue()==null);
boolean bKey = (getKey()!=null)?(getKey().equals(ciOther.getKey())):(ciOther.getKey()==null);
return bValue && bKey;
} catch (RemoteException rmie) {
Shop.getTheShop().logException(rmie);
throw new RuntimeException("Could not complete equality-check, as a RemoteException occured!");
}
}
/**
* Returns a String-representation of this CatalogItem, containing its key,
* Value and the Catalogs name it is contained in.
*
* @return a String-representation of this CatalogItem
*/
public String toString() {
String sRetVal = "CatalogItem \""+sKey+"\"";
try {
sRetVal += " in Catalog "+((getCatalog() != null)?(getCatalog().getName()):("null"));
sRetVal += " with Value: "+getValue();
} catch (RemoteException rmie) {
Shop.getTheShop().logException(rmie);
}
return sRetVal;
}
/**
* Returns this class default connection from the DBPersisteneManager, using
218
* a class-wide default-password for protection of this connection.
*
* @return the default-connection for this class
*/
protected Connection getDefaultConnection() {
return Shop.getTheShop().getDBPersistenceManager().getPrivateConnection("Default Connection for class "+getClass().getName(),getClass
());
}
/**
* Returns a Connection from the DBPersistenceManager, that is protected
* by the given password.
*
* @param sConPasswd the password for the connection to be returned
* @return the requested Connection
*/
protected Connection getReservedConnection(String sConPasswd) {
return Shop.getTheShop().getDBPersistenceManager().getPrivateConnection(sConPasswd,getClass());
}
300
305
310
/**
* Returns a previously requested connection to the DBPersistenceManager
* for being used by others.
*
* @param con the Connection to be returned to the DBPersistenceManager
*/
protected void returnConnection(Connection con) {
Shop.getTheShop().getDBPersistenceManager().returnPrivateConnection(con);
}
315
320
/**
* Returns the same as {@link #toString()}, but may be used by remote-connections,
* as {@link java.rmi.server.RemoteObject#toString()} returns a description of the
* Stub at client-side
*
* @return the String describing this object
* @throws RemoteException whenever an error occurs during remote-access
*/
public String toRemoteString() throws RemoteException {
return toString();
}
325
330
335
ANHANG C. QUELLTEXTE
}
Listing C.41: epoint.data.rmi.CatalogItemIterator
5
10
15
20
25
30
35
40
/*
* CatalogItemIterator.java
*
* Created on 9. September 2001, 18:37
*/
package epoint.data.rmi;
import epoint.data.*;
import java.util.*;
import java.rmi.*;
import epoint.data.exception.*;
/**
* This implementation of a RemoteIterator is especially for CatalogItems in
* a {@link CatalogImpl}. In all meanings it behaves as a normal {@link java.util.Iterator Iterator}
* but is remotely available instead of locally generated. This iterator takes
* a snapshot of the visibile {@link Catalog#keySet(DataBasket)} and then
* returns the items, using the {@link Catalog#get(String,DataBasket)} method.
* VetoExceptions of listeners vetoing the editing of CatalogItems are always
* converted into RuntimeExceptions.<br>
* A temporary remotely available Listener is automatically attached to the
* Catalog for registering any concurrent modifications of the Catalog and then
* to set this Iterator as being invalid!
*
* @author Danny Poppe
* @version 1.0
*/
public class CatalogItemIterator extends RemoteIteratorImpl implements RemoteIterator {
/**
* The Catalog, this iterator has been created for.
*/
transient private Catalog c = null;
/**
* The DataBasket that is used to look at the items in the Catalog.
*/
transient private DataBasket db = null;
/**
* If <code>true</code> the CatalogItems that shall be returned are returned
* for bing edited by the given DataBasket.
*/
transient private boolean bForEdit = false;
C.1. PACKAGE EPOINT.DATA
45
50
55
60
65
70
75
80
85
90
95
100
105
110
115
120
125
/**
* If this boolean is <code>true</code>, this Iterator can be used to
* get the next elements if available and to remove the currently returned
* element. Otherwise a {@link ConcurrentModificationException} is thrown
* in these cases.
*/
transient private boolean bValid = true;
/**
* This List contains the keys of those CatalogItems, that are left to
* be returned by the iterator.
*/
transient private LinkedList lKeys = new LinkedList();
/**
* The currently returned CatalogItem.
*/
transient private CatalogItem ciCurrent = null;
/**
* A Listener derived from this class is attached to every Catalog for which
* this listener was created. It listens to any visible structural modification
* of the Catalog and informs the iterator if it is still valid (using
* {@link CatalogItemIterator#setInvalid(TransactionItem)}.
*
* @author Danny Poppe
* @version 1.0
*/
private class ModifyListener extends ContainerListenerAdapter {
/**
* The databasket for which the operation must be visible to
* set the iterator invalid.
*/
private DataBasket db;
/**
* The iterator, that shall be set invalid.
*/
private CatalogItemIterator it;
/**
* Creates a new Listener ready to be attached to any Catalog that generates
* an iterator.
*
* @param it the iterator that shall be informed about structural modifications
*
of the Catalog
* @param db the DataBasket for which the operation must be visible to have
*
an effect on the Stock
* @throws RemoteException whenever an error occurs during remote-access
*/
public ModifyListener(CatalogItemIterator it, DataBasket db) throws RemoteException {
this.it = it;
this.db = db;
}
/**
* Every committed add-action of another DataBasket sets the iterator to be invalid!
*
* @param e the events description
* @throws RemoteException whenever an error occurs during remote-access
*/
public void committedAddTransactionItem(ContainerChangeEvent e) throws RemoteException {
if (!e.getBasket().equals(db)) it.setInvalid(e.getAffectedItem());
}
/**
* Committed editing only affects the iterator, if this is done by
* another DataBasket.
*
* @param e the events description
* @throws RemoteException whenever an error occurs during remote-access
*/
public void committedEditTransactionItem(ContainerChangeEvent e) throws RemoteException {
if (!e.getBasket().equals(db)) it.setInvalid(e.getAffectedItem());
}
/**
* Committed remove-actions do not effect the iterator!
*
* @param e the events description
* @throws RemoteException whenever an error occurs during remote-access
*/
public void committedRemoveTransactionItem(ContainerChangeEvent e) throws RemoteException {
}
/**
* Add-actions only affect the Iterator, if the DataBaskets are equal!
*
* @param e the events description
* @throws RemoteException whenever an error occurs during remote-access
*/
public void addedItem(ContainerChangeEvent e) throws RemoteException {
if (e.getBasket().equals(db)) it.setInvalid(e.getAffectedItem());
}
/**
* Remove-actions always affect the iterator.
219
220
130
135
140
145
150
155
160
165
170
175
180
185
190
195
200
205
210
215
}
ANHANG C. QUELLTEXTE
*
* @param e the events description
* @throws RemoteException whenever an error occurs during remote-access
*/
public void removedTransactionItem(ContainerChangeEvent e) throws RemoteException {
it.setInvalid(e.getAffectedItem());
}
/**
* Rolled-back add-actions only affect the iterator, if they have the
* same DataBasket.
*
* @param e the events description
* @throws RemoteException whenever an error occurs during remote-access
*/
public void rolledbackAddTransactionItem(ContainerChangeEvent e) throws RemoteException {
if (e.getBasket().equals(db)) it.setInvalid(e.getAffectedItem());
}
/**
* Rolled-back remove-actions always affect the iterator.
*
* @param e the events description
* @throws RemoteException whenever an error occurs during remote-access
*/
public void rolledbackRemoveTransactionItem(ContainerChangeEvent e) throws RemoteException {
it.setInvalid(e.getAffectedItem());
}
/**
* Rolled back edit-actions only affect the iterator, if they have the
* same DataBasket.
*
* @param e the events description
* @throws RemoteException whenever an error occurs during remote-access
*/
public void rolledbackEditTransactionItem(ContainerChangeEvent e) throws RemoteException {
if (e.getBasket().equals(db)) it.setInvalid(e.getAffectedItem());
}
/**
* Editing does not affect the iterator.
*
* @param e the events description
* @throws RemoteException whenever an error occurs during remote-access
*/
public void editingTransactionItem(ContainerChangeEvent e) throws RemoteException {
}
/**
* Creates new CatalogItemIterator, that iterates over all CatalogItems in
* the given Catalog, that are visible to the given DataBasket.
*
* @param c the Catalog for which this iterator shall return items
* @param db the DataBasket that determines visibility of the CatalogItems
*
and relative to which the CatalogItems are edited
* @param bForEdit if <code>true</code> the CatalogItems will be returned
*
for editing (in this case, the DataBasket must not be <code>null</code>)
* @throws RemoteException whenever an error occurs during remote-access
*/
public CatalogItemIterator(Catalog c, DataBasket db, boolean bForEdit) throws RemoteException {
this.c = c;
this.db = db;
this.bForEdit = bForEdit;
this.lKeys.addAll(c.keySet(db));
c.addContainerListener(new ModifyListener(this,db));
}
/**
* Returns <code>true</code> only, if this Iterator has mor elements that can be retrieved
* by subsequent calls to {@link #next()}.
*
* @return <code>true</code> if {@link #next()} is able to return more CatalogItems
* @throws RemoteException whenever an error occurs during remote-access
*/
public boolean hasNext() throws RemoteException {
return lKeys.size() > 0;
}
/**
* Returns the next CatalogItem in this iterator.
*
* @return the next CatalogItem in this iterator
* @throws RemoteException whenever an error occurs during remote-access
* @throws NoSuchElementException if this iterator has no more elements that can be returned
* @throws ConcurrentModificationException if the underlying Catalog
*
has been concurrently modified (other than by this iterator)
*/
public Object next() throws RemoteException, NoSuchElementException, ConcurrentModificationException {
if (!hasNext()) throw new NoSuchElementException("There are no more elements available in this iterator!");
if (!bValid) throw new ConcurrentModificationException("The Catalog this iterator is based on, was concurrently modified!");
C.1. PACKAGE EPOINT.DATA
220
225
}
String sKey = (String)lKeys.removeFirst();
try {
return c.get(sKey,db,bForEdit);
} catch (VetoException ve) {
throw new IllegalStateException("A listener vetoes the editing of the requested item: "+ve);
} catch (DataBasketConflictException dbce) {
throw new IllegalStateException("Could not retrieve the item for editing, as your DataBasket conflicts with another: "+dbce);
}
/**
* Removes the currently returned CatalogItem from the underlying Catalog,
* using the provided DataBasket or an anonymous DataBasket by a call
* to the appropriate methods of {@link Catalog}.
*
* @throws RemoteException whenever an error occurs during remote-access
*/
public void remove() throws RemoteException {
if (!bValid) throw new ConcurrentModificationException("The Catalog this iterator is based on, was concurrently modified!");
try {
c.remove(ciCurrent.getKey(),db);
} catch (Exception ve) {
throw new RuntimeException(ve.getClass().getName()+" occured: "+ve.getMessage());
}
}
230
235
240
245
250
255
260
221
}
/**
* This method is called by the ModificationListener, if it detects a
* a modification of the underlying Stock. In this case the Listener is
* detached from the Stock and this Iterator is set to be invalid.
* Subsequent calls to {@link #next()} or {@link #remove()} will result
* in a {@link ConcurrentModificationException}.
*
* @param ti the StockItem that has caused the structural modification
*
of the Stock
* @throws RemoteException whenever an error occurs during the use of RMI
*/
public void setInvalid(TransactionItem ti) throws RemoteException {
CatalogItem ci = (CatalogItem)ti;
if (ci.equals(ciCurrent)) return;
this.bValid = false;
throw new DetachListenerException();
}
Listing C.42: epoint.data.rmi.ContainerListenerAdapter
5
10
15
20
25
30
35
/*
* ContainerListener.java
*
* Created on 12. Oktober 2001, 16:06
*/
package epoint.data.rmi;
import epoint.data.*;
import epoint.data.exception.*;
import java.rmi.RemoteException;
import java.beans.PropertyChangeEvent;
/**
* This is a default (empty) implementation of the {@link ContainerListener
* ContainerListener interface}. And may be subclassed by all ContainerListeners
* that shall be remotely available.
*
* @see ContainerListener
* @see ListenableContainer
* @author Danny Poppe
* @version 1.0
*/
public class ContainerListenerAdapter extends java.rmi.server.UnicastRemoteObject
/**
* Creates new ContainerListener
*
* @throws RemoteException whenever an error occurs during remote-access
*/
public ContainerListenerAdapter() throws RemoteException {
}
/**
* Called whenever a TransactionItem was added to a DBEntryTarget within
* a transaction that has not yet committed.
*
* @param e an event object describing the event.
* @throws RemoteException whenever an error occurs during remote-access
*/
implements
ContainerListener {
222
40
45
50
55
60
65
70
75
80
85
90
95
100
105
110
115
120
125
public void
}
ANHANG C. QUELLTEXTE
addedItem(ContainerChangeEvent e)
throws
RemoteException {
/**
* Called whenever the adding of a TransactionItem was commited. This is
* always after {@link #addedItem(ContainerChangeEvent)} has been called
* before.
*
* @param e an event object describing the event.
* @throws RemoteException whenever an error occurs during remote-access
*/
public void committedAddTransactionItem(ContainerChangeEvent e) throws RemoteException {
}
/**
* Called whenever the adding of a TransactionItem was rolled back. This is
* always after {@link #addedItem(ContainerChangeEvent)} has been called
* before.
*
* @param e an event object describing the event.
* @throws RemoteException whenever an error occurs during remote-access
*/
public void rolledbackAddTransactionItem(ContainerChangeEvent e) throws RemoteException {
}
/**
* Called to ask whether a TransactionItem may be removed within a transaction.
* If one of the listeners vetos the removal, all listeners that had already
* been asked will receive a {@link #noRemoveTransactionItem noRemoveTransactionItem}
* event, otherwise this event is followed by a {@link #removedTransactionItem(ContainerChangeEvent)}
* event.
*
* @param e an event object describing the event.
* @throws VetoException if the listener wants to veto the removal.
* @throws RemoteException whenever an error occurs during remote-access
*/
public void canRemoveTransactionItem(ContainerChangeEvent e) throws VetoException, RemoteException {
}
/**
* Called if this listener has already agreed with the removal of a TransactionItem, to
* signal, that the item is not removed.<br>
* This may be the case after a {@link #canRemoveTransactionItem(ContainerChangeEvent)}
* event.
*
* @param e an event object describing the event.
* @throws RemoteException whenever an error occurs during remote-access
*/
public void noRemoveTransactionItem(ContainerChangeEvent e) throws RemoteException {
}
/**
* Called whenever a TransactionItem is removed from the DBEntryTarget within
* a transaction. This event can only occur after the
* {@link #canRemoveTransactionItem(ContainerChangeEvent)} event successfully
* occured.
*
* @param e an event object describing the event.
* @throws RemoteException whenever an error occurs during remote-access
*/
public void removedTransactionItem(ContainerChangeEvent e) throws RemoteException {
}
/**
* Called whenever the removal of a TransactionItem has commited. This event
* can only occur after the {@link #removedTransactionItem(ContainerChangeEvent)}.
*
* @param e an event object describing the event.
* @throws RemoteException whenever an error occurs during remote-access
*/
public void committedRemoveTransactionItem(ContainerChangeEvent e) throws RemoteException {
}
/**
* Called whenever the removal of a TransactionItem was rolled back. This event
* can only occur after the {@link #removedTransactionItem(ContainerChangeEvent)}.
*
* @param e an event object describing the event.
* @throws RemoteException whenever an error occurs during remote-access
*/
public void rolledbackRemoveTransactionItem(ContainerChangeEvent e) throws RemoteException {
}
/**
* Called to ask whether a TransactionItem may be edited. If one of the listeners vetos the editing, all
* Listeners that had already been asked will receive a {@link #noEditTransactionItem noEditTransactionItem} event.
*
C.1. PACKAGE EPOINT.DATA
* @param e an event object describing the event.
* @throws VetoException if the listener wants to veto the editing.
* @throws RemoteException whenever an error occurs during remote-access
*/
public void canEditTransactionItem(ContainerChangeEvent e) throws VetoException, RemoteException {
}
130
135
/**
* Called for each listener that already agreed with an editing that was
* then rejected by another listener.
*
* @param e an event object describing the event.
* @throws RemoteException whenever an error occurs during remote-access
*/
public void noEditTransactionItem(ContainerChangeEvent e) throws RemoteException {
}
140
145
/**
* Called whenever editing a TransactionItem was started. This event may be
* accompanied by a <code>removedTransactionItem</code> and a
* <code>addedTransactionItem</code> event, but this is implementation-specific.
*
* @param e an event object describing the event.
* @throws RemoteException whenever an error occurs during remote-access
*/
public void editingTransactionItem(ContainerChangeEvent e) throws RemoteException {
}
150
155
/**
* Called whenever editing a TransactionItem was commited. This can only occur
* after an {@link #editingTransactionItem(ContainerChangeEvent)} has occured.
*
* @param e an event object describing the event.
* @throws RemoteException whenever an error occurs during remote-access
*/
public void committedEditTransactionItem(ContainerChangeEvent e) throws RemoteException {
}
160
165
/**
* Called whenever editing a TransactionItem was rolled back. This can only occur
* after an {@link #editingTransactionItem(ContainerChangeEvent)} has occured.
*
* @param e an event object describing the event.
* @throws RemoteException whenever an error occurs during remote-access
*/
public void rolledbackEditTransactionItem(ContainerChangeEvent e) throws RemoteException {
}
170
175
/**
* Called whenever a specific property of the container has changed.
* This is implemented for at least the name of the container.
*
* @param event an event object describing the event
* @throws RemoteException whenever an error occurs during remote-access
*/
public void propertyChange(PropertyChangeEvent event) throws RemoteException {
}
180
185
}
Listing C.43: epoint.data.rmi.CountingStockImpl
5
10
15
20
package epoint.data.rmi;
import java.rmi.RemoteException;
import epoint.data.*;
import epoint.data.exception.*;
import java.util.Iterator;
import epoint.sale.Shop;
import epoint.db.exception.DBAccessException;
import java.util.NoSuchElementException;
/**
* This is the default-implementation of the {@link CountingStock
* CountingStock interface}. For more documentation about CountingStocks read
* the documentation of {@link CountingStock} and {@link AbstractStock}.<br>
* <br>
* This CountingStock uses a normal StockItem for each transaction that adds
* or removes a number of StockItems for a key in the BaseCatalog. The StockItems
* itself internally contain a {@link DecimalValue} to represent the number
* of items added or removed.<br>
* If StockItems are added as an object, only their key is evaluated and used
* to add a single element for that key. If StockItems are to be returned, they
* are normally not persistent and contain a StringValue that describes their
* association to the corresponding StockItem.<br>
* StockItems of a CountingStock always loose their object-identity and can never
* be edited directly! That means in contrast to a {@link StoringStock} the
223
224
25
30
35
40
45
50
55
60
65
70
75
80
85
90
95
100
105
110
* StockItems added to a CountingStock can never be retrieved as the objects
* they have been.<br>
* <br>
* As CountingStocks are remotely available Containers, they must be instantiated
* at the Shop, which means, they need to be requested by a client (EPoint) using
* the {@link epoint.sale.ShopServices ShopServices}.
*
* @see AbstractStock
* @see StoringStock
* @see StockItem
* @author Danny Poppe
* @version 1.0
*/
public class CountingStockImpl extends AbstractStock implements CountingStock {
////////////////////////////////////////////////////////////////////////////////
//
NEW CLASS CONSTANTS
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//
NEW CLASS VARIABLES
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//
OVERRIDDEN CLASS VARIABLES
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//
NEW INSTANCE VARIABLES
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//
OVERRIDDEN INSTANCE VARIABLES
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//
CONSTRUCTORS
////////////////////////////////////////////////////////////////////////////////
/**
* Creates a new CountingStock with the given name and based upon the
* given Catalog, which is automatically made persistent.
*
* @param sName the name of this CountingStock, which must be unique under
*
all names of existing Stocks
* @param cBaseCatalog the base-Catalog for this Stock
* @throws RemoteException whenever an error occurs during remote-access
*/
public CountingStockImpl(String sName, Catalog cBaseCatalog) throws RemoteException {
super(sName,cBaseCatalog);
}
////////////////////////////////////////////////////////////////////////////////
//
INTERFACE PersistentObject
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//
INTERFACE Stock
////////////////////////////////////////////////////////////////////////////////
/**
* Always returns <code>true</code> for this class and all sub-classes, to
* indicate this Stock as beeing a CountingStock.
*
* @return <code>true</code>
* @throws RemoteException whenever an error occurs during remote-access
*/
nal public boolean isCountingStock() throws RemoteException {
return true;
}
/**
* Adds a number of counting StockItems for the given key in the BaseCatalog
* to this CountingStock, using the given DataBasket
*
* @param sKey the key of the CatalogItem in the BaseCatalog for which to add
*
a number of counting StockItems
* @param iCount the number of virtually added StockItems, which are counting
*
the associated CatalogItem
* @param db the DataBasket that symbolizes the transaction in which to perform
*
this action
* @throws NotEditableException if this Stock is currently not editable
* @throws IllegalArgumentException if the <code>iCount</code> is negative
* @throws RemoteException whenever an error occurs during remote-access
* @throws NoSuchKeyException if the given key does not exist in the base-catalog,
*
or is at least not visible to the given databasket
ANHANG C. QUELLTEXTE
C.1. PACKAGE EPOINT.DATA
*/
public void
115
120
125
130
135
140
145
150
155
160
165
170
175
180
185
190
195
try
{
225
add(String sKey, int iCount, DataBasket db)
IllegalArgumentException {
throws
RemoteException, NoSuchKeyException, NotEditableException,
synchronized(getPropertyLock()) {
if (iCount == 0) return;
if (iCount < 0) throw new IllegalArgumentException("You
if
(!isEditable())
the remove-method instead!");
throw new NotEditableException("The
if (db == null) {
add(new StockItemImpl(sKey,null));
return;
}
if
cannot add a negative number of StockItems to a CountingStock! Use
stock " + getName() + " is currently not editable!");
(getCatalog().contains(sKey,db)) {
synchronized(getItemsLock()) {
Iterator itAdded = getAddedItems(sKey).iterator();
StockItem siAdded = null;
Iterator itRemoved = getRemovedItems(sKey).iterator();
StockItem siRemoved = null;
while(itAdded.hasNext()) {
siAdded = (StockItem)itAdded.next();
if (siAdded.getBasket().equals(db))
break;
siAdded = null;
}
while(itRemoved.hasNext()) {
siRemoved = (StockItem)itRemoved.next();
if (siRemoved.getBasket().equals(db))
break;
siRemoved = null;
}
if (siRemoved != null) {
// ok there were removed items in this transaction before... lets
// correct the number...
DecimalValue vOld = (DecimalValue)siRemoved.getValue();
DecimalValue vNew = new DecimalValue(vOld.longValue() - iCount);
siRemoved.setValue(vNew);
// if the new number is zero or less, we dont have a removing-transaction anymore
if (vNew.longValue() <= 0) {
// the new number of added items results in the
// elimination of a previous removing-transaction
// so lets rollback the previous removal-transaction
// and maybe create a new adding-transaction instead
RemoteIterator it = db.iterator(siRemoved);
DBEntry dbe = null;
while (it.hasNext()) {
dbe = (DBEntry)it.next();
if (dbe.getTarget().equals(this) && ((StockItem)dbe.getTransactionItem()).getName().equals(sKey))
break;
dbe = null;
}
if (dbe == null)
throw new RuntimeException("It seems as we have an internal Framework-Error! A DataBasket does not contain
the appropriate entry for the removal of a Stocktem!");
String sPasswd = "Protected connection #" + Shop.getTheShop().getGlobalUniqueNumber();
RemoteRunnable r = null;
try {
// this implicitly fires a rolledBack-event
r = rollbackTransaction(dbe, sPasswd);
db.removeTransaction(dbe, sPasswd);
} catch(RuntimeException rte) {
returnConnection(getReservedConnection(sPasswd));
throw rte;
}
returnConnection(getReservedConnection(sPasswd));
r.run(null);
// if the new number of items is greater than the removed items before
// we need to create an adding transaction, we do this in a recursive call
// which terminates without doing something in the case of zero items added
add(sKey,vNew.negate().intValue(),db);
}
} else if(siAdded != null) {
// we have already added some items in this transaction ... so lets correct
// the number
int iOld = ((DecimalValue)siAdded.getValue()).intValue();
siAdded.setValue(new DecimalValue(iOld+iCount));
StockItem siEvent = siAdded.getShallowClone(true);
siEvent.setPersistent(null);
siEvent.setValue(new DecimalValue(iCount));
fireAddedItem(new ContainerChangeEventImpl(siEvent,this,db));
} else {
// we have to create a new DBEntry that adds the new StockItem
String sConPasswd = "Private Connection-Adding to CountingStock #"+Shop.getTheShop().getGlobalUniqueNumber();
try {
getReservedConnection(sConPasswd).setAutoCommit(false);
} catch (Exception e) {
Shop.getTheShop().logException(e);
226
ANHANG C. QUELLTEXTE
200
205
210
215
220
225
}
230
}
235
}
240
245
250
255
260
265
270
275
280
}
}
}
}
}
}
StockItem si = new StockItemImpl(sKey, new DecimalValue(iCount));
getAddedItems(sKey).add(si);
si.setStock(this,sConPasswd);
try {
Shop.getTheShop().getDBPersistenceManager().createStockItem(si, true, false, getReservedConnection(sConPasswd));
} catch(DBAccessException e) {
Shop.getTheShop().logException(e);
try {
getReservedConnection(sConPasswd).rollback();
} catch (Exception e2) {
Shop.getTheShop().logException(e2);
} nally {
returnConnection(getReservedConnection(sConPasswd));
}
throw new RuntimeException("An error occured while accessing the database: "+e);
}
DBEntry dbe = new DBEntryImpl(db, DBEntryImpl.TRANSACTION_ADD, this, si, null);
db.add(dbe,sConPasswd);
si.setBasket(db,sConPasswd);
try {
getReservedConnection(sConPasswd).commit();
} catch (Exception e) {
Shop.getTheShop().logException(e);
throw new RuntimeException("An error occured while accessing the database: "+e);
} nally {
returnConnection(getReservedConnection(sConPasswd));
}
fireAddedItem(new ContainerChangeEventImpl(si, this, db));
else {
throw new
NoSuchKeyException("The name of the StockItem you want to add does not match the key of a visible CatalogItem
in the BaseCatalog of this Stock!");
catch(RuntimeException
e) {
Shop.getTheShop().logException(e);
throw e;
/**
* This method adds a single StockItem that counts one for the CatalogItem in
* the base-catalog that has the same key as the name of the StockItem.
* This operation is performed, using an anonymous DataBasket.
*
* @param si the StockItem which name is used to determine the key of
*
the CatalogItem for which to add a couning StockItem
* @throws RemoteException whenever an error occurs during remote-access
* @throws NoSuchKeyException if the name of the StockItem does not match a
*
visible key of a CatalogItem in the base-catalog
*/
public void add(StockItem si) throws RemoteException, NoSuchKeyException {
add(si.getName(),1);
}
/**
* This method adds the specified number of counting StockItems to the
* CatalogItem with the given key in the base-catalog. This is done,
* using an anonymous DataBasket.
*
* @param sKey the key of the CatalogItem for which to add the StockItems
* @param iCount the number of counting StockItems to add
* @throws RemoteException whenever an error occurs during remote-access
* @throws NoSuchKeyException if the name of the StockItem does not match a
*
visible key of a CatalogItem in the base-catalog
*/
public void add(String sKey, int iCount) throws RemoteException, NoSuchKeyException {
DataBasket db = new DataBasketImpl();
add(sKey,iCount,db);
db.commit();
try {
Shop.getTheShop().getDBPersistenceManager().deleteDataBasket(db, getDefaultConnection());
returnConnection(getDefaultConnection());
} catch (Exception e) {
returnConnection(getDefaultConnection());
Shop.getTheShop().logException(e);
}
/*
synchronized (getPropertyLock()) {
if (getCatalog().contains(sKey,null)) {
synchronized (getItemsLock()) {
StockItem si = null;
if (getStockItems(sKey).size() != 0) {
si = (StockItem)getStockItems(sKey).iterator().next();
si.setValue(((DecimalValue)si.getValue()).add(new DecimalValue(iCount)));
} else {
C.1. PACKAGE EPOINT.DATA
285
}
290
}
295
300
305
310
315
320
325
330
335
340
345
350
355
360
365
}
*/
227
si = new StockItemImpl(sKey,new DecimalValue(iCount));
getStockItems(sKey).add(si);
}
} else throw new NoSuchKeyException("The name of the StockItem you want to add does not match the key of a visible CatalogItem in
the BaseCatalog of this Stock!");
/**
* This method adds a single StockItem that counts one for the CatalogItem in
* the base-catalog that has the same key as the name of the StockItem.
* This operation is performed, using the given DataBasket.
*
* @param si the StockItem which name is used to determine the key of
*
the CatalogItem for which to add a couning StockItem
* @param db the DataBasket, used for this transaction
* @throws RemoteException whenever an error occurs during remote-access
* @throws NoSuchKeyException if the name of the StockItem does not match a
*
visible key of a CatalogItem in the base-catalog
*/
public void add(StockItem si, DataBasket db) throws RemoteException, NoSuchKeyException {
if (db == null) {
add (si.getName(),1);
} else {
add(si.getName(),1,db);
}
}
/**
* Removes a number of counting StockItems for the given key in the BaseCatalog
* from this CountingStock, using the given DataBasket.
*
* @param sKey the key of the CatalogItem in the BaseCatalog for which to remove
*
a number of counting StockItems
* @param iCount the number of virtually removed StockItems, which are counting
*
the associated CatalogItem
* @param db the DataBasket that symbolizes the transaction in which to perform
*
this action
* @throws RemoteException whenever an error occurs during remote-access
* @throws IllegalArgumentException if the number of StockItems that shall be removed
*
exceeds the number of available StockItems or if the provided number
*
is negative
* @throws VetoException if a registered Listener vetoes the removal of the StockItems
*/
public void remove(String sKey, int iCount, DataBasket db) throws RemoteException, VetoException, IllegalArgumentException {
try {
synchronized(getPropertyLock()) {
if (iCount == 0) return;
if (iCount < 0) throw new IllegalArgumentException("You cannot remove a negative number of StockItems from a CountingStock!
Use the add-method instead!");
if (iCount > size(sKey,db)) throw new IllegalArgumentException("You cannot remove more StockItems from a CountingStock than
available!");
if (!isEditable())
throw new NotEditableException("The stock " + getName() + " is currently not editable!");
if (db == null) {
remove(sKey,iCount);
return;
}
//if (getCatalog().contains(sKey,db)) {
synchronized(getItemsLock()) {
Iterator itAdded = getAddedItems(sKey).iterator();
StockItem siAdded = null;
Iterator itRemoved = getRemovedItems(sKey).iterator();
StockItem siRemoved = null;
while(itAdded.hasNext()) {
siAdded = (StockItem)itAdded.next();
if (siAdded.getBasket().equals(db)) break;
siAdded = null;
}
while(itRemoved.hasNext()) {
siRemoved = (StockItem)itRemoved.next();
if (siRemoved.getBasket().equals(db)) break;
siRemoved = null;
}
if (siAdded != null) {
// ok there were added items in this transaction before... lets
// correct the number...
StockItem siEvent = siAdded.getShallowClone(true);
siEvent.setPersistent(null);
siEvent.setValue(new DecimalValue(iCount));
siEvent.setStock(this,null);
fireCanRemoveTransactionItem(new ContainerChangeEventImpl(siEvent,this,db));
DecimalValue vOld = (DecimalValue)siAdded.getValue();
DecimalValue vNew = new DecimalValue(vOld.longValue() - iCount);
// if the new number is zero or less, we dont have an adding-transaction anymore
if (vNew.longValue() <= 0) {
228
370
375
380
385
390
395
400
405
410
415
420
425
430
435
440
445
450
455
ANHANG C. QUELLTEXTE
// the new number of removed items results in the
// elimination of a previous adding-transaction
// so lets rollback the previous adding-transaction
// and maybe create a new removing-transaction instead
RemoteIterator it = db.iterator(siAdded);
DBEntry dbe = null;
while (it.hasNext()) {
dbe = (DBEntry)it.next();
if (dbe.getTarget().equals(this) && ((StockItem)dbe.getTransactionItem()).getName().equals(sKey))
break;
dbe = null;
}
if (dbe == null)
throw new RuntimeException("It seems as we have an internal Framework-Error! A DataBasket does not contain
the appropriate entry for the addition of a Stocktem!");
String sPasswd = "Protected connection #" + Shop.getTheShop().getGlobalUniqueNumber();
RemoteRunnable r = null;
try {
// this implicitly fires a rolledBack-event
r = rollbackTransaction(dbe, sPasswd);
db.removeTransaction(dbe, sPasswd);
} catch(RuntimeException rte) {
throw rte;
} nally {
returnConnection(getReservedConnection(sPasswd));
}
r.run(null);
// if the new number of items is greater than the removed items before
// we need to create an adding transaction, we do this in a recursive call
// which terminates without doing something in the case of zero items added
remove(sKey,vNew.negate().intValue(),db);
} else {
siAdded.setValue(vNew);
}
} else if(siRemoved != null) {
// we have already removed some items in this transaction ... so lets correct
// the number
int iOld = ((DecimalValue)siRemoved.getValue()).intValue();
StockItem siEvent = siRemoved.getShallowClone(true);
siEvent.setPersistent(null);
siEvent.setValue(new DecimalValue(iCount));
siEvent.setStock(this,null);
fireCanRemoveTransactionItem(new ContainerChangeEventImpl(siEvent,this,db));
siRemoved.setValue(new DecimalValue(iOld+iCount));
Iterator it = getStockItems(sKey).iterator();
StockItem siNormal = (StockItem)it.next();
siNormal.setValue(((DecimalValue)siNormal.getValue()).subtract(new DecimalValue(iCount)));
if (((DecimalValue)siNormal.getValue()).intValue() == 0) {
Shop.getTheShop().getDBPersistenceManager().deleteStockItem(siNormal,getDefaultConnection());
returnConnection(getDefaultConnection());
getStockItems(sKey).remove(siNormal);
}
fireRemovedTransactionItem(new ContainerChangeEventImpl(siEvent,this,db));
} else {
// we have to create a new DBEntry that removes the new StockItem
StockItem si = new StockItemImpl(sKey, new DecimalValue(iCount));
si.setStock(this,null);
fireCanRemoveTransactionItem(new ContainerChangeEventImpl(si,this,db));
Iterator it = getStockItems(sKey).iterator();
StockItem siNormal = (StockItem)it.next();
siNormal.setValue(((DecimalValue)siNormal.getValue()).subtract(new DecimalValue(iCount)));
String sConPasswd = "Private Connection-Adding to CountingStock #"+Shop.getTheShop().getGlobalUniqueNumber();
try {
getReservedConnection(sConPasswd).setAutoCommit(false);
} catch (Exception e) {
Shop.getTheShop().logException(e);
}
getRemovedItems(sKey).add(si);
try {
Shop.getTheShop().getDBPersistenceManager().createStockItem(si, false, true, getReservedConnection(sConPasswd));
if (((DecimalValue)siNormal.getValue()).intValue() == 0) {
Shop.getTheShop().getDBPersistenceManager().deleteStockItem(siNormal,getReservedConnection(sConPasswd));
getStockItems(sKey).remove(siNormal);
}
} catch(DBAccessException e) {
Shop.getTheShop().logException(e);
try {
getReservedConnection(sConPasswd).rollback();
} catch (Exception e2) {
Shop.getTheShop().logException(e2);
} nally {
returnConnection(getReservedConnection(sConPasswd));
}
throw new RuntimeException("An error occured while accessing the database: "+e);
}
DBEntry dbe = new DBEntryImpl(db, DBEntryImpl.TRANSACTION_REMOVE, this, si, null);
db.add(dbe,sConPasswd);
si.setBasket(db,sConPasswd);
C.1. PACKAGE EPOINT.DATA
229
try
{
getReservedConnection(sConPasswd).commit();
(Exception e) {
Shop.getTheShop().logException(e);
throw new RuntimeException("An error occured while accessing the database: "+e);
} nally {
returnConnection(getReservedConnection(sConPasswd));
}
fireRemovedTransactionItem(new ContainerChangeEventImpl(si, this, db));
}
460
465
470
}
475
480
485
490
495
500
505
510
515
520
525
530
535
540
}
catch
}
}
//} else {
//throw new NoSuchKeyException("The name of the StockItem you want to remove does not match the key of a visible CatalogItem
in the BaseCatalog of this Stock!");
//}
catch(RuntimeException
e) {
Shop.getTheShop().logException(e);
throw e;
}
}
/**
* Decreases the number of StockItems for the associated CatalogItem by one.
* This may result in a {@link NoSuchElementException} if there are no
* more StockItems for the associated CatalogItem.
*
* @param si the StockItem for which the number of associated CatalogItems is
*
decreased
* @param db the DataBasket that symbolizes the transaction in which to
*
remove the StockItem
* @return a non-persistent StockItem as a symbol for the removed counting StockItem
* @throws RemoteException whenever an error occurs during remote-access
* @throws VetoException if a listener vetoes the removal of the given StockItem
*/
public StockItem remove(StockItem si, DataBasket db) throws RemoteException, VetoException {
if (db == null) {
remove(si.getName(),1);
} else {
remove(si.getName(),1,db);
}
return new StockItemImpl(si.getName(),new StringValue(getName()+" - CountingStockItem"));
}
/**
* Decreases the number of StockItems for the associated CatalogItem by one,
* using an anonymous DataBasket.
* This may result in a {@link NoSuchElementException} if there are no
* more StockItems for the associated CatalogItem.
*
* @param si the StockItem for which the number of associated CatalogItems is
*
decreased
* @return a non-persistent StockItem as a symbol for the removed counting StockItem
* @throws RemoteException whenever an error occurs during remote-access
* @throws VetoException if a listener vetoes the removal of the given StockItem
*/
public StockItem remove(StockItem si) throws RemoteException, VetoException {
remove(si.getName(),1);
return new StockItemImpl(si.getName(),new StringValue(getName()+" - CountingStockItem"));
}
// documentation is inherited
public void remove(String sKey, int iCount) throws RemoteException, VetoException {
DataBasket db = new DataBasketImpl();
remove(sKey,iCount,db);
db.commit();
try {
Shop.getTheShop().getDBPersistenceManager().deleteDataBasket(db, getDefaultConnection());
returnConnection(getDefaultConnection());
} catch (Exception e) {
returnConnection(getDefaultConnection());
Shop.getTheShop().logException(e);
}
}
/**
* This method returns an iterator over newly created instances of {@link StockItem}
* for the number of StockItems visible to the given DataBasket for the given key in
* this CountingStock.<br>
* Manipulations of these instances do not reflect in any changes within the Stock,
* so it is meaningless to supply a <code>true</code> or <code>false</code>
* for <code>bForEdit</code>.<br>
* See {@link ASStockItemIterator} for more details about the returned Iterator
*
* @param sKey the key of the CatalogItem for which to return the iterator
* @param bForEdit <code>true</code> if the returned instance shall be editable
* @param db the DataBasket that shall be used to look at the StockItems
* @return {@inheritDoc}
* @throws RemoteException whenever an error occurs during remote-access
*/
230
545
550
555
560
565
570
575
580
585
590
595
600
605
610
615
620
625
630
public RemoteIterator get(String sKey, boolean
return super.get(sKey,bForEdit,db);
ANHANG C. QUELLTEXTE
bForEdit, DataBasket db)
throws
RemoteException {
}
/**
* This method returns an iterator over newly created instances of {@link StockItem}
* for the number of all StockItems visible to a <code>null</code> DataBasket
* for the given key in this CountingStock.<br>
* See {@link ASStockItemIterator} for more details about the returned Iterator.
*
* @param sKey the key of the CatalogItem for which to return the iterator
* @return {@inheritDoc}
* @throws RemoteException whenever an error occurs during remote-access
*/
public RemoteIterator get(String sKey) throws RemoteException {
return super.get(sKey);
}
/**
* Returns an iterator over all StockItems in this CountingStock. The newly
* created instances contain a {@link StringValue}, that contains
* information about the corresponding key and number of the StockItem for
* the key.<br>
* See {@link ASStockItemIterator} for more details about the returned Iterator.
*
* @param bForEdit <code>true</code> if the returned instances of StockItem
*
shall be editable
* @param db the DataBasket that shall be used to look at the StockItems
* @return {@inheritDoc}
* @throws RemoteException whenever an error occurs during remote-access
*/
public RemoteIterator iterator(DataBasket db, boolean bForEdit) throws RemoteException {
return super.iterator(db, bForEdit);
}
/**
* Returns an iterator over all StockItems associated with the given key
* in this CountingStock. The newly
* created instances contain a {@link StringValue}, that contains
* information about the corresponding key and number of the StockItem for
* the key.<br>
* See {@link ASStockItemIterator} for more details about the returned Iterator.
*
* @param sKey the key for which to return the StockItems in this Stock
* @param bForEdit <code>true</code> if the returned instances of StockItem
*
shall be editable
* @param db the DataBasket that shall be used to look at the StockItems
* @return {@inheritDoc}
* @throws RemoteException whenever an error occurs during remote-access
*/
public RemoteIterator iterator(String sKey, DataBasket db, boolean bForEdit) throws RemoteException {
return super.iterator(sKey, db, bForEdit);
}
/**
* Sums up all StockItems in this CountingStock, that are visbile to
* the given DataBasket.
*
* @param db the DataBasket that shall be used to look at the StockItems
* @return the sum of all visible StockItems
* @throws RemoteException whenever an error occurs during remote-access
*/
public int size(DataBasket db) throws RemoteException {
int iSum = 0;
Iterator it = keySet(db).iterator();
while (it.hasNext()) {
String sKey = (String)it.next();
if (!getCatalog().contains(sKey,db)) continue;
Iterator itNormal = getStockItems(sKey).iterator();
Iterator itAdded = getAddedItems(sKey).iterator();
while (itNormal.hasNext()) {
StockItem si = (StockItem)itNormal.next();
iSum += ((DecimalValue)si.getValue()).intValue();
}
while (itAdded.hasNext()) {
StockItem si = (StockItem)itAdded.next();
if (si.getBasket().equals(db))
iSum += ((DecimalValue)si.getValue()).intValue();
}
}
return iSum;
}
/**
* Returns the sum of StockItems accessible with the given DataBasket, that
* are available for the given key.
*
* @param sKey the key for which to return this CountingStocks size
* @param db the DataBasket to use for looking at the specified item
C.1. PACKAGE EPOINT.DATA
635
640
645
650
655
660
665
670
675
680
685
690
695
700
705
710
715
231
* @return the number of visible counting StockItems for the given key in this Stock
* @throws RemoteException whenever an error occurs during remote-access
*/
public int size(String sKey, DataBasket db) throws RemoteException {
if (!getCatalog().contains(sKey,db)) return 0;
int iSum = 0;
Iterator it = getStockItems(sKey).iterator();
while (it.hasNext()) {
StockItem si = (StockItem)it.next();
iSum += ((DecimalValue)si.getValue()).intValue();
}
it = getAddedItems(sKey).iterator();
while (it.hasNext()) {
StockItem si = (StockItem)it.next();
if (si.getBasket().equals(db))
iSum += ((DecimalValue)si.getValue()).intValue();
}
return iSum;
}
////////////////////////////////////////////////////////////////////////////////
//
INTERFACE DBEntryTarget
////////////////////////////////////////////////////////////////////////////////
// documentation is inherited
public boolean isValidTransaction(DBEntry dbe) throws RemoteException {
if (!(dbe.getTransactionItem() instanceof StockItem)) return false;
if (!dbe.getTarget().equals(this)) return false;
StockItem si = (StockItem)dbe.getTransactionItem();
if (!(si.getValue() instanceof DecimalValue)) return false;
DataBasket db = dbe.getBasket();
DecimalValue dv = (DecimalValue)si.getValue();
DecimalValue dvCheck = new DecimalValue(0);
Iterator it = null;
if (dbe.getTransactionType().equals(dbe.TRANSACTION_ADD)) {
it = getAddedItems(si.getName()).iterator();
} else if (dbe.getTransactionType().equals(dbe.TRANSACTION_REMOVE)) {
it = getRemovedItems(si.getName()).iterator();
} else return false;
while (it.hasNext()) {
StockItem siTemp = (StockItem)it.next();
if (!siTemp.getBasket().equals(db)) {
continue;
} else {
dvCheck = dvCheck.add((DecimalValue)siTemp.getValue());
}
}
if (dv.equals(dvCheck)) {
return true;
} else return false;
}
// documentation is inherited
public RemoteRunnable commitTransaction(DBEntry dbe, String sConPasswd) throws RemoteException {
synchronized (getPropertyLock()) {
synchronized (getItemsLock()) {
if (!isValidTransaction(dbe)) throw new RuntimeException("Detected illegal transaction-data in a DBEntry deliviered to commit
in CountingStock!");
java.sql.Connection con = getReservedConnection(sConPasswd);
StockItem siCommit = (StockItem)dbe.getTransactionItem();
System.out.println("\n\nCommitting : "+dbe.getTransactionType()+" of "+siCommit.toRemoteString());
System.out.println("\t"+siCommit.getValue());
System.out.println("\t"+siCommit.getOID());
System.out.println("\t"+siCommit.getTransactionItemIdentifier());
DecimalValue dvCommit = (DecimalValue)siCommit.getValue();
if (dbe.getTransactionType().equals(dbe.TRANSACTION_ADD)) {
// an adding transaction has to commit now
Iterator it = getAddedItems(siCommit.getName()).iterator();
StockItem siAdded = null;
System.out.println("\n\nSize of added is "+getAddedItems(siCommit.getName()).size());
while (it.hasNext()) {
siAdded = (StockItem)it.next();
if (siAdded.getBasket().equals(dbe.getBasket())) {
System.out.println("Found "+siAdded.toRemoteString());
it.remove();
System.out.println("\n\nSize of added is "+getAddedItems(siCommit.getName()).size());
break;
}
siAdded = null;
}
if (siAdded == null) throw new RuntimeException("Found no added item for a registered adding-transaction of a DBEntry in
a CountingStock!");
DecimalValue dvAdded = (DecimalValue)siAdded.getValue();
if (dvAdded.subtract(dvCommit).intValue() != 0) throw new RuntimeException("Found a registered adding transaction that
adds more or less CountingStockItems than registered in the CountingStock itself for this transaction!");
try {
Shop.getTheShop().getDBPersistenceManager().deleteStockItem(siAdded,con);
siAdded.setStock(null,sConPasswd);
232
ANHANG C. QUELLTEXTE
}
720
725
730
735
740
745
750
755
760
765
770
775
780
785
790
795
800
siAdded.setBasket(null,sConPasswd);
(Exception e) {
getAddedItems(siCommit.getName()).add(siAdded);
Shop.getTheShop().logException(e);
siAdded.setStock(this,sConPasswd);
siAdded.setBasket(dbe.getBasket(),sConPasswd);
throw new RuntimeException("Could not commit into defined persistent state, as an Exception occured: "+e);
catch
}
System.out.println("Size fo normal StockItems is: "+getStockItems(siCommit.getName()).size());
if (getStockItems(siCommit.getName()).size() == 0) {
StockItem siNew = new StockItemImpl(siCommit.getName(),dvCommit);
siNew.setStock(this,sConPasswd);
siNew.setBasket(dbe.getBasket(),sConPasswd);
try {
Shop.getTheShop().getDBPersistenceManager().createStockItem(siNew,false,false,con);
} catch (Exception e) {
Shop.getTheShop().logException(e);
throw new RuntimeException("Could not commit into defined persistent state, as an Exception occured: "+e);
}
siNew.setBasket(null,sConPasswd);
getStockItems(siNew.getName()).add(siNew);
} else {
StockItem si = (StockItem)getStockItems(siCommit.getName()).iterator().next();
si.setValue(((DecimalValue)si.getValue()).add(dvCommit), sConPasswd);
}
nal StockItem siThread = new StockItemImpl(siCommit.getName(),new DecimalValue(dvAdded.intValue()));
nal CountingStockImpl thisStock = this;
nal DataBasket dbThread = dbe.getBasket();
RemoteRunnable r = new RemoteRunnableImpl() {
public void run(Object[] o) throws RemoteException {
try {
thisStock.fireCommittedAddTransactionItem(new ContainerChangeEventImpl(siThread,thisStock,dbThread));
} catch (RemoteException rmie) {
Shop.getTheShop().logException(rmie);
}
}
};
return r;
} else if (dbe.getTransactionType().equals(dbe.TRANSACTION_REMOVE)) {
// a removing transaction has to commit now
Iterator it = getRemovedItems(siCommit.getName()).iterator();
StockItem siRemoved = null;
while (it.hasNext()) {
siRemoved = (StockItem)it.next();
if (siRemoved.getBasket().equals(dbe.getBasket())) {
it.remove();
break;
}
siRemoved = null;
}
if (siRemoved == null) throw new RuntimeException("Found no removed item for a registered removing-transaction of a
DBEntry in a CountingStock!");
DecimalValue dvRemoved = (DecimalValue)siRemoved.getValue();
if (dvRemoved.subtract(dvCommit).intValue() != 0) throw new RuntimeException("Found a registered removing transaction
that removes more or less CountingStockItems than registered in the CountingStock itself for this
transaction!");
try {
Shop.getTheShop().getDBPersistenceManager().deleteStockItem(siRemoved,con);
siRemoved.setStock(null,sConPasswd);
siRemoved.setBasket(null,sConPasswd);
} catch (Exception e) {
getRemovedItems(siCommit.getName()).add(siRemoved);
Shop.getTheShop().logException(e);
siRemoved.setStock(this,sConPasswd);
siRemoved.setBasket(dbe.getBasket(),sConPasswd);
throw new RuntimeException("Could not commit into defined persistent state, as an Exception occured: "+e);
}
nal StockItem siThread = new StockItemImpl(siCommit.getName(),new DecimalValue(dvRemoved.intValue()));
nal CountingStockImpl thisStock = this;
nal DataBasket dbThread = dbe.getBasket();
RemoteRunnable r = new RemoteRunnableImpl() {
public void run(Object[] o) throws RemoteException {
try {
thisStock.fireCommittedRemoveTransactionItem(new ContainerChangeEventImpl(siThread,thisStock,dbThread));
} catch (RemoteException rmie) {
Shop.getTheShop().logException(rmie);
}
}
};
return r;
} else {
throw new RuntimeException("Found illegal transaction-type \""+dbe.getTransactionType()+"\" to commit in CountingStock!")
;
}
}
}
}
C.1. PACKAGE EPOINT.DATA
805
810
815
820
825
830
835
840
845
850
855
860
865
870
875
880
233
// documentation is inherited
public RemoteRunnable rollbackTransaction(DBEntry dbe, String sConPasswd) throws RemoteException {
synchronized (getPropertyLock()) {
synchronized (getItemsLock()) {
if (!isValidTransaction(dbe)) throw new RuntimeException("Detected illegal transaction-data in a DBEntry deliviered to commit
in CountingStock!");
java.sql.Connection con = getReservedConnection(sConPasswd);
StockItem siRollback = (StockItem)dbe.getTransactionItem();
System.out.println("\n\nCommitting : "+dbe.getTransactionType()+" of "+siRollback.toRemoteString());
System.out.println("\t"+siRollback.getValue());
System.out.println("\t"+siRollback.getOID());
System.out.println("\t"+siRollback.getTransactionItemIdentifier());
DecimalValue dvRollback = (DecimalValue)siRollback.getValue();
String sKey = siRollback.getName();
if (dbe.getTransactionType().equals(dbe.TRANSACTION_ADD)) {
// an adding transaction has to be rolled now
System.out.println("\n\nSize of added is "+getAddedItems(siRollback.getName()).size());
Iterator it = getAddedItems(sKey).iterator();
StockItem siAdded = null;
while (it.hasNext()) {
siAdded = (StockItem)it.next();
if (siAdded.getBasket().equals(dbe.getBasket())) {
System.out.println("Found "+siAdded.toRemoteString());
it.remove();
System.out.println("\n\nSize of added is "+getAddedItems(siRollback.getName()).size());
break;
}
siAdded = null;
}
if (siAdded == null) throw new RuntimeException("Found no added item for a registered adding-transaction of a DBEntry in
a CountingStock!");
DecimalValue dvAdded = (DecimalValue)siAdded.getValue();
if (dvAdded.subtract(dvRollback).intValue() != 0) throw new RuntimeException("Found a registered adding transaction that
adds more or less CountingStockItems than registered in the CountingStock itself for this transaction!");
try {
Shop.getTheShop().getDBPersistenceManager().deleteStockItem(siAdded,con);
siAdded.setStock(null,sConPasswd);
siAdded.setBasket(null,sConPasswd);
} catch (Exception e) {
getAddedItems(sKey).add(siAdded);
Shop.getTheShop().logException(e);
siAdded.setStock(this,sConPasswd);
siAdded.setBasket(dbe.getBasket(),sConPasswd);
throw new RuntimeException("Could not rollback into defined persistent state, as an Exception occured: "+e);
}
nal StockItem siThread = new StockItemImpl(siRollback.getName(),new DecimalValue(dvAdded.intValue()));
nal CountingStockImpl thisStock = this;
nal DataBasket dbThread = dbe.getBasket();
RemoteRunnable r = new RemoteRunnableImpl() {
public void run(Object[] o) throws RemoteException {
try {
thisStock.fireRolledbackAddTransactionItem(new ContainerChangeEventImpl(siThread,thisStock,dbThread));
} catch (RemoteException rmie) {
Shop.getTheShop().logException(rmie);
}
}
};
return r;
} else if (dbe.getTransactionType().equals(dbe.TRANSACTION_REMOVE)) {
// a removing transaction has to commit now
Iterator it = getRemovedItems(sKey).iterator();
StockItem siRemoved = null;
while (it.hasNext()) {
siRemoved = (StockItem)it.next();
if (siRemoved.getBasket().equals(dbe.getBasket())) {
it.remove();
break;
}
siRemoved = null;
}
if (siRemoved == null) throw new RuntimeException("Found no removed item for a registered removing-transaction of a
DBEntry in a CountingStock!");
DecimalValue dvRemoved = (DecimalValue)siRemoved.getValue();
if (dvRemoved.subtract(dvRollback).intValue() != 0) throw new RuntimeException("Found a registered removing transaction
that removes more or less CountingStockItems than registered in the CountingStock itself for this
transaction!");
try {
Shop.getTheShop().getDBPersistenceManager().deleteStockItem(siRemoved,con);
} catch (Exception e) {
getRemovedItems(sKey).add(siRemoved);
Shop.getTheShop().logException(e);
throw new RuntimeException("Could not rollback into defined persistent state, as an Exception occured: "+e);
}
if (getStockItems(sKey).size() == 0) {
StockItem siNew = new StockItemImpl(sKey,dvRollback);
siNew.setStock(this,sConPasswd);
siNew.setBasket(dbe.getBasket(),sConPasswd);
try {
234
ANHANG C. QUELLTEXTE
}
885
890
895
900
905
910
}
915
920
925
930
}
}
Shop.getTheShop().getDBPersistenceManager().createStockItem(siNew,false,false,con);
(Exception e) {
Shop.getTheShop().logException(e);
siNew.setStock(null,sConPasswd);
throw new RuntimeException("Could not rollback into defined persistent state, as an Exception occured: "+e);
catch
}
siNew.setBasket(null,sConPasswd);
getStockItems(sKey).add(siNew);
} else {
StockItem si = (StockItem)getStockItems(sKey).iterator().next();
si.setValue(((DecimalValue)si.getValue()).add(dvRollback), sConPasswd);
}
nal StockItem siThread = new StockItemImpl(siRollback.getName(),new DecimalValue(dvRemoved.intValue()));
nal CountingStockImpl thisStock = this;
nal DataBasket dbThread = dbe.getBasket();
RemoteRunnable r = new RemoteRunnableImpl() {
public void run(Object[] o) throws RemoteException {
try {
thisStock.fireRolledbackRemoveTransactionItem(new ContainerChangeEventImpl(siThread,thisStock,dbThread));
} catch (RemoteException rmie) {
Shop.getTheShop().logException(rmie);
}
}
};
return r;
} else {
throw new RuntimeException("Found illegal transaction-type \""+dbe.getTransactionType()+"\" to commit in CountingStock!")
;
}
////////////////////////////////////////////////////////////////////////////////
//
OVERRIDDEN CLASS METHODS
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//
OVERRIDDEN INSTANCE METHODS
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//
NEW CLASS METHODS
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//
NEW INSTANCE METHODS
////////////////////////////////////////////////////////////////////////////////
}
Listing C.44: epoint.data.rmi.DataBasketImpl
5
10
15
20
25
30
/*
* DataBasketImpl.java
*
* Created on 11. September 2001, 13:14
*/
package epoint.data.rmi;
import java.util.*;
import epoint.sale.*;
import epoint.data.*;
import java.rmi.*;
import java.sql.Connection;
/**
* This is the default implementation for the {@link DataBasket DataBasket interface}.
* For more information about DataBaskets, read there!<br>
* <br>
* This implementation is remotely available and as such may not be instantiated
* at the EPoints environment. Instead it must be requested using the {@link epoint.sale.ShopServices
* ShopServices}. The name of a DataBasket is unique under all names of existent
* DataBaskets and it cannot change. As such, it may be used as a reference to the
* persistent DataBasket, rather than by its OID.<br>
* <br>
* Commit and RollBack in this DataBasket is implemented as one background-transaction
* that committs (successful commit or rollback of the DataBasket) or rolls back (failed
* commit or rollback of the DataBasket) in its entirety. This way the persistent state
* always stays consistent.
*
* @see DBEntry
* @see DBEntryTarget
* @see TransactionItem
* @author Danny Poppe
* @version 1.0
C.1. PACKAGE EPOINT.DATA
35
40
45
50
55
60
65
70
75
80
85
90
95
100
105
110
115
120
*/
public class
DataBasketImpl
extends
java.rmi.server.UnicastRemoteObject
235
implements
epoint.data.DataBasket {
/**
* The name of the DataBasket is automatically generated and
* part of the persistent state of an object. As key-information it should
* also made explicitely persistent.
*/
transient private String sName;
/**
* This list contains all entries of this basket. These are explicitly
* made persistent and not part of this persistent object.
*/
transient private List lDBEntries = Collections.synchronizedList(new LinkedList());
/**
* The state of a DataBasket belongs to the persistent state of a
* databasket.
*/
private String sState = this.DB_USED;
/**
* The ordered index is counted up, whenever a new entry is added to
* the databasket. The current index is applied to the added entry
* and ensures, that all parts of the transaction are ordered.
* This index is also part of the persistent state.
*/
private int iOrderIndex = Integer.MIN_VALUE;
/**
* The information, if this object is persistent is also
* part of the persistent state of this object -> is always true in the
* serialized version in the database
*/
transient private String sOID = null;
/**
* Creates new DataBasketImpl with a default unique name.
*
* @throws RemoteException whenever an error occurs during remote-access
*/
public DataBasketImpl() throws RemoteException{
sName = "DataBasket #"+Shop.getTheShop().getGlobalUniqueNumber();
try {
Shop.getTheShop().getDBPersistenceManager().createDataBasket(this, getDefaultConnection());
} catch (Exception e) {
e.printStackTrace();
}
returnConnection(getDefaultConnection());
}
// documentation is inherited
public void loadPersistentData(Map mProperties, String sConPasswd) throws RemoteException {
lDBEntries = Collections.synchronizedList(new LinkedList());
sName = (String)mProperties.get(DataBasket.PERSISTENT_PROPERTY_BASKETNAME);
try {
lDBEntries = Shop.getTheShop().getDBPersistenceManager().getDBEntries(this,getReservedConnection(sConPasswd));
} catch (epoint.db.exception.DBAccessException e) {
e.printStackTrace();
}
boolean bBubble = true;
while (bBubble) {
bBubble = false;
for (int iCnt = 0; iCnt < (lDBEntries.size()-1); iCnt++) {
DBEntry fst = (DBEntry)lDBEntries.get(iCnt);
DBEntry snd = (DBEntry)lDBEntries.get(iCnt+1);
if (fst.getOrderIndex() > snd.getOrderIndex()) {
bBubble = true;
Object o = lDBEntries.get(iCnt);
lDBEntries.set(iCnt,lDBEntries.get(iCnt+1));
lDBEntries.set(iCnt+1,o);
}
}
}
Iterator it = lDBEntries.iterator();
}
/*******************************************************************************
*
DataBasket
******************************************************************************/
/**
* Adds a new DBEntry to this DataBasket which describes one change
* as part of the whole Transaction of this DataBasket. The given Entry
* is enqueued at the of the list of all other DBEntries added before.
*
* @param dbe the DataBasketEntry that shall be part of this DataBaskets
236
125
130
135
140
145
150
155
160
165
170
175
180
185
190
195
200
205
ANHANG C. QUELLTEXTE
*
Transaction
* @param sConPasswd the password that protects the connection in which
*
the DBEntry is made persistent
* @throws RemoteException whenever an error occurs during remote-access
*/
public void add(DBEntry dbe, String sConPasswd) throws RemoteException {
try {
if (!dbe.getBasket().getName().equals(this.getName())) throw new RuntimeException("The given DBEntry was not created for this
DataBasket!");
if (Arrays.asList(new Object[]{this.DB_FAILED,this.DB_ROLLBACK,this.DB_COMMIT}).contains(sState))
throw new IllegalStateException("This DataBasket is currently unusable!");
lDBEntries.add(dbe);
try {
Shop.getTheShop().getDBPersistenceManager().createDBEntry(dbe,getReservedConnection(sConPasswd));
} catch (epoint.db.exception.DBAccessException e) {
Shop.getTheShop().logException(e);
throw new RuntimeException("An error occured while accessing the database: "+e);
}
} catch (RuntimeException e) {
Shop.getTheShop().logException(e);
throw e;
}
}
/**
* Returns all DBEntries of this DataBasket, that belong to
* the given TransactionItem. See {@link DBEIterator} for
* details about the Iterator that is returned.
*
* @param ti the TransactionItem that must be described by the
*
DBEntries returned by the requested Iterator
* @throws RemoteException whenever an error occurs during remote-access
* @return a {@link DBEIterator} over the DBEntries in this DataBasket
*/
public RemoteIterator iterator(TransactionItem ti) throws RemoteException {
return new DBEIterator(this,ti);
}
/**
* Returns <code>true</code> if, if this DataBasket has finished a
* commit or rollback - that might have been successful or not.
*
* @return <code>false</code> if this DataBasket is currently
*
committing or rolling back its DBEntries
* @throws RemoteException whenever an error occurs during remote-access
*/
nal public boolean hasFinished() throws RemoteException {
return (sState.equals(this.DB_READY) || sState.equals(this.DB_FAILED));
}
/**
* Rolls back all changes previously made to DBEntryTargets, using this
* DataBasket. Therefore a single background-transaction is used
* to update the persistent states of the manipulated TransactionItems.
*
* @throws RemoteException whenever an error occurs during remote-access
*/
public void rollback() throws RemoteException {
if (Arrays.asList(new Object[]{this.DB_FAILED,this.DB_ROLLBACK,this.DB_COMMIT}).contains(sState))
throw new IllegalStateException("This DataBasket is currently unusable!");
sState = this.DB_ROLLBACK;
Connection con = null;
try {
String sPasswd = getName()+" -- rollback() #"+Shop.getTheShop().getGlobalUniqueNumber();
con = getReservedConnection(sPasswd);
con.setAutoCommit(false);
List lRunnables = new LinkedList();
while (lDBEntries.size() > 0) {
DBEntry dbe = (DBEntry)lDBEntries.remove(lDBEntries.size()-1);
dbe.initCache(sPasswd);
RemoteRunnable r = dbe.getTarget().rollbackTransaction(dbe,sPasswd);
lRunnables.add(r);
Shop.getTheShop().getDBPersistenceManager().deleteDBEntry(dbe,con);
}
con.commit();
returnConnection(con);
sState = this.DB_READY;
Iterator itRunnables = lRunnables.iterator();
while (itRunnables.hasNext()) {
RemoteRunnable r = (RemoteRunnable)itRunnables.next();
r.run(null);
itRunnables.remove();
}
} catch (Exception e) {
try {
con.rollback();
returnConnection(con);
} catch (Exception e2) {
C.1. PACKAGE EPOINT.DATA
210
215
220
225
230
235
240
245
250
255
260
265
270
275
280
285
290
295
}
}
e2.printStackTrace();
}
e.printStackTrace();
sState = this.DB_FAILED;
/**
* Committs all changes previously made to DBEntryTargets, using this
* DataBasket. Therefore a single background-transaction is used
* to update the persistent states of the manipulated TransactionItems.
*
* @throws RemoteException whenever an error occurs during remote-access
*/
public void commit() throws RemoteException {
if (Arrays.asList(new Object[]{this.DB_FAILED,this.DB_ROLLBACK,this.DB_COMMIT}).contains(sState))
throw new IllegalStateException("This DataBasket is currently unusable!");
sState = this.DB_COMMIT;
java.sql.Connection con = null;
try {
String sPasswd = getName()+" -- commit() #"+Shop.getTheShop().getGlobalUniqueNumber();
con = getReservedConnection(sPasswd);
con.setAutoCommit(false);
List lRunnables = new LinkedList();
while (lDBEntries.size() > 0) {
DBEntry dbe = (DBEntry)lDBEntries.remove(lDBEntries.size()-1);
dbe.initCache(sPasswd);
RemoteRunnable r= dbe.getTarget().commitTransaction(dbe,sPasswd);
lRunnables.add(r);
Shop.getTheShop().getDBPersistenceManager().deleteDBEntry(dbe,con);
}
con.commit();
returnConnection(con);
sState = this.DB_READY;
Iterator itRunnables = lRunnables.iterator();
while (itRunnables.hasNext()) {
RemoteRunnable r = (RemoteRunnable)itRunnables.next();
r.run(null);
itRunnables.remove();
}
} catch (Exception e) {
try {
con.rollback();
returnConnection(con);
} catch (Exception e2) {
e2.printStackTrace();
}
e.printStackTrace();
sState = this.DB_FAILED;
}
}
/**
* Returns an iterator that returns all DBEntries from this DataBasket.
* See {@link DBEIterator} for more details about the returned Iterator.
*
* @return an iterator returning all DBEntries in this DataBasket
* @throws RemoteException whenever an error occurs during remote-access
*/
public RemoteIterator iterator() throws RemoteException {
return new DBEIterator(this);
}
/**
* Returns the next index available for a DBEntry that will be added
* to this DataBasket. The indexes are returned in ascending order.
* This index onyl defines the order of DBEntries in the DataBasket!
* This is not the index used to access the DBEntry in this DataBasket!
*
* @return an <code>int</code> that may be used to index a DBEntry that
*
will then be added to this DataBasket
* @throws RemoteException whenever an error occurs during remote-access
*/
nal public int getNextOrderedIndex() throws RemoteException {
return iOrderIndex++;
}
/**
* Returns the number of DBEntries, contained in this DataBasket.
*
* @return the number of DBEntries in this DataBasket
* @throws RemoteException whenever an error occurs during remote-access
*/
nal public int size() throws RemoteException {
return lDBEntries.size();
}
/**
237
238
300
305
310
315
320
325
330
335
340
345
350
355
360
365
370
375
380
ANHANG C. QUELLTEXTE
* Returns the DBEntry that has the specified index in this DataBasket.
* This is not the index stored with the DBEntry, which is only used
* for defining the order of DBEntries.
*
* @param iIdx the index of the DBEntry to return (must be between
*
zero and the size of this DataBasket
* @return the requested DBEntry
* @throws RemoteException whenever an error occurs during remote-access
*/
nal public DBEntry get(int iIdx) throws RemoteException {
return ((DBEntry)lDBEntries.get(iIdx));
}
/*******************************************************************************
*
Nameable
******************************************************************************/
/**
* This method is not supported in this implementation and always throws
* a NotEditableException.
*
* @param sName does not matter
* @throws RemoteException whenever an error occurs during remote-access
*/
nal public void setName(String sName) throws RemoteException {
throw new epoint.data.exception.NotEditableException("The name of a databasket may not be edited!");
}
/**
* Returns the name of this DataBasket.
*
* @throws RemoteException whenever an error occurs during remote-access
* @return the name of this DataBasket
*/
nal public String getName() throws RemoteException {
return sName;
}
///////////////////////////////////////////////////////////////////////////////
//
PersistentObject
//////////////////////////////////////////////////////////////////////////////
// documentation inherited
nal public String getSerializedForm() throws RemoteException {
return Shop.getPersistenceSerializer().toString(this);
}
// documentation inherited
nal public String getOID()
return sOID;
}
throws
// documentation inherited
nal public boolean isPersistent()
return sOID != null;
}
RemoteException {
throws
// documentation inherited
nal public void setPersistent(String sOID)
this.sOID = sOID;
}
RemoteException {
throws
RemoteException {
/**
* Removes the given DBEntry from this DataBasket. Never use this method
* directly, as this may result in data-inconsistency. This method is provided
* ONLY for framework-internal reasons.
*
* @param dbe the DBEntry that shall be removed from this DataBasket
* @param sConPasswd the password that protects the connection in which the
*
persistent state of the DBEntry is updated
* @throws RemoteException whenever an error occurs during remote-access
*/
public void removeTransaction(DBEntry dbe, String sConPasswd) throws RemoteException {
if (Arrays.asList(new Object[]{this.DB_FAILED,this.DB_ROLLBACK,this.DB_COMMIT}).contains(sState))
throw new IllegalStateException("This DataBasket is currently unusable!");
try {
Shop.getTheShop().getDBPersistenceManager().deleteDBEntry(dbe,getReservedConnection(sConPasswd));
} catch (epoint.db.exception.DBAccessException e) {
Shop.getTheShop().logException(e);
throw new RuntimeException("An error occured while accessing the database: "+e);
}
lDBEntries.remove(dbe);
}
// documentation inherited
public void updatePersistentObject(List lPropertyKeys, String sCon)
throws
RemoteException {
C.1. PACKAGE EPOINT.DATA
385
390
395
}
if (!isPersistent()) return;
List lAllowed = Arrays.asList(new Object[]{PersistentObject.PERSISTENT_PROPERTY_SERIALIZED,
DataBasket.PERSISTENT_PROPERTY_BASKETNAME
});
if (!lAllowed.containsAll(lPropertyKeys)) throw new IllegalArgumentException(
"At least one property specified for updating the persistent object could not be understood!");
try {
Shop.getTheShop().getDBPersistenceManager().updateDataBasket(this, lPropertyKeys, getReservedConnection(sCon));
} catch (Exception e) {
e.printStackTrace();
}
/**
* Decides if the given Object is equal to this DataBasket. This is the case
* if the given object is a DataBasket with the same name as this DataBasket.
*
* @param o the object that shall be compared to this DataBasket
* @return <code>true</code> if the given object is a DataBasket equal to this
*/
nal public boolean equals(Object o) {
if (!(o instanceof DataBasket)) return false;
try {
return ((DataBasket)o).getName().equals(getName());
} catch (RemoteException rmie) {
Shop.getTheShop().logException(rmie);
throw new RuntimeException("Could not complete equality-check, as a RemoteException occured!");
}
}
400
405
410
415
/**
* Returns a String-representation of this DataBasket, that contains the
* name of this DataBasket.
*
* @return a String-representation of this object
*/
public String toString() {
return "DataBasket (\""+sName+"\")";
}
420
425
/**
* Returns a Connection to the database, that is protected by a class-wide
* default-password.
*
* @return the default-connection of this class
*/
protected Connection getDefaultConnection() {
return Shop.getTheShop().getDBPersistenceManager().getPrivateConnection("Default Connection for class "+getClass().getName(),getClass
());
}
430
435
/**
* Returns a Connection to the database, that is protected by the given
* password.
*
* @param sConPasswd the password the protects the requested connection
* @return the requested Connection
*/
protected Connection getReservedConnection(String sConPasswd) {
return Shop.getTheShop().getDBPersistenceManager().getPrivateConnection(sConPasswd,getClass());
}
440
445
/**
* Returns the given Connection that was previously requested to the DBPersistenceManager
* for further use by others.
*
* @param con the connection that shall be returned
*/
protected void returnConnection(Connection con) {
Shop.getTheShop().getDBPersistenceManager().returnPrivateConnection(con);
}
450
455
/**
* Returns the same as {@link #toString()}, but may be used by remote-connections,
* as {@link java.rmi.server.RemoteObject#toString()} returns a description of the
* Stub at client-side
*
* @return the String describing this object
* @throws RemoteException whenever an error occurs during remote-access
*/
public String toRemoteString() throws RemoteException {
return toString();
}
460
465
470
239
}
240
ANHANG C. QUELLTEXTE
Listing C.45: epoint.data.rmi.DBEIterator
5
10
15
20
25
30
35
40
45
50
55
60
65
70
75
80
85
/*
* DBEIterator.java
*
* Created on 11. September 2001, 16:02
*/
package epoint.data.rmi;
import epoint.data.*;
import java.rmi.*;
import java.util.NoSuchElementException;
/**
* This implementation of a RemoteIterator is specific for a DataBasket that
* returns a RemoteIterator over its DBEntries. This Iterator should not be
* used by a framework-user, as it is intended only for framework-internal use.
* Concurrent Modifications of the DataBasket are silently ignored by this
* implementation.
*
* @see DataBasket
* @see DBEntry
* @author Danny Poppe
* @version 1.0
*/
public class DBEIterator extends RemoteIteratorImpl implements RemoteIterator {
/**
* The DataBasket, this iterator has been created for.
*/
private DataBasket db = null;
/**
* The TransactionItem for which this Iterator shall return DBEntries.
*/
private TransactionItem ti = null;
/**
* The current index in the DataBasket of the DBEntry returned by this Iterator.
*/
private int iCurrent = -1;
/**
* Creates a new Iterator over all DBEntries in the given DataBasket.
*
* @param db the DataBasket for which this Iterator shall return DBEntries
* @throws RemoteException whenever an error occurs during remote-access
*/
public DBEIterator(DataBasket db) throws RemoteException {
this(db,null);
}
/**
* Creates a new Iterator over all DBEntries that describe actions relative to
* the given TransactionItem.
*
* @param db the DataBasket for which this Iterator shall return DBEntries
* @param ti the TransactionItem that shall be described by the DBEntries returned
*
by this Iterator
* @throws RemoteException whenever an error occurs during remote-access
*/
public DBEIterator(DataBasket db, TransactionItem ti) throws RemoteException {
this.db = db;
this.ti = ti;
}
/**
* Returns the next index in the ataBasket for which the next DBEntry will be
* returned. Subsequent calls increase the index, until no more DBEntries
* matching the given conditions in the constructor are available. In this
* case <code>-1</code> is returned.
*
* @return the next index of DBEntry in the DataBasket that shall be returned
*
or <code>-1</code> if there are no more elements
* @throws RemoteException whenever an error occurs during remote-access
*/
private int getNextPos() throws RemoteException {
int iCnt = iCurrent+1;
while (iCnt < db.size()) {
if ((ti == null) || (db.get(iCnt).getTransactionItem().getTransactionItemIdentifier().equals(ti.getTransactionItemIdentifier())))
return iCnt;
iCnt++;
}
return -1;
}
/**
* Returns <code>true</code> only, if this Iterator has mor elements that can be retrieved
* by subsequent calls to {@link #next()}.
*
* @return <code>true</code> if {@link #next()} is able to return more DBEntries
* @throws RemoteException whenever an error occurs during remote-access
C.1. PACKAGE EPOINT.DATA
*/
public boolean hasNext() throws
return (getNextPos() != -1);
90
RemoteException {
}
/**
* Returns the next DBEntry in this iterator.
*
* @return the next DBEntry in this iterator
* @throws RemoteException whenever an error occurs during remote-access
* @throws NoSuchElementException if this iterator has no more elements that can be returned
*/
public Object next() throws RemoteException, NoSuchElementException {
if (getNextPos() == -1) throw new java.util.NoSuchElementException();
iCurrent = getNextPos();
return db.get(iCurrent);
}
95
100
105
110
}
/**
* In this implementation always throws the UnsupportedOperationException!
*
* @throws RemoteException whenever an error occurs during remote-access
* @throws UnsupportedOperationExceptionalways in this implementation
*/
public void remove() throws RemoteException, UnsupportedOperationException {
throw new UnsupportedOperationException("Removing DBEntries using the iterator is not supported!");
}
Listing C.46: epoint.data.rmi.DBEntryImpl
5
10
15
20
25
30
35
40
45
50
55
/*
* DBEntryImpl.java
*
* Created on 12. September 2001, 11:15
*/
package epoint.data.rmi;
import java.rmi.*;
import epoint.data.*;
import epoint.data.exception.*;
import java.util.*;
import epoint.sale.Shop;
import java.sql.Connection;
/**
* This is the default implementation for the {@link DBEntry DBEntry interface}.
* For more information about DBEntries, read there.<br>
* <br>
* This implementation supports description of transactions relative to
* {@link Stock Stocks} and {@link Catalog Catalogs} by its two constructors.
* The DBEntry becomes persistent, if it is added to a DataBasket and later
* it is transient after being rolledback or committed.<br>
* DBEntries are normally only created at the {@link DBEntryTarget DBEntryTarget Container}
* that wants to register a transaction within a Databasket. That implies a
* creation at server-side (at the Shop) and never in the EPoints environment.
*
* @see DBEntryTarget
* @see DataBasket
* @see TransactionItem
* @author Danny Poppe
* @version 1.0
*/
public class DBEntryImpl extends java.rmi.server.UnicastRemoteObject implements DBEntry {
/**
* The name of the DataBasket in which this DBEntry is contained in.
*/
transient private String sDBName = null;
/**
* The DataBasket itself, this DBEntry is contained in.
*/
transient private DataBasket dbCache = null;
/**
* The identifier of the TransactionItem that is described by this DBEntry
*/
transient private String sTransItemID = null;
/**
* The TransactionItem itself, that is described by this DBEntry.
*/
transient private TransactionItem tiCache = null;
/**
* The ordered index of this DBEntry in the DataBasket.
*/
transient private int iOrderIndex = -1;
/**
241
242
60
65
70
75
80
85
90
95
100
105
110
115
120
125
130
135
140
ANHANG C. QUELLTEXTE
* The type of the transaction that is described by this DBEntry (one
* of the constants as defined in {@link DBEntry}.
*/
transient private String sType;
/**
* The properties, that are stored together with this DBEntry.
*/
transient private java.util.Map mProperty;
/**
* A reference to the DBEntryTarget of this DBEntry.
*/
transient private DBEntryTarget dbetCache = null;
/**
* This PersistentObjects OID.
*/
transient private String sOID = null;
/**
* If this <code>true</code>, this DBEntry
* a Catalog.
*/
private boolean bCatalog = false;
/**
* If this <code>true</code>, this DBEntry
* a Stock.
*/
private boolean bStock = false;
/**
* If this <code>true</code>, this DBEntry
* a CatalogItem.
*/
private boolean bCatalogItem = false;
/**
* If this <code>true</code>, this DBEntry
* a StockItem.
*/
private boolean bStockItem = false;
/**
* The OID of the persistent DBEntryTarget
*/
private String sDBEntryTargetOID;
describes a transaction relative to
describes a transaction relative to
describes the transaction of
describes the transaction of
/**
* Creates a new DBEntry that describes an operation against a Catalog.
*
* @param db the DataBasket that carried out the operation and to which
*
this DBEntry will be added
* @param sTransType a DBEntryTarget-specific identifier for the type of
*
transaction that took place
* @param cat the Catalog that is target of this DBEntry
* @param ci the CatalogItem that is modified by the transaction
* @param mProperties additional properties for the transaction
* @throws RemoteException whenever an error occurs during remote-access
*/
public DBEntryImpl(DataBasket db, String sTransType, Catalog cat, CatalogItem ci, Map mProperties) throws RemoteException {
sDBName = db.getName();
dbCache = db;
bCatalogItem = true;
sTransItemID = ci.getTransactionItemIdentifier();
tiCache = ci;
bCatalog = true;
sDBEntryTargetOID = cat.getOID();
dbetCache = cat;
iOrderIndex = db.getNextOrderedIndex();
sType = sTransType;
mProperty = mProperties;
}
/**
* Creates a new DBEntry that describes an operation against a Stock.
*
* @param db the DataBasket that carried out the operation and to which
*
this DBEntry will be added
* @param sTransType a DBEntryTarget-specific identifier for the type of
*
transaction that took place
* @param s the Stock that is target of this DBEntry
* @param si the StockItem that is modified by the transaction
* @param mProperties additional properties for the transaction
* @throws RemoteException whenever an error occurs during remote-access
*/
public DBEntryImpl(DataBasket db, String sTransType, Stock s, StockItem si, Map mProperties) throws RemoteException {
sDBName = db.getName();
dbCache = db;
bStockItem = true;
sTransItemID = si.getTransactionItemIdentifier();
tiCache = si;
bStock = true;
sDBEntryTargetOID = s.getOID();
C.1. PACKAGE EPOINT.DATA
145
}
150
155
160
165
170
175
180
185
190
195
200
205
210
215
220
225
243
dbetCache = s;
iOrderIndex = db.getNextOrderedIndex();
sType = sTransType;
mProperty = mProperties;
// documentation is inherited
public void loadPersistentData(Map mProperties, String sConPasswd) throws RemoteException {
sDBName = (String)mProperties.get(DataBasket.PERSISTENT_PROPERTY_BASKETNAME);
iOrderIndex = ((Integer)mProperties.get(DBEntry.PERSISTENT_PROPERTY_ORDER_INDEX)).intValue();
mProperty = (Map)mProperties.get(DBEntry.PERSISTENT_PROPERTY_PROPERTIES);
sTransItemID = (String)mProperties.get(TransactionItem.PERSISTENT_PROPERTY_TRANSACTION_IDENTIFIER);
sType = (String)mProperties.get(DBEntry.PERSISTENT_PROPERTY_TRANSACTION_TYPE);
}
/**
* Returns the DBEntryTarget of this DBEntry.
*
* @throws RemoteException whenever an error occurs during remote-access
* @return the DBEntryTarget of this DBEntry
*/
public DBEntryTarget getTarget() throws RemoteException {
if (dbetCache != null) return dbetCache;
try {
DBEntryTarget dbe = null;
if (bCatalog) dbe = Shop.getTheShop().getDBPersistenceManager().getCatalogByOID(sDBEntryTargetOID,getDefaultConnection());
if (bStock) dbe = Shop.getTheShop().getDBPersistenceManager().getStockByOID(sDBEntryTargetOID,getDefaultConnection());
returnConnection(getDefaultConnection());
return dbe;
} catch (Exception e) {
returnConnection(getDefaultConnection());
Shop.getTheShop().logException(e);
}
throw new UnknownTargetException("Unable to identify the type of the target-id! If a new constructor was added to DBEntryImpl,
getTarget() must be extended to be able to return the target!");
}
// documentation is inherited
public void initCache(String sConPasswd) throws RemoteException {
if (bCatalog) dbetCache = Shop.getTheShop().getDBPersistenceManager().getCatalogByOID(sDBEntryTargetOID,getReservedConnection(
sConPasswd));
if (bStock) dbetCache = Shop.getTheShop().getDBPersistenceManager().getStockByOID(sDBEntryTargetOID,getReservedConnection(sConPasswd));
dbCache = Shop.getTheShop().getDBPersistenceManager().getDataBasket(sDBName,getReservedConnection(sConPasswd));
boolean bAdded = false;
boolean bRemoved = false;
if (sType.equals(TRANSACTION_ADD)) bAdded = true;
if (sType.equals(TRANSACTION_REMOVE)) bRemoved = true;
if (sType.equals(TRANSACTION_MODIFY)) {
bAdded = true;
bRemoved = true;
}
if (bCatalogItem) tiCache = Shop.getTheShop().getDBPersistenceManager().getCatalogItemByTransactionID(sTransItemID,bAdded,bRemoved,
getReservedConnection(sConPasswd));
if (bStockItem) tiCache = Shop.getTheShop().getDBPersistenceManager().getStockItemByTransactionID(sTransItemID,bAdded,bRemoved,
getReservedConnection(sConPasswd));
}
/**
* Returns the DataBasket in which this DBEntry is contained in and relative
* to which the transaction as described by this DBEntry is carried out.
*
* @throws RemoteException whenever an error occurs during remote-access
* @return this DBEntries DataBasket
*/
public DataBasket getBasket() throws RemoteException {
if (dbCache != null) return dbCache;
try {
DataBasket db = Shop.getTheShop().getDBPersistenceManager().getDataBasket(sDBName,getDefaultConnection());
returnConnection(getDefaultConnection());
return db;
} catch (Exception e) {
returnConnection(getDefaultConnection());
Shop.getTheShop().logException(e);
return null;
}
}
/**
* Returns the TransactionItem that is manipulated in the way it is described
* by this DBEntry.
*
* @throws RemoteException whenever an error occurs during remote-access
* @return the TransactionItem of this DBEntry
*/
public TransactionItem getTransactionItem() throws RemoteException {
if (tiCache != null) return tiCache;
try {
boolean bAdded = false;
244
boolean bRemoved = false;
if (sType.equals(TRANSACTION_ADD)) bAdded = true;
if (sType.equals(TRANSACTION_REMOVE)) bRemoved = true;
if (sType.equals(TRANSACTION_MODIFY)) {
bAdded = true;
bRemoved = true;
230
235
240
245
250
255
260
265
270
275
280
285
290
295
300
305
310
ANHANG C. QUELLTEXTE
}
}
TransactionItem ti = null;
if (bCatalogItem) ti = Shop.getTheShop().getDBPersistenceManager().getCatalogItemByTransactionID(sTransItemID,bAdded,bRemoved,
getDefaultConnection());
if (bStockItem) ti = Shop.getTheShop().getDBPersistenceManager().getStockItemByTransactionID(sTransItemID,bAdded,bRemoved,
getDefaultConnection());
returnConnection(getDefaultConnection());
return ti;
} catch (Exception e) {
returnConnection(getDefaultConnection());
Shop.getTheShop().logException(e);
}
throw new UnknownTargetException("Unable to identify the type of the transaction-item! If a new constructor was added to DBEntryImpl
, getTtransactionItem() must be extended to be able to return the transaction-item!");
/**
* Returns the type of transaction, as specified by the <code>DBEntryTarget</code>.
*
* @throws RemoteException whenever an error occurs during remote-access
* @return the type of the transaction described by this DBEntry
*/
public String getTransactionType() throws RemoteException {
return sType;
}
/**
* Returns additional information for the transaction that is
* described by this DBEntry. Each DBEntryTarget may use it to retrieve
* additional properties for a rollback or a commit.
*
* @param sKey the key for the piece of information that shall be returned
* @throws RemoteException whenever an error occurs during remote-access
* @return the requested information or <code>null</code> if the given key
*
has no information associated with it
*/
public String getProperty(String sKey) throws RemoteException {
return (String)mProperty.get(sKey);
}
// documentation is inherited
nal public String getSerializedForm() throws RemoteException {
return epoint.sale.Shop.getPersistenceSerializer().toString(this);
}
/**
* Returns a map of key-value mappings, that where provided, when this DBEntry
* was created. They may contain additional information about the transaction
* described by this DBEntry.
*
* @throws RemoteException whenever an error occurs during remote-access
* @return a map of key-value mappings with additional properties for this DBEntry
*/
public Map getProperties() throws RemoteException {
return mProperty;
}
// documentation is inherited
nal public String getOID()
return sOID;
}
throws
RemoteException {
/**
* Returns the ordered index of this DBEntry in the Databasket.
*
* @throws RemoteException whenever an error occurs during remote-access
* @return the index of this DBEntry in the DataBasket
*/
nal public int getOrderIndex() throws RemoteException {
return iOrderIndex;
}
// documentation is inherited
nal public boolean isPersistent()
return sOID != null;
}
throws
// documentation is inherited
nal public void setPersistent(String sOID)
this.sOID = sOID;
}
RemoteException {
throws
RemoteException {
C.1. PACKAGE EPOINT.DATA
// documentation is inherited
public void updatePersistentObject(List lPropertyKeys, String sCon) throws RemoteException {
if (!isPersistent()) return;
List lAllowed = Arrays.asList(new Object[]{PersistentObject.PERSISTENT_PROPERTY_SERIALIZED,
DataBasket.PERSISTENT_PROPERTY_BASKETNAME,
DBEntry.PERSISTENT_PROPERTY_ORDER_INDEX,
DBEntry.PERSISTENT_PROPERTY_PROPERTIES,
TransactionItem.PERSISTENT_PROPERTY_TRANSACTION_IDENTIFIER,
DBEntry.PERSISTENT_PROPERTY_TRANSACTION_TYPE});
if (!lAllowed.containsAll(lPropertyKeys)) throw new IllegalArgumentException(
"Could not understand at least one of the specified keys to make persistent!");
try {
Shop.getTheShop().getDBPersistenceManager().updateDBEntry(this,lPropertyKeys,getReservedConnection(sCon));
} catch (Exception e) {
Shop.getTheShop().logException(e);
}
}
315
320
325
330
/**
* Returns a Connection to the database, that is protected by a class-wide
* default-password.
*
* @return the default-connection of this class
*/
protected Connection getDefaultConnection() {
return Shop.getTheShop().getDBPersistenceManager().getPrivateConnection("Default Connection for class "+getClass().getName(),getClass
());
}
335
340
/**
* Returns a Connection to the database, that is protected by the given
* password.
*
* @param sConPasswd the password the protects the requested connection
* @return the requested Connection
*/
protected Connection getReservedConnection(String sConPasswd) {
return Shop.getTheShop().getDBPersistenceManager().getPrivateConnection(sConPasswd,getClass());
}
345
350
/**
* Returns the given Connection that was previously requested to the DBPersistenceManager
* for further use by others.
*
* @param con the connection that shall be returned
*/
protected void returnConnection(Connection con) {
Shop.getTheShop().getDBPersistenceManager().returnPrivateConnection(con);
}
355
360
/**
* Returns a String-representation of this DBEntry, that contains the type
* of transaction, identifier of the described TransactionItem and the name
* of the DataBasket, this DBEntry is contained in.
*
* @return s String describing this DBEntry
*/
public String toString() {
return "DBEntry: "+sType+" of "+sTransItemID+" in DataBasket "+sDBName;
}
365
370
/**
* Returns the same as {@link #toString()}, but may be used by remote-connections,
* as {@link java.rmi.server.RemoteObject#toString()} returns a description of the
* Stub at client-side
*
* @return the String describing this object
* @throws RemoteException whenever an error occurs during remote-access
*/
public String toRemoteString() throws RemoteException {
return toString();
}
375
380
385
}
Listing C.47: epoint.data.rmi.PersistentContainerListener
5
245
/*
* PersistentContainerListener.java
*
* Created on 15. Oktober 2001, 18:41
*/
package epoint.data.rmi;
import java.rmi.*;
246
10
15
20
25
30
35
40
45
50
55
60
import
import
import
ANHANG C. QUELLTEXTE
epoint.data.exception.VetoException;
java.beans.PropertyChangeEvent;
epoint.data.*;
/**
* This is a default implementation (adapter) for the {@link PContainerListener
* PContainerListener interface}. Subclasses deriving from this class will be able
* to override the empty bodies of the method to implement specific behaviour of a
* potentially persistent listener.
*
* @see ListenableContainer
* @see ContainerListener
* @see ContainerChangeEvent
* @author Danny Poppe
* @version 1.0
*/
public class PersistentContainerListener extends java.rmi.server.UnicastRemoteObject
{
70
/**
* Creates a new potentially persistent ContainerListener.
*
* @throws RemoteException whenever an error occurs during remote-access
*/
public PersistentContainerListener() throws RemoteException {
}
// documentation is inherited
public void canEditTransactionItem(ContainerChangeEvent e)
}
throws
// documentation is inherited
public void committedRemoveTransactionItem(ContainerChangeEvent e)
// documentation is inherited
public void removedTransactionItem(ContainerChangeEvent e)
}
throws
// documentation is inherited
public void rolledbackAddTransactionItem(ContainerChangeEvent e)
}
// documentation is inherited
public void addedItem(ContainerChangeEvent e)
}
throws
80
85
90
// documentation is inherited
public void editingTransactionItem(ContainerChangeEvent e)
}
95
RemoteException {
throws
throws
throws
// documentation is inherited
public void rolledbackEditTransactionItem(ContainerChangeEvent e)
}
throws
// documentation is inherited
public void noRemoveTransactionItem(ContainerChangeEvent e)
}
RemoteException {
throws
RemoteException {
RemoteException {
RemoteException {
throws
// documentation is inherited
public void committedEditTransactionItem(ContainerChangeEvent e)
}
RemoteException {
throws
// documentation is inherited
public void rolledbackRemoveTransactionItem(ContainerChangeEvent e)
}
throws
VetoException, RemoteException {
RemoteException {
throws
// documentation is inherited
public void committedAddTransactionItem(ContainerChangeEvent e)
}
// documentation is inherited
public void propertyChange(PropertyChangeEvent event)
}
RemoteException {
RemoteException {
// documentation is inherited
public void canRemoveTransactionItem(ContainerChangeEvent e)
}
VetoException, RemoteException {
throws
}
// documentation is inherited
public void noEditTransactionItem(ContainerChangeEvent e)
75
PContainerListener, java.io.Serializable
/**
* Contains the ID of the listener as set by the <code>ListenableContainer</code>.
*/
protected String sListenerID = null;
}
65
implements
RemoteException {
throws
RemoteException {
RemoteException {
C.1. PACKAGE EPOINT.DATA
// documentation is inherited
nal public void setListenerID(String sListenerID)
this.sListenerID = sListenerID;
}
100
105
247
throws
RemoteException {
// documentation is inherited
nal public String getSerializedForm() throws RemoteException {
return epoint.sale.Shop.getPersistenceSerializer().toString(this);
}
}
Listing C.48: epoint.data.rmi.RemoteIteratorImpl
package
5
10
15
import
import
import
epoint.data.rmi;
java.util.*;
epoint.data.RemoteIterator;
java.rmi.*;
/**
* This is a default implementation for the {@link RemoteIterator RemoteIterator interface}.
* Instances of this default implementation provide an empty RemoteIterator.
*
* @see CatalogItemIterator
* @see ASStockItemIterator
* @see Iterator
* @author Danny Poppe
* @version 1.0
*/
public class RemoteIteratorImpl extends java.rmi.server.UnicastRemoteObject implements RemoteIterator, java.io.Serializable {
/**
* Creates a new empty iterator.
*
* @throws RemoteException whenever an error occurs during remote-access
*/
public RemoteIteratorImpl() throws RemoteException {
}
20
25
/**
* Returns <code>true</code> if this Iterator has more elements that may be
* retrieved by the {@link #next()} method.<br>
* The default implementation always returns <code>false</code>.
*
* @return <code>true</code> if this iterator has more elements
* @throws RemoteException whenever an error occurs during remote-access
*/
public boolean hasNext() throws RemoteException {
return false;
}
30
35
/**
* Returns the next element of this iterator.
*
* @throws NoSuchElementException if this iterator doesn't have anymore elements
* @throws RemoteException whenever an error occurs during remote-access
* @return the next object in this iterator
*/
public Object next() throws RemoteException {
throw new NoSuchElementException();
}
40
45
50
/**
* Removes the currently returned element of this Iterator from the underlying
* collection of elements.
*
* @throws UnsupportedOperationException if this iterator does not support
*
removing elements from the underlying collection
* @throws RemoteException whenever an error occurs during remote-access
*/
public void remove() throws RemoteException {
throw new UnsupportedOperationException();
}
55
60
}
Listing C.49: epoint.data.rmi.RemoteRunnableImpl
package epoint.data.rmi;
import java.rmi.*;
import epoint.data.*;
5
/**
248
10
15
20
25
30
35
ANHANG C. QUELLTEXTE
* This is the default implementation for the {@link RemoteRunnable RemoteRunnable interface},
* which does nothing more than providing an empty adapter.<br>
* It may be used to collect work to be done at different locations.
*
* @author Danny Poppe
* @version 1.0
*/
public abstract class RemoteRunnableImpl extends java.rmi.server.UnicastRemoteObject implements RemoteRunnable {
////////////////////////////////////////////////////////////////////////////////
//
NEW CLASS CONSTANTS
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//
NEW CLASS VARIABLES
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//
OVERRIDDEN CLASS VARIABLES
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//
NEW INSTANCE VARIABLES
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//
OVERRIDDEN INSTANCE VARIABLES
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//
CONSTRUCTORS
////////////////////////////////////////////////////////////////////////////////
45
/**
* Creates a new empty RemoteRunnableImpl.
*
* @throws RemoteException whenever an error occurs during remote-access
*/
public RemoteRunnableImpl() throws RemoteException {
}
50
////////////////////////////////////////////////////////////////////////////////
//
INTERFACE RemoteRunnable
////////////////////////////////////////////////////////////////////////////////
40
/**
* Starts the work that was defined by implementing this method.
*
* @param oParams an array of objects that may be passed as parameter
*
to the work that has to be done.
* @throws RemoteException whenever an error occurs during remote-access
*/
public abstract void run(Object[] oParams) throws RemoteException;
55
60
65
70
75
////////////////////////////////////////////////////////////////////////////////
//
OVERRIDDEN CLASS METHODS
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//
OVERRIDDEN INSTANCE METHODS
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//
NEW CLASS METHODS
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//
NEW INSTANCE METHODS
////////////////////////////////////////////////////////////////////////////////
}
Listing C.50: epoint.data.rmi.StockItemImpl
5
10
package epoint.data.rmi;
import epoint.data.*;
import java.util.*;
import java.io.*;
import epoint.sale.Shop;
import epoint.sale.EPoint;
import java.rmi.RemoteException;
import epoint.data.exception.*;
import java.sql.Connection;
/**
C.1. PACKAGE EPOINT.DATA
15
20
25
30
35
40
45
50
55
60
65
70
75
80
85
90
95
* This is the default-implementation for the {@link StockItem StockItem interface}.
* For more documentation about StockItems, read there.<br>
* <br>
* This implementation of a StockItem is remotely available and as such must be
* instantiated in the Shops environment. A client (EPoint) must use the {@link
* epoint.sale.ShopServices ShopServices} to obtain a reference to a StockItem.<br>
* <br>
* A StockItemImpl becomes persistent only, if it is associated with a Stock in any
* way (with or without a transaction} and is not automatically made persistent.
*
* @see StockItem
* @see CatalogItem
* @author Danny Poppe
* @version 1.0
*/
public class StockItemImpl extends java.rmi.server.UnicastRemoteObject implements StockItem {
////////////////////////////////////////////////////////////////////////////////
//
NEW CLASS CONSTANTS
////////////////////////////////////////////////////////////////////////////////
/**
* The Stock in which this StockItem is contained in.
*/
transient private Stock sOwner;
/**
* The name/key of this StockItem
*/
transient private String sName;
/**
* The Value that is associated with this StockItem.
*/
transient private Value vValue;
/**
* This StockItems internal ID which must be unique in a Stock
*/
transient private Long lStockItemID;
/**
* This TransactionItems identifier.
*/
transient private String sTransID;
/**
* The DataBasket by which this StockItem is currently modified/transferred.
*/
transient private DataBasket dbOwner;
/**
* This persistent Objects ID
*/
transient private String sOID;
/**
* If <code>true</code>, this StockItem may be freely edited.
*/
private boolean bEditable = true;
////////////////////////////////////////////////////////////////////////////////
//
NEW CLASS VARIABLES
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//
OVERRIDDEN CLASS VARIABLES
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//
NEW INSTANCE VARIABLES
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//
OVERRIDDEN INSTANCE VARIABLES
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//
CONSTRUCTORS
////////////////////////////////////////////////////////////////////////////////
/**
* Creates a new StockItem with the given name and Value. This StockItem is
* initially not persistent.
*
* @param sName the name of this StockItem which must match a visible key
*
of the base-Catalog of the Stock towhich this StockItem will be added
* @param vValue the Value, this StockItem is associated with
* @throws RemoteException whenever an error occurs during remote-access
*/
public StockItemImpl(String sName, Value vValue) throws RemoteException {
this.sName = sName;
this.vValue = vValue;
this.sTransID = "StockItem TransactionItem #"+epoint.sale.Shop.getTheShop().getGlobalUniqueNumber();
this.lStockItemID = new Long(Shop.getTheShop().getGlobalUniqueNumber());
249
250
100
105
110
115
120
125
130
135
140
145
150
155
160
165
170
175
180
185
ANHANG C. QUELLTEXTE
}
////////////////////////////////////////////////////////////////////////////////
//
INTERFACE StockItem
////////////////////////////////////////////////////////////////////////////////
/**
* Returns the Stock, this StockItem is contained in (may be <code>null</code>)
*
* @throws RemoteException whenever an error occurs during remote-access
* @return the Stock, in which this StockItem is currently contained in
*/
nal public Stock getStock() throws RemoteException {
return sOwner;
}
/**
* Returns the Value, that is stored together with this StockItem.
*
* @return the Value of this StockItem
* @throws RemoteException whenever an error occurs during remote-access
*/
nal public Value getValue() throws RemoteException {
return (vValue!= null)?(vValue.getDeepClone(false)):(null);
}
// documentation is inherited
public CatalogItem getAssociatedItem(DataBasket db)
try {
return getAssociatedItem(db,false);
} catch (VetoException ve) {
Shop.getTheShop().logException(ve);
}
return null;
}
throws
RemoteException {
// documentation is inherited
public CatalogItem getAssociatedItem(DataBasket db, boolean bEditable) throws RemoteException, VetoException {
if (getStock() != null) {
try {
return getStock().getCatalog().get(getName(),db,bEditable);
} catch (DataBasketConflictException dbce) {
throw new VetoException("The Item you want to edit is currently involved in another operation using another databasket! ("+
dbce+")");
}
}
return null;
}
// documentation is inherited
nal public long getInternalID() throws RemoteException {
return lStockItemID.longValue();
}
// documentation is inherited
public StockItem getShallowClone(boolean bEditable) throws RemoteException {
if ((!this.isEditable()) && bEditable && EPoint.isClientApplication()) throw new IllegalArgumentException("You cannot create an
editable clone of not-editable original!");
StockItem siClone = null;
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(this);
siClone = (StockItem)(new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()))).readObject();
} catch (Exception e) {
Shop.getTheShop().logException(e);
return null;
}
Map m = new HashMap();
m.put(this.PERSISTENT_PROPERTY_CATALOGITEMKEY,getName());
m.put(this.PERSISTENT_PROPERTY_STOCK,getStock());
m.put(this.PERSISTENT_PROPERTY_IDENTIFIER,lStockItemID);
m.put(this.PERSISTENT_PROPERTY_TRANSACTION_IDENTIFIER,getTransactionItemIdentifier());
m.put(this.PERSISTENT_PROPERTY_SIVALUE,getValue());
m.put(this.PERSISTENT_PROPERTY_TRANSACTION_BASKET,getBasket());
siClone.loadPersistentData(m,null);
siClone.setEditable(bEditable,null);
siClone.setPersistent(this.getOID());
return siClone;
}
// documentation is inherited
nal public void setEditable(boolean bEditable, String sCon) throws RemoteException {
this.bEditable = bEditable;
updatePersistentObject(Arrays.asList(new Object[]{PersistentObject.PERSISTENT_PROPERTY_SERIALIZED}),sCon);
}
C.1. PACKAGE EPOINT.DATA
190
195
200
205
210
215
220
// documentation is inherited
nal public String getName()
return sName;
}
throws
251
RemoteException {
// documentation is inherited
nal public boolean isEditable() throws RemoteException {
if (getStock() != null) return bEditable && getStock().isEditable();
return bEditable;
}
// documentation is inherited
nal public void setStock(Stock sStock, String sCon) throws RemoteException {
sOwner = sStock;
if (!(sStock == null)) updatePersistentObject(Arrays.asList(new Object[]{PERSISTENT_PROPERTY_STOCK}),sCon);
}
////////////////////////////////////////////////////////////////////////////////
//
INTERFACE PersistentObject
////////////////////////////////////////////////////////////////////////////////
// documentation is inherited
public void loadPersistentData(java.util.Map mProperties, String sConPasswd) throws RemoteException {
try {
this.sOwner = (Stock)mProperties.get(StockItem.PERSISTENT_PROPERTY_STOCK);
this.sName = (String)mProperties.get(StockItem.PERSISTENT_PROPERTY_CATALOGITEMKEY);
this.lStockItemID = (Long)mProperties.get(StockItem.PERSISTENT_PROPERTY_IDENTIFIER);
this.sTransID = (String)mProperties.get(StockItem.PERSISTENT_PROPERTY_TRANSACTION_IDENTIFIER);
this.vValue = (Value)mProperties.get(StockItem.PERSISTENT_PROPERTY_SIVALUE);
this.dbOwner = (DataBasket)mProperties.get(StockItem.PERSISTENT_PROPERTY_TRANSACTION_BASKET);
} catch (Exception e) {
epoint.sale.Shop.getTheShop().logException(e);
}
}
240
// documentation is inherited
public void updatePersistentObject(java.util.List lPropertyKeys, String sCon) throws RemoteException {
if (!isPersistent() || (getStock() == null)) return;
List lAllowed = Arrays.asList(new Object[]{StockItem.PERSISTENT_PROPERTY_SERIALIZED,
StockItem.PERSISTENT_PROPERTY_CATALOGITEMKEY,
StockItem.PERSISTENT_PROPERTY_IDENTIFIER,
StockItem.PERSISTENT_PROPERTY_SIVALUE,
StockItem.PERSISTENT_PROPERTY_STOCK,
StockItem.PERSISTENT_PROPERTY_TRANSACTION_BASKET,
StockItem.PERSISTENT_PROPERTY_TRANSACTION_IDENTIFIER,
StockItem.PERSISTENT_PROPERTY_TRANSACTION_STATE
});
if (!lAllowed.containsAll(lPropertyKeys)) throw new IllegalArgumentException(
"At least one of the properties given for updating the persistent object could not be identified!");
try {
Shop.getTheShop().getDBPersistenceManager().updateStockItem(this,lPropertyKeys,getReservedConnection(sCon));
} catch (Exception e) {
Shop.getTheShop().logException(e);
}
}
245
// documentation is inherited
nal public boolean isPersistent()
return sOID != null;
}
250
// documentation is inherited
nal public String getSerializedForm() throws RemoteException {
return Shop.getPersistenceSerializer().toString(this);
}
255
// documentation is inherited
nal public void setPersistent(String sOID)
this.sOID = sOID;
}
260
// documentation is inherited
nal public String getOID()
return sOID;
}
225
230
235
265
270
throws
throws
RemoteException {
throws
RemoteException {
RemoteException {
////////////////////////////////////////////////////////////////////////////////
//
INTERFACE TransactionItem
////////////////////////////////////////////////////////////////////////////////
/**
* If this Item is currently involved in a Transaction using a DataBasket
* this method returns the DataBasket of the Transaction - otherwise
* it returns <code>null</code>
*
* @return the DataBasket that describes an action on this TransactionItem
* @throws RemoteException whenever an error occurs during remote-access
*/
252
275
280
285
nal public DataBasket
return dbOwner;
ANHANG C. QUELLTEXTE
getBasket()
throws
RemoteException {
}
// documentation is inherited
nal public void setBasket(DataBasket db, String sCon) throws RemoteException {
if (db != null) {
if (dbOwner != null) {
if (!dbOwner.equals(db) && dbOwner.iterator(this).hasNext() && !dbOwner.hasFinished())
throw new RuntimeException("You cannot set a new DataBasket, as this item already belongs to a databasket that has not
yet committed!");
}
}
dbOwner = db;
updatePersistentObject(Arrays.asList(new Object[]{PERSISTENT_PROPERTY_TRANSACTION_STATE,PERSISTENT_PROPERTY_TRANSACTION_BASKET}),sCon)
;
}
290
// documentation is inherited
nal public String getTransactionItemIdentifier()
return sTransID;
}
295
/**
* Returns a String representation of this StockItem, containing its name
* and the Stocks name it is contained in.
*
* @return a String representation of this object
*/
public String toString() {
try {
return "StockItem: "+sName+((sOwner!=null)?(" contained in "+sOwner.getName()):(""));
} catch (RemoteException rmie) {
return "StockItem: "+sName+" [Exception occured during Stock-access: "+rmie.getClass().getName()+"]";
}
}
300
305
310
315
320
325
330
335
340
345
350
355
throws
RemoteException {
/**
* Sets a new Value for this StockItem. If the StockItem is persistent and
* the editing is allowed, its persistent state is automatically updated.
*
* @param vNew the new Value for this StockItem
* @throws RemoteException whenever an error occurs during remote-access
* @throws NotEditableException if this StockItem is not allowed to be edited
*/
nal public void setValue(Value vNew) throws RemoteException, NotEditableException {
if (!isEditable()) throw new NotEditableException("This StockItem is currently not allowed to be edited!");
this.vValue = vNew;
String sUpdatePasswd = getClass()+".updatePersistentObject";
updatePersistentObject(Arrays.asList(new Object[]{StockItem.PERSISTENT_PROPERTY_SIVALUE}),sUpdatePasswd);
returnConnection(getReservedConnection(sUpdatePasswd));
}
// documentation is inherited
nal public void setValue(Value vValue, String sConPasswd) throws RemoteException {
if (!isEditable()) throw new NotEditableException("This StockItem is currently not allowed to be edited!");
this.vValue = vValue;
updatePersistentObject(Arrays.asList(new Object[]{StockItem.PERSISTENT_PROPERTY_SIVALUE}),sConPasswd);
}
/**
* Compares the given object to this StockItem and returns <code>true</code>
* if this and the given object are considered to be equal.<br>
* This is the case if the given object is a StockItem and:
* <ul>
*
<li>if both StockItems are contained in a CountingStock or one is
*
contained in a CountingStock and the other is not contained in any Stock: keys and values must be equal</li>
*
<li>all other cases: internal unique identifiers must be equal</li>
* </ul>
*
* @param o the object that shall be compared to this StockItem
* @return <code>true</code> if the given object is considered to be equal to
*
this StockItem
*/
nal public boolean equals(Object o) {
if (!(o instanceof StockItem)) return false;
StockItem si = (StockItem)o;
try {
Stock sThis = getStock();
Stock sOther = si.getStock();
if (sThis == null) {
if (sOther != null) {
if (sOther.isCountingStock()) {
return si.getName().equals(getName()) && si.getValue().equals(getValue());
}
}
return getInternalID() == si.getInternalID();
} else {
C.1. PACKAGE EPOINT.DATA
if
360
365
370
}
}
375
380
385
}
(sThis.isCountingStock()) {
if (sOther == null) {
return getName().equals(si.getName()) && getValue().equals(si.getValue());
} else if (sOther.isCountingStock()) {
return getName().equals(si.getName()) && getValue().equals(si.getValue());
} else return getInternalID() == si.getInternalID();
} else {
return getInternalID() == si.getInternalID();
}
catch (RemoteException rmie) {
throw new RuntimeException("Could
not complete equality-check, as a remote-exception occured!");
}
////////////////////////////////////////////////////////////////////////////////
//
OVERRIDDEN CLASS METHODS
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//
OVERRIDDEN INSTANCE METHODS
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//
NEW CLASS METHODS
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//
NEW INSTANCE METHODS
////////////////////////////////////////////////////////////////////////////////
390
/**
* Returns a Connection to the database, that is protected by a class-wide
* default-password.
*
* @return the default-connection of this class
*/
protected Connection getDefaultConnection() {
return Shop.getTheShop().getDBPersistenceManager().getPrivateConnection("Default Connection for class "+getClass().getName(),getClass
());
}
395
400
/**
* Returns a Connection to the database, that is protected by the given
* password.
*
* @param sConPasswd the password the protects the requested connection
* @return the requested Connection
*/
protected Connection getReservedConnection(String sConPasswd) {
return Shop.getTheShop().getDBPersistenceManager().getPrivateConnection(sConPasswd,getClass());
}
405
410
/**
* Returns the given Connection that was previously requested to the DBPersistenceManager
* for further use by others.
*
* @param con the connection that shall be returned
*/
protected void returnConnection(Connection con) {
Shop.getTheShop().getDBPersistenceManager().returnPrivateConnection(con);
}
415
420
/**
* Returns the same as {@link #toString()}, but may be used by remote-connections,
* as {@link java.rmi.server.RemoteObject#toString()} returns a description of the
* Stub at client-side
*
* @return the String describing this object
* @throws RemoteException whenever an error occurs during remote-access
*/
public String toRemoteString() throws RemoteException {
return toString();
}
425
430
}
Listing C.51: epoint.data.rmi.StoringStockImpl
5
253
package epoint.data.rmi;
import java.rmi.RemoteException;
import epoint.data.*;
import epoint.data.exception.*;
import epoint.sale.Shop;
import java.util.*;
/**
254
10
15
20
25
30
35
40
45
50
55
60
65
70
75
80
85
90
95
ANHANG C. QUELLTEXTE
* This is the default implementation for the {@link StoringStock StoringStock interface}.
* For more documentation about StoringStocks, read there and in {@link AbstractStock}.<br>
* <br>
* As StoringStocks are remotely available Containers, they must be instantiated
* at the Shop. That means, they need to be requested by a client (EPoint) using
* the {@link epoint.sale.ShopServices ShopServices}.
*
* @see AbstractStock
* @see Catalog
* @see StockItem
* @see CatalogItem
* @author Danny Poppe
* @version 1.0
*/
public class StoringStockImpl extends AbstractStock implements StoringStock {
////////////////////////////////////////////////////////////////////////////////
//
NEW CLASS CONSTANTS
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//
NEW CLASS VARIABLES
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//
OVERRIDDEN CLASS VARIABLES
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//
NEW INSTANCE VARIABLES
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//
OVERRIDDEN INSTANCE VARIABLES
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//
CONSTRUCTORS
////////////////////////////////////////////////////////////////////////////////
/**
* Creates a new StoringStock with the given name and based upon the
* given Catalog, which is automatically made persistent.
*
* @param sName the name of this StoringStock, which must be unique under
*
all names of existing Stocks
* @param cBaseCatalog the base-Catalog for this Stock
* @throws RemoteException whenever an error occurs during remote-access
*/
public StoringStockImpl(String sName, Catalog cBaseCatalog) throws RemoteException {
super(sName,cBaseCatalog);
}
////////////////////////////////////////////////////////////////////////////////
//
INTERFACE Stock
////////////////////////////////////////////////////////////////////////////////
/**
* In this implementation always returns <code>false</code>, as this is no
* StoringStock.
*
* @return <code>false</code>
* @throws RemoteException whenever an error occurs during remote-access
*/
nal public boolean isCountingStock() throws RemoteException {
return false;
}
// documentation is inherited
public void add(StockItem si, DataBasket db) throws RemoteException, DataBasketConflictException {
try {
synchronized(getPropertyLock()) {
if (si == null) throw new NullPointerException("You must provide a StockItem for adding!");
if (!isEditable())
throw new NotEditableException("The stock " + getName() + " is currently not editable!");
if (db == null) {
add(si,null);
return;
}
String sKey = si.getName();
if (!getCatalog().contains(sKey,db)) {
throw new NoSuchKeyException("The name of the StockItem you want to add does not match the key of a visible CatalogItem
in the BaseCatalog of this Stock!");
} else {
synchronized(getItemsLock()) {
if (getEditedItems(sKey).contains(si)) {
// this item is currently edited within some transaction
// we cannot allow a concurrent adding that may commit first
C.1. PACKAGE EPOINT.DATA
100
105
110
115
120
125
130
135
140
145
150
155
160
165
170
175
255
// and will be overwritten by the the second committing
// editing transaction
throw new DataBasketConflictException("The item you want to add is currently edited by a transaction!",
DataBasketConflictException.CONCURRENT_EDITING);
} else if (getStockItems(sKey).contains(si)) {
// this item is already contained in this stock
// we can silently ignore this adding-transaction
return;
} else if (getAddedItems(sKey).contains(si)) {
// this item is not contained but currently added within some transaction
Iterator it = getAddedItems(sKey).iterator();
while (it.hasNext()) {
StockItem siTemp = (StockItem)it.next();
if (siTemp.equals(si) && siTemp.getBasket().equals(db)) return; // this item is already added by the given
transaction
}
throw new DataBasketConflictException("The item you want to add is currently added by another transaction!",
DataBasketConflictException.CONCURRENT_ADDITION);
} else if (getRemovedItems(sKey).contains(si)) {
// this item is currently part of a removing transaction and will be converted into an editing transaction
// only if this is done by the same basket
Iterator itn = getRemovedItems(sKey).iterator();
StockItem siRemoved = null;
while (itn.hasNext()) {
siRemoved = (StockItem)itn.next();
if (siRemoved.equals(si)) {
if (!siRemoved.getBasket().equals(db)) throw new DataBasketConflictException("The item you want to add is
currently removed by another transaction!",DataBasketConflictException.CONCURRENT_REMOVAL);
break;
}
siRemoved = null;
}
if (siRemoved == null) throw new RuntimeException("A Set contains an item that cannot be found by iterating and
comparing using the equals()-method!");
// now we have the siRemoved, that must be converted into an editing transaction
// next we rollback the removal
RemoteIterator itr = db.iterator(siRemoved);
while (itr.hasNext()) {
DBEntry dbe = (DBEntry)itr.next();
if (dbe.getTransactionType().equals(dbe.TRANSACTION_REMOVE) && (dbe.getTarget().equals(this))) {
// we now have the DBEntry that describes the removal
// of the given StockItem relative to this Stock
// and we have the DBEntry we want to change...
// we will roll it back first, so the removed
// item becomes a normal item again
String sConPasswd = "Protected Connection-Adding StoringStock item #"+Shop.getTheShop().getGlobalUniqueNumber
();
try {
getReservedConnection(sConPasswd).setAutoCommit(false);
} catch (Exception e) {
Shop.getTheShop().logException(e);
}
RemoteRunnable r = null;
try {
r = rollbackTransaction(dbe,sConPasswd); // this implicitly fires a rolledBackTransactionItem-event
// the transaction is rolled back now and the old DBEntry is now removed from the basket
db.removeTransaction(dbe,sConPasswd);
} catch (RuntimeException rte) {
Shop.getTheShop().logException(rte);
try {
getReservedConnection(sConPasswd).rollback();
} catch (Exception e1) {
Shop.getTheShop().logException(e1);
} nally {
returnConnection(getReservedConnection(sConPasswd));
}
throw new RuntimeException("Could not complete rollback of a StockItem removing transaction");
}
try {
getReservedConnection(sConPasswd).commit();
} catch (Exception e) {
throw new RuntimeException(e.getClass().getName()+" occured: "+e.getMessage());
} nally {
try {
returnConnection(getReservedConnection(sConPasswd));
} catch (Exception e2) {
Shop.getTheShop().logException(e2);
}
}
r.run(null);
try {
fireCanEditTransactionItem(new ContainerChangeEventImpl(si,this,db));
} catch (VetoException ve) {
//ok one does not want to edit the item we just rolled back from its
//removal
//we will try to have the old state again while removing
//the item again
try {
256
180
185
ANHANG C. QUELLTEXTE
}
}
remove(si,db);
(VetoException ve2) {
//ok, another one does not want to have the old state again
throw new IllegalStateException("Could not complete the request to add an item within the
transaction, it was removed with! "+
"This results in a modifying transaction that could not be completed, because some listener "+
"prevents the item from being edited and another one or the same listener prevents us from "+
"recreating the original state of this transaction, where the item was removed! "+
"The result is that the item is no more part of the transaction!\nVeto was: "+ve2.getMessage());
catch
throw new
IllegalStateException("Could not add the item because it was previously removed by the given
transaction and a listener "+
"prevents us to change the transaction from a removing transaction to a modifying transaction: "+ve.
getMessage());
190
195
200
205
210
215
220
225
230
235
240
245
250
255
260
}
// now we create the modifying transaction with the
// new given item (the original item is rolled back and
// normal part of the catalog again)
sConPasswd = "Protected Connection-Adding StoringStock item #"+Shop.getTheShop().getGlobalUniqueNumber();
try {
getReservedConnection(sConPasswd).setAutoCommit(false);
} catch (Exception e) {
Shop.getTheShop().logException(e);
}
si.setEditable(true,sConPasswd);
getEditedItems(sKey).add(si);
si.setStock(this,sConPasswd);
try {
Shop.getTheShop().getDBPersistenceManager().createStockItem(si,true,true,getReservedConnection(sConPasswd
));
} catch (RuntimeException e) {
Shop.getTheShop().logException(e);
try {
getReservedConnection(sConPasswd).rollback();
} catch (Exception e1) {
Shop.getTheShop().logException(e1);
} nally {
returnConnection(getReservedConnection(sConPasswd));
}
throw e;
}
DBEntry dbeNew = new DBEntryImpl(db,dbe.TRANSACTION_MODIFY,this,si,null);
db.add(dbeNew,sConPasswd);
si.setBasket(db,sConPasswd);
si.setEditable(true,sConPasswd);
try {
getReservedConnection(sConPasswd).commit();
} catch (Exception e) {
Shop.getTheShop().logException(e);
throw new RuntimeException("Error while accessing the database: "+e);
} nally {
returnConnection(getReservedConnection(sConPasswd));
}
fireEditingTransactionItem(new ContainerChangeEventImpl(si,this,db));
return;
}
}// while
// if we are here, something must be wrong:
// we first checked, if the basket that removed the item
// was the same as the given to add the item, but we found no
// DBEntry in the given basket that describes the removal
throw new IllegalStateException("It seems, as we have an internal framework-error!\n"+
"We have a DataBasket that removed an item from this Stock, but does not contain\n"+
"the appropriate entry describing the removal!");
} else {
// this is a new StockItem that must be added within a transaction
String sConPasswd = "Protected Connection-Adding StoringStock item #"+Shop.getTheShop().getGlobalUniqueNumber();
try {
getReservedConnection(sConPasswd).setAutoCommit(false);
} catch (Exception e) {
Shop.getTheShop().logException(e);
}
si.setEditable(false,sConPasswd);
si = si.getShallowClone(true);
// here we are to add a fully new item within a transaction
// and we register it with the stock first
getAddedItems(sKey).add(si);
si.setStock(this,sConPasswd);
try {
Shop.getTheShop().getDBPersistenceManager().createStockItem(si, true, false,getReservedConnection(sConPasswd));
} catch (RuntimeException e) {
Shop.getTheShop().logException(e);
si.setBasket(null,sConPasswd);
try {
getReservedConnection(sConPasswd).rollback();
} catch (Exception e1) {
Shop.getTheShop().logException(e1);
} nally {
C.1. PACKAGE EPOINT.DATA
}
e;
}
// now we register the new transaction in the databasket
DBEntry dbe = new DBEntryImpl(db,DBEntry.TRANSACTION_ADD,this,si,null);
db.add(dbe,sConPasswd);
si.setBasket(db,sConPasswd);
// now we can inform the listeners
try {
getReservedConnection(sConPasswd).commit();
} catch (Exception e) {
Shop.getTheShop().logException(e);
throw new RuntimeException("Error while accessing the database: "+e);
} nally {
returnConnection(getReservedConnection(sConPasswd));
}
fireAddedItem(new ContainerChangeEventImpl(si,this,db));
return;
270
275
280
285
295
300
305
310
315
320
325
330
335
340
345
returnConnection(getReservedConnection(sConPasswd));
throw
265
290
257
}
}
}// synchronize items
}// else
}// synchronized properties
} catch(RuntimeException e) {
Shop.getTheShop().logException(e);
throw e;
}
// documentation is inherited
public void add(StockItem si) throws RemoteException, DataBasketConflictException {
DataBasket db = new DataBasketImpl();
add(si,db);
db.commit();
try {
Shop.getTheShop().getDBPersistenceManager().deleteDataBasket(db, getDefaultConnection());
returnConnection(getDefaultConnection());
} catch (Exception e) {
returnConnection(getDefaultConnection());
Shop.getTheShop().logException(e);
}
}
// documentation is inherited
public StockItem remove(StockItem si, DataBasket db) throws RemoteException, VetoException, DataBasketConflictException {
try {
synchronized(getPropertyLock()) {
if (si == null) throw new NullPointerException("You must provide a StockItem for removal!");
if (!isEditable())
throw new NotEditableException("The stock " + getName() + " is currently not editable!");
if (db == null) return remove(si,null);
String sKey = si.getName();
//if (!getCatalog().contains(sKey,db)) {
//throw new NoSuchKeyException("The name of the StockItem you want to remove does not match the key of a visible CatalogItem
in the BaseCatalog of this Stock!");
//} else {
synchronized(getItemsLock()) {
if (getEditedItems(sKey).contains(si)) {
// this item is currently edited within some transaction
// and can only be removed if it is the same basket
// in the case of a Stock this means, that the editing
// transaction-part is rolled back and the original item is
// removed in a recursive call
Iterator it = getEditedItems(sKey).iterator();
StockItem siEdited = null;
while (it.hasNext()) {
siEdited = (StockItem)it.next();
if (siEdited.equals(si)) {
if (!siEdited.getBasket().equals(db)) throw new DataBasketConflictException("The item you want to remove is
currently edited by another transaction!",DataBasketConflictException.CONCURRENT_EDITING);
}
siEdited = null;
}
Iterator itNormal = getStockItems(siEdited.getName()).iterator();
StockItem siNormal = null;
while (itNormal.hasNext()) {
siNormal = (StockItem)itNormal.next();
if (siNormal.getTransactionItemIdentifier().equals(siEdited.getTransactionItemIdentifier())) break;
siNormal = null;
}
if ((siEdited == null) || (siNormal == null)) throw new RuntimeException("A set contains a StockItem that cannot
be found with the iterator and the equals()-method");
fireCanRemoveTransactionItem(new ContainerChangeEventImpl(siEdited,this,db));
fireCanRemoveTransactionItem(new ContainerChangeEventImpl(siNormal,this,db));
String sConPasswd = "Private Connection - remove edited StockItem by rollBack #"+Shop.getTheShop().
getGlobalUniqueNumber();
try {
getReservedConnection(sConPasswd).setAutoCommit(false);
258
ANHANG C. QUELLTEXTE
catch (Exception e) {
Shop.getTheShop().logException(e);
}
// siEdited can now be rolled back
RemoteIterator itr = db.iterator(siEdited);
boolean bRemoved = false;
while (itr.hasNext()) {
DBEntry dbe = (DBEntry)itr.next();
if (dbe.getTransactionType().equals(dbe.TRANSACTION_MODIFY) && dbe.getTarget().equals(this)) {
RemoteRunnable r = null;
try {
r = rollbackTransaction(dbe,sConPasswd);
db.removeTransaction(dbe,sConPasswd);
siEdited.setBasket(null,sConPasswd);
siEdited.setStock(null,sConPasswd);
siEdited.setEditable(true,sConPasswd);
getReservedConnection(sConPasswd).commit();
bRemoved = true;
} catch (Exception rte) {
Shop.getTheShop().logException(rte);
siEdited.setBasket(db,sConPasswd);
siEdited.setStock(this,sConPasswd);
getEditedItems(siEdited.getName()).add(siEdited);
try {
getReservedConnection(sConPasswd).rollback();
} catch (Exception e) {
Shop.getTheShop().logException(e);
}
throw new RuntimeException(rte.getClass().getName()+" occured: "+rte.getMessage());
} nally {
returnConnection(getReservedConnection(sConPasswd));
}
r.run(null);
try {
remove(siNormal,db);
} catch (VetoException ve) {
throw new RuntimeException("A listener seems to agree with the removal of a StockItem, and if asked a
second time, it answers: "+ve.getMessage());
}
return siEdited;
}
}
throw new RuntimeException("It seems as we have an internal Framework-error, we have a StockItem removed "+
"by a given basket, but this basket does not contain the appropriate DBEntry!");
} else if (getStockItems(sKey).contains(si)) {
// this item is a normal item in this stock and will be removed now
Iterator it = getStockItems(sKey).iterator();
StockItem siNormal = null;
while (it.hasNext()) {
siNormal = (StockItem)it.next();
if (siNormal.equals(si)) break;
siNormal = null;
}
fireCanRemoveTransactionItem(new ContainerChangeEventImpl(siNormal,this,db));
String sConPasswd = "Private Connection-Removing a StoringStock item #"+Shop.getTheShop().getGlobalUniqueNumber();
try {
getReservedConnection(sConPasswd).setAutoCommit(false);
} catch (Exception e) {
Shop.getTheShop().logException(e);
}
getStockItems(sKey).remove(siNormal);
getRemovedItems(sKey).add(siNormal);
DBEntry dbe = new DBEntryImpl(db,DBEntry.TRANSACTION_REMOVE,this,siNormal,null);
db.add(dbe,sConPasswd);
siNormal.setBasket(db,sConPasswd);
siNormal = siNormal.getShallowClone(true);
siNormal.setStock(null,sConPasswd);
si.setEditable(false,sConPasswd);
si.setStock(null,sConPasswd);
si.setBasket(db,sConPasswd);
try {
getReservedConnection(sConPasswd).commit();
} catch (java.sql.SQLException sqle) {
Shop.getTheShop().logException(sqle);
throw new RuntimeException("An error occured while accessing the database: "+sqle);
} nally {
returnConnection(getReservedConnection(sConPasswd));
}
fireRemovedTransactionItem(new ContainerChangeEventImpl(siNormal,this,db));
return si;
} else if (getAddedItems(sKey).contains(si)) {
// this item is not contained but currently added within some transaction
// if this is the same transaction, we will rollback the previous adding transaction
Iterator it = getAddedItems(sKey).iterator();
StockItem siAdded = null;
while (it.hasNext()) {
siAdded = (StockItem)it.next();
if (si.equals(siAdded)) {
}
350
355
360
365
370
375
380
385
390
395
400
405
410
415
420
425
430
C.1. PACKAGE EPOINT.DATA
if
}
if
440
445
450
455
460
465
470
475
480
490
495
500
505
510
515
(!siAdded.getBasket().equals(db)) throw new DataBasketConflictException("The item you want to remove is
currently added by another transaction!",DataBasketConflictException.CONCURRENT_ADDITION);
break;
435
485
259
}
}
siAdded =
null;
(siAdded ==
null) throw new
RuntimeException("A set contains a StockItem that cannot be found with the iterator
and the equals()-method");
// siAdded can now be rolled back
RemoteIterator itr = db.iterator(siAdded);
String sConPasswd = getClass()+".remove (protected RollBack)";
while (itr.hasNext()) {
DBEntry dbe = (DBEntry)itr.next();
if (dbe.getTransactionType().equals(dbe.TRANSACTION_ADD) && dbe.getTarget().equals(this)) {
RemoteRunnable r = null;
try {
r = rollbackTransaction(dbe,sConPasswd);
db.removeTransaction(dbe, sConPasswd);
returnConnection(getReservedConnection(sConPasswd));
siAdded.setBasket(null,sConPasswd);
siAdded.setStock(null,sConPasswd);
siAdded.setEditable(true,sConPasswd);
} catch (RuntimeException rte) {
Shop.getTheShop().logException(rte);
getAddedItems(siAdded.getName()).add(siAdded);
throw rte;
} nally {
returnConnection(getReservedConnection(sConPasswd));
}
r.run(null);
return siAdded;
}
}
} else if (getRemovedItems(sKey).contains(si)) {
// this item is already part of a removing transaction
Iterator it = getRemovedItems(sKey).iterator();
while (it.hasNext()) {
StockItem siTemp = (StockItem)it.next();
if (siTemp.getBasket().equals(db) && siTemp.equals(si)) return null;
}
throw new DataBasketConflictException("The item you want to remove is already removed by a concurrent transaction
!",DataBasketConflictException.CONCURRENT_REMOVAL);
} else {
// this is an unknown StockItem
return null;
}
}// synchronize items
//}// else
}// synchronized properties
} catch(RuntimeException e) {
Shop.getTheShop().logException(e);
throw e;
}
return null;
// documentation is inherited
public StockItem remove(StockItem si) throws RemoteException, VetoException, DataBasketConflictException {
DataBasket db = new DataBasketImpl();
StockItem siRetVal = remove(si,db);
db.commit();
try {
Shop.getTheShop().getDBPersistenceManager().deleteDataBasket(db, getDefaultConnection());
returnConnection(getDefaultConnection());
} catch (Exception e) {
returnConnection(getDefaultConnection());
Shop.getTheShop().logException(e);
}
return siRetVal;
}
// documentation is inherited
public int size(String sKey, DataBasket db) throws RemoteException {
int iSize = 0;
synchronized (getItemsLock()) {
if (!getCatalog().contains(sKey,db)) return 0;
iSize = getStockItems(sKey).size();
if (db != null) {
Iterator it = getAddedItems(sKey).iterator();
while (it.hasNext()) {
StockItem siTemp = (StockItem)it.next();
if (siTemp.getBasket().equals(db)) iSize++;
}
}
return iSize;
}
}
260
520
525
530
535
540
545
550
555
560
565
570
575
580
585
590
595
600
ANHANG C. QUELLTEXTE
// documentation is inherited
public int size(DataBasket db) throws RemoteException {
synchronized (getItemsLock()) {
int iSize = 0;
Iterator it = keySet(db).iterator();
while (it.hasNext()) {
String sKey = (String)it.next();
iSize += size(sKey,db);
}
return iSize;
}
}
////////////////////////////////////////////////////////////////////////////////
//
INTERFACE DBEntryTarget
////////////////////////////////////////////////////////////////////////////////
// documentation is inherited
public boolean isValidTransaction(DBEntry dbe) throws RemoteException {
if (!(dbe.getTransactionItem() instanceof StockItem)) return false;
if (!dbe.getTarget().equals(this)) return false;
StockItem si = (StockItem)dbe.getTransactionItem();
DataBasket db = dbe.getBasket();
Iterator it = null;
if (dbe.getTransactionType().equals(dbe.TRANSACTION_ADD)) {
it = getAddedItems(si.getName()).iterator();
} else if (dbe.getTransactionType().equals(dbe.TRANSACTION_REMOVE)) {
it = getRemovedItems(si.getName()).iterator();
} else if (dbe.getTransactionType().equals(dbe.TRANSACTION_MODIFY)) {
it = getEditedItems(si.getName()).iterator();
} else return false;
while (it.hasNext()) {
StockItem siTemp = (StockItem)it.next();
if (!(db.getName().equals(siTemp.getBasket().getName()) && (siTemp.getInternalID()==si.getInternalID()))) {
continue;
} else return true;
}
return false;
}
// documentation is inherited
public RemoteRunnable commitTransaction(DBEntry dbe, String sConPasswd) throws RemoteException {
synchronized (getPropertyLock()) {
synchronized (getItemsLock()) {
if (!isValidTransaction(dbe)) throw new RuntimeException("Detected illegal transaction-data in a DBEntry deliviered to commit
in StoringStock!");
java.sql.Connection con = getReservedConnection(sConPasswd);
if (dbe.getTransactionType().equals(dbe.TRANSACTION_ADD)) {
// an adding transaction has to commit now
StockItem si = (StockItem)dbe.getTransactionItem();
String sKey = si.getName();
getAddedItems(sKey).remove(si);
if (!getStockItems(sKey).add(si)) throw new RuntimeException("The item that should be added within a transaction is
already element of this Stock!");
si.setBasket(null,sConPasswd);
if (!si.getStock().equals(this)) si.setStock(this,sConPasswd);
if (si.isEditable()) si.setEditable(false,sConPasswd);
si.updatePersistentObject(Arrays.asList(new Object[]{si.PERSISTENT_PROPERTY_TRANSACTION_STATE}),sConPasswd);
nal StockItem siThread = si;
nal StoringStockImpl thisStock = this;
nal DataBasket dbThread = dbe.getBasket();
RemoteRunnable r = new RemoteRunnableImpl() {
public void run(Object[] o) throws RemoteException {
try {
thisStock.fireCommittedAddTransactionItem(new ContainerChangeEventImpl(siThread,thisStock,dbThread));
} catch (RemoteException rmie) {
Shop.getTheShop().logException(rmie);
}
}
};
return r;
} else if (dbe.getTransactionType().equals(dbe.TRANSACTION_REMOVE)) {
// an item-removal has to commit
StockItem si = (StockItem)dbe.getTransactionItem();
String sKey = si.getName();
if (!getRemovedItems(sKey).remove(si)) throw new RuntimeException("The item that should be removed within a transaction
is not element of those items marked for removal!");
if (!this.equals(si.getStock())) si.setStock(this,sConPasswd);
try {
Shop.getTheShop().getDBPersistenceManager().deleteStockItem(si,con);
} catch (Exception e) {
Shop.getTheShop().logException(e);
getRemovedItems(sKey).add(si);
throw new RuntimeException(e.getMessage());
}
si.setStock(null,sConPasswd);
si.setBasket(null,sConPasswd);
if (!si.isEditable()) si.setEditable(true,sConPasswd);
C.1. PACKAGE EPOINT.DATA
nal
nal
nal
605
610
615
620
625
630
635
640
645
650
655
660
}
665
670
675
680
685
261
}
}
StockItem siThread = si;
StoringStockImpl thisStock = this;
DataBasket dbThread = dbe.getBasket();
RemoteRunnable r = new RemoteRunnableImpl() {
public void run(Object[] o) throws RemoteException {
try {
thisStock.fireCommittedRemoveTransactionItem(new ContainerChangeEventImpl(siThread,thisStock,dbThread));
} catch (RemoteException rmie) {
Shop.getTheShop().logException(rmie);
}
}
};
return r;
} else if (dbe.getTransactionType().equals(dbe.TRANSACTION_MODIFY)) {
// an item-editing has to commit
StockItem siNew = (StockItem)dbe.getTransactionItem();
String sKey = siNew.getName();
StockItem siOld = null;
Iterator it = getStockItems(sKey).iterator();
while (it.hasNext()) {
siOld = (StockItem)it.next();
if (siOld.getName().equals(siNew.getName())) break;
siOld = null;
}
if (siOld == null) throw new RuntimeException("Could not find the original item for the edited version of a StockItem!");
// first delete the original old item
if (!getStockItems(sKey).remove(siOld)) throw new RuntimeException("The set of StockItems does not contain the original
for an edited item!");
if (!getEditedItems(sKey).remove(siNew)) throw new RuntimeException("The set of edited StockItems does not contain the
edited item itself!");
siOld.setBasket(null,sConPasswd);
try {
Shop.getTheShop().getDBPersistenceManager().deleteStockItem(siOld,con);
} catch (Exception e) {
Shop.getTheShop().logException(e);
getStockItems(sKey).add(siOld);
getEditedItems(sKey).add(siNew);
throw new RuntimeException(e.getMessage());
}
siOld.setStock(null,sConPasswd);
siOld.setEditable(true,sConPasswd);
siNew.setBasket(null,sConPasswd);
if (!this.equals(siNew.getStock())) siNew.setStock(this,sConPasswd);
if (siNew.isEditable()) siNew.setEditable(false,sConPasswd);
nal StockItem siThread = siNew;
nal StoringStockImpl thisStock = this;
nal DataBasket dbThread = dbe.getBasket();
RemoteRunnable r = new RemoteRunnableImpl() {
public void run(Object[] o) throws RemoteException {
try {
thisStock.fireCommittedEditTransactionItem(new ContainerChangeEventImpl(siThread,thisStock,dbThread));
} catch (RemoteException rmie) {
Shop.getTheShop().logException(rmie);
}
}
};
return r;
} else {
throw new RuntimeException("Found illegal transaction-type \""+dbe.getTransactionType()+"\" to commit in CountingStock!")
;
}
// documentation is inherited
public RemoteRunnable rollbackTransaction(DBEntry dbe, String sConPasswd) throws RemoteException {
synchronized (getPropertyLock()) {
synchronized (getItemsLock()) {
if (!isValidTransaction(dbe)) throw new RuntimeException("Detected illegal transaction-data in a DBEntry deliviered to
rollback in StoringStock!");
java.sql.Connection con = getReservedConnection(sConPasswd);
if (dbe.getTransactionType().equals(dbe.TRANSACTION_ADD)) {
// an adding transaction has to be rolled now
StockItem si = (StockItem)dbe.getTransactionItem();
String sKey = si.getName();
// we remove the item from this stock
if (!getAddedItems(sKey).remove(si)) throw new RuntimeException("Could not find the added item in the set of items marked
for adding in a transaction for this Stock!");
// just for security-reasons, we check neccessary data
if (!this.equals(si.getStock())) si.setStock(this,sConPasswd);
if (!dbe.getBasket().equals(si.getBasket())) si.setBasket(dbe.getBasket(),sConPasswd);
// and we delete the data in the database
try {
Shop.getTheShop().getDBPersistenceManager().deleteStockItem(si,con);
} catch (Exception e) {
// an error occured, we need to redo all the action, to preserve
// a persistent state
262
ANHANG C. QUELLTEXTE
getAddedItems(sKey).add(si);
Shop.getTheShop().logException(e);
throw new RuntimeException(e.getMessage());
690
695
700
705
710
715
720
725
730
735
740
745
750
755
760
765
}
si.setStock(null,sConPasswd);
si.setBasket(null,sConPasswd);
si.setEditable(true,sConPasswd);
nal StockItem siThread = si;
nal StoringStockImpl thisStock = this;
nal DataBasket dbThread = dbe.getBasket();
RemoteRunnable r = new RemoteRunnableImpl() {
public void run(Object[] o) throws RemoteException {
try {
thisStock.fireRolledbackAddTransactionItem(new ContainerChangeEventImpl(siThread,thisStock,dbThread));
} catch (RemoteException rmie) {
Shop.getTheShop().logException(rmie);
}
}
};
return r;
} else if (dbe.getTransactionType().equals(dbe.TRANSACTION_REMOVE)) {
// an item-removal has to be rolled now
StockItem si = (StockItem)dbe.getTransactionItem();
String sKey = si.getName();
if (!getRemovedItems(sKey).remove(si)) throw new RuntimeException("Could not remove the transaction-removed item in the
set of removed items marked for removal!");
if (!getStockItems(sKey).add(si)) throw new RuntimeException("Could not add previously removed StockItem back to the
normal set of StockItems!");
try {
if (!si.isEditable()) si.setEditable(false,sConPasswd);
if (!this.equals(si.getStock())) si.setStock(this,sConPasswd);
si.setBasket(null,sConPasswd);
si.updatePersistentObject(Arrays.asList(new Object[]{si.PERSISTENT_PROPERTY_TRANSACTION_STATE,si.
PERSISTENT_PROPERTY_TRANSACTION_BASKET}),sConPasswd);
} catch (RuntimeException e) {
// ok, an error occured, we take back all changes now
getRemovedItems(sKey).add(si);
getStockItems(sKey).remove(si);
Shop.getTheShop().logException(e);
throw e;
}
nal StockItem siThread = si;
nal StoringStockImpl thisStock = this;
nal DataBasket dbThread = dbe.getBasket();
RemoteRunnable r = new RemoteRunnableImpl() {
public void run(Object[] o) throws RemoteException {
try {
thisStock.fireRolledbackRemoveTransactionItem(new ContainerChangeEventImpl(siThread,thisStock,dbThread));
} catch (RemoteException rmie) {
Shop.getTheShop().logException(rmie);
}
}
};
return r;
} else if (dbe.getTransactionType().equals(dbe.TRANSACTION_MODIFY)) {
// an item-edit has to be rolled now
StockItem siNew = (StockItem)dbe.getTransactionItem();
String sKey = siNew.getName();
if (!getEditedItems(sKey).remove(siNew)) throw new RuntimeException("Could not remove the edited version of a StockItem
from the set of items within an editing-transaction!");
try {
Shop.getTheShop().getDBPersistenceManager().deleteStockItem(siNew,con);
} catch (Exception e) {
// same as above, we take back all changes now (database changes
// are rolled back with the connection)
Shop.getTheShop().logException(e);
getEditedItems(sKey).add(siNew);
throw new RuntimeException(e.getMessage());
}
siNew.setStock(null,sConPasswd);
siNew.setBasket(null,sConPasswd);
siNew.setEditable(true,sConPasswd);
nal StockItem siThread = siNew;
nal StoringStockImpl thisStock = this;
nal DataBasket dbThread = dbe.getBasket();
RemoteRunnable r = new RemoteRunnableImpl() {
public void run(Object[] o) throws RemoteException {
try {
thisStock.fireRolledbackEditTransactionItem(new ContainerChangeEventImpl(siThread,thisStock,dbThread));
} catch (RemoteException rmie) {
Shop.getTheShop().logException(rmie);
}
}
};
return r;
} else {
throw new RuntimeException("Found illegal transaction-type \""+dbe.getTransactionType()+"\" to commit in CountingStock!")
;
C.2. PACKAGE EPOINT.DB
770
}
775
780
785
}
}
263
}
////////////////////////////////////////////////////////////////////////////////
//
OVERRIDDEN CLASS METHODS
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//
OVERRIDDEN INSTANCE METHODS
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//
NEW CLASS METHODS
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//
NEW INSTANCE METHODS
////////////////////////////////////////////////////////////////////////////////
}
C.2 Package epoint.db
epoint.db.DBPersistenceManager.java. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .263
epoint.db.PSQLDBConnection.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 271
epoint.db.ReferenceCache.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 307
Listing C.52: epoint.db.DBPersistenceManager
5
10
15
20
25
30
35
40
package epoint.db;
import java.sql.Connection;
import java.util.*;
import epoint.db.exception.DBAccessException;
import epoint.data.exception.DuplicateKeyException;
import epoint.data.*;
/**
* This interface defines fundamental requirements for a PersistenceManager
* that shall be used with this frameworks default implementation. It defines
* methods that may be used to persistify the data that is served by the Shop
* to the EPoints and some additional functionality to provide for example
* persistent properties.<br>
* This is not only meant for providing one-time persistence - instead this
* interface is meant to be implemented by a class that provides "on-line"
* persistence. This way applications may crash and restart with the persistent
* state. This implies that the manager provides features of an object-oriented
* database via the JDBC-classes of the standard JDK, which are intended to
* connect to a relational database.<br>
* There is a standard-implementation for PostGreSQL databases available, that
* supports all these features. For implementing this interface, it is important
* to know the internals of the objects that are being made persistent * which fields are serializable and when updates are made to the persistent
* state.<br>
* A cache holding references to life-objects is not only feature, but nesseccary
* to avoid the same persistent object being instanciated more than one time
* in the virtual machine!<br>
* It is intended to have only one "offical" connection to the database, that
* is shared among all requests. This way dead-locks are avoided!<br>
* <br>
* For more documentation read the <a href="package-summary.html">package-comments</a>!
*
* @see ReferenceCache
* @author Danny Poppe
* @version 1.0
*/
public interface DBPersistenceManager {
/**
* Returns the connection that is currently guarded by the specified
* password. That guarantees, that only requests providing the same
* password gain access to the connection they request. All other requests
* are blocked until the connection requested by this call is returned
* by using {@link #returnPrivateConnection(Connection)}.<br>
264
45
50
55
60
65
70
75
80
85
90
95
100
105
110
115
120
125
130
ANHANG C. QUELLTEXTE
* The connection returned by this call is always in <code>autocommit</code>-mode.
*
* @param sConPasswd the password for the connection that shall be returned
* @param c the class that requests the connection (only for keeping track of "lost" connections)
* @throws DBAccessException whenever an error occurs while operation on the database
* @return the requested connection
*/
public Connection getPrivateConnection(String sConPasswd, Class c) throws DBAccessException;
/**
* Returns a connection requested by {@link #getPrivateConnection(String,Class)}
* to the manager for re-use by other requests. This may implicitly be done
* by closing the connection, which must be registered automatically by the
* implementation of <code>getPrivateConnection</code>.
*
* @param con the connection to be returned to the manager
* @throws DBAccessException whenever an error occurs while operation on the database
*/
public void returnPrivateConnection(Connection con) throws DBAccessException;
/**
* Resets the whole manager, which means flushing all caches and initializing
* the database to an empty initial state.
*
* @param con the connection to the Database for this operation
* @throws DBAccessException whenever an error occurs while operation on the database
*/
public void reset(Connection con) throws DBAccessException;
/**
* Deletes all cache-content that holds references to de-serialized persistent
* objects.
*/
public void flushCache();
/**
* Persistifies the given PersistentUserObject in the database for the first
* time.
*
* @param puo the object to persistify
* @param con the connection to the database where the operation shall be carried out
* @throws DBAccessException whenever an error occurs while operation on the database
* @return the object-identifier of the newly persistified object, that may be
*
used to access or reference to the persistent object
*/
public String persistifyObject(PersistentUserObject puo, Connection con) throws DBAccessException;
/**
* Returns a PersistentObject by the given oid.
*
* @param sOID the oid of the persistent object that shall be returned
* @param con the connection to the database that shall be used for this operation
* @throws DBAccessException whenever an error occurs while operation on the database
* @return the requested persistent object or <code>null</code> if no object existed for
*
the given oid
*/
public PersistentObject getPersistentObject(String sOID, Connection con) throws DBAccessException;
/**
* Updates the persistent state of the given PersistentUserObject
*
* @param puo the PersistentUserObject that must already be persistent
* @param con the connection to the database that shall be used for this operation
* @throws DBAccessException whenever an error occurs while operation on the database
*/
public void updatePersistentObject(PersistentUserObject puo, Connection con) throws DBAccessException;
/**
* Deletes the persistent state of the previously persistified PersistentUserObject
* from this PersistenceManager.
*
* @param puo the currently persistent object
* @param con the connection to the database that shall be used for this operation
* @throws DBAccessException whenever an error occurs while operation on the database
*/
public void deletePersistentObject(PersistentUserObject puo, Connection con) throws DBAccessException;
/**
* Sets a new or existent property as being persistent in the database. This is done
* using a newly created connection to the database.
*
* @param sKey the key under which the property is or shall be accessible
* @param sValue a String that contains the value of the property
* @throws DBAccessException whenever an error occurs while operation on the database
*/
public void setProperty(String sKey, String sValue) throws DBAccessException;
/**
* Deletes the persistent state of the property with the given key.
*
* @param sKey the key of a property, which persistent state shall be deleted
* @throws DBAccessException whenever an error occurs while operation on the database
*/
public void deleteProperty(String sKey) throws DBAccessException;
C.2. PACKAGE EPOINT.DB
135
140
145
150
155
160
165
170
175
180
185
190
195
200
205
210
215
220
/**
* Returns a persistent property, which is accessible under the given key.
*
* @param sKey the key of the property that shall be returned
* @return the requested persistent property or <code>null</code> if the given key did not exist
* @throws DBAccessException whenever an error occurs while operation on the database
*/
public String getProperty(String sKey) throws DBAccessException;
/**
* Returns a list of all available properties in the database.
*
* @return a list with keys of all available properties.
* @throws DBAccessException whenever an error occurs while operation on the database
*/
public List getPropertyList() throws DBAccessException;
/**
* Creates a new persistent Catalog in the database.
*
* @param c the Catalog that shall be made persistent in the database
* @param con the connection to the database that shall be used for this operation
* @throws DBAccessException whenever an error occurs while operation on the database
*/
public void createCatalog(Catalog c, Connection con) throws DBAccessException;
/**
* Creates a new persistent CatalogItem in the database. The CatalogItem itself
* must be contained in a Catalog already (whether by transaction or not).<br>
* A CatalogItem may have 4 states as created by combining the two boolean values
* <code>bAdded</code> and <code>bRemoved</code>:
* <ol>
* <li><code>false,false</code>: the item is not part of a transaction and belongs to the catalog</li>
* <li><code>true,false</code>: the item is currently added to the catalog by a transaction</li>
* <li><code>false,true</code>: the item is currently removed from the catalog by a transaction</li>
* <li><code>true,true</code>: the item is the edited copy of a <code>false,false</code> item</li>
* </ol>
*
* @param ci the CatalogItem that shall be made persistent
* @param bAdded a flag for the status of the CatalogItem
* @param bRemoved a flag for the status of the CatalogItem
* @param con the connection to the database that shall be used for this operation
* @throws DBAccessException whenever an error occurs while operation on the database
*/
public void createCatalogItem(CatalogItem ci, boolean bAdded, boolean bRemoved, Connection con) throws DBAccessException;
/**
* Creates a persistent Stock in the database.
*
* @param sStock the Stock that shall be made persistent in the database
* @param con the connection to the database that shall be used for this operation
* @throws DBAccessException whenever an error occurs while operation on the database
*/
public void createStock(Stock sStock, Connection con) throws DBAccessException;
/**
* Creates a new persistent StockItem in the database. The same rules apply
* as by using {@link #createCatalogItem(CatalogItem,boolean,boolean,Connection) createCatalogItem}.
*
* @param si the StockItem that shall be made persistent
* @param bAdded a flag for the transaction-state
* @param bRemoved a flag for the transaction-state
* @param con the connection to the database that shall be used for this operation
* @throws DBAccessException whenever an error occurs while operation on the database
*/
public void createStockItem(StockItem si, boolean bAdded, boolean bRemoved, Connection con) throws DBAccessException;
/**
* Creates a new persistent DataBasket in the database.
*
* @param db the DataBasket that shall be made persistent
* @param con the connection to the database that shall be used for this operation
* @throws DBAccessException whenever an error occurs while operation on the database
*/
public void createDataBasket(DataBasket db, Connection con) throws DBAccessException;
/**
* Creates a new persistent DBEntry in the database, that already needs to belong
* to a DataBasket!
*
* @param dbe the DBEntry to persistify
* @param con the connection to the database that shall be used for this operation
* @throws DBAccessException whenever an error occurs while operation on the database
*/
public void createDBEntry(DBEntry dbe, Connection con) throws DBAccessException;
/**
* Updates the persistent state of a Catalog.
*
* @param c the Catalog that shall be updated in its persistent state
* @param lPropertyKeys a list of keys, that must be understood by this manager
*
to update only parts of the persistent state of the given object (that
*
are usually keys defined in the interfaces and starting with
*
<code>PERSISTENT_PROPERTY</code>)
* @param con the connection to the database that shall be used for this operation
265
266
ANHANG C. QUELLTEXTE
* @throws DBAccessException whenever an error occurs while operation on the database
*/
updateCatalog(Catalog c, List lPropertyKeys, Connection con) throws DBAccessException;
/**
* Updates the given persistent CatalogItem in its persistent state.
*
* @param ci the CatalogItem that needs an update of the persistent state
* @param lPropertyKeys a list of keys, that must be understood by this manager
*
to update only parts of the persistent state of the given object (that
*
are usually keys defined in the interfaces and starting with
*
<code>PERSISTENT_PROPERTY</code>)
* @param con the connection to the database that shall be used for this operation
* @throws DBAccessException whenever an error occurs while operation on the database
*/
public void updateCatalogItem(CatalogItem ci, List lPropertyKeys, Connection con) throws DBAccessException;
/**
* Updates the given persistent Stock in its persistent state.
*
* @param sStock the persistent Stock that needs an update
* @param lPropertyKeys a list of keys, that must be understood by this manager
*
to update only parts of the persistent state of the given object (that
*
are usually keys defined in the interfaces and starting with
*
<code>PERSISTENT_PROPERTY</code>)
* @param con the connection to the database that shall be used for this operation
* @throws DBAccessException whenever an error occurs while operation on the database
*/
public void updateStock(Stock sStock, List lPropertyKeys, Connection con) throws DBAccessException;
/**
* Updates the given persistent StockItem in its persistent state.
*
* @param si the persistent StockItem that needs an update of its persistent state
* @param lPropertyKeys a list of keys, that must be understood by this manager
*
to update only parts of the persistent state of the given object (that
*
are usually keys defined in the interfaces and starting with
*
<code>PERSISTENT_PROPERTY</code>)
* @param con the connection to the database that shall be used for this operation
* @throws DBAccessException whenever an error occurs while operation on the database
*/
public void updateStockItem(StockItem si, List lPropertyKeys, Connection con) throws DBAccessException;
/**
* Updates the given persistent DataBasket in its persistent state.
*
* @param db the persistent DataBasket that needs an update of its persistent state
* @param lPropertyKeys a list of keys, that must be understood by this manager
*
to update only parts of the persistent state of the given object (that
*
are usually keys defined in the interfaces and starting with
*
<code>PERSISTENT_PROPERTY</code>)
* @param con the connection to the database that shall be used for this operation
* @throws DBAccessException whenever an error occurs while operation on the database
*/
public void updateDataBasket(DataBasket db, List lPropertyKeys, Connection con) throws DBAccessException;
/**
* Updates the given persistent DBEntry in its persistent state
*
* @param dbe the persistent DBEntry that needs an update of its persistent state
* @param lPropertyKeys a list of keys, that must be understood by this manager
*
to update only parts of the persistent state of the given object (that
*
are usually keys defined in the interfaces and starting with
*
<code>PERSISTENT_PROPERTY</code>)
* @param con the connection to the database that shall be used for this operation
* @throws DBAccessException whenever an error occurs while operation on the database
*/
public void updateDBEntry(DBEntry dbe, List lPropertyKeys, Connection con) throws DBAccessException;
public void
225
230
235
240
245
250
255
260
265
270
275
280
285
290
295
300
305
/**
* Returns a persistent Catalog from this PersistenceManager.
*
* @param sName the name of the Catalog that shall be returned
* @param con the connection to the database that shall be used for this operation
* @return the requested Catalog, if exists - otherwise <code>null</code>
* @throws DBAccessException whenever an error occurs while operation on the database
*/
public Catalog getCatalog(String sName, Connection con) throws DBAccessException;
/**
* Returns a persistent CatalogItem from this PersistenceManager.
*
* @param sKey the key of the CatalogItem that shall be returned
* @param bAdded see <code>createCatalogItem</code>
* @param bRemoved see <code>createCatalogItem</code>
* @param cCatalog the Catalog in which the requested item is contained in
* @param con the connection to the database that shall be used for this operation
* @return the requested CatalogItem if exists - otherwise <code>null</code>
* @throws DBAccessException whenever an error occurs while operation on the database
*/
public CatalogItem getCatalogItem(String sKey, boolean bAdded, boolean bRemoved, Catalog cCatalog, Connection con)
DBAccessException;
/**
* Returns a persistent Stock from this PersistenceManager.
throws
C.2. PACKAGE EPOINT.DB
310
315
320
325
330
335
340
345
350
355
360
365
370
375
380
385
390
395
267
*
* @param sName the name of the Stock that shall be returned
* @param con the connection to the database that shall be used for this operation
* @return the requested Stock, if exists - otherwise <code>null</code>
* @throws DBAccessException whenever an error occurs while operation on the database
*/
public Stock getStock(String sName, Connection con) throws DBAccessException;
/**
* Returns all StockItems that are persistent in the given Stock as a List.
*
* @param sStock the Stock in which the requested StockItems are contained in
* @param bAdded see <code>createStockItem</code>
* @param bRemoved see <code>createStockItem</code>
* @param con the connection to the database that shall be used for this operation
* @throws DBAccessException whenever an error occurs while operation on the database
* @return a List containing the requested persistent StockItems
*/
public List getStockItems(Stock sStock, boolean bAdded, boolean bRemoved, Connection con) throws DBAccessException;
/**
* Returns all StockItems that are persistent for the given CatalogItem in the given Stock as a List.
*
* @param sStock the Stock in which the requested StockItems are contained in
* @param ci the CatalogItem that must be contained in Catalog referenced by the given Stock and
*
for which the StockItems shall be returned
* @param bAdded see <code>createStockItem</code>
* @param bRemoved see <code>createStockItem</code>
* @param con the connection to the database that shall be used for this operation
* @throws DBAccessException whenever an error occurs while operation on the database
* @return a List containing the requested persistent StockItems
*/
public List getStockItems(Stock sStock, CatalogItem ci, boolean bAdded, boolean bRemoved, Connection con) throws DBAccessException;
/**
* Returns a persistent DataBasket from this PersistenceManager.
*
* @param sName the name of the Databasket that shall be returned
* @param con the connection to the database that shall be used for this operation
* @return the requested DataBasket, if exists - otherwise <code>null</code>
* @throws DBAccessException whenever an error occurs while operation on the database
*/
public DataBasket getDataBasket(String sName, Connection con) throws DBAccessException;
/**
* Returns all persistent DBEntries of a DataBasket in the right order.
*
* @param db the DataBasket for which to return the persistent DBEntries
* @param con the connection to the database that shall be used for this operation
* @throws DBAccessException whenever an error occurs while operation on the database
* @return a List with the requested persistent DBEntries in the right order (in that
*
order they have been created)
*/
public List getDBEntries(DataBasket db, Connection con) throws DBAccessException;
/**
* Returns the persistent Value of a persistent CatalogItem.
*
* @param ci the CatalogItem for whcih to return the persistent Value
* @param con the connection to the database that shall be used for this operation
* @throws DBAccessException whenever an error occurs while operation on the database
* @return the Value that has been made persistent with the given CatalogItem
*/
public Value getValue(CatalogItem ci, Connection con) throws DBAccessException;
/**
* Returns the persistent DataBasket of the persistent TransactionItem for
* which the TransactionItemID is given.
*
* @param sTransItemID the identifier of the TransactionItem for which to return
*
the DataBasket
* @param con the connection to the database that shall be used for this operation
* @throws DBAccessException whenever an error occurs while operation on the database
* @return the persistent DataBasket of the TransactionItem specified by its identifier
*/
public DataBasket getTransactionItemBasket(String sTransItemID, Connection con) throws DBAccessException;
/**
* Returns a persistent Catalog by its OID.
*
* @param sOID the OID of the persistent Catalog to return
* @param con the connection to the database that shall be used for this operation
* @throws DBAccessException whenever an error occurs while operation on the database
* @return the persistent Catalog for the given OID
*/
public Catalog getCatalogByOID(String sOID, Connection con) throws DBAccessException;
/**
* Returns a persistent CatalogItem by its OID.
*
* @param sOID the OID of the persistent CatalogItem to return
* @param con the connection to the database that shall be used for this operation
* @throws DBAccessException whenever an error occurs while operation on the database
* @return the persistent CatalogItem for the given OID
*/
268
ANHANG C. QUELLTEXTE
public
400
405
410
415
420
425
430
435
440
445
450
455
460
465
470
475
480
CatalogItem getCatalogItemByOID(String sOID, Connection con) throws DBAccessException;
/**
* Returns a persistent Stock by its OID.
*
* @param sOID the OID of the persistent Stock to return
* @param con the connection to the database that shall be used for this operation
* @throws DBAccessException whenever an error occurs while operation on the database
* @return the persistent Stock for the given OID
*/
public Stock getStockByOID(String sOID, Connection con) throws DBAccessException;
/**
* Returns a persistent StockItem by its OID.
*
* @param sOID the OID of the persistent StockItem to return
* @param con the connection to the database that shall be used for this operation
* @throws DBAccessException whenever an error occurs while operation on the database
* @return the persistent StockItem for the given OID
*/
public StockItem getStockItemByOID(String sOID, Connection con) throws DBAccessException;
/**
* Returns a persistent DataBasket by its OID.
*
* @param sOID the OID of the persistent DataBasket to return
* @param con the connection to the database that shall be used for this operation
* @throws DBAccessException whenever an error occurs while operation on the database
* @return the persistent DataBasket for the given OID
*/
public DataBasket getDataBasketByOID(String sOID, Connection con) throws DBAccessException;
/**
* Returns a persistent CatalogItem by its TransactionItem identifier.
*
* @param sTransItemID the TransactionItem identifier for which to return the CatalogItem
* @param bAdded see <code>createCatalogItem</code>
* @param bRemoved see <code>createCatalogItem</code>
* @param con the connection to the database that shall be used for this operation
* @throws DBAccessException whenever an error occurs while operation on the database
* @return the requested persistent CatalogItem if available - otherwise <code>null</code>
*/
public CatalogItem getCatalogItemByTransactionID(String sTransItemID, boolean bAdded, boolean bRemoved, Connection con) throws
DBAccessException;
/**
* Returns a persistent StockItem by its TransactionItem identifier.
*
* @param sTransItemID the TransactionItem identifier for which to return the StockItem
* @param bAdded see <code>createCatalogItem</code>
* @param bRemoved see <code>createCatalogItem</code>
* @param con the connection to the database that shall be used for this operation
* @throws DBAccessException whenever an error occurs while operation on the database
* @return the requested persistent StockItem if available - otherwise <code>null</code>
*/
public StockItem getStockItemByTransactionID(String sTransItemID, boolean bAdded, boolean bRemoved, Connection con) throws
DBAccessException;
/**
* Returns a list of persistent Catalog names accessible by this DBPersistenceManager.
*
* @param con the connection to the database that shall be used for this operation
* @throws DBAccessException whenever an error occurs while operation on the database
* @return a list of all Catalog names available
*/
public List getCatalogNames(Connection con) throws DBAccessException;
/**
* Returns a list of persistent Catalogs accessible by this DBPersistenceManager.
*
* @param con the connection to the database that shall be used for this operation
* @throws DBAccessException whenever an error occurs while operation on the database
* @return a list of all Catalogs available
*/
public List getCatalogs(Connection con) throws DBAccessException;
/**
* Returns a list of persistent CatalogItem-keys accessible by this DBPersistenceManager for
* the given Catalog.
*
* @param c the Catalog for which to return the keys
* @param bAdded see <code>createCatalogItem</code>
* @param bRemoved see <code>createcatalogItem</code>
* @param con the connection to the database that shall be used for this operation
* @throws DBAccessException whenever an error occurs while operation on the database
* @return a list of all CatalogItem-keys available for the given Catalog
*/
public List getCatalogItemKeys(Catalog c, boolean bAdded, boolean bRemoved, Connection con)
/**
* Returns a list of persistent CatalogItems accessible by this DBPersistenceManager for
* the given Catalog.
*
* @param c the Catalog for which to return the items
* @param bAdded see <code>createCatalogItem</code>
throws
DBAccessException;
C.2. PACKAGE EPOINT.DB
485
490
495
500
505
510
515
520
525
530
535
540
545
550
555
560
565
* @param bRemoved see <code>createcatalogItem</code>
* @param con the connection to the database that shall be used for this operation
* @throws DBAccessException whenever an error occurs while operation on the database
* @return a list of all CatalogItems available for the given Catalog
*/
public List getCatalogItems(Catalog c, boolean bAdded, boolean bRemoved, Connection con)
/**
* Returns a list of persistent Stock names accessible by this DBPersistenceManager.
*
* @param con the connection to the database that shall be used for this operation
* @throws DBAccessException whenever an error occurs while operation on the database
* @return a list of all Stock names available
*/
public List getStockNames(Connection con) throws DBAccessException;
/**
* Returns a list of persistent Stock names that are associated with the given
* Catalog and being accessible by this DBPersistenceManager.
*
* @param c the Catalog that shall be the base-Catalog of the Stocks returned by this method
* @param con the connection to the database that shall be used for this operation
* @throws DBAccessException whenever an error occurs while operation on the database
* @return a list of all Stock names available for the given Catalog
*/
public List getStockNames(Catalog c, Connection con) throws DBAccessException;
/**
* Returns a list of persistent Stocks accessible by this DBPersistenceManager.
*
* @param con the connection to the database that shall be used for this operation
* @throws DBAccessException whenever an error occurs while operation on the database
* @return a list of all Stocks available
*/
public List getStocks(Connection con) throws DBAccessException;
/**
* Returns a list of persistent Stocks accessible by this DBPersistenceManager,
* that are associated with the given base-Catalog
*
* @param c the base-Catalog the Stocks shall be based on
* @param con the connection to the database that shall be used for this operation
* @throws DBAccessException whenever an error occurs while operation on the database
* @return a list of all Stocks based on the given Catalog
*/
public List getStocks(Catalog c, Connection con) throws DBAccessException;
/**
* Returns a list of all persistent DataBaskets accessible by this DBPersistenceManager.
*
* @param con the connection to the database that shall be used for this operation
* @throws DBAccessException whenever an error occurs while operation on the database
* @return a list of all persistent DataBaskets available
*/
public List getDataBaskets(Connection con) throws DBAccessException;
/**
* Returns the oid of the given persistent Catalog.
*
* @param c the persistent Catalog for which to return the OID
* @param con the connection to the database that shall be used for this operation
* @throws DBAccessException whenever an error occurs while operation on the database
* @return the object identifier for the persistent Catalog
*/
public String getOID(Catalog c, Connection con) throws DBAccessException;
/**
* Returns the oid of the given persistent CatalogItem.
*
* @param ci the persistent CatalogItem for which to return the OID
* @param con the connection to the database that shall be used for this operation
* @throws DBAccessException whenever an error occurs while operation on the database
* @return the object identifier for the persistent CatalogItem
*/
public String getOID(CatalogItem ci, Connection con) throws DBAccessException;
/**
* Returns the oid of the given persistent Stock.
*
* @param s the persistent Stock for which to return the OID
* @param con the connection to the database that shall be used for this operation
* @throws DBAccessException whenever an error occurs while operation on the database
* @return the object identifier for the persistent Stock
*/
public String getOID(Stock s, Connection con) throws DBAccessException;
/**
* Returns the oid of the given persistent StockItem.
*
* @param si the persistent StockItem for which to return the OID
* @param con the connection to the database that shall be used for this operation
* @throws DBAccessException whenever an error occurs while operation on the database
* @return the object identifier for the persistent StockItem
*/
public String getOID(StockItem si, Connection con) throws DBAccessException;
/**
269
throws
DBAccessException;
270
570
575
580
585
590
595
600
605
610
615
620
625
630
635
640
645
650
655
ANHANG C. QUELLTEXTE
* Returns the oid of the given persistent DataBasket.
*
* @param db the persistent DataBasket for which to return the OID
* @param con the connection to the database that shall be used for this operation
* @throws DBAccessException whenever an error occurs while operation on the database
* @return the object identifier for the persistent DataBasket
*/
public String getOID(DataBasket db, Connection con) throws DBAccessException;
/**
* Returns the oid of the given persistent DBEntry.
*
* @param dbe the persistent DBEntry for which to return the OID
* @param con the connection to the database that shall be used for this operation
* @throws DBAccessException whenever an error occurs while operation on the database
* @return the object identifier for the persistent DBEntry
*/
public String getOID(DBEntry dbe, Connection con) throws DBAccessException;
/**
* Deletes the persistent state of the given Catalog.
*
* @param c the Catalog for which the persistent state shall be deleted
* @param con the connection to the database that shall be used for this operation
* @throws DBAccessException whenever an error occurs while operation on the database
* @throws VetoException if the Catalog is not allowed to be deleted, as it
*
is used as a Value in a persistent CatalogItem
*/
public void deleteCatalog(Catalog c, Connection con) throws DBAccessException, epoint.data.exception.VetoException;
/**
* Deletes the persistent state of the given CatalogItem.
*
* @param ci the CatalogItem for which the persistent state shall be deleted
* @param con the connection to the database that shall be used for this operation
* @throws DBAccessException whenever an error occurs while operation on the database
*/
public void deleteCatalogItem(CatalogItem ci, Connection con) throws DBAccessException;
/**
* Deletes the persistent state of the given Stock.
*
* @param sStock the Stock for which the persistent state shall be deleted
* @param con the connection to the database that shall be used for this operation
* @throws DBAccessException whenever an error occurs while operation on the database
*/
public void deleteStock(Stock sStock, Connection con) throws DBAccessException;
/**
* Deletes the persistent state of the given StockItem.
*
* @param si the StockItem for which the persistent state shall be deleted
* @param con the connection to the database that shall be used for this operation
* @throws DBAccessException whenever an error occurs while operation on the database
*/
public void deleteStockItem(StockItem si, Connection con) throws DBAccessException;
/**
* Deletes the persistent state of the given DataBasket.
*
* @param db the DataBasket for which the persistent state shall be deleted
* @param con the connection to the database that shall be used for this operation
* @throws DBAccessException whenever an error occurs while operation on the database
*/
public void deleteDataBasket(DataBasket db, Connection con) throws DBAccessException;
/**
* Deletes the persistent state of the given DBEntry.
*
* @param dbe the DBEntry for which the persistent state shall be deleted
* @param con the connection to the database that shall be used for this operation
* @throws DBAccessException whenever an error occurs while operation on the database
*/
public void deleteDBEntry(DBEntry dbe, Connection con) throws DBAccessException;
/**
* Checks if the given EPointInfo describes an EPoint that has persistent
* data available in the persistent InfoStore in this Manager.
*
* @param epi the EPoint-info that shall be checked for being already persistent
* @return <code>true</code> if the EPointInfo describes an already persistent EPoint
* @throws DBAccessException whenever an error occurs while operation on the database
*/
public boolean isRegisteredEPoint(epoint.sale.EPointInfo epi) throws DBAccessException;
/**
* Registers the EPoint as described by the given EPointInfo in this
* DBPersistenceManager. An InfoStore is automatically reserved for the
* newly created EPoint.
*
* @param epi the EPoint-info that is used to create persistent data for the EPoint
* @throws DBAccessException whenever an error occurs while operation on the database
*/
public void registerEPoint(epoint.sale.EPointInfo epi) throws DBAccessException;
/**
C.2. PACKAGE EPOINT.DB
660
665
670
675
680
685
690
695
}
* Returns the InfoStore-Catalog for the Epoint described by the given
* EPointInfo.
*
* @param epi the EPoint-info that is used to determine the Catalog of the InfoStore
*
associated with the EPoint described by this info
* @return the Catalog that is used by the InfoStore associated with the EPoint
*
described by the given EPointInfo
* @throws DBAccessException whenever an error occurs while operation on the database
*/
public Catalog getInfoStoreCatalog(epoint.sale.EPointInfo epi) throws DBAccessException;
/**
* De-registers the EPoint as described by the given EPointInfo in this
* DBPersistenceManager. An InfoStore is automatically deleted.
*
* @param epi the EPoint-info that is used to delete persistent data for the EPoint
* @throws DBAccessException whenever an error occurs while operation on the database
*/
public void unregisterEPoint(epoint.sale.EPointInfo epi) throws DBAccessException;
/**
* Makes the given LogEntry persistent in the DataBase, using the signature of the
* given EPoint and an additional ID.
*
* @param epi the EPointInfo of the EPoint for which this LogEntry shall be made persistent
* @param iID an ID for the LogEntry (i.e. Gate number)
* @param le the LogEntry to persistify
* @throws DBAccessException whenever an error occurs while operation on the database
*/
public void logPersistent(epoint.sale.EPointInfo epi, int iID, epoint.log.LogEntry le) throws DBAccessException;
/**
* Returns all persistent LogEntries, that match the Filter condition.
*
* @param lf a LogFilter the returned LogEntries must match
* @param epi the EPointInfo for which to return the Logs
* @return a List that contains a LogEntries, each followed by its ID as an <code>Integer</code>
* @throws DBAccessException whenever an error occurs while operation on the database
*/
public List getLogs(epoint.sale.EPointInfo epi, epoint.log.LogFilter lf) throws DBAccessException;
Listing C.53: epoint.db.PSQLDBConnection
5
10
15
20
25
30
35
40
45
package epoint.db;
import epoint.db.exception.*;
import epoint.data.*;
import epoint.data.exception.*;
import java.sql.*;
import java.util.*;
import java.rmi.RemoteException;
import epoint.sale.Shop;
import java.lang.ref.WeakReference;
import java.io.PrintWriter;
import epoint.log.*;
/**
* This is the default implementation of the {@link DBPersistenceManager DBPersistenceManager interface}.<br>
* <br>
* This class in configured using static String-fields that may be set before
* this instances of this class are created. It uses a single connection
* to a PostGreSQL database, that is served to users, that must obtain the
* connection using {@link #getPrivateConnection(String,Class)}. The connection
* is reserved and available for the given password until it is returned using
* {@link #returnPrivateConnection(Connection)}. Until this time the connection
* is not accessible using other passwords! Other requests are blocked until
* the connection is returned or closed by the mentioned method. It is IMPORTANT
* not use the connection, after it has been returned - the Connection must
* explicitly requested for re-use again!<br>
* The PostGreSQL-database that is used must already be created, but is initialized
* with methods of this class! That means previous tables, views and other data
* gets lost and a new structure is created. The structure can be viewed here:
* <a href="TableStructure.html" target="_blank">Standard-Table-Structure</a><br>
* <br>
*
* @author Danny Poppe
* @version 1.0
*/
public class PSQLDBConnection implements DBPersistenceManager {
/**
* The default-user that is used to connect to the database: <code>epoint</code>
*/
public static String DEFAULT_USER = "epoint";
/**
* The default-password that is used to connect to the database: <code>epoint</code>
*/
public static String DEFAULT_PASSWORD = "epoint";
/**
* The default-name of the database to connect to: <code>epoint</code>
271
272
*/
String DEFAULT_BASE = "epoint";
/**
* The default-host that is used to connect to the database: <code>cray.studfb.unibw-muenchen.de</code>
*/
public static String DEFAULT_HOST = "cray.studfb.unibw-muenchen.de";
/**
* The default-port at which the postgresql-database is listening: <code>5432</code>
*/
public static String DEFAULT_PORT = "5432";
/**
* The default-type of the JDBC-connection used to connect to the database: <code>jdbc:postgresql</code>
*/
public static String DEFAULT_TYPE = "jdbc:postgresql";
/**
* The default-driver that is used to connect to the database: <code>org.postgresql.Driver</code>
*/
public static String DEFAULT_DRIVER = "org.postgresql.Driver";
public static
50
55
60
65
70
75
80
85
90
95
100
105
110
115
120
125
130
ANHANG C. QUELLTEXTE
/**
* References the default connection that is used internally.
*/
private Connection conDefault = null;
/**
* The connection used to access the logs
*/
private Connection conLog = null;
/**
* The ReferenceCache used to avoid duplicate objects of the same persistent
* state.
*/
private ReferenceCache refCache;
/**
* The log-file that is used to write information about access to the database
* and other information.
*/
private epoint.log.Log lSQLLog;
/**
* This monitor protects manipulations of Catalog-related data in the database.
*/
private Object m_oCatalogMonitor = new Object();
/**
* This monitor protects manipulations of Stock-related data in the database.
*/
private Object m_oStockMonitorGetter = new Object();
/**
* This monitor protects manipulations of DataBasket-related data in the database.
*/
private Object m_oBasketMonitor = new Object();
/**
* This monitor protects manipulations of Property-related data in the database.
*/
private Object m_oPropertyMonitor = new Object();
/**
* A reference to the Connection that is shared among users of this class.
*/
private Connection conLastExclusive = null;
/**
* Signals, if the exclusive connection is free (<code>true</code>) or currently
* in use <code>false</code>
*/
private boolean bConReturned = true;
/**
* The current password for requests to the exclusive connection.
*/
private String sConPasswd = "Default Passwort - secret #767896rzit6";
/**
* The object that is used to synchronize access to the exclusive
* Connection.
*/
private Object m_oConnectionWait = new Object();
/**
* A ShutDown-Thread that is called if the virtual machine goes off-line
* to close any open connections
*/
private ShutdownThread tShutdownHook = new ShutdownThread();
/**
* This Thread closes all Connections that are still open, if the
* virtual machine shuts down.
*/
protected class ShutdownThread extends Thread {
/**
* contains references to all connections ever registered with this thread
*/
private List lConnections = Collections.synchronizedList(new LinkedList());
C.2. PACKAGE EPOINT.DB
135
140
145
150
155
160
165
170
175
180
185
190
195
200
205
210
215
220
}
273
/**
* Creates a new ShutdownThread.
*/
public void ShutdownThread() {
setPriority(Thread.NORM_PRIORITY);
}
/**
* Checks all registered Connections and closes them, if still open.
*/
public void run() {
Iterator it = lConnections.iterator();
while (it.hasNext()) {
Connection c = (Connection)it.next();
synchronized (c) {
try {
if (!c.isClosed()) {
System.out.println("Closing still open connection "+c);
c.commit();
c.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
/**
* Registers a new Connection to this ShutdownThread to be closed,
* when application is ended.
*
* @param con the connection to be added to this ShutdownThread
*/
public void registerConnection(Connection con) {
lConnections.add(con);
}
/**
* Connects to the database, using default values and sets
*
* @param lSQLLog the logfile for this class
* @param pwDriverLog a log that will be attached to the driver that is
*
used to connect to the database
* @throws NoSuchDriverException if no driver could be found using the
*
default data to connect to the database
*/
public PSQLDBConnection(epoint.log.Log lSQLLog, java.io.PrintWriter pwDriverLog)
Runtime.getRuntime().addShutdownHook(tShutdownHook);
this.lSQLLog = lSQLLog;
refCache = new ReferenceCache(lSQLLog);
DriverManager.setLogWriter(pwDriverLog);
try {
Class.forName(getDriverClass());
} catch (ClassNotFoundException e) {
logError(e.getMessage());
throw new epoint.db.exception.NoSuchDriverException(getDriverClass());
}
}
throws
NoSuchDriverException {
/**
* Connects to the database, while using specified data.
*
* @param sType the jdbc-type of the database
* @param sDriver the named Driver to use for connection
* @param sHost the host to connect to
* @param sPort the port to connect to
* @param sBase the name of the database to use
* @param sUser the user, used to connect to the database
* @param sPassword the password used to connect to the database
* @param lSQLLog a log-file for tis class
* @param pwDriverLog a log to be attached to the driver
* @throws NoSuchDriverException if the specified driver does not exist
*/
public PSQLDBConnection(String sType, String sDriver, String sHost, String sPort, String sBase, String sUser, String sPassword, epoint.log
.Log lSQLLog, PrintWriter pwDriverLog) throws NoSuchDriverException {
this(lSQLLog,pwDriverLog);
DEFAULT_TYPE = sType;
DEFAULT_DRIVER = sDriver;
DEFAULT_HOST = sHost;
DEFAULT_PORT = sPort;
DEFAULT_BASE = sBase;
DEFAULT_USER = sUser;
DEFAULT_PASSWORD = sPassword;
}
/**
* Logs the given information.
274
225
230
235
240
245
250
255
260
265
270
275
280
285
290
295
300
305
ANHANG C. QUELLTEXTE
*
* @param sInfo the String that shall be logged
* @param bImportant if <code>true</code> the information is considered to be important
*/
synchronized protected void logInfo (String sInfo, boolean bImportant) {
lSQLLog.write(new LogEntry("PSQLDBConnection",sInfo,LogEntry.LOG_TYPE_INFO,(bImportant)?(LogEntry.LOG_LEVEL_INFO):(LogEntry.
LOG_LEVEL_EXTENDED)));
}
/**
* Logs the given statement.
*
* @param st the Statement that will be logged
*/
synchronized protected void logSQL (Statement st) {
String sConnection = "unknown Connection";
try {
sConnection = st.getConnection().toString();
} catch (Exception e) {}
lSQLLog.write(new LogEntry("PSQLDBConnection",sConnection+"::"+st,LogEntry.LOG_TYPE_SQLSTATEMENT,LogEntry.LOG_LEVEL_EXTENDED));
}
/**
* Logs the given SQL-String used at the given Connection.
*
* @param sSQL the SQL-String to be logged
* @param con the Connection used for the SQL-String to be executed
*/
synchronized protected void logSQL (String sSQL, Connection con) {
lSQLLog.write(new LogEntry("PSQLDBConnection",con+"::"+sSQL,LogEntry.LOG_TYPE_SQLSTATEMENT,LogEntry.LOG_LEVEL_EXTENDED));
}
/**
* Logs an error-message.
*
* @param sMessage the error-message to be logged
*/
synchronized protected void logError (String sMessage) {
lSQLLog.write(new LogEntry("PSQLDBConnection",sMessage,LogEntry.LOG_TYPE_ERROR,LogEntry.LOG_LEVEL_ERROR));
}
/**
* Returns the default-username for database-connections
*
* @return default user-name for database-connections
*/
protected String getDBUser() {
return DEFAULT_USER;
}
/**
* Returns the default-password for database-connections
*
* @return default password for database-connections
*/
protected String getPassword() {
return DEFAULT_PASSWORD;
}
/**
* Returns the default connection-type (eg. "jdbc:postgresql") for
* database-connections
*
* @return the default type of database-connections
*/
protected String getConType() {
return DEFAULT_TYPE;
}
/**
* Returns the default host-adress (or IP) for
* database-connections
*
* @return the default host-adress
*/
protected String getHost() {
return DEFAULT_HOST;
}
/**
* Returns the default connection-port at the host-adress for
* database-connections
*
* @return the default portnumber for database-connections
*/
protected String getPort() {
return DEFAULT_PORT;
}
/**
C.2. PACKAGE EPOINT.DB
310
315
320
325
330
335
340
345
350
355
360
365
370
* Returns the default database-name to connect to and to use for
* database-connections
*
* @return the default name of the database to connect to
*/
protected String getBaseName() {
return DEFAULT_BASE;
}
/**
* Returns the default class-name of the driver to be used for
* database-connections (e.g. "org.postgresql.Driver")
* The class must be within the class-path!
*
* @return the default driver to load for database-connections
*/
protected String getDriverClass() {
return DEFAULT_DRIVER;
}
/**
* Generates and returns the URL to be used for database-connections
*
* @return the generated URL to be used for database-connections
*/
protected String getUrl() {
return getConType()+"://"+getHost()+":"+getPort()+"/"+getBaseName();
}
/**
* Creates a new connection to the database, using the connection-data,
* as supplied by the appropriate methods in this class.
*
* @return a new Connection to the database
*/
synchronized private Connection getConnection(boolean bExclusive) throws DBAccessException {
try {
if ((conDefault == null) || bExclusive) {
Connection con = DriverManager.getConnection(getUrl(),getDBUser(),getPassword());
tShutdownHook.registerConnection(con);
logInfo("opened new connection "+con+" to "+getUrl()+" for user "+getDBUser(),false);
con.setAutoCommit(true);
con.setTransactionIsolation(con.TRANSACTION_READ_COMMITTED);
if (!bExclusive) {
conDefault = con;
}
return con;
} else {
if (conDefault.isClosed()) {
conDefault = DriverManager.getConnection(getUrl(),getDBUser(),getPassword());
conDefault.setTransactionIsolation(conDefault.TRANSACTION_READ_COMMITTED);
tShutdownHook.registerConnection(conDefault);
logInfo("opened new connection "+conDefault+" to "+getUrl()+" for user "+getDBUser(),false);
}
conDefault.setAutoCommit(true);
return conDefault;
}
} catch (SQLException e) {
logError(e.getMessage());
throw new DBAccessException("getConnection()",
"trying to establish a database-connection to "+getUrl(),
e);
}
}
public
375
380
385
390
275
Connection getPrivateConnection(String sConPasswd, Class c) throws DBAccessException {
System.out.println("Request: "+sConPasswd+" from: "+c.getName());
if (sConPasswd == null) throw new NullPointerException("null passwords are not allowed!");
logInfo("private connection requested from class "+c.getName()+" for password "+sConPasswd,false);
try {
if (conLastExclusive == null) conLastExclusive = getConnection(true);
if (!sConPasswd.equals(this.sConPasswd)) {
synchronized (m_oConnectionWait) {
// only one thread can wait for the connection to be returned
int iWaitMilliSec = 500;
int iMaxWaitMilliSec = 10000;
int iCount = iMaxWaitMilliSec / iWaitMilliSec;
while (!(bConReturned || (iCount == 0))) {
if (conLastExclusive.isClosed()) break;
try {
m_oConnectionWait.wait(iWaitMilliSec);
} catch (InterruptedException ie) {}
iCount--;
}
if (iCount == 0) {
throw new InterruptedException("Waiting to long for return of a DataBase-Connection! (last passwd: "+this.sConPasswd+
")");
}
276
ANHANG C. QUELLTEXTE
if
395
}
400
}
}
405
}
}
415
}
430
435
440
445
450
455
460
465
470
475
480
= sConPasswd;
conLastExclusive = getConnection(true);
logError(e.getMessage());
e.printStackTrace();
}
bConReturned = false;
return conLastExclusive;
public void
425
this.sConPasswd
if (conLastExclusive.isClosed())
catch (Exception e) {
410
420
(!conLastExclusive.isClosed()) {
if (!conLastExclusive.getAutoCommit()) conLastExclusive.setAutoCommit(true);
returnPrivateConnection(Connection con) {
System.out.println("=> Returned!");
logInfo(con+" returned for use",false);
if (!con.equals(conLastExclusive)) logError("Wrong Connection returned!");
bConReturned = true;
/**
* Takes a connection to a database and then retrieves the metadata for
* this connection.
*
* @param con the database-connection to retrieve metadata for
* @return Metadata for the given connection
*/
synchronized nal DatabaseMetaData getMetaData(Connection con, boolean bClose) throws DBAccessException {
logInfo("DataBaseMetaData requested",false);
try {
DatabaseMetaData dbm = con.getMetaData();
return dbm;
} catch (SQLException e) {
logError(e.getMessage());
throw new DBAccessException("getMetaData()",
"trying to get metadata for database at given connection",
e);
} nally {
try {
if (bClose) con.close();
} catch (SQLException e) {
logError(e.getMessage());
throw new DBAccessException("getMetaData","closing connection",e);
}
}
}
/**
* Returns a List of Strings with all table-names in the database which
* is reachable through the given connection.
*
* @param con the connection to the database for which the table-names are
*
to be retrieved
* @return a synchronized List-Object with a String-Object for each table
*
name in the database
*/
synchronized nal protected List getTableNames(Connection con, boolean bClose) throws DBAccessException {
logInfo("request for all table-names...",false);
try {
DatabaseMetaData dbm = getMetaData(con,false);
ResultSet rs = dbm.getTables(null,null,null,new String[]{"TABLE"});
List l = Collections.synchronizedList(new LinkedList());
while (rs.next()) {
l.add(rs.getString(3));
}
if (bClose) con.close();
rs.close();
return l;
} catch (SQLException e) {
logError(e.getMessage());
throw new DBAccessException("getTableNames()","trying to retrieve all table-names in the database",e);
}
}
/**
* Returns a List of Strings with all view-names in the database which
* is reachable through the given connection.
*
* @param con the connection to the database for which the table-names are
*
to be retrieved
* @return a synchronized List-Object with a String-Object for each view
*
name in the database
*/
nal protected List getViewNames(Connection con, boolean bClose) throws DBAccessException {
logInfo("request for all view-names...",false);
C.2. PACKAGE EPOINT.DB
try
485
490
495
}
500
505
510
515
/**
* Creates a new default-connection to the database and reports some information
* about this connection to STDOUT.
*
* @return <code>true</code> if no errors occured during tests
*/
nal public boolean checkConnection(Connection con, boolean bClose) {
java.io.PrintStream o = System.out;
boolean bSuccess = false;
try {
DatabaseMetaData dbm = getMetaData(con,false);
o.println("\tProductname:
"+dbm.getDatabaseProductName());
o.println("\tProductversion:
"+dbm.getDatabaseProductVersion());
o.println("\tDefault isolation: "+dbm.getDefaultTransactionIsolation());
o.println("\t\tTRANSACTION_NONE ("+con.TRANSACTION_NONE+") supported: "+dbm.supportsTransactionIsolationLevel(con.
TRANSACTION_NONE));
o.println("\t\tTRANSACTION_READ_UNCOMMITTED ("+con.TRANSACTION_READ_UNCOMMITTED+") supported: "+dbm.
supportsTransactionIsolationLevel(con.TRANSACTION_READ_UNCOMMITTED));
o.println("\t\tTRANSACTION_READ_COMMITTED ("+con.TRANSACTION_READ_COMMITTED+") supported: "+dbm.
supportsTransactionIsolationLevel(con.TRANSACTION_READ_COMMITTED));
o.println("\t\tTRANSACTION_REPEATABLE_READ ("+con.TRANSACTION_REPEATABLE_READ+") supported: "+dbm.
supportsTransactionIsolationLevel(con.TRANSACTION_REPEATABLE_READ));
o.println("\t\tTRANSACTION_SERIALIZABLE ("+con.TRANSACTION_SERIALIZABLE+") supported: "+dbm.
supportsTransactionIsolationLevel(con.TRANSACTION_SERIALIZABLE));
520
525
530
535
540
545
550
555
560
565
{
DatabaseMetaData dbm = getMetaData(con,false);
ResultSet rs = dbm.getTables(null,null,null,new String[]{"VIEW"});
List l = Collections.synchronizedList(new LinkedList());
while (rs.next()) {
l.add(rs.getString(3));
}
if (bClose) con.close();
rs.close();
return l;
} catch (SQLException e) {
logError(e.getMessage());
throw new DBAccessException("getViewNames()","trying to retrieve all view-names in the database",e);
}
}
o.println("Available tables: "+getTableNames(con,false));
o.print("Closing connection... ");
o.println("successful");
if (bClose) con.close();
bSuccess = true;
} catch (Exception e) {
logError(e.getMessage());
e.printStackTrace();
} nally {
try {
if (bClose) con.close();
} catch (Exception e) {
logError(e.getMessage());
e.printStackTrace();
}
return bSuccess;
}
/**
* Creates a new default-connection to the database and drops all tables and views
* in the database.
*
* @param con the connection to the database that shall be used for this operation
* @param bClose if <code>true</code> the connection will be closed afterwards
*/
nal public void emptyDatabase(Connection con, boolean bClose) throws DBAccessException {
synchronized (this) {
logInfo("deleting all tables and views in base",true);
List lTables = null;
List lViews = null;
try {
int iOldIsolation = con.getTransactionIsolation();
con.setTransactionIsolation(con.TRANSACTION_SERIALIZABLE);
con.setAutoCommit(false);
lTables = getTableNames(con,false);
lViews = getViewNames(con,false);
Iterator it = lTables.iterator();
Statement st = con.createStatement();
while (it.hasNext()) {
String sStatement = "drop table "+it.next();
st.execute(sStatement);
logSQL(sStatement,con);
}
it = lViews.iterator();
while (it.hasNext()) {
String sStatement = "drop view "+it.next();
277
278
570
575
580
585
}
ANHANG C. QUELLTEXTE
st.execute(sStatement);
logSQL(sStatement,con);
}
con.commit();
con.setAutoCommit(true);
con.setTransactionIsolation(iOldIsolation);
} catch (SQLException e) {
logError(e.getMessage());
throw new DBAccessException( "emptyDatabase()",
"trying to delete all tables in database",
e);
} nally {
try {
if (bClose) con.close();
} catch (Exception e) {
logError(e.getMessage());
e.printStackTrace();
}
}
}
synchronized public void
590
}
595
600
605
610
615
620
625
630
635
640
645
650
reset(Connection con)
logInfo("resetting the base...",true);
flushCache();
initializeBase(con,false);
throws
DBAccessException {
/**
* Makes a call to #emptyDataBase() and then creates the default structure
* (tables and views) to manage a persistent application.
*
* @param con the connection to the database that shall be used for this operation
* @throws DBAccessException if an error occurs, while connecting to
*
the database
*/
synchronized nal public void initializeBase(Connection con, boolean bClose) throws DBAccessException {
logInfo("initializing base...",true);
String sStatement = null;
try {
emptyDatabase(con, false);
con.setAutoCommit(false);
con.setTransactionIsolation(con.TRANSACTION_SERIALIZABLE);
Statement s = con.createStatement();
sStatement = "CREATE TABLE Catalogs (CatName TEXT NOT NULL, "+
"Serialized TEXT NOT NULL, "+
"CONSTRAINT Catalogs_PK PRIMARY KEY (CatName));";
logSQL(sStatement,con);
s.execute(sStatement);
sStatement = "CREATE TABLE CatItems (CatName TEXT NOT NULL, "+
"CIKeyName TEXT NOT NULL, "+
"IsCatalog BOOLEAN DEFAULT false NOT NULL, "+
"Value TEXT NOT NULL, "+
"Serialized TEXT NOT NULL, "+
"TransactionID TEXT NOT NULL, "+
"Added BOOLEAN NOT NULL DEFAULT FALSE, "+
"Removed BOOLEAN NOT NULL DEFAULT FALSE, "+
"UNIQUE (TransactionID, Added, Removed), "+
"CONSTRAINT CatItems_PK PRIMARY KEY (CatName, CIKeyName, Added, Removed));";
logSQL(sStatement,con);
s.execute(sStatement);
sStatement = "CREATE TABLE Stocks (StockName TEXT NOT NULL, "+
"CatName TEXT NOT NULL, "+
"IsCounting BOOLEAN DEFAULT false NOT NULL, "+
"Serialized TEXT NOT NULL, "+
"CONSTRAINT Stocks_PK PRIMARY KEY (StockName));";
logSQL(sStatement,con);
s.execute(sStatement);
sStatement = "CREATE TABLE StockItems (StockName TEXT NOT NULL, "+
//"CatName TEXT NOT NULL, "+
//"CIKeyName TEXT NOT NULL, "+
//"CIAdded BOOLEAN NOT NULL, "+
//"CIRemoved BOOLEAN NOT NULL, "+
"RefTransID TEXT NOT NULL, "+
"Identifier BIGINT NOT NULL, "+
"Value TEXT NOT NULL, "+
"Serialized TEXT NOT NULL, "+
"TransactionID TEXT NOT NULL, "+
"Added BOOLEAN NOT NULL DEFAULT FALSE, "+
"Removed BOOLEAN NOT NULL DEFAULT FALSE, "+
//
"UNIQUE (CatName, CIKeyName, CIAdded, CIRemoved, Identifier, Added, Removed), "+
"UNIQUE (TransactionID, Added, Removed), "+
//
"CONSTRAINT StockItems_PK PRIMARY KEY (StockName, CIKeyName, CIAdded, CIRemoved, Identifier, Added
, Removed));";
"CONSTRAINT StockItems_PK PRIMARY KEY (StockName, RefTransID, Identifier, Added, Removed));";
logSQL(sStatement,con);
s.execute(sStatement);
sStatement = "CREATE TABLE EPoints (Name TEXT NOT NULL, "+
C.2. PACKAGE EPOINT.DB
"Location INET NOT NULL, "+
"Description TEXT, "+
"GateCount INTEGER NOT NULL DEFAULT -1, "+
"InfoCatalog TEXT NOT NULL, "+
"CONSTRAINT EPoints_PK PRIMARY KEY (Name, Location));";
655
logSQL(sStatement,con);
s.execute(sStatement);
sStatement = "CREATE TABLE GateStates (Name TEXT NOT NULL, "+
"Location INET NOT NULL, "+
"GateID INTEGER NOT NULL, "+
"GateState TEXT, "+
"CONSTRAINT GateStates_PK PRIMARY KEY (Name, Location, GateID));";
logSQL(sStatement,con);
s.execute(sStatement);
sStatement = "CREATE TABLE LogEntries (Name TEXT NOT NULL, "+
"Location INET NOT NULL, "+
"GateID INTEGER, "+
"Time TIMESTAMP NOT NULL, "+
"LogData TEXT NOT NULL, "+
"CONSTRAINT LogEntries_PK PRIMARY KEY (Name, Location, Time));";
logSQL(sStatement,con);
s.execute(sStatement);
sStatement = "CREATE TABLE TransactionItems (Identifier TEXT NOT NULL, "+
"TransactionHandle TEXT, "+
"CONSTRAINT TransactionItems_PK PRIMARY KEY (Identifier));";
logSQL(sStatement,con);
s.execute(sStatement);
sStatement = "CREATE TABLE TransactionHandles (Name TEXT NOT NULL, "+
"Serialized TEXT NOT NULL, "+
"CONSTRAINT TransactionHandles_PK PRIMARY KEY (Name));";
logSQL(sStatement,con);
s.execute(sStatement);
sStatement = "CREATE TABLE Transactions (Name TEXT NOT NULL, "+
"Identifier TEXT NOT NULL, "+
"OrderIndex BIGINT NOT NULL, "+
"TransType TEXT NOT NULL, "+
"InfoVector TEXT, "+
"Serialized TEXT NOT NULL, "+
"CONSTRAINT Transactions_PK PRIMARY KEY (Name,OrderIndex));";
logSQL(sStatement,con);
s.execute(sStatement);
sStatement = "CREATE TABLE Properties (Key TEXT NOT NULL, "+
"Value TEXT NOT NULL, "+
"CONSTRAINT Properties_PK PRIMARY KEY (Key));";
logSQL(sStatement,con);
s.execute(sStatement);
sStatement = "CREATE TABLE PersistentObjects (Serialized TEXT NOT NULL, "+
"IdentKey TEXT NOT NULL, "+
"CONSTRAINT PersistentObjects_PK PRIMARY KEY (IdentKey));";
logSQL(sStatement,con);
s.execute(sStatement);
sStatement = "ALTER TABLE CatItems ADD CONSTRAINT CatItems_FK1 FOREIGN KEY (CatName) REFERENCES Catalogs (CatName) ON DELETE CASCADE
ON UPDATE CASCADE;";
logSQL(sStatement,con);
s.execute(sStatement);
sStatement = "ALTER TABLE CatItems ADD CONSTRAINT CatItems_FK2 FOREIGN KEY (TransactionID) REFERENCES TransactionItems (Identifier)
;";
660
665
670
675
680
685
690
695
700
705
710
715
//
720
725
730
279
//
//
logSQL(sStatement,con);
s.execute(sStatement);
sStatement = "ALTER TABLE StockItems ADD CONSTRAINT StockItems_FK1 FOREIGN KEY (StockName) REFERENCES Stocks (StockName) ON DELETE
CASCADE ON UPDATE CASCADE;";
logSQL(sStatement,con);
s.execute(sStatement);
sStatement = "ALTER TABLE StockItems ADD CONSTRAINT StockItems_FK2 FOREIGN KEY (TransactionID) REFERENCES TransactionItems (
Identifier);";
logSQL(sStatement,con);
s.execute(sStatement);
sStatement = "ALTER TABLE StockItems ADD CONSTRAINT StockItems_FK3 FOREIGN KEY (CatName,CIKeyName,CIAdded,CIRemoved) REFERENCES
CatItems(CatName,CIKeyName,Added,Removed) ON UPDATE CASCADE;";
logSQL(sStatement);
s.execute(sStatement);
sStatement = "ALTER TABLE StockItems ADD CONSTRAINT StockItems_FK4 FOREIGN KEY (RefTransID) REFERENCES TransactionItems (Identifier)
ON DELETE CASCADE ON UPDATE CASCADE;";
logSQL(sStatement,con);
s.execute(sStatement);
sStatement = "ALTER TABLE EPoints ADD CONSTRAINT EPoints_FK1 FOREIGN KEY (InfoCatalog) REFERENCES Catalogs (CatName) ON DELETE
CASCADE ON UPDATE CASCADE;";
logSQL(sStatement,con);
s.execute(sStatement);
sStatement = "ALTER TABLE LogEntries ADD CONSTRAINT LogEntries_FK FOREIGN KEY (Name, Location) REFERENCES EPoints (Name, Location)
ON DELETE NO ACTION ON UPDATE CASCADE;";
logSQL(sStatement,con);
s.execute(sStatement);
280
735
740
745
750
755
760
765
770
775
780
785
790
795
800
805
810
ANHANG C. QUELLTEXTE
sStatement = "ALTER TABLE GateStates ADD CONSTRAINT GateStates_FK FOREIGN KEY (Name, Location) REFERENCES EPoints (Name, Location)
ON DELETE CASCADE ON UPDATE CASCADE;";
logSQL(sStatement,con);
s.execute(sStatement);
sStatement = "ALTER TABLE Stocks ADD CONSTRAINT Stocks_FK FOREIGN KEY (CatName) REFERENCES Catalogs (CatName) ON DELETE CASCADE ON
UPDATE CASCADE;";
logSQL(sStatement,con);
s.execute(sStatement);
sStatement = "ALTER TABLE TransactionItems ADD CONSTRAINT TransactionItem_FK FOREIGN KEY (TransactionHandle) REFERENCES
TransactionHandles (Name);";
logSQL(sStatement,con);
s.execute(sStatement);
sStatement = "ALTER TABLE Transactions ADD CONSTRAINT Transactions_FK1 FOREIGN KEY (Name) REFERENCES TransactionHandles (Name) ON
DELETE CASCADE ON UPDATE CASCADE;";
logSQL(sStatement,con);
s.execute(sStatement);
sStatement = "ALTER TABLE Transactions ADD CONSTRAINT Transactions_FK2 FOREIGN KEY (Identifier) REFERENCES TransactionItems (
Identifier) ON DELETE CASCADE ON UPDATE CASCADE;";
logSQL(sStatement,con);
s.execute(sStatement);
sStatement = "CREATE VIEW CatItemStates AS ("+
"SELECT TransactionID,Added,Removed,TransactionHandle FROM "+
"CatItems JOIN TransactionItems ON (CatItems.TransactionID=TransactionItems.Identifier));";
logSQL(sStatement,con);
s.execute(sStatement);
sStatement = "CREATE VIEW StockItemStates AS ("+
"SELECT TransactionID,Added,Removed,TransactionHandle FROM "+
"StockItems JOIN TransactionItems ON (StockItems.TransactionID=TransactionItems.Identifier));";
logSQL(sStatement,con);
s.execute(sStatement);
sStatement = "CREATE VIEW TransItemStates AS (SELECT * FROM CatItemStates UNION SELECT * FROM StockItemStates);";
logSQL(sStatement,con);
s.execute(sStatement);
sStatement = "CREATE VIEW StockItemsFull AS (SELECT StockName,"+
"CatName,"+
"CIKeyName,"+
"CatItems.Added AS CIAdded,"+
"CatItems.Removed AS CIRemoved,"+
"RefTransID,"+
"Identifier,"+
"StockItems.Value,"+
"StockItems.Serialized,"+
"StockItems.TransactionID,"+
"StockItems.Added,"+
"StockItems.Removed, "+
"StockItems.oid AS oid_si "+
"FROM CatItems JOIN StockItems ON (CatItems.TransactionID=StockItems.RefTransID))";
logSQL(sStatement,con);
s.execute(sStatement);
sStatement = "CREATE VIEW ExtTIStates AS (";
sStatement +="SELECT TransactionID,Transactions.Name AS Basket, CASE WHEN Added=false AND Removed=false THEN 'normal' "+
"WHEN Added=true AND Removed=false THEN 'added' "+
"WHEN Added=false AND Removed=true THEN 'removed' "+
"WHEN Added=true AND Removed=true THEN 'edited' "+
"END AS STATUS "+
"FROM CatItems LEFT JOIN Transactions ON (Identifier=TransactionId)"+
" UNION "+
"SELECT TransactionID,Transactions.Name AS Basket, case when ciadded=ciremoved and added=false and removed=false then '
normal' "+
"when ciadded=ciremoved and added=true and removed=false then 'added' "+
"when ciadded=ciremoved and added=false and removed=true then 'removed' "+
"when ciadded=ciremoved and added=true and removed=true then 'edited' "+
"when ciadded=true and ciremoved=false and added=true and removed=false then '
added' "+
"when ciadded=false and ciremoved=true and added=false and removed=true then '
removed' "+
"else 'error' "+
"end as status "+
"from stockitemsfull left join transactions on (transactions.identifier=transactionid));";
logSQL(sStatement,con);
s.execute(sStatement);
con.commit();
con.setAutoCommit(true);
} catch (SQLException e) {
logError(e.getMessage());
throw new DBAccessException("initializeBase()",
"trying to create initial state in database (deleting previous and creating standard-tables)\n"+sStatement
,
e);
} nally {
try {
if (bClose) con.close();
} catch (Exception e) {
logError(e.getMessage());
C.2. PACKAGE EPOINT.DB
815
820
}
}
////////////////////////////////////////////////////////////////////////////////
//
SystemProperty
////////////////////////////////////////////////////////////////////////////////
public void
825
830
835
840
845
850
}
}
setProperty(String sKey, String sValue) throws DBAccessException {
Connection con = getConnection(false);
synchronized (getPropertyMonitor()) {
logInfo("accessing database for setting the property "+sKey,false);
if (sValue == null) return;
try {
PreparedStatement st = con.prepareStatement("SELECT * FROM Properties WHERE Key = ?");
st.setString(1,sKey);
logSQL(st);
ResultSet rs = st.executeQuery();
if (rs.next()) {
st = con.prepareStatement("UPDATE Properties SET Value = ? WHERE Key = ?;");
st.setString(1,sValue);
st.setString(2,sKey);
logSQL(st);
st.executeUpdate();
} else {
st = con.prepareStatement("INSERT INTO Properties(Key,Value) VALUES (?,?);");
st.setString(1,sKey);
st.setString(2,sValue);
logSQL(st);
st.executeUpdate();
}
st.close();
} catch (SQLException e) {
logError(e.getMessage());
throw new DBAccessException("setProperty","setting the property named "+sKey,e);
}
}
public
855
860
865
870
}
String getProperty(String sKey) throws DBAccessException {
Connection con = getConnection(false);
synchronized (getPropertyMonitor()) {
logInfo("accessing the database for retrieving the property "+sKey,false);
try {
PreparedStatement st = con.prepareStatement("SELECT Value FROM Properties WHERE Key = ?");
st.setString(1,sKey);
logSQL(st);
ResultSet rs = st.executeQuery();
String sRetVal = null;
if (rs.next()) sRetVal = rs.getString(1);
st.close();
return sRetVal;
} catch (SQLException e) {
logError(e.getMessage());
throw new DBAccessException("getProperty","retrieving property named "+sKey+" from database",e);
}
}
public
875
880
885
890
895
}
List getPropertyList() throws DBAccessException {
Connection con = getConnection(false);
synchronized (getPropertyMonitor()) {
logInfo("accessing the database for retrieving a list of all properties",false);
List l = Collections.synchronizedList(new LinkedList());
try {
PreparedStatement st = con.prepareStatement("SELECT Key FROM Properties");
logSQL(st);
ResultSet rs = st.executeQuery();
while (rs.next()) l.add(rs.getString(1));
st.close();
} catch (SQLException e) {
logError(e.getMessage());
throw new DBAccessException("getPropertyList","retrieving all property-keys from database",e);
}
return l;
}
public void
deleteProperty(String sKey) throws DBAccessException {
Connection con = getConnection(false);
synchronized (getPropertyMonitor()) {
logInfo("accessing the database for delting the property "+sKey,false);
try {
PreparedStatement st = con.prepareStatement("DELETE FROM Properties WHERE Key = ?");
st.setString(1,sKey);
logSQL(st);
st.executeUpdate();
281
282
900
905
910
915
}
}
public nal List getCatalogNames(Connection
synchronized (getCatalogMonitor()) {
925
930
}
945
950
955
960
965
970
}
980
985
con)
throws
DBAccessException {
logInfo("accessing the database for retrieving a list of all catalog-names",false);
try {
Statement st = con.createStatement();
String sStatement = "SELECT CatName FROM Catalogs";
logSQL(sStatement,con);
ResultSet rs = st.executeQuery(sStatement);
List l = Collections.synchronizedList(new LinkedList());
while (rs.next()) {
l.add(rs.getString(1));
}
st.close();
return l;
} catch (SQLException e) {
logError(e.getMessage());
throw new DBAccessException("getCatalogNames","retrieving all catalog-names from database",e);
}
}
public Catalog getCatalog(String sName, Connection
synchronized (getCatalogMonitor()) {
940
975
}
}
st.close();
(SQLException e) {
logError(e.getMessage());
throw new DBAccessException("deleteProperty","deleting property named "+sKey+" from database",e);
catch
////////////////////////////////////////////////////////////////////////////////
//
Catalog
////////////////////////////////////////////////////////////////////////////////
920
935
ANHANG C. QUELLTEXTE
con)
throws
DBAccessException {
logInfo("accessing the database for retrieving the catalog "+sName,false);
Catalog cCatalog = refCache.lookupCatalogCache(sName);
if (cCatalog != null) return cCatalog;
try {
PreparedStatement st = con.prepareStatement("SELECT Serialized,oid FROM Catalogs WHERE CatName = ?");
st.setString(1,sName);
logSQL(st);
ResultSet rs = st.executeQuery();
if (rs.next()) {
String sCatalog = rs.getString(1);
HashMap mProperties = new HashMap();
mProperties.put(cCatalog.PERSISTENT_PROPERTY_CATALOGNAME,sName);
cCatalog = (Catalog)Shop.getPersistenceSerializer().fromString(sCatalog);
cCatalog.setPersistent(rs.getString(2));
cCatalog.loadPersistentData(mProperties,sConPasswd);
// now we need to check if the deserialized Catalog is used as a Value somewhere
// and then set the appropriate flag
PreparedStatement stValueCheck = con.prepareStatement("SELECT oid FROM CatItems WHERE IsCatalog = true AND Value = ?");
stValueCheck.setString(1,cCatalog.getOID());
logSQL(stValueCheck);
ResultSet rs2 = stValueCheck.executeQuery();
while (rs2.next()) {
cCatalog.setPersistentValueFlag(getCatalogItemByOID(rs2.getString(1),con));
}
stValueCheck.close();
refCache.addToCatalogCache(cCatalog);
}
st.close();
} catch (SQLException e) {
logError(e.getMessage());
throw new DBAccessException("getCatalog","retrieving catalog from database by name "+sName,e);
} catch (RemoteException rmie) {
Shop.getTheShop().logException(rmie);
rmie.printStackTrace();
}
return cCatalog;
}
public void createCatalog(Catalog c, Connection con) throws DBAccessException {
synchronized (getCatalogMonitor()) {
String sName = null;
try {
if (c.isPersistent()) throw new RuntimeException("The given catalog is already
marked to be persistent!");
PreparedStatement st = con.prepareStatement("INSERT INTO Catalogs(CatName,Serialized) VALUES( ? , ? )");
sName = c.getName();
logInfo("accessing the database for creating a catalog named "+sName,false);
st.setString(1,sName);
st.setString(2,c.getSerializedForm());
logSQL(st);
st.execute();
st = con.prepareStatement("SELECT oid FROM Catalogs WHERE CatName = ?");
C.2. PACKAGE EPOINT.DB
990
995
1000
}
1005
1010
1025
1030
}
public List getCatalogs(Connection con) throws
synchronized (getCatalogMonitor()) {
1040
1050
}
1060
1065
1070
}
DBAccessException {
logInfo("accessing the database for retrieving a list of all catalogs",false);
List lRetVal = Collections.synchronizedList(new LinkedList());
Iterator itNames = this.getCatalogNames(con).iterator();
while (itNames.hasNext()) {
lRetVal.add(this.getCatalog((String)itNames.next(),con));
}
return lRetVal;
}
public void updateCatalog(Catalog c, List
synchronized (getCatalogMonitor()) {
try {
1055
1075
" cannot be removed from the database, as it is used as value");
(c.isPersistent()) {
String sName = c.getName();
logInfo("accessing the database for deleting a catalog named "+sName,false);
PreparedStatement st = con.prepareStatement("DELETE FROM Catalogs WHERE CatName = ? ");
st.setString(1,c.getName());
logSQL(st);
st.execute();
c.setPersistent(null);
refCache.removeFromCatalogCache(c);
}
} catch (SQLException e) {
logError(e.getMessage());
try {
throw new DBAccessException("deleteCatalog","removing catalog "+c.getName()+" from database",e);
} catch (RemoteException rmie) {
Shop.getTheShop().logException(rmie);
rmie.printStackTrace();
}
} catch (RemoteException e) {
Shop.getTheShop().logException(e);
e.printStackTrace();
}
}
if
1020
1045
st.setString(1,sName);
logSQL(st);
ResultSet rs = st.executeQuery();
rs.next();
c.setPersistent(rs.getString(1));
refCache.addToCatalogCache(c);
st.close();
} catch (SQLException e) {
logError(e.getMessage());
throw new DBAccessException("createCatalog","creating new catalog named "+sName+") in database",e);
} catch (RemoteException rmie) {
Shop.getTheShop().logException(rmie);
rmie.printStackTrace();
}
}
public void deleteCatalog(Catalog c, Connection con) throws DBAccessException, epoint.data.exception.VetoException
synchronized (getCatalogMonitor()) {
try {
if (c.isPersistentValue())
throw new epoint.data.exception.VetoException("The catalog "+c.getName()+
1015
1035
283
lPropertyKeys, Connection con)
throws
DBAccessException {
PreparedStatement st;
logInfo("accessing the database for updating the catalog "+c.getName()+" ("+lPropertyKeys+")",false);
if (lPropertyKeys.contains(c.PERSISTENT_PROPERTY_SERIALIZED)) {
st = con.prepareStatement("UPDATE Catalogs SET CatName = ?, Serialized = ? WHERE oid = ?");
st.setString(1,c.getName());
st.setString(2,c.getSerializedForm());
st.setString(3,c.getOID());
} else {
st = con.prepareStatement("UPDATE Catalogs SET CatName = ? WHERE oid = ?");
st.setString(1,c.getName());
st.setString(2,c.getOID());
}
logSQL(st);
st.execute();
refCache.addToCatalogCache(c);
} catch (RemoteException rmie) {
Shop.getTheShop().logException(rmie);
rmie.printStackTrace();
} catch (SQLException e) {
logError(e.getMessage());
throw new DBAccessException("updateCatalog","",e);
}
}
{
284
1080
public String getOID(Catalog c, Connection
synchronized (getCatalogMonitor()) {
String sOID = null;
try {
1085
1090
1095
1100
}
1110
1115
}
1125
DBAccessException {
logInfo("accessing the database for retrieving the oid of catalog "+c.getName(),false);
PreparedStatement st = con.prepareStatement("SELECT oid FROM Catalogs WHERE CatName = ?");
st.setString(1,c.getName());
logSQL(st);
ResultSet rs = st.executeQuery();
if (rs.next()) sOID = rs.getString(1);
st.close();
} catch (SQLException e) {
logError(e.getMessage());
throw new DBAccessException("getOID(Catalog)","",e);
} catch (RemoteException rmie) {
Shop.getTheShop().logException(rmie);
rmie.printStackTrace();
}
return sOID;
}
Connection con)
throws
DBAccessException {
logInfo("accessing the database for retrieving the catalog with oid "+sOID,false);
try {
PreparedStatement st = con.prepareStatement("SELECT CatName FROM Catalogs WHERE oid = ?");
st.setString(1,sOID);
logSQL(st);
ResultSet rs = st.executeQuery();
Catalog cRetVal = null;
if (rs.next()) {
cRetVal = getCatalog(rs.getString(1),con);
}
st.close();
return cRetVal;
} catch (SQLException e) {
logError(e.getMessage());
throw new DBAccessException("getCatalogByOID","",e);
}
}
public List getCatalogItemKeys(Catalog c, boolean bAdded, boolean
synchronized (getCatalogMonitor()) {
List l = Collections.synchronizedList(new LinkedList());
String sName = null;
try {
1135
1140
1145
1155
throws
////////////////////////////////////////////////////////////////////////////////
//
CatalogItem
////////////////////////////////////////////////////////////////////////////////
1130
1150
con)
public Catalog getCatalogByOID(String sOID,
synchronized (getCatalogMonitor()) {
1105
1120
ANHANG C. QUELLTEXTE
}
bRemoved, Connection con)
throws
DBAccessException {
sName = c.getName();
logInfo("accessing the database for retrieving all keys of catalog "+sName+" that are added="+bAdded+" and removed="+bRemoved,false);
PreparedStatement st = con.prepareStatement("SELECT CIKeyName FROM CatItems WHERE CatName = ? AND Added = ? AND Removed = ?");
st.setString(1,c.getName());
st.setBoolean(2,bAdded);
st.setBoolean(3,bRemoved);
logSQL(st);
ResultSet rs = st.executeQuery();
while (rs.next()) {
l.add(rs.getString(1));
}
st.close();
} catch (SQLException e) {
logError(e.getMessage());
throw new DBAccessException("getCatalogItemKeys","getting all item-keys for catalog "+sName+" from database",e);
} catch (RemoteException rmie) {
Shop.getTheShop().logException(rmie);
rmie.printStackTrace();
}
return l;
}
public void createCatalogItem(CatalogItem ci, boolean bAdded, boolean bRemoved, Connection con) throws DBAccessException
synchronized (getCatalogMonitor()) {
String sItemKey = null; String sCatName = null;
try {
if (ci.getCatalog() == null) {
throw new IllegalStateException("You cannot persistify the CatalogItem "+ci.getKey()+" as it does not belong to a
}
1160
}
Catalog!");
else {
if (!ci.getCatalog().isPersistent())
throw new IllegalStateException("You
persistent Catalog!");
{
persistent
cannot persistify the CatalogItem "+ci.getKey()+" as it does not belong to a
C.2. PACKAGE EPOINT.DB
1165
1170
1175
1180
1185
1190
1195
1200
1205
1210
1215
}
sItemKey = ci.getKey();
sCatName = ci.getCatalog().getName();
logInfo("accessing the database for creating a catalogitem "+sItemKey+" in catalog "+sCatName,false);
PreparedStatement st;
st = con.prepareStatement("SELECT * FROM TransactionItems WHERE Identifier = ?");
st.setString(1,ci.getTransactionItemIdentifier());
logSQL(st);
ResultSet rsTest = st.executeQuery();
if (!rsTest.next()) {
st.close();
// this item is a new one, as it is not removed
st = con.prepareStatement("INSERT INTO TransactionItems(Identifier) VALUES (?)");
st.setString(1,ci.getTransactionItemIdentifier());
logSQL(st);
st.executeUpdate();
}
st = con.prepareStatement("INSERT INTO CatItems(CatName,CIKeyName,IsCatalog,Value,Serialized,TransactionID,Added,Removed) VALUES
(?,?,?,?,?,?,?,?)");
st.setString(1,ci.getCatalog().getName());
st.setString(2,ci.getKey());
st.setBoolean(3,ci.getValue().isCatalog());
if (ci.getValue().isCatalog()) {
Catalog cCatVal = (Catalog)ci.getValue();
cCatVal.setPersistentValueFlag(ci);
st.setString(4,cCatVal.getOID());
} else {
st.setString(4,ci.getValue().getSerializedForm());
}
st.setString(5,ci.getSerializedForm());
st.setString(6,ci.getTransactionItemIdentifier());
st.setBoolean(7,bAdded);
st.setBoolean(8,bRemoved);
logSQL(st);
st.execute();
st = con.prepareStatement("SELECT oid FROM CatItems WHERE CatName = ? AND CIKeyName = ? AND Added = ? AND Removed = ?");
st.setString(1,sCatName);
st.setString(2,sItemKey);
st.setBoolean(3,bAdded);
st.setBoolean(4,bRemoved);
logSQL(st);
ResultSet rs = st.executeQuery();
rs.next();
ci.setPersistent(rs.getString(1));
refCache.addToTItemCache(ci,bAdded,bRemoved);
st.close();
} catch (SQLException e) {
logError(e.getMessage());
throw new DBAccessException("createCatalogItem","creating new catalog-item "+sItemKey+" in catalog "+sCatName,e);
} catch (RemoteException rmie) {
Shop.getTheShop().logException(rmie);
rmie.printStackTrace();
}
}
public
CatalogItem getCatalogItem(String sKey, boolean bAdded, boolean bRemoved, Catalog cCatalog, Connection con) throws
DBAccessException {
(getCatalogMonitor()) {
CatalogItem ci = null; String sCatName = null;
try {
if (!cCatalog.isPersistent()) throw new IllegalStateException("You cannot retrieve a catalogitem from a catalog, that is not
persistent!");
logInfo("accessing the database for retrieving the catalog-item "+sKey+" that was added="+bAdded+" and removed="+bRemoved+" from
catalog "+cCatalog.getName(),false);
PreparedStatement st = con.prepareStatement("SELECT Serialized, TransactionID,Value,IsCatalog,oid FROM CatItems WHERE (CatName = ?)
AND (CIKeyName = ?) AND Added = ? AND REMOVED = ?");
st.setString(1,cCatalog.getName());
st.setString(2,sKey);
st.setBoolean(3,bAdded);
st.setBoolean(4,bRemoved);
logSQL(st);
ResultSet rs = st.executeQuery();
if (rs.next()) {
ci = (CatalogItem)refCache.lookupTItemCache(rs.getString("TransactionID"), bAdded, bRemoved);
if (ci != null) return ci;
String sXMLItem = rs.getString(1);
ci = (CatalogItem)Shop.getPersistenceSerializer().fromString(sXMLItem);
HashMap mProperties = new HashMap();
mProperties.put(ci.PERSISTENT_PROPERTY_CATALOG,cCatalog);
mProperties.put(ci.PERSISTENT_PROPERTY_CIKEY,sKey);
mProperties.put(ci.PERSISTENT_PROPERTY_TRANSACTION_IDENTIFIER,rs.getString(2));
mProperties.put(ci.PERSISTENT_PROPERTY_TRANSACTION_BASKET,getTransactionItemBasket(rs.getString(2),con));
if (rs.getBoolean(4)) {
Catalog cValue = getCatalogByOID(rs.getString(3),con);
mProperties.put(ci.PERSISTENT_PROPERTY_CIVALUE,cValue);
} else {
Object oValue = Shop.getPersistenceSerializer().fromString(rs.getString(3));
mProperties.put(ci.PERSISTENT_PROPERTY_CIVALUE,oValue);
synchronized
1220
1225
1230
1235
1240
285
286
ANHANG C. QUELLTEXTE
1245
}
ci.setPersistent(rs.getString(5));
ci.loadPersistentData(mProperties,sConPasswd);
1250
1255
1260
1265
}
}
st.close();
} catch (SQLException e) {
logError(e.getMessage());
throw new DBAccessException("getCatalogItem","retrieving catalog-item "+sKey+" in catalog "+sCatName+" from database",e);
} catch (RemoteException rmie) {
Shop.getTheShop().logException(rmie);
rmie.printStackTrace();
}
refCache.addToTItemCache(ci,bAdded,bRemoved);
return ci;
}
public List getCatalogItems(Catalog c, boolean bAdded, boolean bRemoved, Connection con) throws DBAccessException {
synchronized (getCatalogMonitor()) {
List lRetVal = Collections.synchronizedList(new LinkedList());
String sCatName = null;
try {
if (!c.isPersistent()) throw new IllegalStateException("You cannot retrieve a catalogitem from a catalog, that is
1270
1275
not persistent
!");
sCatName = c.getName();
logInfo("accessing the database for retrieving all catalogitems that are added="+bAdded+" and removed="+bRemoved+" from catalog "+c.
getName(),false);
Iterator itKeys = this.getCatalogItemKeys(c,bAdded,bRemoved, con).iterator();
while (itKeys.hasNext()) {
lRetVal.add(getCatalogItem((String)itKeys.next(),bAdded, bRemoved, c, con));
}
} catch (RemoteException rmie) {
Shop.getTheShop().logException(rmie);
rmie.printStackTrace();
}
return lRetVal;
}
1280
}
1285
public void deleteCatalogItem(CatalogItem
synchronized (getCatalogMonitor()) {
boolean bAdded = false;
boolean bRemoved = false;
try {
if (ci.isPersistent()) {
try {
1290
1295
1300
1305
1310
1315
1320
}
1325
}
}
ci, Connection con)
throws
DBAccessException {
logInfo("accessing the database for deleting the catalogitem with oid "+ci.getOID(),false);
PreparedStatement st = con.prepareStatement("SELECT COUNT(*) FROM CatItems WHERE TransactionID = ? ");
st.setString(1,ci.getTransactionItemIdentifier());
logSQL(st);
ResultSet rs = st.executeQuery();
rs.next();
int iTrans = rs.getInt(1);
st = con.prepareStatement("SELECT Added, Removed FROM CatItems WHERE oid = ?");
st.setString(1,ci.getOID());
rs = st.executeQuery();
if (rs.next()) {
bAdded = rs.getBoolean("Added");
bRemoved = rs.getBoolean("Removed");
}
st = con.prepareStatement("DELETE FROM CatItems WHERE oid = ? ");
st.setString(1,ci.getOID());
logSQL(st);
st.execute();
if (iTrans == 1) {
// this is not a duplicate item (for editing)
st = con.prepareStatement("DELETE FROM TransactionItems WHERE Identifier = ?");
st.setString(1,ci.getTransactionItemIdentifier());
logSQL(st);
st.execute();
}
ci.setPersistent(null);
if (ci.getValue().isCatalog()) ((Catalog)ci.getValue()).setPersistentValueFlag(ci);
st.close();
} catch (SQLException e) {
logError(e.getMessage());
throw new DBAccessException("deleteCatalogItem","deleting CatalogItem "+ci.getKey()+" in catalog "+ci.getCatalog().
getName()+"from database",e);
}
catch
(RemoteException rmie) {
Shop.getTheShop().logException(rmie);
rmie.printStackTrace();
}
refCache.removeFromTItemCache(ci,bAdded,bRemoved);
}
C.2. PACKAGE EPOINT.DB
1330
public CatalogItem getCatalogItemByOID(String
synchronized (getCatalogMonitor()) {
try {
CatalogItem ci = null;
1335
1340
1345
1350
1355
}
1360
1365
1380
}
DBAccessException {
1395
1400
1405
}
DBAccessException {
Statement st = con.createStatement();
logInfo("accessing the database for retrieving a list of all stock-names",false);
logSQL(st);
ResultSet rs = st.executeQuery("SELECT StockName FROM Stocks");
List l = Collections.synchronizedList(new LinkedList());
while (rs.next()) {
l.add(rs.getString(1));
}
st.close();
return l;
} catch (SQLException e) {
logError(e.getMessage());
throw new DBAccessException("getStockNames","retrieving all stock-names from database",e);
}
}
public List getStockNames(Catalog c, Connection
synchronized (getStockMonitor()) {
String sCatName = null;
try {
1390
1415
throws
PreparedStatement st = con.prepareStatement("SELECT CIKeyName,Added,Removed,CatName FROM CatItems WHERE oid = ?");
st.setString(1,sOID);
logInfo("accessing the database for retrieving the catalogitem with oid "+sOID,false);
logSQL(st);
ResultSet rs = st.executeQuery();
try {
if (rs.next())
ci = getCatalogItem(rs.getString(1),
rs.getBoolean(2),
rs.getBoolean(3),
getCatalog(rs.getString(4),con),
con);
} catch (Exception ioe) {
Shop.getTheShop().logException(ioe);
ioe.printStackTrace();
}
st.close();
return ci;
} catch (SQLException e) {
logError(e.getMessage());
throw new DBAccessException("getCatalogItemByOID","", e);
}
}
public List getStockNames(Connection con) throws
synchronized (getStockMonitor()) {
try {
1375
1410
sOID, Connection con)
////////////////////////////////////////////////////////////////////////////////
//
Stock
////////////////////////////////////////////////////////////////////////////////
1370
1385
287
con)
throws
DBAccessException {
sCatName = c.getName();
logInfo("accessing the database for retrieving a list of allstock-names for catalog "+sCatName, false);
} catch (Exception e) {
Shop.getTheShop().logException(e);
e.printStackTrace();
return null;
}
try {
PreparedStatement st = con.prepareStatement("SELECT StockName FROM Stocks WHERE CatName = ?");
st.setString(1,sCatName);
logSQL(st);
ResultSet rs = st.executeQuery();
List l = Collections.synchronizedList(new LinkedList());
while (rs.next()) {
l.add(rs.getString(1));
}
st.close();
return l;
} catch (SQLException e) {
logError(e.getMessage());
throw new DBAccessException("getStockNames","retrieving all stock-names of Catalog "+sCatName+" from database",e);
}
}
public void createStock(Stock sStock, Connection
synchronized (getStockMonitor()) {
String sStockName = null;
String sCatalogName = null;
try {
con)
throws
DBAccessException {
288
1420
1425
1430
1435
1440
1445
}
ANHANG C. QUELLTEXTE
if (sStock.isPersistent()) throw new RuntimeException("The given stock is already marked to be persistent!");
PreparedStatement st = con.prepareStatement("INSERT INTO Stocks(StockName,CatName,IsCounting,Serialized) VALUES(?,?,?,?)");
sStockName = sStock.getName();
logInfo("accessing the database for creating a stock named "+sStockName, false);
sCatalogName = sStock.getCatalog().getName();
st.setString(1,sStockName);
st.setString(2,sCatalogName);
st.setBoolean(3,sStock.isCountingStock());
st.setString(4,sStock.getSerializedForm());
logSQL(st);
st.execute();
st = con.prepareStatement("SELECT oid FROM Stocks WHERE StockName = ?");
st.setString(1,sStockName);
logSQL(st);
ResultSet rs = st.executeQuery();
rs.next();
sStock.setPersistent(rs.getString(1));
refCache.addToStockCache(sStock);
st.close();
} catch (SQLException e) {
logError(e.getMessage());
throw new DBAccessException("createStock","creating new stock named "+sStockName+") in database",e);
} catch (RemoteException rmie) {
Shop.getTheShop().logException(rmie);
rmie.printStackTrace();
}
}
public nal Stock getStock(String sName,
synchronized (getStockMonitor()) {
1450
1455
1460
1465
1470
1475
1480
}
1485
1500
1505
throws
DBAccessException {
logInfo("accessing the database for retrieving a stock named "+sName, false);
Stock sStock = refCache.lookupStockCache(sName);
if (sStock != null) return sStock;
try {
PreparedStatement st = con.prepareStatement("SELECT Serialized,CatName,IsCounting,oid FROM Stocks WHERE StockName = ?");
st.setString(1,sName);
logSQL(st);
ResultSet rs = st.executeQuery();
if (rs.next()) {
String sSerializedStock = rs.getString(1);
String sCatalogName = rs.getString(2);
boolean bCounting = rs.getBoolean(3);
String sOID = rs.getString(4);
HashMap mProperties = new HashMap();
mProperties.put(sStock.PERSISTENT_PROPERTY_STOCKNAME,sName);
mProperties.put(sStock.PERSISTENT_PROPERTY_BASECATALOG,getCatalog(sCatalogName,con));
mProperties.put(sStock.PERSISTENT_PROPERTY_COUNTINGFLAG,new Boolean(bCounting));
sStock = (Stock)Shop.getPersistenceSerializer().fromString(sSerializedStock);
sStock.setPersistent(sOID);
sStock.loadPersistentData(mProperties,sConPasswd);
}
st.close();
} catch (SQLException e) {
logError(e.getMessage());
refCache.removeFromStockCache(sStock);
throw new DBAccessException("getStock","retrieving stock from database by name "+sName,e);
} catch (RemoteException rmie) {
Shop.getTheShop().logException(rmie);
refCache.removeFromStockCache(sStock);
}
refCache.addToStockCache(sStock);
return sStock;
}
public nal void deleteStock(Stock sStock,
synchronized (getStockMonitor()) {
try {
if (sStock.isPersistent()) {
1490
1495
Connection con)
}
}
Connection con)
throws
DBAccessException {
logInfo("accessing the database for deleting a stock named "+sStock.getName(),false);
PreparedStatement st = con.prepareStatement("DELETE FROM Stocks WHERE StockName = ? ");
st.setString(1,sStock.getName());
logSQL(st);
st.execute();
sStock.setPersistent(null);
refCache.removeFromStockCache(sStock);
catch
(SQLException e) {
logError(e.getMessage());
try {
throw new DBAccessException("deleteStock","removing stock "+sStock.getName()+" from database",e);
} catch (RemoteException rmie) {
Shop.getTheShop().logException(rmie);
rmie.printStackTrace();
}
} catch (RemoteException e) {
Shop.getTheShop().logException(e);
C.2. PACKAGE EPOINT.DB
1510
}
}
}
1525
}
}
}
1545
1550
1555
1565
1570
1575
1580
1585
1590
}
con)
throws
DBAccessException {
logInfo("accessing the database for retrieving a list of all stocks for a given catalog",false);
List lRetVal = Collections.synchronizedList(new LinkedList());
Iterator itNames = this.getStockNames(c,con).iterator();
while (itNames.hasNext()) {
lRetVal.add(this.getStock((String)itNames.next(),con));
}
return lRetVal;
public void updateStock(Stock sStock, List
synchronized (getStockMonitor()) {
try {
1540
1560
}
DBAccessException {
logInfo("accessing the database for retrieving a list of all stocks",false);
List lRetVal = Collections.synchronizedList(new LinkedList());
Iterator itNames = this.getStockNames(con).iterator();
while (itNames.hasNext()) {
lRetVal.add(this.getStock((String)itNames.next(),con));
}
return lRetVal;
public nal List getStocks(Catalog c, Connection
synchronized (getStockMonitor()) {
1530
1535
e.printStackTrace();
public nal List getStocks(Connection con) throws
synchronized (getStockMonitor()) {
1515
1520
289
lPropertyKeys, Connection con)
throws
DBAccessException {
PreparedStatement st;
logInfo("accessing the database for updating the stock "+sStock.getName()+" ("+lPropertyKeys+")",false);
if (lPropertyKeys.contains(sStock.PERSISTENT_PROPERTY_SERIALIZED)) {
st = con.prepareStatement("UPDATE Stocks SET StockName = ?, Serialized = ? WHERE oid = ?");
st.setString(1,sStock.getName());
st.setString(2,sStock.getSerializedForm());
st.setString(3,sStock.getOID());
} else {
st = con.prepareStatement("UPDATE Stocks SET StockName = ? WHERE oid = ?");
st.setString(1,sStock.getName());
st.setString(2,sStock.getOID());
}
logSQL(st);
st.execute();
refCache.addToStockCache(sStock);
} catch (RemoteException rmie) {
Shop.getTheShop().logException(rmie);
rmie.printStackTrace();
} catch (SQLException e) {
logError(e.getMessage());
throw new DBAccessException("updateStock","",e);
}
}
////////////////////////////////////////////////////////////////////////////////
//
StockItem
////////////////////////////////////////////////////////////////////////////////
public nal List getStockItems(Stock sStock, boolean bAdded, boolean bRemoved, Connection con) throws
synchronized (getStockMonitor()) {
List lRetVal = Collections.synchronizedList(new LinkedList());
String sStockName = null;
try {
if (!sStock.isPersistent()) throw new IllegalStateException("You cannot retrieve stockitems from a
DBAccessException {
stock, that is not persistent
!");
sStockName = sStock.getName();
logInfo("accessing the database for retrieving all stockitems for stock "+sStockName+" that are added="+bAdded+" and removed="+
bRemoved,false);
PreparedStatement st = con.prepareStatement("SELECT RefTransID,"+//CatName,"+ // 1
//"CIKeyName,"+
// 2
//"CIAdded,"+
// 3
//"CIRemoved,"+
// 4
"Identifier,"+
// 2
"Value,"+
// 3
"Serialized,"+
// 4
"TransactionId, "+ // 5
"oid_si,"+
// 6
"CatName,"+
// 7
"CIKeyName,"+
// 8
"CIAdded,"+
// 9
"CIRemoved "+
// 10
"FROM StockItemsFull WHERE (StockName = ?) AND "+ // 1
"(Added = ?) AND "+
// 2
290
1595
1600
1605
1610
1615
1620
1625
1630
1635
1640
1645
}
ANHANG C. QUELLTEXTE
"(Removed = ?)");
// 3
st.setString(1,sStockName);
st.setBoolean(2,bAdded);
st.setBoolean(3,bRemoved);
logSQL(st);
ResultSet rs = st.executeQuery();
while (rs.next()) {
String sTransID = rs.getString("TransactionID");
StockItem si = (StockItem)refCache.lookupTItemCache(sTransID,bAdded,bRemoved);
if (si == null) { // nothing in cache
String sRefTransID = rs.getString("RefTransID");
String sCatName = rs.getString("CatName");
String sCIKey = rs.getString("CIKeyName");
boolean bCIAdded = rs.getBoolean("CIAdded");
boolean bCIRemoved = rs.getBoolean("CIRemoved");
if (bCIAdded && bCIRemoved) { // edited
// edited version is skipped, as we only reference
// one transactionitem
continue;
//bCIAdded = false;
//bCIRemoved = false;
}
long lItemID = rs.getLong("Identifier");
String sXMLValue = rs.getString("Value");
String sXMLItem = rs.getString("Serialized");
String sOID = rs.getString("oid_si");
Value vValue = null;
si = (StockItem)Shop.getPersistenceSerializer().fromString(sXMLItem);
vValue = (Value)Shop.getPersistenceSerializer().fromString(sXMLValue);
HashMap mProperties = new HashMap();
mProperties.put(si.PERSISTENT_PROPERTY_CATALOGITEMKEY,sCIKey);
mProperties.put(si.PERSISTENT_PROPERTY_IDENTIFIER,new Long(lItemID));
mProperties.put(si.PERSISTENT_PROPERTY_STOCK,sStock);
mProperties.put(si.PERSISTENT_PROPERTY_TRANSACTION_IDENTIFIER,sTransID);
mProperties.put(si.PERSISTENT_PROPERTY_SIVALUE,vValue);
mProperties.put(si.PERSISTENT_PROPERTY_TRANSACTION_BASKET,
getTransactionItemBasket(sTransID,con));
si.setPersistent(sOID);
si.loadPersistentData(mProperties,sConPasswd);
refCache.addToTItemCache(si, bAdded, bRemoved);
}
lRetVal.add(si);
}
st.close();
} catch (SQLException e) {
logError(e.getMessage());
throw new DBAccessException("getStockItems","retrieving stock-items from stock "+sStockName+" from database",e);
} catch (RemoteException rmie) {
Shop.getTheShop().logException(rmie);
rmie.printStackTrace();
}
return lRetVal;
}
public nal
List getStockItems(Stock sStock, CatalogItem ci, boolean bAdded,
DBAccessException {
(getStockMonitor()) {
List lRetVal = Collections.synchronizedList(new LinkedList());
String sStockName = null;
try {
List l = getStockItems(sStock,bAdded,bRemoved,con);
Iterator it = l.iterator();
while (it.hasNext()) {
StockItem si = (StockItem)it.next();
if (si.getName().equals(ci.getKey())) lRetVal.add(si);
}
} catch (RemoteException rmie) {
Shop.getTheShop().logException(rmie);
}
return lRetVal;
}
boolean
bRemoved, Connection con)
throws
synchronized
1650
1655
1660
}
1665
1670
1675
public nal void createStockItem(StockItem
synchronized (getStockMonitor()) {
String sItemKey = null;
String sCatName = null;
String sStockName = null;
String sRefTransID = null;
PreparedStatement st = null;
boolean bAutoCommit = false;
try {
//
//
//
if
si,
boolean
bAdded,
boolean
bRemoved, Connection con)
throws
DBAccessException {
if (si.getAssociatedItem() == null) {
throw new IllegalStateException("The StockItem you want to persistify has no associated item!");
}
(si.getStock() == null) {
C.2. PACKAGE EPOINT.DB
throw new
1680
1685
1690
1695
1700
1705
1710
1715
1720
1725
1730
1735
1740
1745
1750
1755
1760
}
291
IllegalStateException("You cannot persistify the StockItem "+si.getName()+" as it does not belong to a persistent
Stock!");
else {
if (!si.getStock().isPersistent())
throw new IllegalStateException("You
cannot persistify the StockItem "+si.getName()+" as it does not belong to a
persistent Stock!");
}
sItemKey = si.getName();
sCatName = si.getStock().getCatalog().getName();
sStockName = si.getStock().getName();
st = con.prepareStatement("SELECT TransactionID FROM CatItems WHERE CIKeyName = ?");
st.setString(1,sItemKey);
ResultSet rsTransID = st.executeQuery();
rsTransID.next();
sRefTransID = rsTransID.getString(1); //si.getAssociatedItem(si.getBasket()).getTransactionItemIdentifier();
bAutoCommit = con.getAutoCommit();
logInfo("accessing the database for creating a stockitem "+sItemKey+" in stock "+sStockName,false);
synchronized (con) {
con.setAutoCommit(false);
st = con.prepareStatement("SELECT * FROM TransactionItems WHERE Identifier = ?");
st.setString(1,si.getTransactionItemIdentifier());
logSQL(st);
ResultSet rsTest = st.executeQuery();
if (!rsTest.next()) {
st.close();
// this item is a new one, as it is not removed
st = con.prepareStatement("INSERT INTO TransactionItems(Identifier) VALUES (?)");
st.setString(1,si.getTransactionItemIdentifier());
logSQL(st);
st.executeUpdate();
}
st.close();
st = con.prepareStatement("INSERT INTO StockItems(StockName,"+ // 1
//"CatName,"+
// 2
//"CIKeyName,"+
// 3
//"CIAdded,"+
// 4
//"CIRemoved,"+
// 5
"RefTransID,"+
// 2
"Identifier,"+
// 3
"Value,"+
// 4
"Serialized,"+
// 5
"TransactionID,"+ // 6
"Added,"+
// 7
"Removed) "+
// 8
"VALUES (?,?,?,?,?,?,?,?)");
/*
boolean bCIAdded = false;
boolean bCIRemoved = false;
if (si.getAssociatedItem(si.getBasket()).getBasket() != null) {
DataBasket db = si.getAssociatedItem(si.getBasket()).getBasket();
RemoteIterator it = db.iterator(si.getAssociatedItem(si.getBasket()));
while (it.hasNext()) {
DBEntry dbe = (DBEntry)it.next();
if (dbe.getTarget() instanceof Catalog) {
Catalog c = (Catalog)dbe.getTarget();
if (c.getName().equals(si.getAssociatedItem(si.getBasket()).getCatalog().getName())) {
if (!dbe.getTransactionType().equals(dbe.TRANSACTION_MODIFY)) {
bCIAdded = dbe.getTransactionType().equals(dbe.TRANSACTION_ADD);
bCIRemoved = dbe.getTransactionType().equals(dbe.TRANSACTION_REMOVE);
} else {
bCIAdded = true;
bCIRemoved = true;
}
break;
}
}
}
}
*/
st.setString(1,sStockName);
//st.setString(2,sCatName);
//st.setString(3,sItemKey);
//st.setBoolean(4,bCIAdded);
//st.setBoolean(5,bCIRemoved);
st.setString(2,sRefTransID);
st.setLong(3,si.getInternalID());
st.setString(4,Shop.getPersistenceSerializer().toString(si.getValue()));
st.setString(5,Shop.getPersistenceSerializer().toString(si));
st.setString(6,si.getTransactionItemIdentifier());
st.setBoolean(7,bAdded);
st.setBoolean(8,bRemoved);
logSQL(st);
st.execute();
con.commit();
con.setAutoCommit(true);
st = con.prepareStatement("SELECT oid FROM StockItems WHERE StockName = ? AND "+ // 1
292
ANHANG C. QUELLTEXTE
"RefTransID = ? AND "+ // 2
"Identifier = ? AND "+ // 3
"Added = ? "+
// 4
"AND Removed = ?");
// 5
1765
1770
//
//
//
1775
1780
1785
1790
st.setString(1,sStockName);
st.setString(2,sItemKey);
st.setBoolean(3,bCIAdded);
st.setBoolean(4,bCIRemoved);
st.setString(2,sRefTransID);
st.setLong(3,si.getInternalID());
st.setBoolean(4,bAdded);
st.setBoolean(5,bRemoved);
logSQL(st);
ResultSet rs = st.executeQuery();
rs.next();
si.setPersistent(rs.getString(1));
refCache.addToTItemCache(si,bAdded,bRemoved);
st.close();
}// synchronization
} catch (SQLException e) {
logError(e.getMessage());
String sTrace = "";
for (int iLength = 0; iLength < e.getStackTrace().length; iLength++) sTrace += "\n\t\t"+e.getStackTrace()[iLength];
throw new DBAccessException("createStockItem "+sTrace,"creating new stock-item "+sItemKey+" in stock "+sStockName+" : "+st,e);
} catch (RemoteException rmie) {
Shop.getTheShop().logException(rmie);
rmie.printStackTrace();
} nally {
try {con.setAutoCommit(bAutoCommit);} catch (Exception e) {}
}
}
1795
}
1800
public nal void deleteStockItem(StockItem
synchronized (getStockMonitor()) {
boolean bAdded = false;
boolean bRemoved = false;
try {
if (si.isPersistent()) {
try {
1805
1810
1815
1820
1825
1830
1835
}
si, Connection con)
catch
}
1845
}
refCache.removeFromTItemCache(si,bAdded,bRemoved);
}
1850
DBAccessException {
logInfo("accessing the database for deleting the stockitem "+si.getName()+" from stock "+si.getStock().getName(),false);
String sTransID = si.getTransactionItemIdentifier();
PreparedStatement st = con.prepareStatement("SELECT count(*) FROM StockItems WHERE TransactionID = ? ");
st.setString(1,sTransID);
logSQL(st);
ResultSet rs = st.executeQuery();
rs.next();
int iTrans = rs.getInt(1);
st = con.prepareStatement("SELECT Added, Removed FROM StockItems WHERE oid = ?");
st.setString(1,si.getOID());
rs = st.executeQuery();
if (rs.next()) {
bAdded = rs.getBoolean("Added");
bRemoved = rs.getBoolean("Removed");
}
st = con.prepareStatement("DELETE FROM StockItems WHERE oid = ? ");
st.setString(1,si.getOID());
logSQL(st);
st.execute();
if (iTrans <= 1) { // this may be 0, as the StockItem may have been deleted by
// acommitted deletion of the referenced CatalogItem
// (in this case, this is the committance of the
// database that removes the corresponding StockItems
// this is not a duplicate (edited) item
st = con.prepareStatement("DELETE FROM TransactionItems WHERE Identifier = ?");
st.setString(1,sTransID);
logSQL(st);
st.execute();
}
si.setPersistent(null);
st.close();
} catch (SQLException e) {
logError(e.getMessage());
throw new DBAccessException("deleteStockItem","deleting StockItem "+si.getName()+" in catalog "+si.getStock().getCatalog
().getName()+"from database",e);
}
1840
}
throws
(RemoteException rmie) {
Shop.getTheShop().logException(rmie);
rmie.printStackTrace();
public StockItem getStockItemByOID(String
synchronized (getStockMonitor()) {
try {
sOID, Connection con)
throws
DBAccessException {
C.2. PACKAGE EPOINT.DB
1855
1860
1865
1870
1875
1880
1885
1890
1895
1900
1905
1910
1915
}
StockItem si = null;
PreparedStatement st = con.prepareStatement("SELECT TransactionID, "+ // 1
//"CatName, "+
// 2
//"CIKeyName, "+
// 3
//"CIAdded, "+
// 4
//"CIRemoved, "+
// 5
"RefTransID, "+
// 2
"StockName, "+
// 3
"Added, "+
// 4
"Removed, "+
// 5
"CatName, "+
// 6
"CIKeyName, "+
// 7
"CIAdded, "+
// 8
"CIRemoved, "+
// 9
"oid_si "+
// 10
"FROM StockItemsFull WHERE oid_si = ?");
st.setString(1,sOID);
logInfo("accessing the database for retrieving the stockitem with oid "+sOID,false);
logSQL(st);
ResultSet rs = st.executeQuery();
try {
if (rs.next()) {
String sTransID = rs.getString("TransactionID");
si = (StockItem)refCache.lookupTItemCache(sTransID,rs.getBoolean("Added"),rs.getBoolean("Removed"));
if (si == null) {
String sRefTransID = rs.getString("RefTransID");
String sCatName = rs.getString("CatName");
String sKeyName = rs.getString("CIKeyName");
boolean bCIAdded = rs.getBoolean("CIAdded");
boolean bCIRemoved = rs.getBoolean("CIRemoved");
if (bCIAdded && bCIRemoved) { // edited
bCIAdded = false;
bCIRemoved = false;
}
String sStockName = rs.getString("StockName");
boolean bAdded = rs.getBoolean("Added");
boolean bRemoved = rs.getBoolean("Removed");
Catalog c = getCatalog(sCatName,con);
CatalogItem ci = (CatalogItem)refCache.lookupTItemCache(sRefTransID,bCIAdded,bCIRemoved);
if (ci == null) ci = getCatalogItem(sKeyName,bCIAdded,bCIRemoved,c,con);
Iterator it = getStockItems(getStock(sStockName,con),ci,bAdded,bRemoved,con).iterator();
while (it.hasNext()) {
si = (StockItem)it.next();
if (si.getOID().equals(sOID)) break;
si = null;
}
if (si == null) throw new RuntimeException("It seems as we have an internal framework-error - there should be
StockItem in the database, but it isn't!");
refCache.addToTItemCache(si,bAdded,bRemoved);
}
}
return si;
} catch (Exception ioe) {
Shop.getTheShop().logException(ioe);
ioe.printStackTrace();
} nally {
st.close();
}
return si;
} catch (SQLException e) {
logError(e.getMessage());
throw new DBAccessException("getStockItemByOID","", e);
}
}
public
StockItem getStockItemByTransactionID(String sTransItemID, boolean bAdded, boolean bRemoved, Connection con) throws
DBAccessException {
(getStockMonitor()) {
logInfo("accessing the database for retrieving the stockitem with transaction-id "+sTransItemID,false);
TransactionItem ti = refCache.lookupTItemCache(sTransItemID,bAdded,bRemoved);
if ((ti != null) && (ti instanceof StockItem)) return (StockItem)ti;
try {
PreparedStatement st = con.prepareStatement("SELECT oid FROM StockItems WHERE TransactionID = ? AND Added = ? AND Removed = ?");
st.setString(1,sTransItemID);
st.setBoolean(2,bAdded);
st.setBoolean(3,bRemoved);
logSQL(st);
ResultSet rs = st.executeQuery();
if (rs.next()) {
st.close();
return getStockItemByOID(rs.getString(1),con);
}
st.close();
} catch (SQLException e) {
logError(e.getMessage());
throw new DBAccessException("getStockItemByTransactionID","returning the stockitem for a given oid",e);
} catch (Exception e2) {
synchronized
1920
1925
1930
1935
293
294
}
}
1950
1955
1960
//
1975
1980
1985
1990
1995
2000
2005
2010
2015
2020
}
public void updateStockItem(StockItem si,
synchronized (getStockMonitor()) {
try {
1945
1970
Shop.getTheShop().logException(e2);
e2.printStackTrace();
return null;
1940
1965
ANHANG C. QUELLTEXTE
//
//
List lPropertyKeys, Connection con)
throws
DBAccessException {
logInfo("accessing the database for updating the stockitem "+si.getName()+" in stock "+si.getStock().getName()+" ("+lPropertyKeys+")
",false);
PreparedStatement st;
if (lPropertyKeys.contains(si.PERSISTENT_PROPERTY_TRANSACTION_IDENTIFIER)) {
st = con.prepareStatement("UPDATE TransactionItems SET TransactionHandle = ? WHERE Identifier = ?");
st.setString(1,(si.getBasket() == null)?(null):(si.getBasket().getName()));
st.setString(2,si.getTransactionItemIdentifier());
logSQL(st);
st.execute();
}
int iPosCnt = 1;
String sSerialized = "";
if (lPropertyKeys.contains(si.PERSISTENT_PROPERTY_SERIALIZED)) sSerialized = ", Serialized = ?";
String sValue = "";
if (lPropertyKeys.contains(si.PERSISTENT_PROPERTY_SIVALUE)) sValue = ", Value = ?";
String sTransState = "";
if (lPropertyKeys.contains(si.PERSISTENT_PROPERTY_TRANSACTION_STATE)) sTransState = ", Added = ?, Removed = ?";
st = con.prepareStatement("UPDATE StockItems SET StockName = ?, CatName = ?, CIKeyName = ?, TransactionID = ?"+sSerialized+sValue+
sTransState+" WHERE oid = ?");
st = con.prepareStatement("UPDATE StockItems SET StockName = ?, TransactionID = ?"+sSerialized+sValue+sTransState+" WHERE oid = ?");
st.setString(iPosCnt,si.getStock().getName()); iPosCnt++;
st.setString(iPosCnt,si.getStock().getCatalog().getName()); iPosCnt++;
st.setString(iPosCnt,si.getName()); iPosCnt++;
st.setString(iPosCnt,si.getTransactionItemIdentifier()); iPosCnt++;
if (lPropertyKeys.contains(si.PERSISTENT_PROPERTY_SERIALIZED)) {
st.setString(iPosCnt,si.getSerializedForm()); iPosCnt++;
}
if (lPropertyKeys.contains(si.PERSISTENT_PROPERTY_SIVALUE)) {
st.setString(iPosCnt,si.getValue().getSerializedForm());
iPosCnt++;
}
if (lPropertyKeys.contains(si.PERSISTENT_PROPERTY_TRANSACTION_STATE)) {
boolean bAdded = false;
boolean bRemoved = false;
if (si.getBasket() != null) {
DataBasket db = si.getBasket();
RemoteIterator it = db.iterator(si);
while (it.hasNext()) {
DBEntry dbe = (DBEntry)it.next();
if (dbe.getTarget() instanceof Stock) {
Stock s = (Stock)dbe.getTarget();
if (s.getName().equals(si.getStock().getName())) {
if (!dbe.getTransactionType().equals(dbe.TRANSACTION_MODIFY)) {
bAdded = dbe.getTransactionType().equals(dbe.TRANSACTION_ADD);
bRemoved = dbe.getTransactionType().equals(dbe.TRANSACTION_REMOVE);
} else {
bAdded = true;
bRemoved = true;
}
break;
}
}
}
}
st.setBoolean(iPosCnt,bAdded); iPosCnt++;
st.setBoolean(iPosCnt,bRemoved); iPosCnt++;
StockItem siTempNormal = (StockItem)refCache.lookupTItemCache(si.getTransactionItemIdentifier(),false,false);
StockItem siTempAdded = (StockItem)refCache.lookupTItemCache(si.getTransactionItemIdentifier(),true,false);
StockItem siTempRemoved = (StockItem)refCache.lookupTItemCache(si.getTransactionItemIdentifier(),false,true);
StockItem siTempEdited = (StockItem)refCache.lookupTItemCache(si.getTransactionItemIdentifier(),true,true);
if (siTempNormal != null)
if (si.getOID().equals(siTempNormal.getOID())) refCache.removeFromTItemCache(siTempNormal,false,false);
if (siTempAdded != null)
if (si.getOID().equals(siTempAdded.getOID())) refCache.removeFromTItemCache(siTempAdded,true,false);
if (siTempRemoved != null)
if (si.getOID().equals(siTempRemoved.getOID())) refCache.removeFromTItemCache(siTempRemoved,false,true);
if (siTempEdited != null)
if (si.getOID().equals(siTempEdited.getOID())) refCache.removeFromTItemCache(siTempEdited,true,true);
refCache.addToTItemCache(si,bAdded,bRemoved);
}
st.setString(iPosCnt,si.getOID()); iPosCnt++;
logSQL(st);
st.execute();
} catch (SQLException e) {
logError(e.getMessage());
throw new DBAccessException("updateStockItem()","",e);
} catch (RemoteException re) {
Shop.getTheShop().logException(re);
C.2. PACKAGE EPOINT.DB
2025
2030
}
2040
2045
2050
2055
2060
2065
2070
2080
2085
}
con)
throws
DBAccessException {
logInfo("accessing the database for retrieving the oid of stockitem "+si.getName()+" in stock "+si.getStock().getName(), false);
PreparedStatement st = con.prepareStatement("SELECT oid FROM StockItems WHERE TransactionID = ? AND Added = ? AND Removed = ?");
st.setString(1,si.getTransactionItemIdentifier());
st.setBoolean(2,false);
st.setBoolean(3,false);
if (si.getBasket() != null) {
DataBasket db = si.getBasket();
RemoteIterator it = db.iterator(si);
while (it.hasNext()) {
DBEntry dbe = (DBEntry)it.next();
if (dbe.getTarget() instanceof Stock) {
Stock s = (Stock)dbe.getTarget();
if (s.getName().equals(si.getStock().getName())) {
if (!dbe.getTransactionType().equals(dbe.TRANSACTION_MODIFY)) {
// an adding or removing transaction
st.setBoolean(3,dbe.getTransactionType().equals(dbe.TRANSACTION_ADD));
st.setBoolean(4,dbe.getTransactionType().equals(dbe.TRANSACTION_REMOVE));
} else {
// a modifying transaction
st.setBoolean(3,true);
st.setBoolean(4,true);
}
break;
}
}
}
}
logSQL(st);
ResultSet rs = st.executeQuery();
if (rs.next()) sOID = rs.getString(1);
st.close();
} catch (SQLException e) {
logError(e.getMessage());
throw new DBAccessException("getOID(StockItem)","",e);
} catch (RemoteException rmie) {
Shop.getTheShop().logException(rmie);
rmie.printStackTrace();
}
return sOID;
}
}
////////////////////////////////////////////////////////////////////////////////
//
DataBasket
////////////////////////////////////////////////////////////////////////////////
public void createDataBasket(DataBasket db,
synchronized (getBasketMonitor()) {
String sName = null;
try {
2090
2095
2100
2105
}
2110
re.printStackTrace();
public String getOID(StockItem si, Connection
synchronized (getStockMonitor()) {
synchronized (getStockMonitor()) {
String sOID = null;
try {
2035
2075
}
}
295
Connection con)
throws
DBAccessException {
PreparedStatement st = con.prepareStatement("INSERT INTO TransactionHandles(Name,Serialized) VALUES(?,?)");
sName = db.getName();
logInfo("accessing the database for creating a new basket "+sName,false);
st.setString(1,sName);
st.setString(2,db.getSerializedForm());
logSQL(st);
st.execute();
st = con.prepareStatement("SELECT oid FROM TransactionHandles WHERE Name = ?");
st.setString(1,sName);
logSQL(st);
ResultSet rs = st.executeQuery();
rs.next();
db.setPersistent(rs.getString(1));
st.close();
} catch (SQLException e) {
logError(e.getMessage());
throw new DBAccessException("createDataBasket","",e);
} catch (RemoteException rmie) {
Shop.getTheShop().logException(rmie);
rmie.printStackTrace();
}
refCache.addToBasketCache(db);
}
public
DataBasket getDataBasket(String sName, Connection con)
throws
DBAccessException {
296
2115
2120
2125
2130
2135
2140
}
ANHANG C. QUELLTEXTE
synchronized (getBasketMonitor()) {
DataBasket db = refCache.lookupBasketCache(sName);
if (db != null) return db;
try {
logInfo("accessing the database for retrieving the basket "+sName,false);
PreparedStatement st = con.prepareStatement("SELECT Serialized, oid FROM TransactionHandles WHERE Name = ?");
st.setString(1,sName);
logSQL(st);
ResultSet rs = st.executeQuery();
if (rs.next()) {
String sBasket = rs.getString(1);
db = (DataBasket)Shop.getPersistenceSerializer().fromString(sBasket);
HashMap mProperties = new HashMap();
mProperties.put(db.PERSISTENT_PROPERTY_BASKETNAME,sName);
db.setPersistent(rs.getString(2));
db.loadPersistentData(mProperties,sConPasswd);
}
st.close();
} catch (SQLException e) {
logError(e.getMessage());
throw new DBAccessException("getDataBasket","",e);
} catch (RemoteException rmie) {
Shop.getTheShop().logException(rmie);
rmie.printStackTrace();
}
refCache.addToBasketCache(db);
return db;
}
public void deleteDataBasket(DataBasket db,
synchronized (getBasketMonitor()) {
try {
2145
2150
2155
2160
Connection con)
}
2170
public void createDBEntry(DBEntry dbe, Connection
synchronized (getBasketMonitor()) {
String sName = null;
try {
2180
2185
2190
2195
DBAccessException {
logInfo("accessing the database for deleting the basket "+db.getName(),false);
// if the running transaction has not finished, it needs to be rolled back first
if (!db.hasFinished()) db.rollback();
PreparedStatement st = con.prepareStatement("UPDATE TransactionItems SET TransactionHandle = NULL WHERE TransactionHandle = ?");
st.setString(1,db.getName());
logSQL(st);
st.execute();
st = con.prepareStatement("DELETE FROM TransactionHandles WHERE Name = ?");
st.setString(1,db.getName());
logSQL(st);
st.execute();
db.setPersistent(null);
} catch (SQLException e) {
logError(e.getMessage());
throw new DBAccessException("deleteDataBasket","",e);
} catch (RemoteException rmie) {
Shop.getTheShop().logException(rmie);
rmie.printStackTrace();
}
refCache.removeFromBasketCache(db);
}
2165
2175
throws
con)
throws
DBAccessException {
logInfo("accessing the database for creating a new DBEntry in basket "+dbe.getBasket().getName(),false);
PreparedStatement st = con.prepareStatement("INSERT INTO Transactions(Name,Identifier,OrderIndex,TransType,InfoVector,Serialized)
VALUES(?,?,?,?,?,?)");
sName = dbe.getBasket().getName();
String sIdentifier = dbe.getTransactionItem().getTransactionItemIdentifier();
st.setString(1,sName);
st.setString(2,sIdentifier);
st.setInt(3,dbe.getOrderIndex());
st.setString(4,dbe.getTransactionType());
st.setString(5,epoint.sale.Shop.getPersistenceSerializer().toString(dbe.getProperties()));
st.setString(6,dbe.getSerializedForm());
logSQL(st);
st.execute();
st = con.prepareStatement("UPDATE TransactionItems SET TransactionHandle = ? WHERE Identifier = ?");
st.setString(1,dbe.getBasket().getName());
st.setString(2,sIdentifier);
logSQL(st);
st.execute();
st = con.prepareStatement("SELECT oid FROM Transactions WHERE Name = ? AND OrderIndex = ?");
st.setString(1,sName);
st.setInt(2,dbe.getOrderIndex());
logSQL(st);
ResultSet rs = st.executeQuery();
rs.next();
dbe.setPersistent(rs.getString(1));
st.close();
C.2. PACKAGE EPOINT.DB
}
2200
2205
2210
}
catch
(SQLException e) {
logError(e.getMessage());
throw new DBAccessException("createDBEntry","Inserting data for new transaction ("+sName+") into TransactionHandles",e);
} catch (RemoteException rmie) {
Shop.getTheShop().logException(rmie);
rmie.printStackTrace();
}
}
public List getDataBaskets(Connection con) throws
synchronized (getBasketMonitor()) {
try {
2215
2220
2225
2230
}
2235
DBAccessException {
logInfo("accessing the database for retrieving all databaskets",false);
List lRetVal = Collections.synchronizedList(new LinkedList());
PreparedStatement st = con.prepareStatement("SELECT Name FROM TransactionHandles");
logSQL(st);
ResultSet rs = st.executeQuery();
List lNames = new LinkedList();
try {
while (rs.next()) lNames.add(rs.getString(1));
Iterator it = lNames.iterator();
while (it.hasNext()) lRetVal.add(getDataBasket((String)it.next(),con));
} catch (Exception ioe) {
Shop.getTheShop().logException(ioe);
ioe.printStackTrace();
}
st.close();
return lRetVal;
} catch (SQLException e) {
logError(e.getMessage());
throw new DBAccessException("getDataBaskets","",e);
}
}
public List getDBEntries(DataBasket db, Connection con) throws DBAccessException
synchronized (getBasketMonitor()) {
List lRetVal = Collections.synchronizedList(new LinkedList());
try {
2240
2245
2250
2255
2260
2265
{
logInfo("accessing the database for retrieving all entries of basket "+db.getName(),false);
PreparedStatement st = con.prepareStatement("SELECT Serialized,Identifier,OrderIndex,TransType,InfoVector FROM Transactions WHERE
Name = ? ORDER BY OrderIndex");
st.setString(1,db.getName());
logSQL(st);
ResultSet rs = st.executeQuery();
try {
while (rs.next()) {
DBEntry dbe = (DBEntry)Shop.getPersistenceSerializer().fromString(rs.getString(1));
HashMap mProperties = new HashMap();
mProperties.put(DataBasket.PERSISTENT_PROPERTY_BASKETNAME,db.getName());
mProperties.put(TransactionItem.PERSISTENT_PROPERTY_TRANSACTION_IDENTIFIER,rs.getString(2));
mProperties.put(dbe.PERSISTENT_PROPERTY_ORDER_INDEX,new Integer(rs.getInt(3)));
Map m = (Map)Shop.getPersistenceSerializer().fromString(rs.getString(5));
mProperties.put(dbe.PERSISTENT_PROPERTY_PROPERTIES,m);
mProperties.put(dbe.PERSISTENT_PROPERTY_TRANSACTION_TYPE,rs.getString(4));
dbe.loadPersistentData(mProperties,sConPasswd);
lRetVal.add(dbe);
}
} catch (Exception ioe) {
Shop.getTheShop().logException(ioe);
ioe.printStackTrace();
}
st.close();
} catch (SQLException e) {
logError(e.getMessage());
throw new DBAccessException("getDBEntries","",e);
} catch (RemoteException rmie) {
Shop.getTheShop().logException(rmie);
rmie.printStackTrace();
}
return lRetVal;
}
2270
}
2275
public String getOID(DataBasket db, Connection
synchronized (getBasketMonitor()) {
String sOID = null;
try {
2280
297
con)
throws
DBAccessException {
logInfo("accessing the database for retrieving the oid of basket "+db.getName(),false);
PreparedStatement st = con.prepareStatement("SELECT oid FROM TransactionHandles WHERE Name = ?");
st.setString(1,db.getName());
logSQL(st);
ResultSet rs = st.executeQuery();
if (rs.next()) sOID = rs.getString(1);
st.close();
} catch (SQLException e) {
logError(e.getMessage());
298
2285
}
}
}
2305
2310
}
2325
2330
2340
}
2350
2360
2365
2370
sOID;
}
con)
throws
DBAccessException {
logInfo("accessing the database for retrieving the stock with oid "+sOID,false);
PreparedStatement st = con.prepareStatement("SELECT StockName FROM Stocks WHERE oid = ?");
st.setString(1,sOID);
logSQL(st);
ResultSet rs = st.executeQuery();
if (rs.next()) {
st.close();
return getStock(rs.getString(1),con);
}
st.close();
return null;
} catch (SQLException e) {
logError(e.getMessage());
throw new DBAccessException("getStockByOID","",e);
}
}
con)
throws
DBAccessException {
logInfo("accessing the database for retrieving the oid of stock "+s.getName(),false);
PreparedStatement st = con.prepareStatement("SELECT oid FROM Stocks WHERE StockName = ?");
st.setString(1,s.getName());
logSQL(st);
ResultSet rs = st.executeQuery();
if (rs.next()) sOID = rs.getString(1);
st.close();
} catch (SQLException e) {
logError(e.getMessage());
throw new DBAccessException("getOID(Stock)","",e);
} catch (RemoteException rmie) {
Shop.getTheShop().logException(rmie);
rmie.printStackTrace();
}
return sOID;
}
public DataBasket getDataBasketByOID(String
synchronized (getBasketMonitor()) {
try {
2345
2355
}
public String getOID(Stock s, Connection
synchronized (getStockMonitor()) {
String sOID = null;
try {
2320
2335
Shop.getTheShop().logException(rmie);
rmie.printStackTrace();
public Stock getStockByOID(String sOID, Connection
synchronized (getStockMonitor()) {
try {
2300
2315
throw new DBAccessException("getOID(DataBasket)","",e);
catch (RemoteException rmie) {
return
2290
2295
ANHANG C. QUELLTEXTE
sOID, Connection con)
throws
DBAccessException {
logInfo("accessing the database for retrieving the databasket with oid "+sOID,false);
PreparedStatement st = con.prepareStatement("SELECT Name FROM TransactionHandles WHERE oid = ?");
st.setString(1,sOID);
logSQL(st);
ResultSet rs = st.executeQuery();
if (rs.next()) {
st.close();
return getDataBasket(rs.getString(1),con);
}
st.close();
return null;
} catch (SQLException e) {
logError(e.getMessage());
throw new DBAccessException("getDataBasketByOID","",e);
}
}
public String getOID(CatalogItem ci, Connection
synchronized (getCatalogMonitor()) {
String sOID = null;
try {
con)
throws
DBAccessException {
logInfo("accessing the database for retrieving the oid of catalogitem "+ci.getKey()+" in catalog "+ci.getCatalog().getName(), false);
PreparedStatement st = con.prepareStatement("SELECT oid FROM CatItems WHERE CatName = ? AND CIKeyName = ? AND Added = ? AND Removed
= ?");
st.setString(1,ci.getCatalog().getName());
st.setString(2,ci.getKey());
st.setBoolean(3,false);
st.setBoolean(4,false);
if (ci.getBasket() != null) {
DataBasket db = ci.getBasket();
RemoteIterator it = db.iterator(ci);
while (it.hasNext()) {
C.2. PACKAGE EPOINT.DB
DBEntry dbe = (DBEntry)it.next();
if (dbe.getTarget() instanceof Catalog) {
Catalog c = (Catalog)dbe.getTarget();
if (c.getName().equals(ci.getCatalog().getName())) {
if (!dbe.getTransactionType().equals(dbe.TRANSACTION_MODIFY)) {
// an adding or removing transaction
st.setBoolean(3,dbe.getTransactionType().equals(dbe.TRANSACTION_ADD));
st.setBoolean(4,dbe.getTransactionType().equals(dbe.TRANSACTION_REMOVE));
} else {
// a modifying transaction
st.setBoolean(3,true);
st.setBoolean(4,true);
}
break;
}
}
2375
2380
2385
2390
2395
2400
}
2405
2410
2420
2425
}
con)
2440
2445
2450
}
throws
DBAccessException {
logInfo("accessing the database for retrieving the oid of an entry in databasket "+dbe.getBasket().getName(),false);
PreparedStatement st = con.prepareStatement("SELECT oid FROM Transactions WHERE Name = ? AND OrderIndex = ?");
st.setString(1,dbe.getBasket().getName());
st.setInt(2,dbe.getOrderIndex());
logSQL(st);
ResultSet rs = st.executeQuery();
if (rs.next()) sOID = rs.getString(1);
st.close();
} catch (SQLException e) {
logError(e.getMessage());
throw new DBAccessException("getOID(DBEntry)","",e);
} catch (RemoteException rmie) {
Shop.getTheShop().logException(rmie);
rmie.printStackTrace();
}
return sOID;
}
public Value getValue(CatalogItem ci, Connection
synchronized (getCatalogMonitor()) {
Value v = null;
try {
2435
2455
}
}
logSQL(st);
ResultSet rs = st.executeQuery();
if (rs.next()) sOID = rs.getString(1);
st.close();
} catch (SQLException e) {
logError(e.getMessage());
throw new DBAccessException("getOID(CatalogItem)","",e);
} catch (RemoteException rmie) {
Shop.getTheShop().logException(rmie);
rmie.printStackTrace();
}
return sOID;
}
public String getOID(DBEntry dbe, Connection
synchronized (getBasketMonitor()) {
String sOID = null;
try {
2415
2430
299
con)
throws
DBAccessException {
logInfo("accessing the database for retrieving the value of catalogitem "+ci.getKey()+" in catalog "+ci.getCatalog().getName(),
false);
PreparedStatement st = con.prepareStatement("SELECT IsCatalog,Value FROM CatItems WHERE CatName = ? AND CIKeyName = ?");
st.setString(1,ci.getCatalog().getName());
st.setString(2,ci.getKey());
logSQL(st);
ResultSet rs = st.executeQuery();
if (rs.next()) {
if (rs.getBoolean(1)) {
v = getCatalogByOID(rs.getString(2),con);
} else {
v = (Value)Shop.getPersistenceSerializer().fromString(rs.getString(2));
}
}
st.close();
} catch (SQLException e) {
logError(e.getMessage());
throw new DBAccessException("getOID(DBEntry)","",e);
} catch (RemoteException rmie) {
Shop.getTheShop().logException(rmie);
rmie.printStackTrace();
}
return v;
}
public void
deleteDBEntry(DBEntry dbe, Connection con)
throws
DBAccessException {
300
ANHANG C. QUELLTEXTE
synchronized
try {
2460
2465
2470
2475
//
//
//
//
if
2480
2485
2490
}
2495
2500
(getBasketMonitor()) {
logInfo("Trying to delete a DBEntry:"+
"\n\tBasket: "+dbe.getBasket().getName()+
"\n\tIndex:
"+dbe.getOrderIndex()+
"\n\tType:
"+dbe.getTransactionType()+
"\n\tItem:
"+dbe.getTransactionItem(),false);
logInfo("accessing the database for deleting a DBEntry in catalog "+dbe.getBasket().getName(),false);
PreparedStatement st = con.prepareStatement("DELETE FROM Transactions WHERE Name = ? AND OrderIndex = ?");
String sName = dbe.getBasket().getName();
st.setString(1,sName);
st.setInt(2,dbe.getOrderIndex());
logSQL(st);
st.execute();
dbe.setPersistent(null);
}
}
The transactionitem may already be null, as it could be removed
in the transaction and now the apprpriate DBEntry is removed
in this case the appropriate entry in TransactionItems is already
removed by the delete-method for the transactionitem
(dbe.getTransactionItem() != null) {
st = con.prepareStatement("UPDATE TransactionItems SET TransactionHandle = ? WHERE Identifier = ?");
String sHandle;
try {
sHandle = dbe.getTransactionItem().getBasket().getName();
} catch (Exception e) {
sHandle = null;
}
st.setString(1,sHandle);
st.setString(2,dbe.getTransactionItem().getTransactionItemIdentifier());
logSQL(st);
st.execute();
catch
(SQLException e) {
logError(e.getMessage());
throw new DBAccessException("deleteDBEntry","",e);
} catch (RemoteException rmie) {
Shop.getTheShop().logException(rmie);
rmie.printStackTrace();
}
}
public
CatalogItem getCatalogItemByTransactionID(String sTransItemID, boolean bAdded, boolean bRemoved, Connection con) throws
DBAccessException {
(getCatalogMonitor()) {
logInfo("accessing the database for retrieving the catalogitem with transaction-id "+sTransItemID,false);
TransactionItem ti = refCache.lookupTItemCache(sTransItemID, bAdded, bRemoved);
if ((ti != null) && (ti instanceof CatalogItem)) return (CatalogItem)ti;
try {
PreparedStatement st = con.prepareStatement("SELECT oid FROM CatItems WHERE TransactionID = ? AND Added = ? AND Removed = ?");
st.setString(1,sTransItemID);
st.setBoolean(2,bAdded);
st.setBoolean(3,bRemoved);
logSQL(st);
ResultSet rs = st.executeQuery();
if (rs.next()) {
st.close();
return getCatalogItemByOID(rs.getString(1),con);
}
st.close();
} catch (SQLException e) {
logError(e.getMessage());
throw new DBAccessException("getCatalogItemByTransactionID","return the catitem for a given oid",e);
} catch (Exception e2) {
Shop.getTheShop().logException(e2);
e2.printStackTrace();
}
return null;
}
synchronized
2505
2510
2515
2520
2525
}
2530
2535
2540
public void updateDBEntry(DBEntry dbe, List
synchronized (getBasketMonitor()) {
try {
lPropertyKeys, Connection con)
throws
DBAccessException {
logInfo("accessing the database for updating the DBEntry with oid "+dbe.getOID()+" ("+lPropertyKeys+")",false);
PreparedStatement st = con.prepareStatement("UPDATE TransactionItems SET TransactionHandle = ? WHERE Identifier = ?");
st.setString(1,dbe.getBasket().getName());
st.setString(2,dbe.getTransactionItem().getTransactionItemIdentifier());
logSQL(st);
st.execute();
String sSerialized = "";
if (lPropertyKeys.contains(dbe.PERSISTENT_PROPERTY_SERIALIZED)) sSerialized = ", Serialized = ?";
String sProperties = "";
if (lPropertyKeys.contains(dbe.PERSISTENT_PROPERTY_PROPERTIES)) sProperties = ", InfoVector = ?";
int iPosCnt = 1;
st = con.prepareStatement("UPDATE Transactions SET Name = ?, Identifier = ?, OrderIndex = ?, TransType = ?"+sSerialized+sProperties+
" WHERE oid = ?");
C.2. PACKAGE EPOINT.DB
2545
2550
2555
2560
2565
}
2570
2575
2580
2585
2590
2595
2600
2605
2610
2615
2620
2625
2630
301
st.setString(iPosCnt,dbe.getBasket().getName()); iPosCnt++;
st.setString(iPosCnt,dbe.getTransactionItem().getTransactionItemIdentifier()); iPosCnt++;
st.setInt(iPosCnt,dbe.getOrderIndex()); iPosCnt++;
st.setString(iPosCnt,dbe.getTransactionType()); iPosCnt++;
if (lPropertyKeys.contains(dbe.PERSISTENT_PROPERTY_SERIALIZED)) {
st.setString(iPosCnt,dbe.getSerializedForm());
iPosCnt++;
}
if (lPropertyKeys.contains(dbe.PERSISTENT_PROPERTY_PROPERTIES)) {
st.setString(iPosCnt,Shop.getPersistenceSerializer().toString(dbe.getProperties()));
iPosCnt++;
}
st.setString(iPosCnt,dbe.getOID()); iPosCnt++;
logSQL(st);
st.execute();
} catch (SQLException e) {
logError(e.getMessage());
throw new DBAccessException("updateDBEntry()","",e);
} catch (RemoteException re) {
Shop.getTheShop().logException(re);
re.printStackTrace();
}
}
public void updateCatalogItem(CatalogItem
synchronized (getCatalogMonitor()) {
try {
ci, List lPropertyKeys, Connection con)
throws
DBAccessException {
logInfo("accessing the database for updating the catalogitem "+ci.getKey()+" in catalog "+ci.getCatalog().getName()+" ("+
lPropertyKeys+")",false);
PreparedStatement st;
if (lPropertyKeys.contains(ci.PERSISTENT_PROPERTY_TRANSACTION_IDENTIFIER)) {
st = con.prepareStatement("UPDATE TransactionItems SET TransactionHandle = ? WHERE Identifier = ?");
st.setString(1,(ci.getBasket() == null)?(null):(ci.getBasket().getName()));
st.setString(2,ci.getTransactionItemIdentifier());
logSQL(st);
st.execute();
}
int iPosCnt = 1;
String sSerialized = "";
if (lPropertyKeys.contains(ci.PERSISTENT_PROPERTY_SERIALIZED)) sSerialized = ", Serialized = ?";
String sValue = "";
if (lPropertyKeys.contains(ci.PERSISTENT_PROPERTY_CIVALUE)) sValue = ", Value = ?";
String sTransState = "";
if (lPropertyKeys.contains(ci.PERSISTENT_PROPERTY_TRANSACTION_STATE)) sTransState = ", Added = ?, Removed = ?";
st = con.prepareStatement("UPDATE CatItems SET CatName = ?, CIKeyName = ?, IsCatalog = ?, TransactionID = ?"+sSerialized+sValue+
sTransState+" WHERE oid = ?");
st.setString(iPosCnt,ci.getCatalog().getName()); iPosCnt++;
st.setString(iPosCnt,ci.getKey()); iPosCnt++;
st.setBoolean(iPosCnt,ci.getValue().isCatalog()); iPosCnt++;
st.setString(iPosCnt,ci.getTransactionItemIdentifier()); iPosCnt++;
if (lPropertyKeys.contains(ci.PERSISTENT_PROPERTY_SERIALIZED)) {
st.setString(iPosCnt,ci.getSerializedForm()); iPosCnt++;
}
if (lPropertyKeys.contains(ci.PERSISTENT_PROPERTY_CIVALUE)) {
st.setString(iPosCnt,(ci.getValue().isCatalog())?(((Catalog)ci.getValue()).getOID()):(ci.getValue().getSerializedForm()));
iPosCnt++;
}
if (lPropertyKeys.contains(ci.PERSISTENT_PROPERTY_TRANSACTION_STATE)) {
boolean bAdded = false;
boolean bRemoved = false;
if (ci.getBasket() != null) {
DataBasket db = ci.getBasket();
RemoteIterator it = db.iterator(ci);
while (it.hasNext()) {
DBEntry dbe = (DBEntry)it.next();
if (dbe.getTarget() instanceof Catalog) {
Catalog c = (Catalog)dbe.getTarget();
if (c.getName().equals(ci.getCatalog().getName())) {
if (!dbe.getTransactionType().equals(dbe.TRANSACTION_MODIFY)) {
bAdded = dbe.getTransactionType().equals(dbe.TRANSACTION_ADD);
bRemoved = dbe.getTransactionType().equals(dbe.TRANSACTION_REMOVE);
} else {
bAdded = true;
bRemoved = true;
}
break;
}
}
}
}
st.setBoolean(iPosCnt,bAdded); iPosCnt++;
st.setBoolean(iPosCnt,bRemoved); iPosCnt++;
CatalogItem ciTempNormal = (CatalogItem)refCache.lookupTItemCache(ci.getTransactionItemIdentifier(),false,false);
CatalogItem ciTempAdded = (CatalogItem)refCache.lookupTItemCache(ci.getTransactionItemIdentifier(),true,false);
CatalogItem ciTempRemoved = (CatalogItem)refCache.lookupTItemCache(ci.getTransactionItemIdentifier(),false,true);
CatalogItem ciTempEdited = (CatalogItem)refCache.lookupTItemCache(ci.getTransactionItemIdentifier(),true,true);
if (ciTempNormal != null)
302
ANHANG C. QUELLTEXTE
if (ci.getOID().equals(ciTempNormal.getOID())) refCache.removeFromTItemCache(ciTempNormal,false,false);
(ciTempAdded != null)
if (ci.getOID().equals(ciTempAdded.getOID())) refCache.removeFromTItemCache(ciTempAdded,true,false);
if (ciTempRemoved != null)
if (ci.getOID().equals(ciTempRemoved.getOID())) refCache.removeFromTItemCache(ciTempRemoved,false,true);
if (ciTempEdited != null)
if (ci.getOID().equals(ciTempEdited.getOID())) refCache.removeFromTItemCache(ciTempEdited,true,true);
refCache.addToTItemCache(ci,bAdded,bRemoved);
if
2635
2640
2645
2650
2655
}
public void updateDataBasket(DataBasket db,
synchronized (getBasketMonitor()) {
try {
2660
2665
2670
2675
2680
}
2690
2695
2705
2710
2715
}
throws
List lPropertyKeys, Connection con)
DBAccessException {
logInfo("accessing the database for updating the databasket "+db.getName()+" ("+lPropertyKeys+")",false);
PreparedStatement st;
if (lPropertyKeys.contains(db.PERSISTENT_PROPERTY_SERIALIZED)) {
st = con.prepareStatement("UPDATE TransactionHandles SET Name = ?, Serialized = ? WHERE oid = ?");
st.setString(1,db.getName());
st.setString(2,db.getSerializedForm());
st.setString(3,db.getOID());
} else {
st = con.prepareStatement("UPDATE TransactionHandles SET Name = ? WHERE oid = ?");
st.setString(1,db.getName());
st.setString(2,db.getOID());
}
logSQL(st);
st.execute();
refCache.addToBasketCache(db);
} catch (SQLException e) {
logError(e.getMessage());
throw new DBAccessException("updateDataBasket()","",e);
} catch (RemoteException re) {
Shop.getTheShop().logException(re);
re.printStackTrace();
}
}
public DataBasket getTransactionItemBasket(String
synchronized (getBasketMonitor()) {
try {
2685
2700
}
st.setString(iPosCnt,ci.getOID()); iPosCnt++;
logSQL(st);
st.execute();
} catch (SQLException e) {
logError(e.getMessage());
throw new DBAccessException("updateCatalogItem()","",e);
} catch (RemoteException re) {
Shop.getTheShop().logException(re);
re.printStackTrace();
}
}
sTransItemID, Connection con)
throws
DBAccessException {
logInfo("accessing the database for retrieving the basket of transactionitem "+sTransItemID,false);
PreparedStatement st = con.prepareStatement("SELECT TransactionHandle FROM TransactionItems WHERE Identifier = ?");
st.setString(1,sTransItemID);
logSQL(st);
ResultSet rs = st.executeQuery();
if (rs.next()) {
st.close();
return getDataBasket(rs.getString(1),con);
}
st.close();
} catch (SQLException e) {
logError(e.getMessage());
throw new DBAccessException("getTransactionItemBasket()","",e);
}
return null;
}
////////////////////////////////////////////////////////////////////////////////
//
PersistentObject
////////////////////////////////////////////////////////////////////////////////
public String persistifyObject(PersistentUserObject
synchronized (getPropertyMonitor()) {
puo, Connection con)
throws
DBAccessException {
logInfo("accessing the database for storing a new user-defined object",false);
try {
PreparedStatement st = con.prepareStatement("INSERT INTO PersistentObjects(IdentKey,Serialized) VALUES(?)");
String sIdentKey = Long.toString(Shop.getTheShop().getGlobalUniqueNumber());
st.setString(1,sIdentKey);
st.setString(2,puo.getSerializedForm());
logSQL(st);
st.execute();
st = con.prepareStatement("SELECT oid FROM PersistentObjects WHERE IdentKey = ?");
st.setString(1,sIdentKey);
ResultSet rs = st.executeQuery();
C.2. PACKAGE EPOINT.DB
2720
2725
2730
2735
2740
}
rs.next();
puo.setPersistent(rs.getString(1));
refCache.addToPersistentUserObjectCache(puo);
st.close();
return puo.getOID();
} catch (SQLException e) {
logError(e.getMessage());
e.printStackTrace();
return null;
} catch (RemoteException rmie) {
Shop.getTheShop().logException(rmie);
rmie.printStackTrace();
return null;
}
}
public void updatePersistentObject(PersistentUserObject
synchronized (getPropertyMonitor()) {
try {
if (!puo.isPersistent()) return;
2745
2750
2755
}
2760
2765
2770
2775
2780
2785
2790
2795
}
2805
puo, Connection con)
throws
DBAccessException {
logInfo("accessing the database for updating a user-defined object "+puo.getOID(),false);
PreparedStatement st = con.prepareStatement("UPDATE PersistentObjects SET Serialized = ? WHERE oid = ?");
st.setString(1,puo.getSerializedForm());
st.setString(2,puo.getOID());
logSQL(st);
st.execute();
refCache.addToPersistentUserObjectCache(puo);
} catch (SQLException e) {
logError(e.getMessage());
e.printStackTrace();
} catch (RemoteException rmie) {
Shop.getTheShop().logException(rmie);
rmie.printStackTrace();
}
}
public PersistentObject getPersistentObject(String
synchronized (getPropertyMonitor()) {
2800
303
sOID, Connection con)
throws
DBAccessException {
logInfo("accessing the database for retrieving the object with oid "+sOID,false);
// first lets check all the framework-objects
Object o = getCatalogByOID(sOID,con);
if (o != null) return (PersistentObject)o;
o = getCatalogItemByOID(sOID,con);
if (o != null) return (PersistentObject)o;
o = getDataBasketByOID(sOID,con);
if (o != null) return (PersistentObject)o;
o = getStockByOID(sOID,con);
if (o != null) return (PersistentObject)o;
o = getStockItemByOID(sOID,con);
if (o != null) return (PersistentObject)o;
// now we can try to retrieve the object as an explicit saved PersistentObject
try {
PersistentUserObject puo = refCache.lookupPersistentUserObjectCache(sOID);
if (puo != null) return puo;
PreparedStatement st = con.prepareStatement("SELECT Serialized FROM PersistentObjects WHERE oid = ?");
st.setString(1,sOID);
logSQL(st);
ResultSet rs = st.executeQuery();
if (rs.next()) {
String sSerialized = rs.getString(1);
st.close();
puo = (PersistentUserObject)Shop.getTheShop().getPersistenceSerializer().fromString(sSerialized);
refCache.addToPersistentUserObjectCache(puo);
puo.loadPersistentData(null,sConPasswd); return puo;
}
st.close();
} catch (SQLException e) {
logError(e.getMessage());
e.printStackTrace();
return null;
} catch (RemoteException rmi) {
logError(rmi.getMessage());
rmi.printStackTrace();
}
return null;
}
public void deletePersistentObject(PersistentUserObject
synchronized (getPropertyMonitor()) {
try {
if (!puo.isPersistent()) return;
puo, Connection con)
throws
DBAccessException {
logInfo("accessing the database for deleting the object with oid "+puo.getOID(),false);
PreparedStatement st = con.prepareStatement("DELETE FROM PersistentObjects WHERE oid = ?");
st.setString(1,puo.getOID());
logSQL(st);
304
2810
2815
}
2820
2825
synchronized public void
Connection con = null;
try {
2835
2840
2845
2850
2855
}
2870
}
2880
2885
2890
registerEPoint(epoint.sale.EPointInfo epi)
throws
DBAccessException {
String sEPointName = epi.getEPointName();
String sHost = epi.getHostName();
String sDescr = epi.getDescription();
String sPasswd = getClass()+".registerEPoint";
con = getPrivateConnection(sPasswd,getClass());
logInfo("accessing the database for registering a new epoint "+sEPointName,true);
List l = getCatalogNames(con);
returnPrivateConnection(con);
String sInfoStore = Shop.SYSTEM_PROPERTY_PREFIX+"InfoStore::"+sEPointName+"::"+Shop.getTheShop().getGlobalUniqueNumber();
while (l.contains(sInfoStore)) sInfoStore = Shop.SYSTEM_PROPERTY_PREFIX+"InfoStore::"+sEPointName+"::"+Shop.getTheShop().
getGlobalUniqueNumber();
Catalog c = new epoint.data.rmi.CatalogImpl(sInfoStore);
int iGCount = epi.getGateCount();
con = getPrivateConnection(sPasswd,getClass());
PreparedStatement st = con.prepareStatement("INSERT INTO EPoints(Name,Location,Description,GateCount,InfoCatalog) VALUES(?,?,?,?,?)"
);
st.setString(1,sEPointName);
st.setString(2,sHost);
st.setString(3,sDescr);
st.setInt(4,iGCount);
st.setString(5,sInfoStore);
logSQL(st);
st.execute();
} catch (SQLException e) {
logError(e.getMessage());
e.printStackTrace();
} catch (Exception e2) {
Shop.getTheShop().logException(e2);
e2.printStackTrace();
} nally {
returnPrivateConnection(con);
}
synchronized public void
Connection con = null;
try {
2865
2875
st.execute();
refCache.removeFromPersistentUserObjectCache(puo.getOID());
puo.setPersistent(null);
} catch (SQLException e) {
logError(e.getMessage());
e.printStackTrace();
} catch (RemoteException rmie) {
Shop.getTheShop().logException(rmie);
rmie.printStackTrace();
}
}
////////////////////////////////////////////////////////////////////////////////
//
EPoint
////////////////////////////////////////////////////////////////////////////////
2830
2860
ANHANG C. QUELLTEXTE
unregisterEPoint(epoint.sale.EPointInfo epi)
throws
DBAccessException {
logInfo("accessing the database for deleting a registered epoint "+epi.getEPointName(),true);
Catalog cInfoStore = getInfoStoreCatalog(epi);
con = getPrivateConnection(getClass()+".unregisterEPoint",getClass());
deleteCatalog(cInfoStore,con);
} catch (Exception e2) {
Shop.getTheShop().logException(e2);
e2.printStackTrace();
} nally {
returnPrivateConnection(con);
}
public
Catalog getInfoStoreCatalog(epoint.sale.EPointInfo epi) throws DBAccessException {
Connection con = null;
synchronized (getCatalogMonitor()) {
try {
logInfo("accessing the database for retrieving the info-store of epoint "+epi.getEPointName(),false);
String sName = epi.getEPointName();
String sHost = epi.getHostName();
con = getPrivateConnection(getClass()+".isRegisteredEPoint",getClass());
PreparedStatement st = con.prepareStatement("SELECT InfoCatalog FROM EPoints WHERE Name = ? AND Location = ?");
st.setString(1,sName);
st.setString(2,sHost);
logSQL(st);
ResultSet rs = st.executeQuery();
if (!rs.next()) {
st.close();
return null;
}
String sInfoStore = rs.getString(1);
st.close();
C.2. PACKAGE EPOINT.DB
}
2895
2900
2905
2910
}
2920
2925
2930
2940
2945
2950
2955
2960
2965
2970
2975
2980
return getCatalog(sInfoStore,con);
catch (SQLException e) {
logError(e.getMessage());
e.printStackTrace();
return null;
} catch (RemoteException re) {
Shop.getTheShop().logException(re);
re.printStackTrace();
return null;
} nally {
returnPrivateConnection(con);
}
synchronized public boolean
Connection con = null;
try {
2915
2935
}
}
isRegisteredEPoint(epoint.sale.EPointInfo epi)
throws
DBAccessException {
logInfo("accessing the database for checking if epoint "+epi.getEPointName()+" is registered",false);
String sName = epi.getEPointName();
String sHost = epi.getHostName();
con = getPrivateConnection(getClass()+".isRegisteredEPoint",getClass());
PreparedStatement st = con.prepareStatement("SELECT * FROM EPoints WHERE Name = ? AND Location = ?");
st.setString(1,sName);
st.setString(2,sHost);
logSQL(st);
ResultSet rs = st.executeQuery();
boolean bRetVal = rs.next();
st.close();
return bRetVal;
} catch (SQLException e) {
logError(e.getMessage());
e.printStackTrace();
return false;
} catch (RemoteException re) {
Shop.getTheShop().logException(re);
re.printStackTrace();
return false;
} nally {
returnPrivateConnection(con);
}
public void flushCache() {
synchronized (getPropertyMonitor())
}
305
{
logInfo("flushing the reference-cache of all persistent objects!",true);
refCache.flushCache();
}
/**
* Returns the monitor guarding database-access on Stock-data. This icludes
* the Catalog-monitor.
*
* @return a monitoring object
*/
synchronized protected Object getStockMonitor() {
if (m_oCatalogMonitor != null) {
// if somebody uses the catalog-monitor, we will wait, until
// it is released and set it to null -- that symbolizes, that
// the getCatalogMonitor has to wait for the release of
// m_oStockMonitorGetter before returning its monitor
synchronized (m_oCatalogMonitor) {
m_oCatalogMonitor = null;
}
}
if (m_oStockMonitorGetter == null) m_oStockMonitorGetter = new Object();
return m_oStockMonitorGetter;
}
/**
* Returns the monitor guarding database-access on Catalog-data.
*
* @return a monitoring object
*/
synchronized protected Object getCatalogMonitor() {
if (m_oCatalogMonitor == null) {
// that means, the StockMonitor owns this monitor too -- lets wait
// for it!
synchronized (m_oStockMonitorGetter) {
m_oCatalogMonitor = new Object();
}
}
return m_oCatalogMonitor;
}
/**
* Returns the monitor guarding database-access on properties-data.
306
2985
2990
2995
3000
3005
3010
3015
3020
3025
3030
3035
3040
3045
3050
3055
3060
3065
ANHANG C. QUELLTEXTE
*
* @return a monitoring object
*/
synchronized protected Object getPropertyMonitor() {
return m_oPropertyMonitor;
}
/**
* Returns the monitor guarding database-access on DataBasket-data.
*
* @return a monitoring object
*/
synchronized protected Object getBasketMonitor() {
return m_oBasketMonitor;
}
/**
* Returns the monitor guarding database-access on the LogEntries table.
*
* @return a monitoring object
*/
synchronized protected Object getLogMonitor() {
try {
if (conLog == null) conLog = getConnection(true);
if (conLog.isClosed()) conLog = getConnection(true);
} catch (Exception e) {
throw new RuntimeException(e.getClass()+" occured: "+e.getMessage());
}
return conLog;
}
/**
* Makes the given LogEntry persistent in the DataBase, using the signature of the
* given EPoint and an additional ID.
*
* @param epi the EPointInfo of the EPoint for which this LogEntry shall be made persistent
* @param iID an ID for the LogEntry (i.e. Gate number)
* @param le the LogEntry to persistify
* @throws DBAccessException whenever an error occurs while operation on the database
*/
public void logPersistent(epoint.sale.EPointInfo epi, int iID, epoint.log.LogEntry le) throws DBAccessException {
try {
synchronized (getLogMonitor()) {
PreparedStatement st = null;
st = conLog.prepareStatement("INSERT INTO LogEntries(Name, Location, GateID, Time, LogData) VALUES (?,?,?,?,?)");
st.setString(1,epi.getEPointName());
st.setString(2,java.net.InetAddress.getByName(epi.getHostName()).getHostAddress());
st.setInt(3,iID);
st.setTimestamp(4,new java.sql.Timestamp(le.getTime().getTime()));
st.setString(5,Shop.getTheShop().getPersistenceSerializer().toString(le));
st.execute();
}
} catch (SQLException sqle) {
Shop.getTheShop().logException(sqle);
throw new RuntimeException(sqle.getClass()+" occured: "+sqle.getMessage());
} catch (RemoteException rmie) {
Shop.getTheShop().logException(rmie);
throw new RuntimeException(rmie.getClass()+" occured: "+rmie.getMessage());
} catch (java.net.UnknownHostException uhe) {
Shop.getTheShop().logException(uhe);
throw new RuntimeException(uhe.getClass()+" occured: "+uhe.getMessage());
}
}
/**
* Returns all persistent LogEntries, that match the Filter condition.
*
* @param lf a LogFilter the returned LogEntries must match
* @param epi the EPointInfo for which to return the Logs
* @return a List that contains a LogEntries, each followed by its ID as an <code>Integer</code>
* @throws DBAccessException whenever an error occurs while operation on the database
*/
public List getLogs(epoint.sale.EPointInfo epi, epoint.log.LogFilter lf) throws DBAccessException {
try {
List lRetVal = Collections.synchronizedList(new LinkedList());
synchronized (getLogMonitor()) {
PreparedStatement st = null;
st = conLog.prepareStatement("SELECT GateID, LogEntry FROM LogEntries WHERE Name = ? AND Location = ?");
st.setString(1,epi.getEPointName());
st.setString(2,java.net.InetAddress.getByName(epi.getHostName()).getHostAddress());
ResultSet rs = st.executeQuery();
while (rs.next()) {
Integer iGate = new Integer(rs.getInt(1));
String sEntry = rs.getString(2);
epoint.log.LogEntry le = (epoint.log.LogEntry)Shop.getTheShop().getPersistenceSerializer().fromString(sEntry);
if (lf != null) if (!lf.isLogged(le)) continue;
lRetVal.add(le);
lRetVal.add(iGate);
C.2. PACKAGE EPOINT.DB
3070
}
}
3075
3080
}
}
return lRetVal;
catch (SQLException
sqle) {
Shop.getTheShop().logException(sqle);
throw new RuntimeException(sqle.getClass()+" occured: "+sqle.getMessage());
} catch (RemoteException rmie) {
Shop.getTheShop().logException(rmie);
throw new RuntimeException(rmie.getClass()+" occured: "+rmie.getMessage());
} catch (java.net.UnknownHostException uhe) {
Shop.getTheShop().logException(uhe);
throw new RuntimeException(uhe.getClass()+" occured: "+uhe.getMessage());
}
}
Listing C.54: epoint.db.ReferenceCache
5
10
15
20
25
30
35
40
45
50
55
60
65
/*
* ReferenceCache.java
*
* Created on 7. Oktober 2001, 14:23
*/
package epoint.db;
import java.util.*;
import epoint.data.*;
import java.lang.ref.*;
import epoint.log.*;
import epoint.sale.Shop;
/**
* The ReferenceCache is used to increase read-access-speed to the persistent
* versions of objects. This is done while referencing a number of persistent
* objects by strong references, so that the objects still need to update
* their persistent versions, but when requested from their persistent state
* may be looked up in the cache.<br>
* Additional performance is achieved by referencing objects that are not longer
* referenced by strong references, are afterwards referenced by weak-references.
* This way there is still a chance to get them, even if they were not referenced
* for some time, but then requested from the DBPersistenceManager.
*
* @see DBPersistenceManager
* @see PersistentObject
* @author Danny Poppe
* @version 1.0
*/
public class ReferenceCache {
/**
* Maps Catalog-names to WeakReferences to the Catalogs.
*/
private Map mCatalogCache = Collections.synchronizedMap(new HashMap(100));
/**
* Maps DataBasket-names to WeakReferences to the DataBaskets.
*/
private Map mBasketCache = Collections.synchronizedMap(new HashMap(100));
/**
* Maps identifiers of TransactionItems to WeakReferences of the TransactionItems.
*/
private Map mTItemCache = Collections.synchronizedMap(new HashMap(100));
/**
* Maps names of Stocks to WeakReferences to the Stocks.
*/
private Map mStockCache = Collections.synchronizedMap(new HashMap(100));
/**
* Maps oids of persistent objects to WeakReferences to the persistent objects.
*/
private Map mObjectCache = Collections.synchronizedMap(new HashMap(100));
/**
* Additionally to the WeakReferences in the HashMaps this List contains
* some strong references to prevent the Weak references from being cleared.
*/
private LinkedList llStrongRef = new LinkedList();
/**
* Stores the number of maximum strong references hold to objects in this
* cache. This is the maximum number of objects guaranteed to be in the cache.
*/
public static int MAXSTRONG = 500;
/**
* This log is used to log access to this cache.
*/
private Log lActionLog;
/**
* Creates new ReferenceCache
307
308
*/
70
75
80
85
90
95
100
105
110
115
120
125
130
135
140
145
150
protected
}
ANHANG C. QUELLTEXTE
ReferenceCache() {
/**
* Creates a new ReferenceCache, that logs actions to the given log.
*
* @param lActionLog the Log that shall be used to log actions
*/
public ReferenceCache(Log lActionLog) {
this.lActionLog = lActionLog;
}
/**
* Creates a new strong reference to the given object.
*
* @param o the object that shall be strongly referenced
*/
synchronized protected void createStrongRef(Object o) {
llStrongRef.addFirst(o);
if (llStrongRef.size() > MAXSTRONG) llStrongRef.removeLast();
}
/**
* Looks up this cache for a Catalog of the given name and returns
* the Catalog if available or <code>null</code> if not.
*
* @param sName the name of the Catalog that shall be looked up in the cache
* @return the requested Catalog referenced in this cache or <code>null</code>
*/
public Catalog lookupCatalogCache(String sName) {
if (sName == null) return null;
lActionLog.write(new LogEntry("ReferenceCache","Looking for Catalog "+sName+" in Cache", LogEntry.LOG_TYPE_INFO, LogEntry.
LOG_LEVEL_DEBUG));
WeakReference wr = (WeakReference)mCatalogCache.get(sName);
if (wr != null) {
Catalog c = (Catalog)wr.get();
if (c!=null) {
lActionLog.write(new LogEntry("ReferenceCache","Found Catalog "+sName+" in Cache", LogEntry.LOG_TYPE_INFO, LogEntry.
LOG_LEVEL_DEBUG));
return c;
}
}
return null;
}
/**
* Adds a new Catalog to this cache.
*
* @param c the Catalog that shall be added to this cache
*/
public void addToCatalogCache(Catalog c) {
if (c == null) return;
try {
lActionLog.write(new LogEntry("ReferenceCache","Adding Catalog "+c.getName()+" to Cache", LogEntry.LOG_TYPE_INFO, LogEntry.
LOG_LEVEL_DEBUG));
mCatalogCache.put(new String(c.getName()),new WeakReference(c));
createStrongRef(c);
} catch (Exception e) {
Shop.getTheShop().logException(e);
}
}
/**
* Removes the given Catalog from this cache.
*
* @param c the Catalog that shall be no longer available in this cache
*/
public void removeFromCatalogCache(Catalog c) {
if (c == null) return;
try {
lActionLog.write(new LogEntry("ReferenceCache","Removing Catalog "+c.getName()+" from Cache", LogEntry.LOG_TYPE_INFO, LogEntry.
LOG_LEVEL_DEBUG));
mCatalogCache.remove(c.getName());
llStrongRef.remove(c);
} catch (Exception e) {
Shop.getTheShop().logException(e);
}
}
/**
* Completely deletes the CatalogCache.
*/
public void flushCatalogCache() {
lActionLog.write(new LogEntry("ReferenceCache","Starting new Thread to flush Catalog-Cache", LogEntry.LOG_TYPE_INFO, LogEntry.
LOG_LEVEL_DEBUG));
mCatalogCache = Collections.synchronizedMap(new HashMap(100));
Thread t = new Thread("Flushing CatalogCache Thread") {
C.2. PACKAGE EPOINT.DB
309
public void
run() {
System.runFinalization();
System.gc();
155
}
160
165
170
175
180
185
190
195
200
205
210
215
220
225
230
}
};
t.run();
/**
* Looks up the Stock cache for a Stock of the given name.
*
* @param the name of a Stock
* @return the Stock of the given name if found in this Stock,
*
otherwise <code>null</code>
*/
public Stock lookupStockCache(String sName) {
if (sName == null) return null;
lActionLog.write(new LogEntry("ReferenceCache","Looking for Stock "+sName+" in Cache", LogEntry.LOG_TYPE_INFO, LogEntry.
LOG_LEVEL_DEBUG));
WeakReference wr = (WeakReference)mStockCache.get(sName);
if (wr != null) {
Stock s = (Stock)wr.get();
if (s!=null) {
lActionLog.write(new LogEntry("ReferenceCache","Found Stock "+sName+" in Cache", LogEntry.LOG_TYPE_INFO, LogEntry.
LOG_LEVEL_DEBUG));
return s;
}
}
return null;
}
/**
* Adds the given Stock to this cache.
*
* @param s the Stock that shall be added to this cache
*/
public void addToStockCache(Stock s) {
if (s == null) return;
try {
lActionLog.write(new LogEntry("ReferenceCache","Adding Stock "+s.getName()+" to Cache", LogEntry.LOG_TYPE_INFO, LogEntry.
LOG_LEVEL_DEBUG));
mStockCache.put(new String(s.getName()),new WeakReference(s));
createStrongRef(s);
} catch (Exception e) {
Shop.getTheShop().logException(e);
}
}
/**
* Removes the given Stock from this cache.
*
* @param s the Stock that shall be removed from this cache
*/
public void removeFromStockCache(Stock s) {
if (s == null) return;
try {
lActionLog.write(new LogEntry("ReferenceCache","Removing Stock "+s.getName()+" from Cache", LogEntry.LOG_TYPE_INFO, LogEntry.
LOG_LEVEL_DEBUG));
mCatalogCache.remove(s.getName());
} catch (Exception e) {
Shop.getTheShop().logException(e);
}
}
/**
* Completely deletes all Stocks from this cache.
*/
public void flushStockCache() {
lActionLog.write(new LogEntry("ReferenceCache","Starting new Thread to flush Stock-Cache", LogEntry.LOG_TYPE_INFO, LogEntry.
LOG_LEVEL_DEBUG));
mStockCache = Collections.synchronizedMap(new HashMap(100));
Thread t = new Thread("Flushing StockCache Thread") {
public void run() {
System.runFinalization();
System.gc();
}
};
t.run();
}
/**
* Looks up this cache for a DataBasket of the given name.
*
* @param sName the name of the DataBasket that shall be retrieved from this cache
* @return the DataBasket in this cache if found, otherwise <code>null</code>
*/
public DataBasket lookupBasketCache(String sName) {
if (sName == null) return null;
310
235
240
245
250
255
260
265
270
275
280
285
290
295
300
305
310
315
}
ANHANG C. QUELLTEXTE
lActionLog.write(new LogEntry("ReferenceCache","Looking for Basket "+sName+" in Cache", LogEntry.LOG_TYPE_INFO, LogEntry.
LOG_LEVEL_DEBUG));
WeakReference wr = (WeakReference)mBasketCache.get(sName);
if (wr != null) {
DataBasket d = (DataBasket)wr.get();
if (d!=null) return d;
}
return null;
/**
* Adds the given DataBasket to this cache.
*
* @param db the DataBasket that shall be added to this cache.
*/
public void addToBasketCache(DataBasket db) {
try {
if (db == null) return;
lActionLog.write(new LogEntry("ReferenceCache","Adding Basket "+db.getName()+" to Cache", LogEntry.LOG_TYPE_INFO, LogEntry.
LOG_LEVEL_DEBUG));
mBasketCache.put(new String(db.getName()),new WeakReference(db));
createStrongRef(db);
} catch (Exception e) {
Shop.getTheShop().logException(e);
}
}
/**
* Removes the given DataBasket from this cache.
*
* @param db the DataBasket that shall be removed from this cache.
*/
public void removeFromBasketCache(DataBasket db) {
if (db == null) return;
try {
lActionLog.write(new LogEntry("ReferenceCache","Removing Basket "+db.getName()+" from Cache", LogEntry.LOG_TYPE_INFO, LogEntry.
LOG_LEVEL_DEBUG));
mBasketCache.remove(db.getName());
llStrongRef.remove(db);
} catch (Exception e) {
Shop.getTheShop().logException(e);
}
}
/**
* Completely removes all DataBaskets from this cache.
*/
public void flushBasketCache() {
lActionLog.write(new LogEntry("ReferenceCache","Starting new Thread to flush Basket-Cache", LogEntry.LOG_TYPE_INFO, LogEntry.
LOG_LEVEL_DEBUG));
mBasketCache = Collections.synchronizedMap(new HashMap(100));
Thread t = new Thread("Flushing BasketCache Thread") {
public void run() {
System.runFinalization();
System.gc();
}
};
t.run();
}
/**
* Looks up this cache for a TransactionItem with the given
* TransactionID.
*
* @param sTransItemID the TransactionID of the TransactionItem to be returned
* @return the requested TransactionItem or <code>null</code> if not available
*/
public TransactionItem lookupTItemCache(String sTransItemID,boolean bAdded, boolean bRemoved) {
if (sTransItemID == null) return null;
lActionLog.write(new LogEntry("ReferenceCache","Looking for TransactionItem "+sTransItemID+" in Cache", LogEntry.LOG_TYPE_INFO,
LogEntry.LOG_LEVEL_DEBUG));
WeakReference wr = (WeakReference)mTItemCache.get(sTransItemID+bAdded+bRemoved);
if (wr != null) {
TransactionItem ti = (TransactionItem)wr.get();
if (ti!=null) return ti;
}
return null;
}
/**
* Adds the given TransactionItem to this cache.
*
* @param ti the TransactionItem that shall be added to this cache
*/
public void addToTItemCache(TransactionItem ti, boolean bAdded,
try {
if (ti == null) return;
boolean
bRemoved) {
C.2. PACKAGE EPOINT.DB
320
}
325
330
335
340
345
350
355
360
365
370
375
380
385
390
395
311
lActionLog.write(new LogEntry("ReferenceCache","Adding TransactionItem "+ti.getTransactionItemIdentifier()+" ("+ti.getClass()+")
to Cache", LogEntry.LOG_TYPE_INFO, LogEntry.LOG_LEVEL_DEBUG));
mTItemCache.put(new String(ti.getTransactionItemIdentifier()+bAdded+bRemoved),new WeakReference(ti));
createStrongRef(ti);
} catch (Exception e) {
Shop.getTheShop().logException(e);
}
/**
* Removes the given TransactionItem from this cache.
*
* @param ti the TransactionItem to be removed from this cache
*/
public void removeFromTItemCache(TransactionItem ti,boolean bAdded, boolean bRemoved) {
if (ti == null) return;
try {
lActionLog.write(new LogEntry("ReferenceCache","Removing TransactionItem "+ti.getTransactionItemIdentifier()+" from Cache",
LogEntry.LOG_TYPE_INFO, LogEntry.LOG_LEVEL_DEBUG));
mTItemCache.remove(ti.getTransactionItemIdentifier()+bAdded+bRemoved);
llStrongRef.remove(ti);
} catch (Exception e) {
Shop.getTheShop().logException(e);
}
}
/**
* Completely removes all TransactionItems from this Cache.
*/
public void flushTItemCache() {
lActionLog.write(new LogEntry("ReferenceCache","Starting new Thread to flush TransactionItem-Cache", LogEntry.LOG_TYPE_INFO, LogEntry.
LOG_LEVEL_DEBUG));
mTItemCache = Collections.synchronizedMap(new HashMap(100));
Thread t = new Thread("Flushing TransactionItemCache Thread") {
public void run() {
System.runFinalization();
System.gc();
}
};
t.run();
}
/**
* Adds a PersistentUserObject object to this cache.
*
* @param puo the object that shall be added to this cache
*/
public void addToPersistentUserObjectCache(PersistentUserObject puo) {
try {
if (puo == null) return;
lActionLog.write(new LogEntry("ReferenceCache","Adding PersistentUserObject "+puo.getOID()+" ("+puo.getClass()+") to Cache",
LogEntry.LOG_TYPE_INFO, LogEntry.LOG_LEVEL_DEBUG));
mObjectCache.put(new String(puo.getOID()),new WeakReference(puo));
createStrongRef(puo);
} catch (Exception e) {
Shop.getTheShop().logException(e);
}
}
/**
* Removes the PersistentUserObject with the given OID from this cache.
*
* @param sOID the OID of the PersistentUserObject to be removed from this cache
*/
public void removeFromPersistentUserObjectCache(String sOID) {
if (sOID == null) return;
try {
lActionLog.write(new LogEntry("ReferenceCache","Removing PersistentUserObjectCache "+sOID+" from Cache", LogEntry.LOG_TYPE_INFO,
LogEntry.LOG_LEVEL_DEBUG));
mTItemCache.remove(sOID);
} catch (Exception e) {
Shop.getTheShop().logException(e);
}
}
/**
* Looks up the PersistentUserObject with the given OID in this cache.
*
* @param sOID the OID of the PersistentUserObject to be returned
* @return the requested object or <code>null</code> if not available
*/
public PersistentUserObject lookupPersistentUserObjectCache(String sOID) {
if (sOID == null) return null;
lActionLog.write(new LogEntry("ReferenceCache","Looking for PersistentUserObject "+sOID+" in Cache", LogEntry.LOG_TYPE_INFO, LogEntry.
LOG_LEVEL_DEBUG));
WeakReference wr = (WeakReference)mObjectCache.get(sOID);
if (wr != null) {
PersistentUserObject puo = (PersistentUserObject)wr.get();
312
}
400
}
if
(puo!=null)
return
puo;
return null;
/**
* Deletes all PersistentUserObjects from this cache.
*/
public void flushPersistentUserObjectCache() {
lActionLog.write(new LogEntry("ReferenceCache","Starting new Thread to flush PersistentUserObject-Cache", LogEntry.LOG_TYPE_INFO,
LogEntry.LOG_LEVEL_DEBUG));
mObjectCache = Collections.synchronizedMap(new HashMap(100));
Thread t = new Thread("Flushing PersistentUserObjectCache Thread") {
public void run() {
System.runFinalization();
System.gc();
}
};
t.run();
}
405
410
415
/**
* Deletes all referenced objects from this cache.
*/
public void flushCache() {
llStrongRef.clear();
flushCatalogCache();
flushBasketCache();
flushTItemCache();
flushStockCache();
flushPersistentUserObjectCache();
}
420
425
430
ANHANG C. QUELLTEXTE
}
C.2.1 Package epoint.db.exception
epoint.db.exception.DBAccessException.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .312
epoint.db.exception.NoSuchDriverException.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .313
Listing C.55: epoint.db.exception.DBAccessException
5
/*
* DBAccessException.java
*
* Created on 10. Juli 2001, 12:43
*/
package epoint.db.exception;
import java.sql.SQLException;
10
15
20
25
30
/**
* This Exception is thrown whenever an error occurs, while trying to access
* the database.
*
* @author Danny Poppe
* @version 1.0
*/
public class DBAccessException extends RuntimeException {
/**
* Creates new <code>DBAccessException</code> without detail message.
*/
private DBAccessException() {
}
/**
* Constructs an <code>DBAccessException</code> with the specified detail message.
*
* @param sMethod the name of the method that threw the exception
* @param sAction describes the action that was carried out with the database
* @param e the original exception thrown by the driver
*/
public DBAccessException(String sMethod, String sAction, SQLException e) {
super("An error occured while accessing the database!\n"+
C.3. PACKAGE EPOINT.LOG
313
"Method: "+sMethod+"\n"+
"Action: "+sAction+"\n"+
"Original Message was: "+e.getMessage()+"\n");
35
}
}
Listing C.56: epoint.db.exception.NoSuchDriverException
5
/*
* NoSuchDriver.java
*
* Created on 9. Juli 2001, 16:00
*/
package
10
15
epoint.db.exception;
/**
* This exception is thrown whenever the specified driver-class could not be
* found within the CLASSPATH. Check this variable or specify the correct
* class-name (full qualified!).
*
* @author Danny Poppe
* @version 1.0
*/
public class NoSuchDriverException extends java.lang.Exception {
/**
*/
20
private
}
25
30
}
NoSuchDriverException() {
/**
* Constructs an <code>NoSuchDriverException</code> with for the specified
* driver-class.
*
* @param sClassName the classname of the Driver that could not be found
*/
public NoSuchDriverException(String sClassName) {
super("The given driver "+sClassName+" could not be found in the class-path!");
}
C.3 Package epoint.log
epoint.log.Log.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 313
epoint.log.LogEntry.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 317
epoint.log.LogFilter.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 319
Listing C.57: epoint.log.Log
5
10
15
/*
* Log.java
*
* Created on 25. April 2001, 14:23
*/
package epoint.log;
import java.io.*;
import java.util.*;
/**
* This class implements a log that may be used to log various formatted
* information to a {@link java.io.Writer}. There are several possible
* formats pre-defined, that may be extended.<br>
* A Log may always be opened whether for reading or for writing to it.
* This implementation supports output written as XML or plain text, whereas
* the XML output may be a serialized output of XML-objects, that may later
* be de-serialized.<br>
* <br>
314
20
25
30
35
40
45
50
55
60
65
70
75
80
85
90
95
100
105
ANHANG C. QUELLTEXTE
* This class is in ALPHA state and is not yet finished! At the time of writing
* this comment, only writing is supported, in form of plain text and XML.
* Normal Java-serialization, as reading a Log is not supported yet!
*
* @see LogEntry
* @see LogFilter
* @author Danny Poppe
* @version 0.1alpha
*/
public class Log {
/**
* Contains the LogEntries in this Log.
*/
private nal static java.util.List lLogs = java.util.Collections.synchronizedList(new java.util.LinkedList());
/**
* Designates this Log as a read-only log.
*/
public nal static int READLOG = 100;
/**
* Designates this Log as a write-only Log.
*/
public nal static int WRITELOG = 200;
/**
* Defines a normal XML-logfile that logs its output as XML-formatted
* informations.
*/
public nal static String LOGTYPE_XML = "normal XML logfile";
/**
* Defines a xml-object Log, where the information is logged as objects,
* that are serialized to XML and then written to the Log.
*/
public nal static String LOGTYPE_XMLOBJ = "xml-object logfile";
/**
* Defines a plain-text Log, where the logged information is formatted
* as plain text.
*/
public nal static String LOGTYPE_TXT = "plain text logfile";
/**
* Defines a Log that uses normal Serialization to write the LogEntry information.
*/
public nal static String LOGTYPE_OBJ = "serialized objects logfile";
/**
* A spacer that is used in XML-output to make it readable by users without
* a XML-viewer.
*/
private nal static String XML_ENTRY_DELIMITER = "<!-- next entry -->\n";
/**
* If <code>true</code> this Log is currently closing all open Log (shutdown).
*/
private static boolean bClosingAll = false;
/**
* Contains the type of this Log.
*/
private String sType;
/**
* Contains this Logs read/write mode.
*/
private int iRWType;
/**
* Contains the number of LogEntries in this Log.
*/
private long lLength = 0;
/**
* Contains the default LogFilter, that filters nothing and allows
* all LogEntries to be logged.
*/
private LogFilter lf = new LogFilter() {
public boolean isLogged(LogEntry le) {
return true;
}
};
/**
* Wraps around the Writer to which the LogEntries are written to create
* buffered efficient Writer.
*/
private BufferedWriter wLog = null;
/**
* Creates a Log that is used to read from the given StringReader
*
* @param rLog the StringReader from which the LogEntries are to be read.
* @param sLogType the type of the Log that is to be read by this Log
*/
C.3. PACKAGE EPOINT.LOG
110
115
120
125
130
public Log(java.io.StringReader rLog, String sLogType) {
throw new RuntimeException("not implemented yet!");
}
/**
* Creates a Log that is used to write LogEntries to the given Writer.
*
* @param wLog the Writer to which the LogEntries shall be written
* @param sLogType the type of the Log that shall be written
*/
public Log(Writer wLog, String sLogType) {
if (sLogType.equals(LOGTYPE_OBJ)) throw new RuntimeException("Only the OBJ-Log is not implemented!");
this.sType = sLogType;
this.iRWType = WRITELOG;
open(wLog);
lLogs.add(this);
}
/**
* Closes this Log.
*/
synchronized public void close() {
if (!bClosingAll) lLogs.remove(this);
if (wLog == null) return;
try {
if (sType.equals(LOGTYPE_XML) || sType.equals(LOGTYPE_XMLOBJ))
135
}
140
}
145
150
155
160
165
170
175
180
185
190
315
wLog.write("</Log>\n");
(sType.equals(LOGTYPE_TXT)) {
wLog.write("["+(new java.util.Date())+"] log closed!\n");
{
else if
}
wLog.close();
}catch (Exception e) {
e.printStackTrace();
}
/**
* Opens this writing Log with a new Writer while closing this Log before.
*
* @param wLog the new Writer for writing LogEntries
*/
synchronized protected void open(Writer wLog) {
try {
close();
this.wLog = new BufferedWriter(wLog,65536);
if (sType.equals(LOGTYPE_XML) || sType.equals(LOGTYPE_XMLOBJ)) {
wLog.write(getXMLHeader());
wLog.write("<Log opened='"+(new java.util.Date())+"' type= '"+sType+"'>\n");
} else if (sType.equals(LOGTYPE_TXT)) {
wLog.write("["+(new java.util.Date())+"] log opened\n");
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Writes a new LogEntry to this writing Log, formatting the output as
* specified by the type of this Log.
*
* @param lengththe LogEntry to be written
*/
synchronized public void write(LogEntry le) {
if (iRWType == READLOG) throw new RuntimeException("You cannot write to a READ-Log!");
if (wLog == null) return;
if (!lf.isLogged(le)) return;
lLength++;
String sOut = null;
try {
if (sType.equals(LOGTYPE_XMLOBJ)) {
sOut = XML_ENTRY_DELIMITER + epoint.sale.Shop.getXMLConverter().toXMLString(le,true);
} else if (sType.equals(LOGTYPE_XML)) {
sOut = XML_ENTRY_DELIMITER + createPlainXML(le);
} else if (sType.equals(LOGTYPE_TXT)) {
sOut = XML_ENTRY_DELIMITER+"["+(new java.util.Date())+"] Type: "+le.getType()+
" Priority: "+le.getLevel()+
" Source: "+le.getSource()+
" Logged Object: "+((le.getEmbeddedObject() != null)?(le.getEmbeddedObject().
getClass().getName()):("null"))+"\n"+
le.getMessage()+"\n";
}
wLog.write(sOut+"\n");
} catch (Exception e) {
e.printStackTrace();
}
}
/**
316
195
200
205
210
215
220
225
230
235
240
245
250
255
260
265
270
275
280
ANHANG C. QUELLTEXTE
* Converts the given String to a String that may be written as XML data.
* (Converts tags).
*
* @param sData the String that shall be converted into a valid XML-String
* @return a String that may safely be written into a XML-field
*/
protected String convertToXMLData(String sData) {
StringTokenizer st = new StringTokenizer(sData,"<\n",true);
StringWriter sw = new StringWriter(sData.length());
while (st.hasMoreTokens()) {
String sToken = st.nextToken();
if (sToken.equals("\n")) sToken = "\n\t";
if (sToken.equals("<")) sToken = "<";
sw.write(sToken);
}
return sw.toString();
}
/**
* Converts the given LogEntry into a XML-String that is appended to
* a XML-file.
*
* @param lengththe LogEntry to be converted
* @return the XML-String that desscribes the LogEntry
*/
public String createPlainXML(LogEntry le) {
String sEmbedded;
try {
sEmbedded = epoint.sale.Shop.getXMLConverter().toXMLString(le.getEmbeddedObject(),true)+"\n";
} catch (Exception e) {
sEmbedded = "Exception occured while generating xml!";
}
return "<logentry time='"+le.getTime()+"' "+
"type='"+le.getType()+"' "+
"priority='"+le.getLevel()+"' "+
"source='"+le.getSource()+"'>\n"+
"\t<message>\n"+
"\t"+convertToXMLData(le.getMessage())+"\n"+
"\t</message>\n"+
"\t<object>\n"+
sEmbedded+
"\t</object>\n"+
"</logentry>\n";
}
/**
* Returns the header that must be written to XML-files.
*
* @return the header of a XML file.
*/
public String getXMLHeader() {
return "<?xml version='1.0' encoding='UTF-8'?>\n";
// <!DOCTYPE koml SYSTEM "http://www.inria.fr/koala/XML/koml12.dtd">
}
/**
* The pendant to <code>createPlainXML</code> that creates the LogEntry
* from the given SString previously created using the mentioned method.
*
* @param sXML a LogEntry that was created using <code>createPlainXML</code>
* @return the LogEntry described by the given String
*/
public LogEntry fromPlainXML(String sXML) {
throw new RuntimeException("This feature is not yet implemented!");
}
/**
* Reads the LogEntry at the given index in this reading Log file.
*
* @param lIndex the index at which to read the LogEntry
* @return LogEntry the LogEntry at the specified index
*/
synchronized public LogEntry read(long lIndex) {
throw new RuntimeException("Reading the log is not yet implemented!");
// if (wLog == null) return;
// if (iRWType == WRITELOG) throw new RuntimeException("You cannot read fom a WRITE-Log!");
}
/**
* Returns the number of LogEntries in this Log.
*
* @return the number of LogEntries in this Log
*/
synchronized public long length() {
return lLength;
}
/**
C.3. PACKAGE EPOINT.LOG
* Sets a filter for LogEntries being read or written by this Log.
*
* @param lfNew the new LogFilter for this Log
*/
synchronized public void setLogFilter(LogFilter lfNew) {
lf = lfNew;
}
285
290
295
300
}
/**
* Closes all Logs created using this class.
*/
synchronized public static void closeAllLogs() {
bClosingAll = true;
java.util.Iterator it = lLogs.iterator();
while (it.hasNext()) {
Log l = (Log)it.next();
l.close();
it.remove();
}
bClosingAll = false;
}
Listing C.58: epoint.log.LogEntry
5
/*
* LogEntry.java
*
* Created on 25. April 2001, 14:52
*/
package
10
15
20
25
30
35
40
45
50
55
60
epoint.log;
/**
* A LogEntry contains the information that is written to a Log. It is highly
* configurable and the information it carries may be evaluated by a LogFilter.
*
* @author Danny Poppe
* @version 1.0
*/
public class LogEntry implements java.io.Serializable {
/**
* Specifies the source of this LogEntry.
*/
private String Source;
/**
* Specifies the time, when this LogEntry was created.
*/
private String Time;
/**
* Specifies the type of this LogEntry, in meanings of specifying
* the information-type.
*/
private String Type;
/**
* Contains the level of this LogEntry in a hierachy of importance
*/
private int Level;
/**
* Contains the message of this LogEntry
*/
private String Message;
/**
* Contains an embedded object that may be logged with this LogEntry.
*/
private java.io.Serializable EmbeddedObject;
/**
* Specifies this LogEntry as a pure information about something.
*/
public nal static String LOG_TYPE_INFO = "Info";
/**
* Specifies this LogEntry as carrying information about an error.
*/
public nal static String LOG_TYPE_ERROR = "Error";
/**
* Specifies this LogEntry as containing a SQL-statement.
*/
public nal static String LOG_TYPE_SQLSTATEMENT = "SQL";
/**
* Specifies this LogEntry as a LogLevel ERROR, hwich is the highest.
*/
public nal static int LOG_LEVEL_ERROR = 100;
/**
* Specifies this LogEntry as a LogLevel INFO, which is the second highest.
317
318
*/
LOG_LEVEL_INFO
= 200;
/**
* Specifies this LogEntry as a LogLevel EXTENDED, which is the third highest.
*/
public nal static int LOG_LEVEL_EXTENDED = 300;
/**
* Specifies this LogEntry as a LogLLevel DEBUG, which is the lowest.
*/
public nal static int LOG_LEVEL_DEBUG = 400;
public nal static int
65
70
75
80
85
90
95
100
105
110
115
120
125
130
135
140
145
/**
* Specifies the default LogLevel for a LogEntry
*/
public static int DEFAULT_LEVEL = LOG_LEVEL_EXTENDED;
/**
* Specifies the default LogType for a LogEntry
*/
public static String DEFAULT_TYPE = LOG_TYPE_INFO;
/**
* Creates a new LogEntry for the given source with the given
* message of default type and default level.
*
* @param sSource the source for this LogEntry
* @param sLogText the message of this LogEntry
*/
public LogEntry(String sSource, String sLogText) {
this(sSource,sLogText,DEFAULT_TYPE,DEFAULT_LEVEL);
}
/**
* Creates a new LogEntry for the given source with the given
* message of the given type and default level.
*
* @param sSource the source for this LogEntry
* @param sLogText the message of this LogEntry
* @param sLogType the type of this LogEntry
*/
public LogEntry(String sSource, String sLogText, String sLogType) {
this(sSource, sLogText, sLogType, DEFAULT_LEVEL);
}
/**
* Creates a new LogEntry for the given source with the given
* message of the given type and level.
*
* @param sSource the source for this LogEntry
* @param sLogText the message of this LogEntry
* @param sLogType the type of this LogEntry
* @param iLevel the level of this LogEntry
*/
public LogEntry(String sSource, String sLogText, String sLogType, int iLevel) {
Source = sSource;
Message = sLogText;
Type = sLogType;
Level = iLevel;
Time = java.text.DateFormat.getDateTimeInstance().format(new java.util.Date());
}
/**
* Sets an embedded object to be logged with this LogEntry
*/
nal public void setObject(java.io.Serializable o) {
try {
EmbeddedObject = o;
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Returns the level of this LogEntry
*/
nal public int getLevel() {
return Level;
}
/**
* Returns the type of this LogEntry
*/
nal public String getType() {
return Type;
}
/**
* Returns the systems time, when and where this LogEntry has been created.
*/
ANHANG C. QUELLTEXTE
C.4. PACKAGE EPOINT.SALE
319
nal public java.util.Date getTime() {
try {
return java.text.DateFormat.getDateTimeInstance().parse(Time);
} catch (Exception e) {
150
155
}
}
e.printStackTrace();
return null;
/**
* Returns the message of this LogEntry.
*/
nal public String getMessage() {
return Message;
}
160
165
/**
* Returns the source of this LogEntry
*/
nal public String getSource() {
return Source;
}
170
175
}
/**
* Returns the embedded object of this LogEntry, if any - otherwise <code>null</code>.
*/
nal public Object getEmbeddedObject() {
return EmbeddedObject;
}
Listing C.59: epoint.log.LogFilter
5
/*
* LogFilter.java
*
* Created on 8. Oktober 2001, 16:20
*/
package
10
15
20
25
epoint.log;
/**
* A LogFilter may be attached to any Log for filtering LogEntries that will be
* read or written.
*
* @see Log
* @see LogFilter
* @author Danny Poppe
* @version 1.0
*/
public interface LogFilter {
/**
* Returns <code>true</code> only if the given LogEntry may be logged.
*
* @param le the LogEntry that shall be considered for being loggged or not
* @return <code>true</code> if the given LogEntry may be logged
*/
public boolean isLogged(LogEntry le);
}
C.4 Package epoint.sale
epoint.sale.EPoint.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 320
epoint.sale.EPointInfo.java. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .322
epoint.sale.EPointProcess.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 323
epoint.sale.Gate.java. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .324
epoint.sale.ParamFile.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 325
epoint.sale.Serializer.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 326
epoint.sale.Shop.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 327
epoint.sale.ShopServices.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 335
320
ANHANG C. QUELLTEXTE
epoint.sale.ShopServicesIdent.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 337
epoint.sale.Transition.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 337
Listing C.60: epoint.sale.EPoint
5
10
15
20
25
30
35
40
45
50
55
60
65
70
75
package epoint.sale;
import epoint.sale.rmi.*;
import epoint.sale.exception.*;
import epoint.data.InformationStore;
import epoint.data.InformationStoreImpl;
/**
* The EPoint is the central class for creating applications based on a Shop
* as the server for an EPoint as the client. When an EPoint is created, it uses
* RMI to connect to a running Shop, that serves the data that is used to run
* the EPoint.<br>
* An EPoint can therefore be imagined as a remote service-point of a distributed
* application. It can be used to implement different service-points for different
* views for different customers.<br>
* For accessing a Shops services, the EPoint registers at the Shop, using the
* {@link EPointInfo} and then (if successfully registered) is granted access
* to the {@link ShopServices}. Using these services, the EPoint can create persistent
* data at the Shop, that may be shared with other EPoints connected to the Shop.
* Once the data is persistent, it is guaranteed by the Shop, that (if not deleted
* by other EPoints) the data may also be accessed, if the EPoint is restarted.<br>
* <br>
* After registering at the Shop, the EPoint has access to its {@link InformationStore}
* that is used to store information about the runtime of the EPoint. The InfoStore
* is automatically associated with every EPoint at the Shop. So if an EPoint crashes
* or is shutdown, the InfoStore may be used to detect this and use the available
* data to re-create its old state or analyze it for error-detection.
*
* @see Shop
* @see ShopServices
* @see EPointInfo
* @see InformationStore
* @author Danny Poppe
* @version 1.0
*/
public class EPoint extends java.lang.Object {
/**
* Defines a state of the InfoStore, whereas this EPoint previously run
* and did shutdown normally.
*/
public nal static int DEFINED_PERSISTENT_STATE = InformationStore.DEFINED_PERSISTENT_STATE;
/**
* Defines a state of the InfoStore, whereas this EPoint previously run,
* but did not normally shutdown.
*/
public nal static int UNDEFINED_PERSISTENT_STATE = InformationStore.UNDEFINED_PERSISTENT_STATE;
/**
* Defines a state of the InfoStore, whereas this EPoint was never registered
* before at the Shop.
*/
public nal static int NEW_PERSISTENT_STATE = InformationStore.NOT_PERSISTENT_STATE;
/**
* The ShopServices of the Shop that are used by this EPoint * this is set when successfully calling {@link #registerWithShop(String, String, EPointInfo)}.
*/
private static ShopServices ssServices = null;
/**
* This is set to <code>true</code> if this EPoint is instanciated
* and prevents a Shop to be started together with this EPoint
* (because the Shop has to be a server and needs to be started
* on it own)
*/
private static boolean bClientApplication = false;
/**
* The name of this EPoint - which must be unique under all EPoints registered
* with a Shop.
*/
private String sName = null;
/**
* The InformationStore of this EPoint.
*/
private InformationStore infostore = null;
/**
* The identity-card of this EPoint, which is used to identify this EPoint
* with a Shop.
*/
private EPointInfo epi = null;
/**
* Creates new EPoint, that registers with a Shop at the given location.
C.4. PACKAGE EPOINT.SALE
80
85
90
95
100
105
110
115
120
125
130
135
140
145
150
155
160
321
*
* @param sShopHost the hostname, where the Shop for this EPoint is running
* @param sServiceName the name under which this may reaches the RMI-service for registering itself to the Shop
* @param sEPointName the name of this EPoint
* @param epi the EPointInfo that identifies this EPoint to any Shop
* @throws NoRunningShopException if no Shop could be found at the specified location
* @throws PermissionDeniedException if the Shop did not allow this EPoint to register for
*
accessing the ShopServices
*/
protected EPoint(String sShopHost, String sServiceName, String sEPointName, EPointInfo epi) throws NoRunningShopException,
PermissionDeniedException {
setClientApplication();
this.sName = sEPointName;
this.epi = epi;
try {
if (!epi.getEPointName().equals(sEPointName)) throw new IllegalArgumentException("The given EPoint-name \""+sEPointName+"\" does
not equal the information provided in the EPoint-Info (\""+epi.getEPointName()+"\")!");
} catch (java.rmi.RemoteException rmie) {
rmie.printStackTrace();
}
registerWithShop(sShopHost,sServiceName,getEPointInfo());
}
/**
* Starts this EPoint, which makes the InfoStore available.
*/
public void start() {
infostore.openService();
}
/**
* Closes this EPoint while committing all changes registered in the
* informationstore. Afterwards this EPoint is not registered with a Shop
* anymore.
*/
public void shutdown() {
infostore.closeService(true);
ssServices = null;
infostore = null;
}
/**
* Returns the state of a previous run of this EPoint relative to the
* currently registered Shop, as defined in {@link epoint.data.InformationStore#getPersistenceState()}
*
* @return the state of persistence for this EPoint (compared to previous runs)
*/
public int getPersistenceState() {
return infostore.getPersistenceState();
}
/**
* Returns the information regarding this EPoint, which is used to register
* with the Shop. By default the same information as given in the constructor
* is returned.
*
* @return this EPoints EPointInfo
*/
nal public EPointInfo getEPointInfo() {
return epi;
}
/**
* Returns the name of this EPoint.
*
* @return this EPoints name
*/
nal public String getName() {
return sName;
}
/**
* This method marks the running application to be a client-application and
* prevents a Shop to be instanciated in the running application.
* If there is already a running Shop this method will throw a RunTimeException!
*/
nal public static void setClientApplication() {
if (Shop.isServerApplication()) throw new RuntimeException("This application is already registered to be a server(Shop)-application
! It can wether be a client (EPoint) or a server (Shop) application!");
bClientApplication = true;
}
/**
* Returns <code>true</code> only if this apllication is marked as
* a client-application (eg. setClientApplication() has been successfully
* called)
*
* @return <code>true</code> if this call is being made in client-application
322
165
ANHANG C. QUELLTEXTE
*/
nal public static boolean isClientApplication()
return bClientApplication;
{
}
170
/**
* Returns the global InformationStore for this EPoint, which allows
* to make objects persistent in the Shop for later retrieval. This can only
* be returned after a successful registration at the Shop.
*
* @return this EPoints InfoStore
*/
nal public epoint.data.InformationStore getInformationStore() {
if (infostore == null) throw new RuntimeException("You must first successful register with a Shop to gain access to its services!");
return infostore;
}
175
180
/**
* Returns the ShopServices of the Shop that, this EPoint is registered with.
*
* @return the ShopServices of this EPoints Shop
*/
nal public static ShopServices getShopServices() {
if (ssServices == null) throw new RuntimeException("You must first successful register with a Shop to gain access to its services!")
;
return ssServices;
}
185
190
195
200
205
210
215
}
/**
* Registers this EPoint with a Shop at the given URL, while using the
* information supplied with the EPointInfo of this EPoint.
*
* @param sShopHostName the host-name, where the Shop is running
* @param sShopServiceName the named services, under which the ShopIdentServices
*
is reachable at the Shop
* @param epi this EPoints identity card
* @throws NoRunningShopException if no Shop could be found at the given location
* @throws PermissionDeniedException if the Shop did not allow this EPoint
*
to acccess the ShopServices
*/
nal public void registerWithShop(String sShopHostName, String sShopServiceName, EPointInfo epi) throws epoint.sale.exception.
NoRunningShopException, epoint.sale.exception.PermissionDeniedException {
ShopServicesIdent ssi = null;
try {
ssi = (ShopServicesIdent)java.rmi.Naming.lookup("//"+sShopHostName+"/"+sShopServiceName);
ssServices = ssi.identify(epi);
infostore = new InformationStoreImpl(epi);
} catch (java.rmi.NotBoundException nbe) {
throw new NoRunningShopException("Could not find a Shop at the given host ("+sShopHostName+")!");
} catch (java.net.MalformedURLException mfue) {
throw new NoRunningShopException("The given host seems to be malformed ("+sShopHostName+")!");
} catch (java.rmi.RemoteException e) {
throw new NoRunningShopException(e.getMessage());
}
}
Listing C.61: epoint.sale.EPointInfo
5
10
15
20
25
/**
* EPointInfo.java
*
*
* Created: Sun Jul 22 20:35:02 2001
*
* @author <a href="mailto:Administrator@OLYMP"></a>
* @version
*/
package
import
import
import
import
epoint.sale;
java.io.FileNotFoundException;
java.io.Serializable;
java.rmi.Remote;
java.rmi.RemoteException;
/**
* Contains information about the EPoint and is used to identify at the Shop.
*
* @author Danny Poppe
* @version 1.0
*/
public interface EPointInfo extends Remote, Serializable {
/**
* The property name for the EPoint name.
*/
C.4. PACKAGE EPOINT.SALE
nal public static
String EPOINTNAME_KEY = "EPointName";
/**
* The property name for the description of the EPoint.
*/
nal public static String DESCRIPTION_KEY = "EPointDescription";
/**
* The property name for the number of Gates at the given EPoint
*/
nal public static String GATECOUNT_KEY = "EPointGateCount";
30
35
/**
* Returns the information provided by the given property-name.
*
* @param sKey the property name
* @return the property for the given key
*/
public String getParam (String sKey) throws RemoteException;
/**
* Returns the name of the EPoint
*
* @return the EPoints name
*/
public String getEPointName() throws RemoteException;
/**
* Returns a brief description of the EPoint.
*
* @return the EPoint-description
*/
public String getDescription() throws RemoteException;
/**
* Returns the hostname, wher the EPoint is running.
*/
public String getHostName() throws RemoteException;
/**
* Returns the number of Gates at the EPoint.
*/
public int getGateCount() throws RemoteException;
40
45
50
55
60
65
}
Listing C.62: epoint.sale.EPointProcess
5
/*
* Process.java
*
* Created on 3. August 2001, 17:46
*/
package
10
15
20
25
30
35
40
epoint.sale;
/**
* The EPoints operations are described by processes that are composed of
* Gates and a single Transition going from one Gate to other Gates, depending
* on the previosu Gate. A Gate is always a point where data
* is collected or manipulated and within a Transition the data is evaluated
* and upon the evaluation it is considered which is the next Gate.<br>
* A process is a complex data-structure that may be modified "on-line" when it
* is running. As such it can be implemented as a dynamic or a static process.<br>
* A Process should normally be implemented as a Thread to be able of supporting
* this interfaces methods!
*
* @see Gate
* @see Transition
* @author Danny Poppe
* @version 1.0
*/
public interface EPointProcess {
/**
* Sets the Start-Gate for this process.
*
* @param gate the Gate that shall be the start Gate for this process
*/
public void setStartGate(Gate gate);
/**
* Returns the StartGate of this process, which is the first Gate of all.
*
* @return the first Gate of this process, when it is started.
*/
public Gate getStartGate();
/**
* Sets the Gate where this process is ended, which is the final Gate,
* when reached.
*
* @param gate the Gate where this
*/
public void setQuitGate(Gate gate);
323
324
45
ANHANG C. QUELLTEXTE
/**
* Returns the Gate at which this Process ends.
*
* @return the final Gate of this process
*/
public Gate getQuitGate();
/**
* Sets the Gate at which to jump, if an error occurs.
*
* @param gate the Gate that shall be used to jump to if an error occurs
*/
public void setErrorGate(Gate gate);
/**
* Returns the Gate that is the current Error Gate.
*
* @return the current Error Gate
*/
public Gate getErrorGate();
50
55
60
/**
* Adds a new Gate to this process.
*
* @param gate the Gate for this process
*/
public void addGate(Gate gate);
/**
* Returns a Gate by its name
*
* @param sGateName the name of the Gate
* @return the Gate of the given name
*/
public Gate getGate(String sGateName);
/**
* Returns current Gate of this process or <code>null</code> if the
* process is not started.
*
* @return the current Gate
*/
public Gate getCurrentGate();
65
70
75
80
85
/**
* Sets a new Transition for the given Gate.
*
* @param gate the Gate for which a new transition is set
* @param transition the Transition for the given Gate
*/
public void setTransition(Gate gate, Transition transition);
90
/**
* Starts this process with the current Start Gate set.
*/
public void start();
95
100
105
110
115
120
}
/**
* Returns <code>true</code> if this process is currently running.
*/
public boolean isRunning();
/**
* Suspends this process at the next Gate, if currently in Transition.
*/
public void suspend();
/**
* Returns <code>true</code> if this Process is currently suspended.
*/
public boolean isSuspended();
/**
* Stops this process immediately by jumping to the final gate.
*/
public void stop();
/**
* Restarts this EPoint if it is currently suspended.
*/
public void resume();
/**
* Kills this process immediately.
*/
public void kill();
Listing C.63: epoint.sale.Gate
5
/*
* Gate.java
*
* Created on 3. August 2001, 17:46
*/
C.4. PACKAGE EPOINT.SALE
package
10
15
20
25
30
35
40
epoint.sale;
/**
* A Gate is that point within a Process, where data is collected and manipulated.
* A Gate always has a Transition that leads to the next Gate. If a process is
* started, it may only be stopped at Gates - thats why when Gates are started
* the data within the InfoStore should normally be committed.
*
* @see EPointProcess
* @see Gate
* @see Transition
* @author Danny Poppe
* @version 1.0
*/
public interface Gate {
/**
* Returns this Gates name, by which it is identified.
*
* @return this Gates name
*/
public String getGateName();
/**
* Returns the process in which this Gate is contained in.
*
* @return this Gates process
*/
public EPointProcess getProcess();
/**
* Exits this Gate, while calling its Transition.
*/
public void exitGate();
}
Listing C.64: epoint.sale.ParamFile
5
/*
* ParamFile.java
*
* Created on 10. September 2001, 11:53
*/
package epoint.sale;
import java.io.*;
10
15
20
25
30
35
40
45
/**
* This class provides features for supporting parameter-files for
* configuring various actions.
* It takes a file and reads in information that is organized in lines
* as key-value pairs divided by "=". The information may then be read
* providing the key for the requested information.
*
* @author Danny Poppe
* @version 1.0
*/
public class ParamFile {
/**
* The HashMap storing the key-value pairs of the parameter-file
*/
private java.util.HashMap hmParam = new java.util.HashMap();
/**
* The files input from which the information is read.
*/
private InputStreamReader isr = null;
private String sFileName = null;
/**
* Creates a new ParamFile for this given file-path and reads in the
* information in that file.
*
* @param sFileName the name of the file for being used by this ParamFile
*
-- the file must be accessible within the classpath!
*/
public ParamFile(String sFileName) {
this.sFileName = sFileName;
System.out.print("Using parameter-file \""+sFileName+"\" ... ");
try {
isr = new InputStreamReader(ClassLoader.getSystemResourceAsStream(sFileName));
decodeFile();
System.out.println("found!");
} catch (Exception e) {
System.out.println(" not found! ("+e+")");
}
}
325
326
/**
* Decodes the given file by reading and parsing the information
*
*/
private void decodeFile () throws java.io.FileNotFoundException {
try {
char[] cbuf = new char[]{'_'};
String sContent = "";
while(isr.read(cbuf) != -1) {
sContent = sContent+cbuf[0];
}
java.util.StringTokenizer st = new java.util.StringTokenizer(sContent,"\n");
while (st.hasMoreTokens()) {
String sLine = st.nextToken();
sLine = sLine.trim();
if (sLine.startsWith("#")) continue;
java.util.StringTokenizer stLine = new java.util.StringTokenizer(sLine,"=", true);
String sKey = stLine.nextToken().trim();
String sEqual = stLine.nextToken().trim();
String sValue = stLine.nextToken().trim();
if (sEqual.equals("=")) {
setParam(sKey.toLowerCase(),sValue);
}
}
} catch (Exception e) {
throw new java.io.FileNotFoundException("Could not decode the file: "+e.getMessage());
}
}
50
55
60
65
70
75
/**
* Sets a parameter that may later be retrieved from this ParamFile.
*
* @param sKey the key for which to set the information
* @param sValue the Value that shall be available under the given key
*/
private void setParam(String sKey, String sValue) {
hmParam.put(sKey.toLowerCase(),sValue);
}
80
85
/**
* Returns the information available for the given key, that could be found
* in the provided parameter-file.
*
* @param sKey the property-key for the information
* @return the information or <code>null</code> if the key did not exist
*/
public String getParam(String sKey) {
return (String)hmParam.get(sKey.toLowerCase());
}
90
95
/**
* Returns a list with all property-names found in the parameter-file.
*
* @return a list of all properties
*/
public java.util.List getParamList() {
return new java.util.LinkedList(hmParam.keySet());
}
100
105
110
115
ANHANG C. QUELLTEXTE
}
/**
* Returns the full URL of the file being used.
*
* @return the full path to the parameter-file
*/
public java.net.URL getFullPath() {
return ClassLoader.getSystemResource(sFileName);
}
Listing C.65: epoint.sale.Serializer
5
/*
* Serializer.java
*
* Created on 15. Oktober 2001, 12:19
*/
package
10
15
epoint.sale;
/**
* A Serializer converts Objects into Strings and back..
*
* @author Danny Poppe
* @version 1.0
*/
public interface Serializer {
C.4. PACKAGE EPOINT.SALE
/**
* Converts the given Object into a String.
*
* @param o the Object that shall be converted
* @return the String representing the Object
*/
public String toString(Object o);
/**
* Converts the given String back into an object.
*
* @param sObject the String representing the Object
* @return the Object represented by the String
*/
public Object fromString(String sObject);
20
25
30
}
Listing C.66: epoint.sale.Shop
5
10
15
20
25
30
35
40
45
50
55
60
65
package epoint.sale;
import epoint.data.*;
import epoint.data.exception.*;
import epoint.log.*;
import epoint.sale.rmi.*;
import java.util.*;
import epoint.db.*;
import java.io.Serializable;
import java.sql.Connection;
/**
* The Shop is the central class for building an EPoint-application. It acts as
* a server for all EPoints and as this it provides services for them. Within
* a single thread there can only exist whether a single Shop or many EPoints over
* all the time.<br>
* When a Shop is started <a href="http://java.sun.com/products/jdk/rmi/index.html">RMI</a>
* is used to serve a single named object which acts as an access-point, where
* EPoints must identify to retrieve the Shops services.<br>
* The Shop itself uses a {@link epoint.db.DBPersistenceManager PersistenceManager}
* to make a defined set of data persistent, which may later be used to restart
* the Shop after an unexpected shutdown or even to analyze a run of the application.<br>
* For easy configuration of an existing application parameter-files may be used
* to configure a Shop. This includes configuration of the database that shall be
* used for persistence, the host at which the RMI-server runs or which xml-Parser
* shall be used. For this purpose the constructor may be called with a fully
* qualified filename that includes pairs of keys and values. The keys that are recognized
* are defined in the constants in this class.
*
* @see epoint.sale.EPoint
* @see epoint.db.DBPersistenceManager
*
* @author Danny Poppe
* @version 1.0
*/
public class Shop {
////////////////////////////////////////////////////////////////////////////////
//
NEW CLASS CONSTANTS
////////////////////////////////////////////////////////////////////////////////
/**
* The default-prefix, that is used to create Framework-internal
* names and identifiers. In addition there is often appended
* another name and a unique number that ist retrieved by
* {@link #getGlobalUniqueNumber()}. Such names and properties
* should never be accessed directly!
*/
nal public static String SYSTEM_PROPERTY_PREFIX = "internal_";
/**
* Default property name for the current unique number that is
* maintained by the Shop and should never be accessed
* directly!
*/
nal public static String UNIQUE_NUMBER_PROPERTY = SYSTEM_PROPERTY_PREFIX+"shop.unique_number";
/**
* Indicates the Shop set, but not running and performing no action to change this state.
*/
nal public static int SHOP_STOPPED = 0;
/**
* Indicates the Shop running and ready for access by other EPoints.
*/
nal public static int SHOP_RUNNING = 1;
/**
* Indicates the Shop being suspended and ready for limited access.
*/
nal public static int SHOP_SUSPENDED = 2;
327
328
70
75
80
85
90
95
100
105
110
115
120
125
130
135
140
145
150
ANHANG C. QUELLTEXTE
/**
* Indicates the Shop beeing in an abnormal state, which may occur after errors.
*/
nal public static int SHOP_UNDEFINED = 128;
////////////////////////////////////////////////////////////////////////////////
//
NEW CLASS VARIABLES
////////////////////////////////////////////////////////////////////////////////
/**
* The name for the database-name property, which is used in the
* configuration-file for the Shop
*/
public static String PROPERTY_DATABASE_NAME = "DatabaseName";
/**
* The name for the database-host property, which is used in the
* configuration-file for the Shop
*/
public static String PROPERTY_DATABASE_HOST = "DatabaseHost";
/**
* The name for the RMI-server-host property, which is used in the
* configuration-file for the Shop
*/
public static String PROPERTY_RMI_HOST = "ServerHost";
/**
* The name for the RMI-server-host property, which is used in the
* configuration-file for the Shop
*/
public static String PROPERTY_XML_PARSER = "XMLParser";
/**
* This is the default-Hostname, if there are problems identifying the
* host, where the RMI-Service is run at. This is normally done calling
* {@link #getRMIServiceHost()} which tries to identify the host. Setting the
* host using this reference should only be done, if problems occur.
*/
public static String DEFAULT_HOSTNAME = null;
/**
* This is the path to the rmiregistry. The default assumes this program to
* be in the PATH of the environment.
*/
public static String RMIREGISTRY = "rmiregistry";
/**
* If the registry-process is started by the Shop, this process represents
* it and can be used to stop the registry.
*/
private static Process REGISTRYPROCESS = null;
/**
* The one and only Shop that is started by a server application
* (there always exists only one Shop at a given point time).
*/
private static Shop sTheShop = null;
/**
* This variable holds a reference to the standard PersistenceManager
* that is used to make all relevant data persistent for the Shop
* and the EPoints.
*/
private static DBPersistenceManager dbcon = null;
/**
* This flag is set to <code>true</code> if this Shop is instanciated
* and prevents an EPoint to be started together with this Shop
* (because the EPoint has to be a client and needs to be started
* on it own).<br>
* Using a check on this flag ({@link #isServerApplication()}) a mechanism
* can be implemented, that allows RemoteServerObjects to be started
* in the Clients-Environment as a Wrapper for the real ServerObject in the
* Server.
*/
private static boolean bServerApplication = false;
/**
* Contains the default-XMLConverter, until re-set to another
*/
private static epoint.xml.XMLConverter xmlConverter = new epoint.xml.XMLConverterImpl();
/**
* The default-Serializer, that is used, whenever an object needs to be serialized
* or de-serialized from/to a String. The default-Serializer is
* implemented using the default-XMLConverter to serialize from and to Srings
*/
private static Serializer sSerializer = new Serializer() {
public String toString(Object o) {
try {
return Shop.getXMLConverter().toXMLString(o);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public Object fromString(String sSerializable) {
try {
C.4. PACKAGE EPOINT.SALE
155
}
160
165
170
175
180
185
190
195
200
205
210
215
220
225
230
235
240
329
};
}
}
return Shop.getXMLConverter().fromXMLString(sSerializable);
catch (Exception e) {
e.printStackTrace();
return null;
/**
* This Log is used to log all errors reported to the Shop as done with
* {@link #logException(Exception)}.
*/
protected static Log lErrorLog;
/**
* This Log is used to log all errors reported to the Shop as done with
* {@link #logInfo(String,String,int)}.
*/
protected static Log lInfoLog;
/**
* This Log is used internally, when instanciating the DBPersistenceManager,
* which is using the log for logging persistence-specific data.
*/
protected static Log lPersistenceLog;
/**
* Reflects the current status of the Shop, as defined by the constants SHOP_...
*/
private static int iShopStatus = SHOP_UNDEFINED;
/**
* This object is a monitor that protects access to the Shop, while its
* state is changing.
*/
private static Object m_StateChange = new Object();
////////////////////////////////////////////////////////////////////////////////
//
NEW INSTANCE VARIABLES
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//
OVERRIDDEN INSTANCE VARIABLES
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//
CONSTRUCTORS
////////////////////////////////////////////////////////////////////////////////
/**
* Creates a new Shop
*/
protected Shop() {
this(null);
}
/**
* Creates a new Shop and initializes some default-variables. When calling
* this constructor, one is unable to instanciate an EPoint within the same
* thread, as the running thread is marked as the server-application for
* the epoint-environment.<br>
* The fully qualified path to a parameter-file may be passed, that defines
* the runtime-properties for the Shop-server. The queried parameters are
* {@link #PROPERTY_DATABASE_NAME}, {@link #PROPERTY_DATABASE_HOST}, {@link #PROPERTY_RMI_HOST},
* {@link #PROPERTY_XML_PARSER}.
*/
protected Shop(String sParameterFile) {
if ((sParameterFile != null) && (!sParameterFile.equals(""))) {
epoint.sale.ParamFile pf = new epoint.sale.ParamFile(sParameterFile);
if (pf.getParam(PROPERTY_DATABASE_NAME) != null) epoint.db.PSQLDBConnection.DEFAULT_BASE = pf.getParam(PROPERTY_DATABASE_NAME);
if (pf.getParam(PROPERTY_DATABASE_HOST) != null) epoint.db.PSQLDBConnection.DEFAULT_HOST = pf.getParam(PROPERTY_DATABASE_HOST);
if (pf.getParam(PROPERTY_RMI_HOST) != null) epoint.sale.Shop.DEFAULT_HOSTNAME = pf.getParam(PROPERTY_RMI_HOST);
if (pf.getParam(PROPERTY_XML_PARSER) == null) {
System.setProperty("org.xml.sax.parser","fr.dyade.koala.xml.sax.SAXParser");
} else System.setProperty("org.xml.sax.parser",pf.getParam(PROPERTY_XML_PARSER));
}
try {
synchronized (m_StateChange) {
setServerApplication();
lErrorLog = new Log(new java.io.FileWriter("log.shop.error.xml",true),Log.LOGTYPE_XML);
lInfoLog = new Log(new java.io.FileWriter("log.shop.info.xml",false),Log.LOGTYPE_XML);
lPersistenceLog = new Log(new java.io.FileWriter("log.shop.persistence.txt",false),Log.LOGTYPE_TXT);
}
} catch (Exception e) {
e.printStackTrace();
System.out.println("Could not correctly start the Shop!");
System.exit(1);
}
}
////////////////////////////////////////////////////////////////////////////////
330
245
250
255
260
265
270
ANHANG C. QUELLTEXTE
//
OVERRIDDEN CLASS METHODS
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//
OVERRIDDEN INSTANCE METHODS
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//
NEW CLASS METHODS
////////////////////////////////////////////////////////////////////////////////
/**
* This method marks the running application to be a server-application and
* prevents an EPoint to be instanciated in the running application.
* If there is already a running EPoint this method will throw a RuntimeException!
*/
nal public synchronized static void setServerApplication() {
if (EPoint.isClientApplication()) throw new RuntimeException("This application is already registered to be a client(EPoint)application! It can wether be a client (EPoint) or a server (Shop) application");
bServerApplication = true;
}
/**
* Returns <code>true</code> only if this apllication is already marked as
* a server-application (eg. setServerApplication() has been successfully
* called)
*
* @return <code>true</code> if this method is called within the Shops environment
*/
nal public synchronized static boolean isServerApplication() {
return bServerApplication;
}
275
280
285
290
295
300
305
310
315
320
325
/**
* Sets a new Shop as the current. An old Shop must be shut down before
* and the new Shop is set as the current * but needs explicitely to be started again!
*
* @param sNewShop the new Shop that replaces the old one
*/
nal public static void setTheShop(Shop sNewShop) {
if (EPoint.isClientApplication()) throw new RuntimeException("You cannot create a Shop in a clients environment!");
synchronized (m_StateChange) {
int iCurrentLevel = Shop.getTheShop().getRunLevel();
if (!((iCurrentLevel == SHOP_STOPPED) || (iCurrentLevel == SHOP_UNDEFINED))) throw new RuntimeException("A new Shop can only be
set, if an old one was completely stopped before!");
sTheShop = sNewShop;
if (sNewShop.equals(null)) {
iShopStatus = SHOP_UNDEFINED;
} else iShopStatus = SHOP_STOPPED;
}
}
/**
* Returns the current running Shop, if there is one. If the running thread
* is the client-application, one can only use {@link EPoint#getShopServices() getShopServices()}
* in EPoint.
*
* @return the current Shop
*/
nal public static Shop getTheShop() {
synchronized (m_StateChange) {
if (EPoint.isClientApplication()) throw new RuntimeException("The Shop cannot be directly accessed in the client's environment!
Please use EPoint.getShopServices() instead!");
return sTheShop;
}
}
/**
* Returns the RunLevel of the Shop, as reflected by the constants defined
* in this class.
*
* @return the current runlevel of this Shop
*/
public static int getRunLevel() {
synchronized (m_StateChange) {
return iShopStatus;
}
}
/**
* Sets the new RunLevel for the Shop and performs neccessary actions to
* change the runlevel while calling the appropriate methods of the Shop.
*
* @param iNewRunLevel the next runlevel for the current Shop
C.4. PACKAGE EPOINT.SALE
*/
330
335
public static void setRunLevel(int iNewRunLevel)
synchronized (m_StateChange) {
switch (iNewRunLevel) {
case SHOP_RUNNING: {
switch (getRunLevel()) {
case SHOP_RUNNING: break;
case SHOP_STOPPED: {
}
}
Shop.getTheShop().start();
break;
SHOP_SUSPENDED: {
Shop.getTheShop().resume();
break;
default: {
throw new
345
}
}
}
break;
}
Shop.getTheShop().shutdown();
break;
case
case
355
}
SHOP_STOPPED: break;
SHOP_SUSPENDED: {
Shop.getTheShop().shutdown();
break;
default: {
throw new
360
}
365
}
}
break;
}
Shop.getTheShop().suspend();
break;
case SHOP_STOPPED: {
throw new RuntimeException("The
Shop must be running, if you want to suspend it!");
case SHOP_SUSPENDED: break;
default: {
throw new RuntimeException("The
Shop you want to start is in an undefined state!");
}
375
}
380
}
}
break;
default: throw new
400
405
410
415
RuntimeException("The Shop you want to start is in an undefined state!");
case SHOP_SUSPENDED: {
switch (getRunLevel()) {
case SHOP_RUNNING: {
370
395
RuntimeException("The Shop you want to start is in an undefined state!");
case SHOP_STOPPED: {
switch (getRunLevel()) {
case SHOP_RUNNING: {
350
390
{
case
340
385
331
}
}
}//synchronized
IllegalArgumentException("The given RunLevel is unknown!");
/**
* Returns the command to be executed, when starting the RMI-registry. The default
* assumes, that "rmiregistry" is in the path and simply calls it at the commandline
* without path. The rmiregistry is part of the standard JDK and is found in the /bin
* subdirctory of the installation-path.
*
* @return a command that may be passed to the shell to start the registry-server
*/
public static String getRegistryCommandLine() {
return RMIREGISTRY;
}
/**
* Returns the default-hostname, while first checking the {@link #RMIREGISTRY}
* variable. If this is not set to <code>null</code> (default) it is returned.
* Otherwise it is tried to determine the host-adress automatically. If this
* fails too, the exception is printed and then <code>localhost</code> is returned.
*
* @return the host at which this Shop is running
*/
nal public static String getRMIServiceHost() {
try {
if (DEFAULT_HOSTNAME != null) return DEFAULT_HOSTNAME;
return java.net.InetAddress.getLocalHost().getHostAddress();
} catch (Exception e) {
e.printStackTrace();
return "localhost";
}
}
332
420
425
430
435
440
445
450
455
460
465
470
475
480
485
490
495
500
ANHANG C. QUELLTEXTE
/**
* Returns the name under which the ShopIdentServices will be available
* using RMI.
*
* @return the named service in RMI for the ShopIdentServices
*/
public static String getRMIServiceName() {
return "SHOPIDENT";
}
/**
* Returns the URL, under which the ShopIdentServices are reachable using
* RMI
*
* @return the UURL to the ShopIdentServices
*/
nal public static String getRMIServiceURL() {
return "//"+getRMIServiceHost()+"/"+getRMIServiceName();
}
/**
* Starts the RMI-registry process and therefore allows EPoints to connect
* to the given ShopServicesIdent object to identify themselves and retrieve
* the ShopServices object from it.
*
* @param the ShopServicesIdent, that is served for the epoints which
*
want to connect to the Shop and need to identify
*/
public synchronized static void startRMI(ShopServicesIdent ssi) {
if (getTheShop() == null) throw new RuntimeException("A Shop is not yet set!");
if (REGISTRYPROCESS != null) throw new RuntimeException("There is already an active registry-process!");
try {
System.out.println("Starting RMI-registryserver...");
REGISTRYPROCESS = Runtime.getRuntime().exec(getRegistryCommandLine());
System.out.println("Waiting 3 seconds for registry-process ready for listening...");
Object oWait = new Object();
synchronized (oWait) {
oWait.wait(3000);
}
System.out.println("Binding ShopServicesIdent in registry...");
java.rmi.Naming.rebind(getRMIServiceURL(), ssi);
} catch (Exception e) {
e.printStackTrace();
System.out.println(e);
}
}
/**
* Kills a running registry-process which has been started using the
* startRMI()-method
*/
public static void stopRMI() {
if (getTheShop() == null) throw new RuntimeException("A Shop is not yet set!");
try {
System.out.println("Removing name-binding from registry...");
java.rmi.Naming.unbind(getRMIServiceURL());
System.out.println("Killing registry-process...");
REGISTRYPROCESS.destroy();
REGISTRYPROCESS = null;
} catch (Exception e) {
e.printStackTrace();
System.out.println(e);
}
}
/**
* Opens the current Shop while starting the RMI-registry
*/
public static void start() {
synchronized (m_StateChange) {
if (getTheShop() == null) throw new RuntimeException("A Shop is not yet set!");
Shop.getTheShop().startRMI(Shop.getTheShop().getIdentServices());
iShopStatus = SHOP_RUNNING;
}
}
/**
* Closes the current Shop while stopping the RMI-registry
*/
public static void shutdown() {
synchronized (m_StateChange) {
if (getTheShop() == null) throw new RuntimeException("A Shop is not yet set!");
Shop.getTheShop().stopRMI();
System.runFinalization();
iShopStatus = SHOP_STOPPED;
}
}
C.4. PACKAGE EPOINT.SALE
505
510
515
520
525
530
535
540
545
550
555
560
565
570
575
580
585
590
/**
* Resumes the Shop, if it is currently suspended.
*/
public static void resume() {
throw new RuntimeException("Feature not implemented yet!");
}
/**
* Suspends this Shop.
*/
public static void suspend() {
throw new RuntimeException("Feature not implemented yet!");
}
/**
* Returns the XMLConverter that shall be used with the Shop. The default
* is a converter that converts Objects to XML and back.
*
* @return the XMLConverter to be used for the entire application (also EPoints)
*/
public static epoint.xml.XMLConverter getXMLConverter() {
return xmlConverter;
}
/**
* Returns the Serializer that is used with the Shop. The standard-Serializer
* uses the default-XMLConverter to convert XML-Strings into objects and back.
*
* @return the Serializer for the entire application for creating persistent
*
states of objects
*/
public static Serializer getPersistenceSerializer() {
return sSerializer;
}
////////////////////////////////////////////////////////////////////////////////
//
NEW INSTANCE METHODS
////////////////////////////////////////////////////////////////////////////////
/**
* Returns the PersistenceManager for this Shop which allows to
* make all data persistent
*
* @return the PersistenceManager for this Shop
*/
public epoint.db.DBPersistenceManager getDBPersistenceManager() {
try {
if (dbcon == null) {
dbcon = new epoint.db.PSQLDBConnection(lPersistenceLog,null);
}
} catch (Exception e) {
logException(e);
return null;
}
return dbcon;
}
/**
* Sets a new persistent property which may later be retrieved by it key.
* One must be aware, that keys starting with {@link #SYSTEM_PROPERTY_PREFIX}
* are reserved for framework-internal data!
*
* @param sKey the key for which to store a value
* @param sValue the value that may be retrieved by its key
*/
public void setPersistentProperty(String sKey, String sValue) {
try {
this.getDBPersistenceManager().setProperty(sKey,sValue);
} catch (Exception e) {
logException(e);
}
}
/**
* Returns a persistent property by its key.
*
* @param the key of the property
* @return the value of the property
*/
public String getPersistentProperty(String sKey) {
try {
return this.getDBPersistenceManager().getProperty(sKey);
} catch (Exception e) {
logException(e);
}
return null;
333
334
}
595
600
605
610
615
620
625
630
635
640
645
650
655
660
665
670
675
/**
* Deletes a persistent property - so it is no longer persistent and may
* not be retrieved by getPersistentProperty()
*
* @param the key of the of the property
*/
public void deletePersistentProperty(String sKey) {
try {
this.getDBPersistenceManager().deleteProperty(sKey);
} catch (Exception e) {
logException(e);
}
}
/**
* Returns the ShopServices for this Shops instance and the given EPoint.
* The ShopServices may be extended to satisfy the programmers requirements.
* One must consider, if access to specific methods should be granted or not
* based on the EPointInfo (eg. if every EPoint is allowed to delete any Catalog...)<br>
* This way different services for different clients may be offered.<br>
* That means that the EPointInfo represents the identity of the client to
* the Shop.
*
* @param epi the EPointInfo for which to return the ShopServices
* @return the ShopServices as provided for the EPoint
*/
public ShopServices getShopServices(EPointInfo epi) {
try {
return new ShopServicesImpl(epi);
} catch (java.rmi.RemoteException e) {
e.printStackTrace();
System.out.println(e);
return null;
}
}
/**
* Returns the default object that is exported via the RMI-registry to other
* clients. Using this object other clients may identify themselves to the Shop
* which may then offer its Shop-Services or not.
*
* @return the ShopIdentServices for this application
*/
public ShopServicesIdent getIdentServices() {
try {
return new ShopServicesIdentImpl();
} catch (java.rmi.RemoteException e) {
e.printStackTrace();
System.out.println(e);
return null;
}
}
/**
* This returns a global unique long-number which is always unique,
* when using this method with the same persistence manager. The number
* remains unique even after stopping and restarting the Shop without
* re-initialization of the PersistenceManager.
*
* @return an application-wide unique long
*/
public long getGlobalUniqueNumber() {
String sLong = getPersistentProperty(this.UNIQUE_NUMBER_PROPERTY);
if ((sLong == null) || (sLong == "")) {
setPersistentProperty(this.UNIQUE_NUMBER_PROPERTY,""+(Long.MIN_VALUE+1));
return Long.MIN_VALUE;
} else {
long lLong = Long.parseLong(sLong);
long lLongNew = lLong + 1;
setPersistentProperty(this.UNIQUE_NUMBER_PROPERTY,""+lLongNew);
return lLong;
}
}
/**
* Logs an exception to the standard-exception-log, which may be set in the
* constructor.
*
* @param e the Exception to be logged
*/
public void logException(Exception e) {
e.printStackTrace();
java.io.StringWriter sw = new java.io.StringWriter();
e.printStackTrace(new java.io.PrintWriter(sw));
StackTraceElement ste = e.getStackTrace()[0];
ANHANG C. QUELLTEXTE
C.4. PACKAGE EPOINT.SALE
680
}
lErrorLog.write(new LogEntry(sw.toString(),e.getClass().getName()+" occured in "+ste.getClassName()+"."+ste.getMethodName()+" line "+
ste.getLineNumber(),LogEntry.LOG_TYPE_ERROR,LogEntry.LOG_LEVEL_ERROR));
/**
* Logs an information to the standard-information-log, which may be set in the
* constructor.
*
* @param sSource the source of the information
* @param sInfo the information
* @param iLogEntryLevel the level of the information
*/
public void logInfo(String sSource, String sInfo, int iLogEntryLevel) {
lInfoLog.write(new LogEntry(sSource,sInfo,LogEntry.LOG_TYPE_INFO,iLogEntryLevel));
}
685
690
695
/**
* Returns a String representation of this Shop.
*
* @return a String describing this Shop
*/
public String toString() {
return "EPoint Shop version 1.0 running at runlevel "+getRunLevel()+"\n"+
"Serving ShopIdentServices at "+getRMIServiceURL();
}
700
705
/**
* Returns the Shops default connection to the database.
*
* @return the default connection to the database
*/
public Connection getDefaultConnection() {
return Shop.getTheShop().getDBPersistenceManager().getPrivateConnection("Default Connection for class "+getClass().getName(),getClass
());
}
710
/**
* Returns a connection to the database, that will be protected by the given
* password.
*
* @param sConPasswd the password for the connection
* @return the requested connection for exclusive use
*/
public Connection getReservedConnection(String sConPasswd) {
return Shop.getTheShop().getDBPersistenceManager().getPrivateConnection(sConPasswd,getClass());
}
715
720
725
/**
* Returns the given Connection to the DBPersistenceManager
*
* @param con the connection to return
*/
public void returnConnection(Connection con) {
Shop.getTheShop().getDBPersistenceManager().returnPrivateConnection(con);
}
730
}
Listing C.67: epoint.sale.ShopServices
5
10
15
20
25
335
package epoint.sale;
import java.rmi.*;
import epoint.data.*;
import epoint.data.exception.*;
import epoint.log.*;
/**
* This interface defines the services granted by the Shop to other shops and
* EPoints. They are implemented as an Remote-interface and available via RMI.
* The methods defined in this interface must be implemented at least by
* the ShopServices. Extending interfaces may also extend the functionality
* provided to the EPoints.
*
* @author Danny Poppe
* @version 1.0
*/
public interface
ShopServices extends Remote, java.io.Serializable {
/**
* Returns an application-wide persistent unique number.
*
* @return a new unique long
* @throws RemoteException whenever an error occurs during the use of RMI
*/
public long getGlobalUniqueNumber() throws RemoteException;
336
30
35
40
45
50
55
60
65
70
75
80
85
90
95
100
105
110
ANHANG C. QUELLTEXTE
/**
* Returns the name of the InfoStore for the registered EPoint.
*
* @return the name of the InfoStore for the registering EPoint
* @throws RemoteException whenever an error occurs during the use of RMI
*/
public String getInfoStoreName() throws RemoteException;
/**
* Creates a new persistent Catalog with the given name at the Shop.
*
* @return a remote reference to the persistent Catalog
* @param sName the name of the new Catalog
* @throws DuplicateKeyException if the name already exists
* @throws RemoteException whenever an error occurs during the use of RMI
*/
public Catalog createPersistentCatalog(String sName) throws RemoteException, DuplicateKeyException;
/**
* Creates a new persistent CountingStock with the given name at the Shop.
*
* @return a remote reference to the persistent CountingStock
* @param sName the name for the new Stock
* @param cBaseCatalog the persistent Catalog, the new Stock shall be based on
* @throws DuplicateKeyException if the name already exists
* @throws RemoteException whenever an error occurs during the use of RMI
*/
public CountingStock createPersistentCountingStock(String sName, Catalog cBaseCatalog) throws RemoteException, DuplicateKeyException;
/**
* Creates a new persistent StoringStock with the given name at the Shop.
*
* @return a remote reference to the persistent StoringStock
* @param sName the name for the new Stock
* @param cBaseCatalog the persistent Catalog, the new Stock shall be based on
* @throws DuplicateKeyException if the name already exists
* @throws RemoteException whenever an error occurs during the use of RMI
*/
public StoringStock createPersistentStoringStock(String sName, Catalog cBaseCatalog) throws RemoteException, DuplicateKeyException;
/**
* Returns a persistent Catalog that already exists at the Shop with the
* given name.
*
* @param sName the name of the persistent Catalog
* @return a reference to the persistent Catalog
* @throws RemoteException whenever an error occurs during the use of RMI
*/
public Catalog getPersistentCatalog(String sName) throws RemoteException;
/**
* Returns a persistent Stock that already exists at the Shop with the
* given name.
*
* @param sName the name of the persistent Stock
* @return a reference to the persistent Stock
* @throws RemoteException whenever an error occurs during the use of RMI
*/
public Stock getPersistentStock(String sName) throws RemoteException;
/**
* Creates a new persistent DataBasket at the Shop.
*
* @return a remote reference to the persistent DataBasket
* @throws RemoteException whenever an error occurs during the use of RMI
*/
public DataBasket createPersistentDataBasket() throws RemoteException;
/**
* Returns a persistent DataBasket that already exists at the Shop with the
* given name.
*
* @param sBasketName the name of the persistent DataBasket
* @return a reference to the persistent DataBasket
* @throws RemoteException whenever an error occurs during the use of RMI
*/
public DataBasket getPersistentDataBasket(String sBasketName) throws RemoteException;
/**
* Creates a new CatalogItem with the given key and value at the Shop,
* that may be inserted into a Catalog.
*
* @param sKey the key of the new CatalogItem
* @param v the Value of the new CatalogItem
* @return a remote reference to the CatalogItem at the Shop
* @throws RemoteException whenever an error occurs during the use of RMI
*/
public CatalogItem createCatalogItem(String sKey, Value v) throws RemoteException;
/**
* Creates a new StockItem with the given key and value at the Shop,
* that may be inserted into a Stock.
*
* @param sKey the key of the new StockItem
* @param v the Value of the new StockItem
* @return a remote reference to the StockItem at the Shop
* @throws RemoteException whenever an error occurs during the use of RMI
C.4. PACKAGE EPOINT.SALE
115
*/
StockItem createStockItem(String sKey, Value v) throws RemoteException;
/**
* Returns a persistent Object that already exists at the Shop with the
* given OID.
*
* @param sOID the oid of the persistent Object
* @return a reference to the persistent Object
* @throws RemoteException whenever an error occurs during the use of RMI
*/
public PersistentObject getPersistentObject(String sOID) throws RemoteException;
/**
* Logs an event central at the Shop.
*
* @param epi the EPoints EPointInfo
* @param le the LogEntry that shall be logged by the Shop
* @throws RemoteException whenever an error occurs during the use of RMI
*/
public void logEvent(EPointInfo epi, LogEntry le) throws RemoteException;
public
120
125
130
135
}
Listing C.68: epoint.sale.ShopServicesIdent
5
/*
* ShopServicesIdent.java
*
* Created on 28. August 2001, 16:36
*/
package
10
15
20
25
30
35
import
import
import
epoint.sale;
java.rmi.*;
epoint.sale.exception.PermissionDeniedException;
epoint.sale.EPointInfo;
/**
* This interface defines the only way to gain access to Shop. As such it
* defines a method that must be called to register with the Shop. As the
* parameter is evaluated, the Shop decides whether to allow access or not.
* The ShopServicesIdent is the only RemoteObject served by the Shop, using
* the rmiregistry.
*
* @see ShopServices
* @author Danny Poppe
* @version 1.0
*/
public interface ShopServicesIdent extends java.rmi.Remote, java.io.Serializable {
/**
* Allows an EPoint to gain access to Shop, using its EPointInfo to
* identify itself.
*
* @param id the data that the EPoint provides to identify itself
* @return the ShopServices of the Shop, if access is allowed
* @throws RemoteException whenever an error occurs during the use of RMI
* @throws PermissionDeniedException if this method decides to decline
*
access to Shop - based on the provided information
*/
public ShopServices identify(EPointInfo id) throws RemoteException, PermissionDeniedException;
}
Listing C.69: epoint.sale.Transition
5
/*
* Transition.java
*
* Created on 3. August 2001, 17:46
*/
package
10
15
20
epoint.sale;
/**
* A transition is used to evaluate the data modified or provided at one Gate
* to calculate teh next Gate to switch to.
*
* @see Gate
* @see EPointProcess
* @author Danny Poppe
* @version 1.0
*/
public interface Transition {
/**
* Returns the process in which this transition is registered.
*
337
338
* @return the process of this transition
*/
EPointProcess getProcess();
/**
* Returns the Gate upon this transition is based (eg. the Gate from which
* this transition comes)
*
* @return the current Gate
*/
public Gate getGate();
/**
* Based upon {@link #getGate()} this method decides where to go in the next
* step, when leaving the transition.
*
* @return the Gate to go
*/
public Gate perform();
public
25
30
35
40
ANHANG C. QUELLTEXTE
}
C.4.1 Package epoint.sale.exception
epoint.sale.exception.NoRunningShopException.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 338
epoint.sale.exception.PermissionDeniedException.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .338
Listing C.70: epoint.sale.exception.NoRunningShopException
5
/*
* NoRunningShopException.java
*
* Created on 6. September 2001, 14:15
*/
package
10
15
/**
*
* @author Danny Poppe
* @version 1.0
*/
public class NoRunningShopException
extends
java.lang.Exception {
/**
* Creates new <code>NoRunningShopException</code> without detail message.
*/
public NoRunningShopException() {
}
20
25
30
epoint.sale.exception;
}
/**
* Constructs an <code>NoRunningShopException</code> with the specified detail message.
* @param msg the detail message.
*/
public NoRunningShopException(String msg) {
super(msg);
}
Listing C.71: epoint.sale.exception.PermissionDeniedException
5
/*
* PermissionDeniedException.java
*
* Created on 5. September 2001, 14:43
*/
package
10
epoint.sale.exception;
/**
*
* @author Danny Poppe
* @version 1.0
*/
public class PermissionDeniedException
extends
java.lang.Exception {
C.4. PACKAGE EPOINT.SALE
15
/**
* Creates new <code>PermissionDeniedException</code> without detail message.
*/
private PermissionDeniedException() {
}
20
25
30
339
}
/**
* Constructs an <code>PermissionDeniedException</code> with the specified detail message.
* @param msg the detail message.
*/
public PermissionDeniedException(String msg) {
super(msg);
}
C.4.2 Package epoint.sale.rmi
epoint.sale.rmi.EPointInfoImpl.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 339
epoint.sale.rmi.ShopServicesIdentImpl.java. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .341
epoint.sale.rmi.ShopServicesImpl.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .341
Listing C.72: epoint.sale.rmi.EPointInfoImpl
package
5
10
15
20
25
30
35
40
45
50
import
import
import
import
import
import
import
import
import
import
import
import
import
import
epoint.sale.rmi;
epoint.data.StringValue;
epoint.sale.EPointInfo;
epoint.sale.EPointInfo;
epoint.sale.EPointInfo;
java.io.FileNotFoundException;
java.rmi.RemoteException;
java.rmi.server.UnicastRemoteObject;
java.util.Map;
java.util.Iterator;
java.util.List;
java.util.StringTokenizer;
java.io.*;
epoint.sale.ParamFile;
epoint.sale.*;
/**
* This is a default implementation for the EPointInfo interface and provides
* additional methods to set additional properties that may be checked by the Shop.
*
* @see ShopServicesIdent
* @see ShopServices
* @author Danny Poppe
* @version 1.0
*/
public class
EPointInfoImpl
extends
UnicastRemoteObject
implements
EPointInfo {
/**
* Contains a mapping of key-value pairs of the properties for this
* EPointInfo.
*/
private Map params = java.util.Collections.synchronizedMap(new java.util.HashMap());
/**
* Creates a new EPointInfo for an EPoint of the given name with the given
* description.
*
* @param sEPointName the name of the described EPoint
* @param sDescription a brief description of the described EPoint
* @throws RemoteException whenever an error occurs during the use of RMI
*/
private EPointInfoImpl (String sEPointName, String sDescription) throws RemoteException {
super ();
// Minimum-parameters
setParam(this.EPOINTNAME_KEY.toLowerCase(),sEPointName);
setParam(this.DESCRIPTION_KEY.toLowerCase(),sDescription);
}
340
55
60
65
70
75
80
85
90
95
100
ANHANG C. QUELLTEXTE
/**
* Creates a new EPointInfo, using the parameters defined in the
* given parameter file.
*
* @param sParamFilePath the path to the parameter-file
* @throws RemoteException whenever an error occurs during the use of RMI
*/
public EPointInfoImpl (String sParamFilePath) throws RemoteException {
this("Default EPoint","not given");
ParamFile pfParam = new ParamFile(sParamFilePath);
Iterator it = pfParam.getParamList().iterator();
while (it.hasNext()) {
String sKey = (String)it.next();
setParam(sKey, pfParam.getParam(sKey));
}
}
/**
* Creates a new EPointInfo, using the key-value mappings in the provided Map.
*
* @param mParamStrings a mapping of key-value pairs for the properties
*
of this EPointInfo
* @throws RemoteException whenever an error occurs during the use of RMI
*/
public EPointInfoImpl (Map mParamStrings) throws RemoteException {
this("Default EPoint","not given");
setParams (mParamStrings);
}
/**
* Sets the specified property.
*
* @param sKey the property-name
* @param sValue the value of the property
*/
public void setParam(String sKey, String sValue) {
params.put(sKey,sValue);
}
/**
* Adds the parameters defined in the key-value mapping of the given
* Map to this EPointInfo.
*
* @param mParamStrings adds the parameters in the given Map to this EPointInfo
*/
private void setParams (Map mParamStrings) {
params.putAll(mParamStrings);
}
public String getDescription() throws RemoteException {
return (String)params.get(DESCRIPTION_KEY.toLowerCase());
}
105
public
try
110
115
}
String getHostName() throws RemoteException {
{
java.net.InetAddress ia = java.net.InetAddress.getByName(getClientHost());
return ia.getHostAddress();
} catch (Exception e) {
e.printStackTrace();
System.out.println(e);
return null;
}
public String getEPointName() throws RemoteException {
return (String)params.get(EPOINTNAME_KEY.toLowerCase());
}
120
public String getParam(String sKey) throws RemoteException
return (String)params.get(sKey.toLowerCase());
{
}
125
130
135
public
}
List getParamList() throws RemoteException {
java.util.Iterator it = this.params.keySet().iterator();
java.util.LinkedList ll = new java.util.LinkedList();
while (it.hasNext()) ll.add(it.next());
return ll;
public int getGateCount() throws RemoteException {
try{
return Integer.parseInt(getParam(GATECOUNT_KEY.toLowerCase()));
} catch (Exception e) {
return -1;
}
}
C.4. PACKAGE EPOINT.SALE
341
}// EPointInfoImpl
Listing C.73: epoint.sale.rmi.ShopServicesIdentImpl
5
/*
* ShopServicesIdentImpl.java
*
* Created on 28. August 2001, 16:37
*/
package
10
15
20
25
import
import
import
import
import
import
import
epoint.sale.rmi;
java.rmi.*;
java.rmi.server.*;
java.rmi.registry.*;
java.sql.Connection;
java.net.MalformedURLException;
epoint.sale.*;
epoint.sale.exception.*;
/**
* This is the default implementation for the ShopServivesIdent interface and
* allows all EPoints access to the ShopServices.
*
* @author Danny Poppe
* @version 1.0
*/
public class ShopServicesIdentImpl extends java.rmi.server.UnicastRemoteObject
implements
ShopServicesIdent {
/**
* Creates a new ShopServicesIdentImpl that allows all EPoints
* access to the default ShopServices.
*/
public ShopServicesIdentImpl() throws RemoteException {
}
30
public boolean isRegistered(EPointInfo epi) throws RemoteException {
try {
return Shop.getTheShop().getDBPersistenceManager().isRegisteredEPoint(epi);
} catch (Exception e) {
35
40
}
}
e.printStackTrace();
System.out.println(e);
return false;
public
45
50
55
}
ShopServices identify(EPointInfo id) throws RemoteException, PermissionDeniedException {
System.out.println("New EPoint registered \""+id.getEPointName()+"\" from "+id.getHostName());
try {
if (Shop.getTheShop().getDBPersistenceManager().isRegisteredEPoint(id)) {
System.out.println("\tpersistent data available!");
} else {
Shop.getTheShop().getDBPersistenceManager().registerEPoint(id);
System.out.println("\tno persistent data available - newly registered!");
}
} catch (epoint.db.exception.DBAccessException dba) {
dba.printStackTrace();
System.out.println(dba);
}
return Shop.getTheShop().getShopServices(id);
private Connection getDefaultConnection() {
return Shop.getTheShop().getDBPersistenceManager().getPrivateConnection("Default
60
());
}
Connection for class "+getClass().getName(),getClass
private Connection getReservedConnection(String sConPasswd) {
return Shop.getTheShop().getDBPersistenceManager().getPrivateConnection(sConPasswd,getClass());
65
}
private void
70
}
returnConnection(Connection con) {
Shop.getTheShop().getDBPersistenceManager().returnPrivateConnection(con);
}
Listing C.74: epoint.sale.rmi.ShopServicesImpl
5
/*
* ShopServicesImpl.java
*
* Created on 28. August 2001, 16:33
*/
342
ANHANG C. QUELLTEXTE
package
10
15
20
25
30
35
40
import
import
import
import
import
import
import
import
import
import
import
epoint.sale.rmi;
java.rmi.*;
java.rmi.server.*;
java.rmi.registry.*;
java.net.MalformedURLException;
java.sql.Connection;
epoint.sale.*;
epoint.data.*;
epoint.data.rmi.*;
epoint.data.exception.*;
epoint.log.*;
epoint.db.exception.DBAccessException;
/**
* This is the default implementation of the ShopServices interface and uses
* default-mechanisms to provide the required functionality.
*
* @author Danny Poppe
* @version 1.0
*/
public class ShopServicesImpl extends java.rmi.server.UnicastRemoteObject implements ShopServices {
/**
* The EPointInfo of the EPoint that has access to this Shopservices.
*/
private EPointInfo epi;
/**
* Constructs ShopServicesImpl object and exports it on default port.
*/
public ShopServicesImpl(EPointInfo epi) throws RemoteException {
super();
if (!Shop.isServerApplication() || EPoint.isClientApplication()) throw new RuntimeException("The ShopServices can only be created in
a pure server-environment!");
this.epi = epi;
}
public DataBasket createPersistentDataBasket() throws
return new DataBasketImpl();
45
RemoteException {
}
public Catalog createPersistentCatalog(String
return new CatalogImpl(sName);
sName)
throws
RemoteException, DuplicateKeyException {
}
50
public
55
60
65
70
}
Catalog getPersistentCatalog(String sName) throws RemoteException {
Connection con = null;
try {
con = Shop.getTheShop().getDBPersistenceManager().getPrivateConnection("ShopServicesImpl.getPersistentCatalog",this.getClass());
Catalog c = Shop.getTheShop().getDBPersistenceManager().getCatalog(sName,con);
return c;
} catch (DBAccessException e) {
Shop.getTheShop().logException(e);
return null;
} nally {
try {
Shop.getTheShop().getDBPersistenceManager().returnPrivateConnection(con);
} catch (Exception e) {
Shop.getTheShop().logException(e);
}
}
public long getGlobalUniqueNumber() throws RemoteException
return Shop.getTheShop().getGlobalUniqueNumber();
public CatalogItem createCatalogItem(String
return new CatalogItemImpl(sKey,v);
75
{
}
sKey, Value v)
throws
RemoteException {
}
public StockItem createStockItem(String
return new StockItemImpl(sKey,v);
sKey, Value v)
throws
RemoteException {
}
80
public
try
}
85
}
90
String getInfoStoreName()
{
throws
RemoteException {
return Shop.getTheShop().getDBPersistenceManager().getInfoStoreCatalog(epi).getName();
catch (Exception e) {
}
public
Shop.getTheShop().logException(e);
return null;
DataBasket getPersistentDataBasket(String sName) throws RemoteException {
Connection con = null;
try {
con = Shop.getTheShop().getDBPersistenceManager().getPrivateConnection("getPersistentDataBasket",getClass());
C.5. PACKAGE EPOINT.TEST
}
95
100
}
105
343
return Shop.getTheShop().getDBPersistenceManager().getDataBasket(sName,con);
catch (Exception e) {
Shop.getTheShop().logException(e);
return null;
} nally {
try {
Shop.getTheShop().getDBPersistenceManager().returnPrivateConnection(con);
} catch (Exception e) {
Shop.getTheShop().logException(e);
}
}
public
110
115
120
}
PersistentObject getPersistentObject(String sOID) throws RemoteException {
Connection con = null;
try {
con = Shop.getTheShop().getDBPersistenceManager().getPrivateConnection("getPersistentObject",getClass());
return Shop.getTheShop().getDBPersistenceManager().getPersistentObject(sOID,con);
} catch (Exception e) {
Shop.getTheShop().logException(e);
return null;
} nally {
try {
Shop.getTheShop().getDBPersistenceManager().returnPrivateConnection(con);
} catch (Exception e) {
Shop.getTheShop().logException(e);
}
}
public void logEvent(EPointInfo epi,
throw new RuntimeException("Not
125
}
LogEntry le) throws RemoteException {
implemented yet!");
public StoringStock createPersistentStoringStock(String
return new StoringStockImpl(sName, cBaseCatalog);
sName, Catalog cBaseCatalog)
throws
RemoteException, DuplicateKeyException {
}
130
public CountingStock createPersistentCountingStock(String
return new CountingStockImpl(sName, cBaseCatalog);
sName, Catalog cBaseCatalog)
throws
RemoteException, DuplicateKeyException {
}
public
135
140
145
150
}
Stock getPersistentStock(String sName) throws RemoteException {
Connection con = null;
try {
con = Shop.getTheShop().getDBPersistenceManager().getPrivateConnection("getPersistentStock",getClass());
return Shop.getTheShop().getDBPersistenceManager().getStock(sName,con);
} catch (Exception e) {
Shop.getTheShop().logException(e);
return null;
} nally {
try {
Shop.getTheShop().getDBPersistenceManager().returnPrivateConnection(con);
} catch (Exception e) {
Shop.getTheShop().logException(e);
}
}
}
C.5 Package epoint.test
C.5.1 Package epoint.test.server
epoint.sale.exception.NoRunningShopException.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 338
epoint.sale.exception.PermissionDeniedException.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 338
epoint.sale.rmi
344
ANHANG C. QUELLTEXTE
epoint.test.server.MyShop.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .344
Listing C.75: epoint.test.server.MyShop
5
/*
* MyShop.java
*
* Created on 6. September 2001, 09:51
*/
package epoint.test.server;
import javax.swing.*;
10
15
/**
*
* @author Danny Poppe
* @version 1.0
*/
public class MyShop extends epoint.sale.Shop{
public static void
20
25
30
35
40
}
45
main(String args[]) throws Exception {
System.out.println("Setting the Shop...");
setTheShop(new MyShop("epoint/shop.param"));
System.out.println("Using Database named \""+epoint.db.PSQLDBConnection.DEFAULT_BASE+"\" at "+epoint.db.PSQLDBConnection.DEFAULT_HOST);
System.out.println("Running Shop at "+getRMIServiceHost());
System.out.println("Starting the Shop...");
getTheShop().start();
int iSelection = -1;
while (iSelection != 0) {
iSelection = JOptionPane.showOptionDialog(null,
"Select Option for the Shop!",
"Shop is running...",
JOptionPane.YES_NO_OPTION,
JOptionPane.INFORMATION_MESSAGE,
null,
new String[]{"Close Shop","Reset DataBase"},
"Reset DataBase");
if (iSelection == 1) {
getTheShop().getDBPersistenceManager().reset(getTheShop().getDefaultConnection());
getTheShop().returnConnection(getTheShop().getDefaultConnection());
}
}
epoint.log.Log.closeAllLogs();
getTheShop().shutdown();
System.out.println("Finished!");
System.exit(0);
private MyShop(String
super (sFile);
sFile) {
}
50
}
C.5.2 Package epoint.test.client
epoint.test.client.SampleCatalogListener.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 344
epoint.test.client.TestEPoint.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 345
Listing C.76: epoint.test.client.SampleCatalogListener
5
10
/*
* SampleCatalogListener.java
*
* Created on 16. Oktober 2001, 13:51
*/
package epoint.test.client;
import epoint.data.rmi.*;
import epoint.data.*;
import epoint.log.*;
import java.rmi.*;
C.5. PACKAGE EPOINT.TEST
import
import
15
20
345
epoint.data.exception.VetoException;
javax.swing.*;
/**
*
* @author Danny Poppe
* @version 1.0
*/
public class SampleCatalogListener
private
extends
ContainerListenerAdapter {
Catalog cRegistered;
public
25
}
SampleCatalogListener(Catalog c) throws RemoteException {
cRegistered = c;
((ListenableContainer)c).addContainerListener(this);
public void
30
}
addedItem(ContainerChangeEvent e) throws RemoteException {
//JOptionPane.showMessageDialog(null,"An item was added: "+((CatalogItem)e.getAffectedItem()).getKey()+" to Catalog "+((Catalog)e.
getTransactionTarget()).getName()+" using DataBasket: "+((e.getBasket() == null)?("none"):(e.getBasket().getName()))
,"SampleContainerListener",JOptionPane.INFORMATION_MESSAGE);
public void
35
}
canRemoveTransactionItem(ContainerChangeEvent e) throws RemoteException, VetoException {
//int iDecision = JOptionPane.showConfirmDialog(null,"Do you want to allow an item to be removed: "+((CatalogItem)e.getAffectedItem()).
getKey()+" from Catalog "+((Catalog)e.getTransactionTarget()).getName()+" using DataBasket: "+((e.getBasket() == null
)?("none"):(e.getBasket().getName())),"SampleContainerListener",JOptionPane.YES_NO_OPTION);
//if (iDecision == JOptionPane.NO_OPTION) throw new VetoException("The user was asked, whether to allow this removal and he doesn't
want this!");
}
Listing C.77: epoint.test.client.TestEPoint
5
10
15
20
25
30
35
40
/*
* TestEPoint.java
*
* Created on 6. September 2001, 09:53
*/
package epoint.test.client;
import epoint.sale.*;
import epoint.sale.rmi.*;
import epoint.sale.exception.*;
import epoint.data.rmi.*;
import epoint.data.*;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.Iterator;
/**
*
* @author Danny Poppe
* @version 1.0
*/
public class TestEPoint
private nal static
public
public
public
public
public
public
nal
nal
nal
nal
nal
nal
static
static
static
static
static
static
extends
EPoint {
String EPOINT_NAME = "Default EPoint";
String
String
String
String
String
String
PERSISTENT_PROPERTY_BASKET1 = "DataBasket 1";
PERSISTENT_PROPERTY_BASKET2 = "DataBasket 2";
CATALOG1_NAME = "Test Catalog 1";
CATALOG2_NAME = "Test Catalog 2";
COUNTINGSTOCK_NAME = "Test CountingStock";
STORINGSTOCK_NAME = "Test StoringStock";
private
private
private
private
private
private
private
private
DataBasket db1 = null;
DataBasket db2 = null;
Catalog c1 = null;
Catalog c2 = null;
CountingStock cs = null;
StoringStock ss = null;
CatalogItem ci = null;
StockItem si = null;
private
RemoteIterator it =
null;
45
private static JFrame jf = null;
private JPanel jpMain = null;
private JTextArea jtaMain = null;
50
private JPanel getStockPanel(Stock s) throws
nal JPanel jpMain = new JPanel();
nal JPanel jpSelector = new JPanel();
Exception {
346
ANHANG C. QUELLTEXTE
nal
nal
nal
55
JPanel jpBasketSelector = new JPanel();
JPanel jpKeySelector = new JPanel();
JPanel jpItemEditor = new JPanel();
jpMain.setLayout(new BoxLayout(jpMain,BoxLayout.Y_AXIS));
jpSelector.setLayout(new BoxLayout(jpSelector,BoxLayout.X_AXIS));
jpBasketSelector.setLayout(new BoxLayout(jpBasketSelector,BoxLayout.X_AXIS));
jpKeySelector.setLayout(new BoxLayout(jpKeySelector,BoxLayout.X_AXIS));
60
String sName = ((s.isCountingStock())?("CountingStock"):("StoringStock"))+s.getName();
nal
nal
nal
nal
String NO_BASKET = "No DataBasket";
String BASKET1 = "DataBasket 1";
String BASKET2 = "DataBasket 2";
JComboBox jcbBasket = new JComboBox(new Object[]{NO_BASKET,BASKET1,BASKET2});
jcbBasket.setEditable(false);
jcbBasket.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
JComboBox jcb = (JComboBox)e.getSource();
DataBasket dbSelected = null;
switch (jcb.getSelectedIndex()) {
case 0: dbSelected = null; break;
case 1: dbSelected = db1; break;
case 2: dbSelected = db2; break;
}
// update keys, editor
}
});
65
70
75
80
DataBasket dbSelected = null;
switch (jcbBasket.getSelectedIndex()) {
case 0: dbSelected = null; break;
case 1: dbSelected = db1; break;
case 2: dbSelected = db2; break;
85
}
nal JComboBox jcbItem = new JComboBox(s.keySet(dbSelected).toArray());
jcbItem.setEditable(false);
jcbItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
JComboBox jcb = (JComboBox)e.getSource();
String sKey = (String)jcb.getSelectedItem();
// update items and size for key
}
});
90
95
100
105
110
115
jpMain.add(new JLabel(sName));
jpMain.add(jpSelector);
jpMain.add(jpItemEditor);
}
jpMain;
private static void showDialog(String sTitle,String sMessage) throws Exception {
class Switch {
private boolean bSwitch = false;
private Object oSleeper = new Object();
public void switchOn() { bSwitch = true; synchronized (oSleeper) { oSleeper.notifyAll();}}
public void switchOff() { bSwitch = false; synchronized (oSleeper) {oSleeper.notifyAll();}}
public boolean isOn() { return bSwitch; }
public void sleep() throws Exception { synchronized(oSleeper) {oSleeper.wait();} }
120
125
130
135
return
}
}
JDialog jd = new JDialog(jf, false);
jd.setDefaultCloseOperation(jd.DO_NOTHING_ON_CLOSE);
jd.setTitle(sTitle);
jd.getContentPane().setLayout(new BorderLayout());
jd.getContentPane().add(new JLabel(sMessage),BorderLayout.NORTH);
JButton jb = new JButton("OK");
nal Switch sw = new Switch();
jb.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
sw.switchOn();
}
});
jd.getContentPane().add(jb,BorderLayout.SOUTH);
jd.pack();
jd.setLocation(300, 20);
jd.setVisible(true);
sw.sleep();
jd.dispose();
private void resetFrame(String
if (jf == null) {
jf = new JFrame("EPoint
sTitle,
Tests");
boolean
bNewFrame)
throws
Exception {
C.5. PACKAGE EPOINT.TEST
140
}
if
145
150
155
}
160
347
jpMain = new JPanel();
jf.getContentPane().add(new JScrollPane(jpMain));
jpMain.setLayout(new BoxLayout(jpMain,BoxLayout.Y_AXIS));
jf.setExtendedState(jf.MAXIMIZED_BOTH);
(bNewFrame) {
jtaMain = new JTextArea(10,60);
jtaMain.setEditable(false);
JPanel jpInside = new JPanel(new java.awt.BorderLayout());
jpInside.add(new JLabel(sTitle),java.awt.BorderLayout.NORTH);
jpInside.add(jtaMain,java.awt.BorderLayout.CENTER);
jpMain.add(jpInside);
}
jf.setTitle(sTitle);
jtaMain.setText("");
jf.setDefaultCloseOperation(jf.DO_NOTHING_ON_CLOSE);
jf.setVisible(true);
private void
}
println(String sContent) {
jtaMain.append(sContent+"\n");
private void
165
170
175
180
185
190
195
200
205
210
215
220
225
}
print(String sContent) {
jtaMain.append(sContent);
/**
* Creates new TestEPoint
*/
public TestEPoint(String sName) throws Exception {
super(Shop.getRMIServiceHost(), Shop.getRMIServiceName(), sName,
}
private void
new
EPointInfoImpl("epoint.param"));
showSummary() throws Exception {
resetFrame("Summary",true);
println("Without DataBasket:");
println("========================================================================================");
println("-> "+c1.toRemoteString());
println("Keyset: "+c1.keySet(null));
println("Size : "+c1.size(null));
it = c1.iterator(null,false);
while (it.hasNext()) {
CatalogItem ciTemp = (CatalogItem)it.next();
println("=> "+ciTemp.toRemoteString());
println("\tValue:
"+ciTemp.getValue());
println("\tBasket: "+((ciTemp.getBasket() != null)?(ciTemp.getBasket().toRemoteString()):("null")));
println("\tEditable: "+ciTemp.isEditable());
println("\t[oid:"+ciTemp.getOID()+"TransID:"+ciTemp.getTransactionItemIdentifier()+"]");
}
println("\n");
println("-> "+c2.toRemoteString());
println("Keyset: "+c2.keySet(null));
println("Size : "+c2.size(null));
it = c2.iterator(null,false);
while (it.hasNext()) {
CatalogItem ciTemp = (CatalogItem)it.next();
println("=> "+ciTemp.toRemoteString());
println("\tValue:
"+ciTemp.getValue());
println("\tBasket: "+((ciTemp.getBasket() != null)?(ciTemp.getBasket().toRemoteString()):("null")));
println("\tEditable: "+ciTemp.isEditable());
println("\t[oid:"+ciTemp.getOID()+"TransID:"+ciTemp.getTransactionItemIdentifier()+"]");
}
println("\n");
println("-> "+cs.toRemoteString());
println("Keyset: "+cs.keySet(null));
println("Size : "+cs.size(null));
Iterator itKeys = cs.keySet(null).iterator();
while (itKeys.hasNext()) {
String sKey = (String)itKeys.next();
println("\tSize of "+sKey+": "+cs.size(sKey,null));
}
it = cs.iterator(null,false);
while (it.hasNext()) {
StockItem siTemp = (StockItem)it.next();
println("=> "+siTemp.toRemoteString());
println("\tValue:
"+siTemp.getValue());
println("\tBasket:
"+((siTemp.getBasket() != null)?(siTemp.getBasket().toRemoteString()):("null")));
println("\tEditable: "+siTemp.isEditable());
println("\t[oid:"+siTemp.getOID()+"TransID:"+siTemp.getTransactionItemIdentifier()+"]");
}
println("\n");
println("-> "+ss.toRemoteString());
println("Keyset: "+ss.keySet(null));
println("Size : "+ss.size(null));
itKeys = ss.keySet(null).iterator();
while (itKeys.hasNext()) {
String sKey = (String)itKeys.next();
348
230
235
240
245
250
255
260
265
270
275
280
285
290
295
300
305
310
315
ANHANG C. QUELLTEXTE
println("\tSize of "+sKey+": "+ss.size(sKey,null));
}
it = ss.iterator(null,false);
while (it.hasNext()) {
StockItem siTemp = (StockItem)it.next();
println("=> "+siTemp.toRemoteString());
println("\tValue:
"+siTemp.getValue());
println("\tBasket:
"+((siTemp.getBasket() != null)?(siTemp.getBasket().toRemoteString()):("null")));
println("\tEditable: "+siTemp.isEditable());
println("\t[oid:"+siTemp.getOID()+"TransID:"+siTemp.getTransactionItemIdentifier()+"]");
}
println("\n\n");
println("With "+db1.toRemoteString());
println("========================================================================================");
println("-> "+c1.toRemoteString());
println("Keyset: "+c1.keySet(db1));
println("Size : "+c1.size(db1));
it = c1.iterator(db1,false);
while (it.hasNext()) {
CatalogItem ciTemp = (CatalogItem)it.next();
println("=> "+ciTemp.toRemoteString());
println("\tValue:
"+ciTemp.getValue());
println("\tBasket: "+((ciTemp.getBasket() != null)?(ciTemp.getBasket().toRemoteString()):("null")));
println("\tEditable: "+ciTemp.isEditable());
println("\t[oid:"+ciTemp.getOID()+"TransID:"+ciTemp.getTransactionItemIdentifier()+"]");
}
println("\n");
println("-> "+c2.toRemoteString());
println("Keyset: "+c2.keySet(db1));
println("Size : "+c2.size(db1));
it = c2.iterator(db1,false);
while (it.hasNext()) {
CatalogItem ciTemp = (CatalogItem)it.next();
println("=> "+ciTemp.toRemoteString());
println("\tValue:
"+ciTemp.getValue());
println("\tBasket:
"+((ciTemp.getBasket() != null)?(ciTemp.getBasket().toRemoteString()):("null")));
println("\tEditable: "+ciTemp.isEditable());
println("\t[oid:"+ciTemp.getOID()+"TransID:"+ciTemp.getTransactionItemIdentifier()+"]");
}
println("\n");
println("-> "+cs.toRemoteString());
println("Keyset: "+cs.keySet(db1));
println("Size : "+cs.size(db1));
itKeys = cs.keySet(db1).iterator();
while (itKeys.hasNext()) {
String sKey = (String)itKeys.next();
println("\tSize of "+sKey+": "+cs.size(sKey,db1));
}
it = cs.iterator(db1,false);
while (it.hasNext()) {
StockItem siTemp = (StockItem)it.next();
println("=> "+siTemp.toRemoteString());
println("\tValue:
"+siTemp.getValue());
println("\tBasket: "+((siTemp.getBasket() != null)?(siTemp.getBasket().toRemoteString()):("null")));
println("\tEditable: "+siTemp.isEditable());
println("\t[oid:"+siTemp.getOID()+"TransID:"+siTemp.getTransactionItemIdentifier()+"]");
}
println("\n");
println("-> "+ss.toRemoteString());
println("Keyset: "+ss.keySet(db1));
println("Size : "+ss.size(db1));
itKeys = ss.keySet(db1).iterator();
while (itKeys.hasNext()) {
String sKey = (String)itKeys.next();
println("\tSize of "+sKey+": "+ss.size(sKey,db1));
}
it = ss.iterator(db1,false);
while (it.hasNext()) {
StockItem siTemp = (StockItem)it.next();
println("=> "+siTemp.toRemoteString());
println("\tValue:
"+siTemp.getValue());
println("\tBasket: "+((siTemp.getBasket() != null)?(siTemp.getBasket().toRemoteString()):("null")));
println("\tEditable: "+siTemp.isEditable());
println("\t[oid:"+siTemp.getOID()+"TransID:"+siTemp.getTransactionItemIdentifier()+"]");
}
println("\n\n");
println("With "+db2.toRemoteString());
println("========================================================================================");
println("-> "+c1.toRemoteString());
println("Keyset: "+c1.keySet(db2));
println("Size : "+c1.size(db2));
it = c1.iterator(db2,false);
while (it.hasNext()) {
CatalogItem ciTemp = (CatalogItem)it.next();
println("=> "+ciTemp.toRemoteString());
println("\tValue:
"+ciTemp.getValue());
C.5. PACKAGE EPOINT.TEST
println("\tBasket:
"+((ciTemp.getBasket() != null)?(ciTemp.getBasket().toRemoteString()):("null")));
println("\tEditable: "+ciTemp.isEditable());
println("\t[oid:"+ciTemp.getOID()+"TransID:"+ciTemp.getTransactionItemIdentifier()+"]");
}
println("\n");
println("-> "+c2.toRemoteString());
println("Keyset: "+c2.keySet(db2));
println("Size : "+c2.size(db2));
it = c2.iterator(db2,false);
while (it.hasNext()) {
CatalogItem ciTemp = (CatalogItem)it.next();
println("=> "+ciTemp.toRemoteString());
println("\tValue:
"+ciTemp.getValue());
println("\tBasket: "+((ciTemp.getBasket() != null)?(ciTemp.getBasket().toRemoteString()):("null")));
println("\tEditable: "+ciTemp.isEditable());
println("\t[oid:"+ciTemp.getOID()+"TransID:"+ciTemp.getTransactionItemIdentifier()+"]");
}
println("\n");
println("-> "+cs.toRemoteString());
println("Keyset: "+cs.keySet(db2));
println("Size : "+cs.size(db2));
itKeys = cs.keySet(db2).iterator();
while (itKeys.hasNext()) {
String sKey = (String)itKeys.next();
println("\tSize of "+sKey+": "+cs.size(sKey,db2));
}
it = cs.iterator(db2,false);
while (it.hasNext()) {
StockItem siTemp = (StockItem)it.next();
println("=> "+siTemp.toRemoteString());
println("\tValue:
"+siTemp.getValue());
println("\tBasket:
"+((siTemp.getBasket() != null)?(siTemp.getBasket().toRemoteString()):("null")));
println("\tEditable: "+siTemp.isEditable());
println("\t[oid:"+siTemp.getOID()+"TransID:"+siTemp.getTransactionItemIdentifier()+"]");
}
println("\n");
println("-> "+ss.toRemoteString());
println("Keyset: "+ss.keySet(db2));
println("Size : "+ss.size(db2));
itKeys = ss.keySet(db2).iterator();
while (itKeys.hasNext()) {
String sKey = (String)itKeys.next();
println("\tSize of "+sKey+": "+ss.size(sKey,db2));
}
it = ss.iterator(db2,false);
while (it.hasNext()) {
StockItem siTemp = (StockItem)it.next();
println("=> "+siTemp.toRemoteString());
println("\tValue:
"+siTemp.getValue());
println("\tBasket:
"+((siTemp.getBasket() != null)?(siTemp.getBasket().toRemoteString()):("null")));
println("\tEditable: "+siTemp.isEditable());
println("\t[oid:"+siTemp.getOID()+"TransID:"+siTemp.getTransactionItemIdentifier()+"]");
}
320
325
330
335
340
345
350
355
360
365
370
375
380
385
390
395
400
}
println("\n\n");
/**
* This method is called if this is the first run of this EPoint and the Shop
* has no persistent data in the InfoStore for this EPoint!
*
* @throws Exception if any error occurs
*/
private void runNewEPoint() throws Exception {
start();
/**
* InfoStore Test
*
* We create and delete a property.
*/
resetFrame("Testing the InformationStore...",true);
showDialog("Next Test...","Starting EPoint the first time and checking basic Operations!");
println("Inserting new property: "+PERSISTENT_PROPERTY_BASKET1+" with value \"Property Test 1\"");
getInformationStore().addObject(PERSISTENT_PROPERTY_BASKET1,"Property Test 1");
println("Replacing the property with value \"Property Test 2\"");
getInformationStore().setObject(PERSISTENT_PROPERTY_BASKET1,"Property Test 2");
println("Deleting this property!");
getInformationStore().deleteProperty(PERSISTENT_PROPERTY_BASKET1);
getInformationStore().commit();
/**
* Creation Tests
*
* We create 2 DataBaskets, whose names are stored in the InfoStore.
* The we create two Catalogs. To one of them a RemoteListener
* is attached, that informs about addings of items and asks for allowance
* to remove items.
* Additionally a CountingStock is created for the first Catalog and
* a StoringStock for the second Catalog.
349
350
405
410
415
ANHANG C. QUELLTEXTE
*/
resetFrame("Creating DataBaskets and Catalogs...",true);
showDialog("Next Test...","Checking Remote-Creation of Catalogs and DataBaskets!");
println("Creating two new Databaskets and storing their names within the InformationStore!");
db1 = getShopServices().createPersistentDataBasket();
println("Created DataBasket db1: "+db1.getName());
db2 = getShopServices().createPersistentDataBasket();
println("Created DataBasket db2: "+db2.getName());
println("");
println("Storing their names in the InformationStore:");
getInformationStore().addString(PERSISTENT_PROPERTY_BASKET1,db1.getName());
getInformationStore().addString(PERSISTENT_PROPERTY_BASKET2,db2.getName());
println("\t"+PERSISTENT_PROPERTY_BASKET1+"::"+getInformationStore().getString(PERSISTENT_PROPERTY_BASKET1)+
"\n\t"+PERSISTENT_PROPERTY_BASKET2+"::"+getInformationStore().getString(PERSISTENT_PROPERTY_BASKET2));
420
425
430
435
440
445
450
455
460
465
470
475
480
485
490
println("");
println("Creating new Catalog \""+CATALOG1_NAME+"\" and \""+CATALOG2_NAME+"\"");
c1 = getShopServices().createPersistentCatalog(CATALOG1_NAME);
c2 = getShopServices().createPersistentCatalog(CATALOG2_NAME);
println("Creating and attaching a RemoteListener for "+CATALOG1_NAME);
new SampleCatalogListener(c1);
println("");
println("Creating a CountingStock \""+COUNTINGSTOCK_NAME+"\" based on "+c1.toRemoteString());
cs = getShopServices().createPersistentCountingStock(COUNTINGSTOCK_NAME,c1);
println("Creating a StoringStock \""+STORINGSTOCK_NAME+"\" based on "+c2.toRemoteString());
ss = getShopServices().createPersistentStoringStock(STORINGSTOCK_NAME,c2);
/**
* Adding CatalogItems Test
*
* Here we create 3 CatalogItems for each Catalog. The 3rd of each item
* is added using the first of our two DataBaskets.
* Afterwards we iterate through the first Catalog two times, showing
* the content. The first time without DataBasket (where only the first
* two added items should be visible - as the DataBasket has not committed
* yet) and the second time with the DataBasket, so that the 3rd item
* should also be visible.
*/
resetFrame("Adding items to a Catalog...",true);
showDialog("Next Test...","Checking Adding CatalogItems!");
println("Creating new CatalogItem \"Key#1\" with StringValue \"StringValue 1\"");
CatalogItem ciKey1 = getShopServices().createCatalogItem("Key#1",new StringValue("StringValue 1"));
println("Creating new CatalogItem \"Key#2\" with StringValue \"StringValue 2\"");
CatalogItem ciKey2 = getShopServices().createCatalogItem("Key#2",new StringValue("StringValue 2"));
println("Creating new CatalogItem \"Key#3\" with StringValue \"StringValue 3\"");
CatalogItem ciKey3 = getShopServices().createCatalogItem("Key#3",new StringValue("StringValue 3"));
println("\tadding "+ciKey1.toRemoteString()+" to "+c1.toRemoteString()+" without DataBasket");
c1.add(ciKey1);
println("\tadding "+ciKey2.toRemoteString()+" to "+c1.toRemoteString()+" without DataBasket");
c1.add(ciKey2);
println("\tadding "+ciKey3.toRemoteString()+" to "+c1.toRemoteString()+" using "+db1.toRemoteString());
c1.add(ciKey3,db1);
println("Creating new CatalogItem \"Key-alpha\" with StringValue \"StringValue alpha\"");
ciKey1 = getShopServices().createCatalogItem("Key-alpha",new StringValue("StringValue alpha"));
println("Creating new CatalogItem \"Key-beta\" with StringValue \"StringValue beta\"");
ciKey2 = getShopServices().createCatalogItem("Key-beta",new StringValue("StringValue beta"));
println("Creating new CatalogItem \"Key-gamma\" with StringValue \"StringValue gamma\"");
ciKey3 = getShopServices().createCatalogItem("Key-gamma",new StringValue("StringValue gamma"));
println("\tadding "+ciKey1.toRemoteString()+" to "+c2.toRemoteString()+" without DataBasket");
c2.add(ciKey1);
println("\tadding "+ciKey2.toRemoteString()+" to "+c2.toRemoteString()+" without DataBasket");
c2.add(ciKey2);
println("\tadding "+ciKey3.toRemoteString()+" to "+c2.toRemoteString()+" using "+db1.toRemoteString());
c2.add(ciKey3,db1);
/**
* Adding StockItems Test
*
* Now we want to add some items to the Stocks. We add to the CountingStock:
* to "Key#1": 1(without db) + 4(with db)
* to "Key#2": 9(without db)
* to "Key#3": 16(with db)
* And we add to the StoringStock:
* to "Key-alpha": 2(without db) + 1(with db)
* to "Key-beta" : 2(without db)
* to "Key-gamma": 1(with db)
*
* Finally we iterate through the Stocks with and without db to show the contents
*/
resetFrame("Adding items to the Stocks...",true);
showDialog("Next Test...","Checking adding items to a Stock!");
println("Creating new (Counting)StockItem \"Key#1\" without value");
StockItem csi1 = getShopServices().createStockItem("Key#1",null);
println("Creating new (Counting)StockItem \"Key#2\" without value");
C.5. PACKAGE EPOINT.TEST
495
500
505
510
351
StockItem csi2 = getShopServices().createStockItem("Key#2",null);
println("Creating new (Counting)StockItem \"Key#3\" without value");
StockItem csi3 = getShopServices().createStockItem("Key#3",null);
println("\n----------------\n");
println("Creating new
StockItem ssi1_1ndb =
println("Creating new
StockItem ssi1_2ndb =
println("Creating new
StockItem ssi1_3wdb =
StockItem \"Key-alpha\" with StringValue \"alpha-key #1 - no basket\"");
getShopServices().createStockItem("Key-alpha",new StringValue("alpha-key - no basket"));
StockItem \"Key-alpha\" with DecimalValue \"2.56\"");
getShopServices().createStockItem("Key-alpha",new DecimalValue(2.56));
StockItem \"Key-alpha\" with StringValue \"alpha-key #3 - with basket\"");
getShopServices().createStockItem("Key-alpha",new StringValue("alpha-key #3 - with basket"));
println("Creating new
StockItem ssi2_1ndb =
println("Creating new
StockItem ssi2_2ndb =
println("Creating new
StockItem ssi3_1wdb =
StockItem \"Key-beta\" without value without Basket");
getShopServices().createStockItem("Key-beta",null);
StockItem \"Key-beta\" with StringValue \"beta-Key without Basket\"");
getShopServices().createStockItem("Key-beta",new StringValue("beta-Key without Basket"));
StockItem \"Key-gamma\" with StringValue \"gamma-Key with Basket\"");
getShopServices().createStockItem("Key-gamma",new StringValue("gamma-Key with Basket"));
println("\n----------------\n");
515
520
525
println("Adding "+csi1.toRemoteString()+" to "+cs.toRemoteString()+" without DataBasket");
cs.add(csi1);
println("Adding "+csi2.toRemoteString()+" to "+cs.toRemoteString()+" without DataBasket");
cs.add(csi2);
println("Adding "+csi3.toRemoteString()+" to "+cs.toRemoteString()+" using "+db1.toRemoteString());
cs.add(csi3,db1);
println("Adding 4 items with key \"Key#1\" to "+cs.toRemoteString()+" using "+db1.toRemoteString());
cs.add("Key#1",4,db1);
println("Adding 8 items with key \"Key#2\" to "+cs.toRemoteString()+" without DataBasket");
cs.add("Key#2",8);
println("Adding 16 items with key \"Key#3\" to "+cs.toRemoteString()+" using "+db1.toRemoteString());
cs.add("Key#3",16,db1);
println("\n----------------\n");
530
535
540
545
550
555
560
565
570
575
println("Adding "+ssi1_1ndb.toRemoteString()+" to "+ss.toRemoteString()+" without DataBasket");
ss.add(ssi1_1ndb);
println("Adding "+ssi1_2ndb.toRemoteString()+" to "+ss.toRemoteString()+" without DataBasket");
ss.add(ssi1_2ndb);
println("Adding "+ssi1_3wdb.toRemoteString()+" to "+ss.toRemoteString()+" with "+db1.toRemoteString());
ss.add(ssi1_3wdb,db1);
println("Adding "+ssi2_1ndb.toRemoteString()+" to "+ss.toRemoteString()+" without DataBasket");
ss.add(ssi2_1ndb);
println("Adding "+ssi2_2ndb.toRemoteString()+" to "+ss.toRemoteString()+" with "+db1.toRemoteString());
ss.add(ssi2_2ndb);
println("Adding "+ssi3_1wdb.toRemoteString()+" to "+ss.toRemoteString()+" with "+db1.toRemoteString());
ss.add(ssi3_1wdb,db1);
//
// Here we give a Summary over all content!
//
showSummary();
//
// Removing CatalogItems Test
//
resetFrame("Removing items from a Catalog...",true);
showDialog("Next Test...","Checking removing CatalogItems!");
println("Removing \"Key-alpha\" from "+c2.toRemoteString()+" using "+db1.toRemoteString());
println("\t=> "+c2.remove("Key-alpha",db1).toRemoteString());
println("Removing Item \"Key#3\" from "+c1.toRemoteString()+" using "+db1.toRemoteString());
println("\t=> "+c1.remove("Key#3",db1).toRemoteString());
//
// Editing Test
//
resetFrame("Editing items ...",true);
showDialog("Next Test...","Checking editing Items!");
println("Setting value of \"Key-beta\" in "+c2.toRemoteString()+" to StringValue \"EDITED!\", using "+db1.toRemoteString());
ci = c2.get("Key-beta",db1,true);
ci.setValue(new StringValue("EDITED!"));
println("Setting value of \"Key-beta\" for 2 items in "+ss.toRemoteString()+" to StringValue \"EDITED!\", using "+db1.toRemoteString())
;
it = ss.get("Key-beta",true,db1);
int iCount = 0;
while (it.hasNext() && (iCount < 2)) {
StockItem si = (StockItem)it.next();
si.setValue(new StringValue("EDITED!"));
iCount++;
}
println("Adding 1 item of key Key#2 to "+cs.toRemoteString()+" using "+db1.toRemoteString());
cs.add("Key#2",1,db1);
352
ANHANG C. QUELLTEXTE
println("Adding 2 items of key Key#2 to "+cs.toRemoteString()+" using "+db2.toRemoteString());
cs.add("Key#2",2,db2);
580
//
// Here we give a Summary over all content!
//
showSummary();
//
// Rollback Test
//
showDialog("Next Test...","Checking DataBasket Rollback");
resetFrame("DataBasket rollback-Test...",true);
585
590
println("Rolling back "+db1.toRemoteString());
db1.rollback();
//
// Here we give a Summary over all content!
//
showSummary();
//
// Commit after shutdown
//
resetFrame("DataBasket-commit after Shutdown of this EPoint",true);
showDialog("Next Test...","Checking a DatabasketCommit after Shutdown!\nYou must restart this EPoint after this test!");
595
600
605
println("Removing Key#1 from "+c1.toRemoteString()+" using "+db1.toRemoteString());
println("\t=>"+c1.remove("Key#1",db1).toRemoteString());
610
print("Creating new CatalogItem: ");
ci = getShopServices().createCatalogItem("Key#1",new StringValue("The ney Key#1 CatalogItem"));
println(ci.toRemoteString()+" that will be added to "+c1.toRemoteString()+" using "+db1.toRemoteString());
c1.add(ci,db1);
println("Adding two Key#2 items from "+cs.toRemoteString()+" using "+db1.toRemoteString());
cs.add("Key#2",2,db1);
615
println("Removing all Key#2 items from "+cs.toRemoteString()+" using an iterator with "+db2.toRemoteString());
it = cs.iterator("Key#2",db2,false);
while (it.hasNext()) {
StockItem si = (StockItem)it.next();
println("\t=> "+si.toRemoteString());
it.remove();
}
620
//
// Here we give a Summary over all content!
//
showSummary();
625
630
635
640
645
650
}
shutdown();
JOptionPane.showMessageDialog(null,"Please restart this EPoint now (You may click OK and leave the window open for comparing content!)"
);
/**
* This method is called, if this EPoint was normally closed and is restarted now.
* That means the InfoStore contains committed data that may be used for a restart.
*
* @throws Exception if some error occurs
*/
private void runShutdownEPoint() throws Exception {
JOptionPane.showMessageDialog(null,"EPoint has restarted! Retrieving persistent data and committing the DataBasket!");
System.out.println("EPoint was closed successfully ...restart EPoint");
start();
String sBasket1 = (String)getInformationStore().getObject(PERSISTENT_PROPERTY_BASKET1);
String sBasket2 = (String)getInformationStore().getObject(PERSISTENT_PROPERTY_BASKET2);
c1 = getShopServices().getPersistentCatalog(CATALOG1_NAME);
c2 = getShopServices().getPersistentCatalog(CATALOG2_NAME);
ss = (StoringStock)getShopServices().getPersistentStock(STORINGSTOCK_NAME);
cs = (CountingStock)getShopServices().getPersistentStock(COUNTINGSTOCK_NAME);
db1 = getShopServices().getPersistentDataBasket(sBasket1);
db2 = getShopServices().getPersistentDataBasket(sBasket2);
showSummary();
resetFrame("Finishing DataBaskets...",true);
showDialog("Finishing DataBaskets...","Rolling back and Committing the Baskets");
655
println("Rolling back "+db1.toRemoteString());
db1.rollback();
println("Committing "+db2.toRemoteString());
db2.commit();
660
showSummary();
665
}
showDialog("Finished!","Aborting the EPoint after >OK< - simulating a crash!");
System.out.println("Abort EPoint...no shutdown!!!");
System.exit(0);
C.6. PACKAGE EPOINT.XML
353
/**
* This method is called, if a crash was detetected in a previous run of this
* EPoint which means that the InfoStore contains data, that was not in a
* committed state.
*
* @throws Exception if some error occurs
*/
private void runCrashedEPoint() throws Exception {
JOptionPane.showMessageDialog(null,"EPoint has restarted and found an undefined persistent state!");
System.out.println("EPoint was aborted ... try to continue...");
start();
// try to find the databasket
if (getInformationStore().containsKey(PERSISTENT_PROPERTY_BASKET1)) {
String sBasket = (String)getInformationStore().getObject(PERSISTENT_PROPERTY_BASKET1);
db1 = getShopServices().getPersistentDataBasket(sBasket);
System.out.println("Found DataBasket!");
} else {
System.out.println("DataBasket not found!");
}
/* try to find the catalog */
c1 = getShopServices().getPersistentCatalog(CATALOG1_NAME);
if (c1 != null) {
System.out.println("Found Catalog!");
} else
System.out.println("Catalog not found!");
// if catalog and databasket exists try to find the catalogitem
if ((c1 != null) && (db1 != null) && (c1.contains("Mein Key #1",db1))) {
System.out.println(c1.get("Mein Key #1",db1,false).getValue().toString());
} else {
System.out.println("Key \"Mein Key #1\" not found!");
}
shutdown();
}
670
675
680
685
690
695
700
public static void
705
710
715
720
725
}
}
main(String args[]) throws Exception {
System.setProperty("org.xml.sax.parser","fr.dyade.koala.xml.sax.SAXParser");
try {
TestEPoint ep1 = new TestEPoint(EPOINT_NAME);
switch (ep1.getPersistenceState()) {
case EPoint.NEW_PERSISTENT_STATE: {
ep1.runNewEPoint();
break;
}
case InformationStore.DEFINED_PERSISTENT_STATE: {
ep1.runShutdownEPoint();
break;
}
case InformationStore.UNDEFINED_PERSISTENT_STATE: {
ep1.runCrashedEPoint();
break;
}
}
}
catch (Exception e) {
e.printStackTrace();
System.out.println(e);
}
showDialog("Exiting...","Finished EPoint!");
System.exit(0);
C.6 Package epoint.xml
epoint.xml.XMLConverter.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 353
epoint.xml.XMLConverterImpl.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 355
epoint.xml.XMLObjectClass.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 357
Listing C.78: epoint.xml.XMLConverter
package
epoint.xml;
354
5
10
15
20
25
30
35
40
45
50
55
60
65
70
75
80
85
90
import
imp
Herunterladen