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 zr | 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 ±&¡ ¡ © £~© ¦ ¡ ² ¯ £ ¤ £ pq r q s t u v r o wy z {U{ o wp,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 Ks gs,6K 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 q7 >?@A>BC DE FBGH "$#%&' () ( * &+,&-/.$+.0&' ! 1!23451768 9: ;6<= U!VWXU7YZ [\ ]Y^T_ $q q7 }5~ 5T 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)&gt;0 &amp;&amp; y.compareTo(z)&gt;0)</tt> implies * <tt>x.compareTo(z)&gt;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)&gt;0 &amp;&amp; y.compareTo(z)&gt;0)</tt> implies * <tt>x.compareTo(z)&gt;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 = "&lt;"; 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