Eingangsbeispiel - Dokumentation Table of contents 1 Dokumentation im Code und JavaDoc.............................................................................. 2 2 Persistence Layer............................................................................................................... 2 3 2.1 Lesende Operation - getStudent(long id)...................................................................... 2 2.2 Schreibende Operation - addStudent............................................................................. 3 2.3 Referenzen.....................................................................................................................4 Presentation Layer..............................................................................................................4 3.1 ExportComboModel...................................................................................................... 5 3.2 StudentenTableModel....................................................................................................6 3.3 Graphical User Interface (View)................................................................................... 6 3.4 Referenzen.....................................................................................................................9 Copyright © 2006 Institut fuer Softwaretechnik und interaktive Systeme All rights reserved. Eingangsbeispiel - Dokumentation 1. Dokumentation im Code und JavaDoc Das Beispiel ist nach den Konventionen von JavaDoc dokumentiert. Für jedes Package, jede Klasse und jede Funktion gibt es eine Beschreibung. Mit dem Ant-Task javadoc wird aus diesen Beschreibungen eine HTML-Version erstellt. Sie befindet sich in diesem Beispiel im Unterverzeichnis doc/api/ und kann mit jedem Browser gelesen werden. Zusätzlich zu den Informationen für die JavaDoc enthält der SourceCode Kommentare, um die Verständlichkeit des Codes zu erhöhen. 2. Persistence Layer Als Persistenzebene (= Persistence layer) bezeichnet man jenen Teil des Programms, der für die dauerhafte Speicherung der Daten zuständig ist. Bei der objektorientierten Entwicklung werden meist ganze Objekte in die Datenbank gespeichert und wieder aus dieser gelesen. Um von der verwendeten Datenbank möglichst unabhängig zu sein, verwendet man sogenannte Data Access Objects (kurz DAO). Ein DAO stellt Funktionen zur Verfügung, mit denen einzelne Objekte in die Datenbank geschrieben und aus der Datenbank gelesen werden können. Das DAO kümmert sich dabei um den Aufbau einer Verbindung zur Datenbank, das Senden der Befehle, empfangen der Daten usw. Da die verbreitesten Datenbanken relationale Datenbanken sind, die mit Tabellen arbeiten, müssen die Datenfelder der Objekte auf eine oder mehrere Tabellen und Tabellenspalten zugeordnet werden. Diese Zuordnung nennt man Mapping. Auch das Mapping gehört zur Aufgabe eines DAOs. Das Basisbeispiel arbeitet mit einem sehr einfachen DAO, das fixe SQL Statements enthält, diese befüllt und an die Datenbank sendet. 2.1. Lesende Operation - getStudent(long id) Die Funktion getStudent(long id) liefert eine Instanz von Student, das die Daten mit der übergebenen id enthält. Das dazugehörende SQL Statement ist als Konstante in der DAO Klasse enthalten: private static String QUERY_STUDENT_GET_BYID nachname, email from studenten where id=?"; = "select id, matnr, vorname, Mit diesem String wird ein Statement erzeugt. Danach wird der Parameter (das Fragezeichen) des Statements gesetzt: Page 2 Copyright © 2006 Institut fuer Softwaretechnik und interaktive Systeme All rights reserved. Eingangsbeispiel - Dokumentation PreparedStatement ps = getConnection().prepareStatement(QUERY_STUDENT_GET_BYID); ps.setLong(1, id.longValue()); Nach der Ausführung des Statements steht ein ResultSet zur Verfügung, aus dem die ausgewählten Daten geholt werden können: ResultSet rs = ps.getResultSet(); Mit der Methode createStudent wird eine neue Instanz der Klasse Student angelegt und mit den Daten aus dem ResultSet befüllt: Student s = createStudent(rs); Fertig - nun kann die Instanz zurückgegeben werden. 2.2. Schreibende Operation - addStudent Die Funktion addStudent(Student) legt ein übergebenes Student Objekt in der Datenbank an. In der Datenbank repräsentiert eine Zeile in der Tabelle studenten einen Studenten. Jede Zeile wird über eine eindeutige, laufende ID identifiziert. Diese ID wird von der Datenbank verwaltet und für jeden neuen Studenten automatisch erzeugt. Unsere Funktion muss also: • die Felder des Student Objekts den Spalten der Tabelle zuordnen (sie mappen). • die von der Datenbank erzeugte ID holen und dem Student Objekt zurückgeben In unserem Beispiel ist es sehr unwahrscheinlich, dass zwei Einfügen-Operationen gleichzeitig ausgeführt werden, aber in einer großen Applikation kann dies durchaus vorkommen. Um zu gewährleisten, dass jene ID, die sich das Programm von der Datenbank holt, jene ID ist, die für unser Student Objekt vergeben wurde, führen wir beide Statements in einer Transaktion durch. Nachdem eine Datenbankverbindung geholt wurde, wird der Insert Befehl vorbereitet. Der SQL String dazu: private static String QUERY_STUDENT_INSERT = "INSERT INTO studenten (matnr, vorname, nachname, email) values (?, ?, ?, ?)"; Danach wird das Statement mit Daten befüllt und das Statement ausgeführt. psInsert.setString(1, student.getMatnr()); psInsert.setString(2, student.getVorname()); psInsert.setString(3, student.getNachname()); Page 3 Copyright © 2006 Institut fuer Softwaretechnik und interaktive Systeme All rights reserved. Eingangsbeispiel - Dokumentation psInsert.setString(4, student.getEmail()); psInsert.execute(); HSQLDb hat für die neue Zeile eine ID generiert, die mit folgendem Statement ausgelesen werden kann: private static String QUERY_GET_LAST_ID = "CALL IDENTITY()"; Wir führen dieses Statement aus - bekommen wir von der Datenbank keine Antwort, rollen wir die gesamte Transaktion zurück: Statement stmtId = dbConnection.createStatement(); stmtId.execute(QUERY_GET_LAST_ID); rsKey = stmtId.getResultSet(); if(!rsKey.next()) { log.error("Error: Inserting student did not generate a key"); dbConnection.rollback(); return 0L; } Danach lesen wir den Key aus und schließen die Transaktion ab: // "@p0" ist der Name der Spalte die hsqldb für die Id verwendet id = new Long(rsKey.getLong("@p0")); dbConnection.commit(); Nun setzen wir die ausgelesene ID noch im Student Objekt und geben die ID zurück. student.setId(id); return id; 2.3. Referenzen Data Access Object: Beschreibung des Data Access Object Patterns auf sun.com 3. Presentation Layer Der Presentation Layer ist jener Teil des Programms, der für die Präsentation (Anzeige) der Daten zuständig ist. Überlicherweise greift man bei der Programmierung des Presentation Layers auf das MVC-Modell zurück. Page 4 Copyright © 2006 Institut fuer Softwaretechnik und interaktive Systeme All rights reserved. Eingangsbeispiel - Dokumentation Beim Model-View-Controller-Modell (=MVC-Modell) handelt es sich um ein Architekturmuster zur Trennung von Software-Systemen in die drei Einheiten Datenmodell (engl. Model), Präsentation (engl. View) und Programmsteuerung (engl. Controller). Das Datenmodell enthält die dauerhaften Daten der Anwendung. Das Model hat lesenden Zugriff auf diverse Speicher wie zum Beispiel Datenbanken. Das Model kennt weder die View noch den Controller, es weiß also gar nicht, wie, ob und wie oft es dargestellt und verändert wird. Änderungen im Model werden allerdings über einen Update-Mechanismus bekannt gegeben, indem ein Event ausgelöst wird. Die Darstellungsschicht (View) präsentiert die Daten in der Regel - jedoch nicht notwendigerweise - zwecks Anzeige. Die Programmlogik sollte aus dem View entfernt werden. Der View kennt das Model und ist dort registriert, um sich selbständig aktualisieren zu können. Der Controller verwaltet die Schichten, nimmt von ihnen Benutzeraktionen entgegen, wertet diese aus und hat schreibenden Zugriff auf das Modell. Er enthält die Intelligenz und steuert den Ablauf (engl. Workflow) der Anwendung. Das MVC-Muster trifft allerdings keine Aussage über die Positionierung der Geschäftslogik innerhalb der MVC-Klassen. Diese kann je nach Anwendungsfall besser im Controller aufgehoben sein oder besser in das Modell verlagert werden. Das Basisprogramm besitzt 2 Models, das ExportComboModel für die Verwaltung der verschiedenen Exportformate und das StudentenTableModel zum Lesen der Daten aus der Datenbank mit Hilfe des DAO. Eine getrennte Controller Schicht ist im Basisprogramm nicht vorhanden, die Verwaltung des Ablaufs erfolgt direkt im View. 3.1. ExportComboModel Das ExportComboModel hat die Aufgabe, alle vorhandenen Export Formate (im Basisprogramm XML und HTML) zu verwalten, und die ComboBox des Swing Interfaces mit den jeweiligen Auswahlmöglichkeiten zu befüllen. Im Konstruktor wird zunächst eine Liste erzeugt, in der die Export Formate gespeichert werden. exportFilter = new ArrayList|Export|(); Danach werden die jeweiligen Klassen der einzelnen Exportformate hinzugefügt. Page 5 Copyright © 2006 Institut fuer Softwaretechnik und interaktive Systeme All rights reserved. Eingangsbeispiel - Dokumentation exportFilter.add(new XmlExportImport()); exportFilter.add(new HtmlExport()); Dann wird noch mit Hilfe der Funktion setExportFilter(exportFilter); ein Log Statement ausgegeben, und das erste Objekt der Liste mit selectedItem = exportFilter.get(0); ausgewählt. Will man ein neues Export Format unterstützen, braucht man nur eine weitere Klasse in die Liste einfügen, und das Swing Interface wird automatisch upgedatet und das neue Export Format steht zur Verfügung. 3.2. StudentenTableModel Das StudentenTableModel hat die Aufgabe, die Daten aus der Datenbank auszulesen und diese für die Anzeige im Swing Interface aufzubereiten. Ausserdem updated es das Swing Interface, zum Beispiel wenn die Sortierreihenfolge geändert wird. Im Konstrukor wird zunächst das DAO mit studentDAO = new JdbcStudentDAO(); initialisiert, danach wird die Sortierreihenfolge festgelegt mit this.order = order; und die komplette Datenbank wird mit der Methode readData() ausgelesen. Die Methode reaData() benutzt das DAO um die Datenbank auszulesen: studenten = studentDAO.getStudents(order); Wird die Sortierreihenfolge geändert, muss die Datenbank neu ausgelesen werden, um die Daten in der neuen Reihenfolge zu bekommen, ausserdem muss das Swing Interface mit der Methode fireTableDataChanged(); aktualisiert werden. public void setOrder(String order) { log.info("Order Changed: reading data..."); this.order = order; readData(); fireTableDataChanged(); } Nach dem Aufruf der Methode setOrder(String order) wird die neue, richtig sortierte Liste im Swing Interface angezeigt. 3.3. Graphical User Interface (View) Das Graphical User Interface (=GUI) ist komplett aus Swing-Komponenten aufgebaut. In der Klasse MainFrame befinden sich alle Swing Komponenten und die ActionListener für die Page 6 Copyright © 2006 Institut fuer Softwaretechnik und interaktive Systeme All rights reserved. Eingangsbeispiel - Dokumentation jeweiligen Elemente. In der Klasse MessageDlg wird ein kleines Swing Dialog Fenster mit Statusinformationen (Bestätigung, Fehleranzeige) erzeugt. 3.3.1. MainFrame Klasse Im Konstruktor werden zunächst die beiden init Methoden für die Models und die Komponenten aufgerufen. initModels(); initComponents(); Ausserdem wird ein eigener Window Listener hinzugefügt, der zum Schliessen des Swing Fensters notwendig ist. addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent arg0) { super.windowClosing(arg0); terminateApplication(); } }); Die Methode initModels(); initialsiert das StudentenTableModel und das ExportComboModel. Dabei wird für das StudentenTableModel das Default Sortierkiterium "Matrikelnummer" festgelegt. In der Methode initComponents(); werden alle Swing Element erzeugt und plaziert, sowie mit Action Listenern versehen. Die Vorgehensweise ist folgende: • Zuerst erzeugt man das MainPanel mit JPanel mainPanel = new JPanel(); • Dann platzieren wir das MainPanel mit getContentPane().add(mainPanel); und versehen es mit einem LayoutManager mainPanel.setLayout(new BorderLayout(5,5)); Anschliessend wollen wir das MainPanel in drei Bereiche North, Center und South unterteilen, wir erzeugen also drei weitere Panels, wofür wir JToolbars verwenden, die ähnlich wie normale Swing Panels, nur etwas flexibler zu verwenden sind. Diese Panels versehen wir wieder mit einem LayoutManager und fügen sie unserem MainPanel hinzu. • JPanel centerPanel = new JPanel(); erzeugt das Panel • centerPanel.setLayout(new BorderLayout()); und centerPanel.setBorder(BorderFactory.createEmptyBorder(10,5,0,10)); erzeugen das Layout • mainPanel.add(centerPanel, BorderLayout.CENTER); fügt das Panel an der gewünschten Stelle im MainPanel ein. Anschliessend werden weiter Komponenten wie Labels, Drop Down Felder, Tables und Page 7 Copyright © 2006 Institut fuer Softwaretechnik und interaktive Systeme All rights reserved. Eingangsbeispiel - Dokumentation Buttons in die jeweiligen Panels eingefügt. Einige speziellere Implementationen werden jetzt noch genauer beschrieben: Für die Tabelle im Center Panel wird eine JTabel Komponente verwendet JTable table = new JTable();. Auch das StudentenTableModel muss der JTable hinzugefügt werden, das erfolgt mit table.setModel(studentenTM); Die Tabelle wird vom Model mit Daten gefüllt, bzw. im Falle einer Datenänderung automatisch upgedatet. Das Drop Down Feld für die Auswahl des Sortierkriteriums wird mit einem ItemListener versehen, um auf Änderungen reagieren zu können. Der ItemListener wird gleich direkt beim Hinzufügen implementiert, bei einer Änderung wird die Methode sortOrderChanged() aufgerufen, der Code sieht folgendermassen aus: selectOrderCB.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent ie) { if (ie.getStateChange() == ItemEvent.SELECTED) { log.debug("Order Combo State Changed: " + ie.getItem()); sortOrderChanged(ie.getItem().toString()); } } }); Die Methode sortOrderChanged() ruft einfach die Methode studentenTM.setOrder(order); des StudentenTableModels mit dem neuen Sortierkriterium auf, und das Model updated die Tabelle. Für das Drop Down Feld zum Auswählen der Export Formate wird das ExportComboModel mit exportCB.setModel(exportComboModel); hinzugefügt und ein Tooltip mit exportCB.setToolTipText("Exportformat auswŠhlen, danach |Export| drŸcken"); erzeugt. Für alle Buttons wird ein ActionListener hinzugefügt, die jeweilige Implementation wird gleich in der MainFrame Klasse vorgenommen. Daher kann man alle ActionListener mit this implementieren, noch ein Beispiel zur Verdeutlichung: exportBtn.addActionListener(this); Der ActionListener überprüft, welcher Button das Event ausgelöst hat, und ruft entweder die Methode terminateApplication(); zum Beenden des Programms oder die Methode export(); für den Export Dialog auf. Die Methode export() liest zuerst das vom Benutzer gewählte Exportformat, bzw. die zu benutzende ExportKlasse aus. Export export = (Export)exportComboModel.getSelectedItem(); Danach wird ein JFileChooser Dialog angezeigt. um Pfad und Dateinamen für die Exportdatei zu erhalten. Nach einer Abfrage, oder der Dialog erfolgreich beendet wurde if (returnVal == JFileChooser.APPROVE_OPTION) wird der Pfad ausgelesen String filename = jfc.getSelectedFile().getPath(); und die Methode export.write(studentenTM.getStudenten(), filename); der Export Klasse aufgerufen. Danach wird ein Message Dialog erzeugt, der Page 8 Copyright © 2006 Institut fuer Softwaretechnik und interaktive Systeme All rights reserved. Eingangsbeispiel - Dokumentation entweder einer Bestätigung oder eine Fehlermeldung anzeigt. MessageDlg mdlg = new MessageDlg(this, "Daten wurden erfolgreich exportiert!"); 3.3.2. MessageDlg Klasse Diese Klasse erzeugt in kleines Dialog Fenster für die Bestätigung bzw. das Anzeigen eines Fehlers bei Aufrufen der Export Funktion im Hauptfenster. Die Klasse für den MessageDialog wird von der Swing Klasse JDialog abgeleitet public class MessageDlg extends JDialog und anschliessend im Konstruktor mit normalen Swing Komponenten wie Panels, Labels und einem Button versehen. Der Label wird mit dem übergebenen String initialisiert, textMsg = new JLabel(message); der entweder die Bestätigungs- oder die Fehlermeldung enthält. Anschliessend wird noch die Methode pack(); aufgerufen, welche die Grösse des Fensters an die aktuellen Komponenten angepasst. Dann wird noch der Action Listener für den OK Button implementiert und die MessageDlg Klasse ist fertig. 3.4. Referenzen • Sun Swing/JFC Page 9 Copyright © 2006 Institut fuer Softwaretechnik und interaktive Systeme All rights reserved.