• Nie Znaleziono Wyników

Podczas implementacji aplikacji spotkano się z wieloma zagadnieniami programistycznymi a także, z następującymi problemami:

 Pobieranie Metadanych z bazy i tworzenie modelu obiektowego reprezentującego go.  Mapowanie typów bazy danych na typy języka programowania.

 Dynamiczne mapowanie typów danych języka na zapytania.  Stworzenie silnika dodatków

71

Pierwszym modułem systemu jest moduł pobierający z bazy danych informacji o jej schemacie i strukturze. System za pomocą narzędzi dostępnych w sterowniku JDBC pobiera meta-dane dotyczące konkretnej bazy danych i zapisuję je w obiektach (diagram klas na Rys. 6.2.1). Dzięki temu programista może swobodnie korzystać z modelu i uzyskiwać potrzebne informacje do tworzenia zapytań.

Rys. 6.2.1 Schemat modelu meta-danych

Bazę danych reprezentuje obiekt Link, który jest identyfikowany za pomocą unikalnej pary obiektów Schema i Catalog. Dzieje się tak, dlatego, że niektóre bazy zawierają swoje bazy w jednostkach zwanych Schema a inne w Catalog. Różnica ta np. występuje w innych wersjach MySQL. Kiedyś baza przechowywana była w Schema, a teraz występuje w

Catolog.

Model całej bazy tworzony jest w klasie o tej samej nazwie. Zwiera on wiele Linków w kolekcji, która powiązana jest z kolekcją tabel. Tabela może zawierać zero lub więcej wierszy, zero lub wiele kolumn. Jedna kolumna może zawierać wiele indeksów. Każda kolumna zawiera jeden typ.

Model ten jest źródłem meta-danych w programie. Pozwala na dynamiczne tworzenie zapytań.

72 6.2.1 Moduł dynamicznego mapowania typów bazy danych na typy języka oraz typów języka

programowania na zapytania SQL.

Zgodnie z podrozdziałem producenci baz danych dostarczają wiele niestandardowych typów danych. Aby system był elastyczny i miał możliwości adaptacji do nowych typów danych lub zmian standardów sterowników JDBC, zapis mapowań typów danych na typy języka programowania powinien być zapisany w oddzielnym pliku konfiguracyjnym. Najbardziej intuicyjnym formatem i najlepiej obsługiwanym wydaje się zapis w formacie XML. Sterowniki JDCB dostarczają następujących metod do mapowania typów danych(tab. 6.2.1).

Tab. 6.2.1 Metody sterownika JDBC do mapowania typów danych[13]

getBlob(int columnIndex): Blob

getBoolean(int columnIndex) : boolean getByte(int columnIndex) : byte

getBytes(int columnIndex) : byte[]

getCharacterStream(int columnIndex): Reader

getClob(int columnIndex): Clob getDate(int columnIndex) : Date getDouble(int columnIndex): double getFloat(int columnIndex) : float getInt(int columnIndex) : int getLong(int columnIndex) : long

getNCharacterStream(int columnIndex): Reader

getNClob(int columnIndex) : NClob getNString(int columnIndex) : String getObject(int columnIndex) : Object getRef(int columnIndex): Ref

getShort(int columnIndex) : short

getSQLXML(int columnIndex) : SQLXML getString(int columnIndex) : String

getTime(int columnIndex) : Time getTimestamp(int columnIndex) : Timestamp

getURL(String columnLabel) : URL

W tradycyjnym podejściu programistycznym zapytania do bazy danych mapowane są od razu na bazę danych.

W podejściu dynamicznym zastosowano następujące rozwiązanie. Za pomocą pliku XML opracowano formę mapowania typów bazy danych na klasy pośrednie (Listing 6.2.1). Klasy pośrednie muszą zawierać pusty konstruktor, aby mieć możliwość instancjonowania, czyli stworzenia obiektu za pomocą mechanizmu refleksji, w tym wypadku wywołania metody Class.getInstance(). Typy pośrednie są konieczne, ponieważ niektóre typy plików Java nie mają możliwości instancjonowania. Dzięki utworzonym klasą pośrednim z pomocą mechanizmu przeciążenia metod, wybierałby odpowiednią metodę do mapowania danych z bazy.

<?xml version="1.0" encoding="UTF-8"?> <mapping>

73 <map><name>BOOL</name><class>TypyDanych.BooleanType</class></map>

<map><name>TINYINT</name><class> TypyDanych.IntegerType</class></map> <map><name>TINYINT UNSIGNED</name><class>

TypyDanych.IntegerType</class></map>

<map><name>BIGINT</name><class>TypyDanych.IntegerType </class></map> <map><name>BIGINT UNSIGNED</name><class>TypyDanych.IntegerType </class></map>

<map><name>LONG VARBINARY</name><class>TypyDanych.IntegerType</class></map> <map><name>MEDIUMBLOB</name><class>TypyDanych.BlobType</class></map>

<map><name>LONGBLOB</name><class>TypyDanych.BlobType</class></map> <map><name>BLOB</name><class> TypyDanych.BlobType</class></map> <map><name>TINYBLOB</name><class>TypyDanych.BlobType</class></map> <map><name>VARBINARY</name><class>TypyDanych.BlobType</class></map> <map><name>BINARY</name><class>TypyDanych.BlobType</class></map>

<map><name>LONG VARCHAR</name><class>TypyDanych.StringType</class></map> <map><name>MEDIUMTEXT</name><class>TypyDanych.StringType </class></map> <map><name>LONGTEXT</name><class>TypyDanych.StringType</class></map> <map><name>TEXT</name><class>TypyDanych.StringType</class></map> <map><name>TINYTEXT</name><class>TypyDanych.StringType</class></map> <map><name>CHAR</name><class>TypyDanych.StringType</class></map> <map><name>NUMERIC</name><class>TypyDanych.IntegerType</class></map> <map><name>DECIMAL</name><class> TypyDanych.BigDecimalType</class></map> <map><name>INTEGER</name><class> TypyDanych.IntegerType</class></map> <map><name>INTEGER UNSIGNED</name><class>TypyDanych.IntegerType </class></map>

<map><name>INT</name><class> TypyDanych.IntegerType</class></map>

<map><name>INT UNSIGNED</name><class>TypyDanych.IntegerType </class></map> <map><name>MEDIUMINT</name><class> TypyDanych.IntegerType</class></map> <map><name>MEDIUMINT UNSIGNED</name><class>TypyDanych.IntegerType </class></map>

<map><name>SMALLINT</name><class> TypyDanych.IntegerType</class></map> <map><name>SMALLINT UNSIGNED</name><class>

TypyDanych.IntegerType</class></map>

<map><name>FLOAT</name><class> TypyDanych.FloatType</class></map> <map><name>DOUBLE</name><class> TypyDanych.DoubleType</class></map> <map><name>DOUBLE PRECISION</name><class>TypyDanych.DoubleType </class></map>

<map><name>REAL</name><class> TypyDanych.DoubleType</class></map> <map><name>VARCHAR</name><class>TypyDanych.StringType</class></map> <map><name>ENUM</name><class>TypyDanych.StringType</class></map> <map><name>SET</name><class>TypyDanych.StringType</class></map> <map><name>DATE</name><class> TypyDanych.DateType</class></map> <map><name>TIME</name><class> TypyDanych.TimeType</class></map>

<map><name>DATETIME</name><class> TypyDanych.TimestampType</class></map> <map><name>TIMESTAMP</name><class>TypyDanych.TimestampType </class></map> </mapping>

Listing 6.2.1 Przykładowe mapowania typów bazy danych na typy pośrednie.

Opisany schemat zaimplementowany został w formie wzorca projektowego Wizytator (diagram dla dołączony na rysunku 6.2.2) oraz zastosowania interfejsów programistycznych. Podstawowym interfejsem jest interfejs VisitorClient, który realizują klasy pośredniczące.

74

Każda taka klasa musi posiadać instancjonowany przykładowy obiekt języka Java (na rys. Value), na który będzie mapował typ bazy danych. Poza tym w systemie są trzy interfejsy, które reprezentują określone działania podczas mapowania plików z bazy do aplikacji i na odwrót.

Działania te są zróżnicowane w zależności od typu danych języka programowanie tzw. przeciążenie metod. W trakcje wykonywania programu instancjonowany jest wiec typ pośredni danych, następnie wykonywana jest jego metoda Accept z odpowiednim działaniem jako parametr. A wewnątrz tej metody, na dołączonej klasie działania wywoływana jest jej wewnętrzna metoda z odpowiednim obiektem docelowym, na który chcemy mapować nasze dane.

75 6.2.2 Silnik dodatków

W aplikacji zaimplementowano system do obsługi pluginów. System przeszukuje wybrany katalog (w projekcie PluginManager/PLUGINS) szukając bibliotek z rozszerzeniem .jar, następnie przeszukuje klasy zawarte w plikach .jar w poszukiwaniu zaimplementowanego interfejsu IPluginWigdet (reprezentacja dodatku w projekcie), Jeśli odnajdzie odpowiednią klasę, rejestruje ją w systemie. System sprawdza dodatki przy każdym uruchomieniu. W systemie musi zawierać się zestaw standardowych dodatków, aby móc poprawnie działać. System nie wymaga rejestrowania dodatków w dodatkowych plikach np. XML.

6.2.3 Tworzenie nowych dodatków

Tworzenie nowych dodatków odbywa się za pomocą interfejsu IPluginWidget

(Listing 6.2.1). Ewentualnie możemy dziedziczyć klasę abstrakcyjną PluginAdapter z projektu PluginManager.Interface.

Listing 6.2.1 Kod interfejsu programistycznego IPluginWidget

Public interface IPluginWidget {

//Zwraca typ pośredni, któremu przypisany jest ten dodatek

public Class getMidleType();

//Zwraca typ Jezyka Java, na który będzie mapowana wartość.

public Class getFinalType();

//określa nazwę Dodatku.

public String getName();

//określa sposób wylidacji typu danych

public boolean validateType(Object val);

//inicjuje dodatek

public void init(Table table,Column col);

//pozwala na stworzenie nowego obiektu za pomoca metody.

public IPluginWidget getNewInstance();

//pozwala na zaiplementowanie odświerzenia dodatku w miarę potrzeby

public void update();

//obsługa komend wejścia-wyjścia dla dodatku public void setValue(Object val);

public Object getValue();

//obsługa źródła danych

public void setDataSource(IDataSource source);

public IDataSource getDataSource();

public void setTableRepresentation(ITableRepresentation representation);

76

//Edytory i Renderery do prezentacji danych

public IWidgetEditor getEditor();

public IWidgetRenderer getRenderer();

//obsługa Walidatorów do spradzanie poprawności danych wejściowych w dodatkach�

public void setValidator(IValidator validator);

public IValidator getValidator();

}

Pierwszą metodą do zrealizowania w interfejsie IPluginWidget jest przypisanie w metodzie getMidleType() odnośnika do klasy pośredniczącej w mapowaniu typów (klasy te znajdują się w katalogu ). Przypisanie to spowoduje, że otrzymane dane będę pobierane i wysyłane z udziałem klasy pośredniczącej. Informacja o typie, na jaki będzie mapowany typ pośredniczący zapisana jest w metodzie getfinalType(). Następnie określamy nazwę dodatku (ze względu na przejrzystość nazwy nie mogą się powtarzać). Należy wybrać taką wartość, aby nazwa mogła rozróżnić konkretny dodatek na ekranie.

Walidacja typu validateType() to metoda odpowiadająca, za zweryfikowanie czy dane przyjęte do dodatku mogą być edytowane czy też nie.

Dalej w interfejsie należy wykonać konieczne zainicjowanie dodatku odpowiednią wartością klasy Table i Column z meta-modelu. Każdy dodatek (jego instancja) powinien wiedzieć, jaką tabelę i kolumnę obsługuję. Należy zapamiętać referencje do tych obiektów.

Konieczne jest też zaimplementowanie ciała metody getNewInstance() tworzącej nową instancje klasy (jest to wymagane przez silnik dodatków).

Dalej musimy zaimplementować obsługę wejścia i wyjścia dla dodatku zewnętrznego -metody zapisane w listingu 6.2.2.

Listing 6.2.2 metody obsługujące wejścia-wyjścia dodatku.

public void setValue(Object val);

public Object getValue();

Obsługa źródła danych i walidatorów zaimplementowana została w klasie abstrakcyjnej

PluginAdapter, a ich działanie polegają na przechowaniu referencji do źródeł danych oraz do modelu tabeli JTable- reprezentującego dane oraz przechowanie referencji do walidatorów.

77

Listing 6.2.3 metody odpowiedzialne za edytowanie i wyświetlanie komórek w tabeli.

//Edytory i Renderery do prezentacji danych

public IWidgetEditor getEditor();

public IWidgetRenderer getRenderer();

Metody mają na celu rozszerzenie tzw. edytorów i wykreślaczy klasycznej kontrolki Javy JTable o funkcjonalności potrzebnej do dobrej prezentacji i edycji danych. Metody te powinny tworzyć nowe obiekty edytorów, ponieważ dodatki mogą być wykorzystywane przez wiele kolumn. Każdy edytor powinien dziedziczyć po klasie abstrakcyjnej

WidgetEditor i analogicznie każdy wykreślacz powinien dziedziczyć po klasie

WidgetRenderer.

Dzięki zastosowanemu dziedziczeniu pozostało tylko obsłużenie metod wymaganych przez edytora (Listing 6.2.3)i wykreślacza (Listing 6.2.4)JTabel.

Listing 6.2.3 metoda zwracająca Component do wyświetlania komórek w tabeli.

@Override

public abstract Component getTableCellRendererComponent(JTable table,

Object value, boolean isSelected, boolean hasFocus, int row, int column);

Listing 6.2.4 metody wymagane przez edytora komórek w komponencie JTable @Override

public Object getCellEditorValue();

@Override

public Component getTableCellEditorComponent(JTable table, Object

value, boolean isSelected, int row, int column) ;

Jeśli programista będzie chciał użyć komponentu do wyskakujących okienek, znajduje się on w projekcie PluginManager i występuje pod nazwą PopOutBean. W pełni współpracuje on z komponentem WidgetRenderer. Aby użyć komponentu z wyskakującym okienkiem należy stworzyć przykładowy guzik z prezentacją wartości, którą chcemy edytować, następnie stworzyć nowy obiekt z parametrami konstruktora

Listing 6.2.5 Konstruktor klasy PopOutBean

PopOutBean(Container container, JButton button, int mode,WidgetEditor editor)

Poszczególne atrybuty w konstruktorze PopOutBean (Listing 6.2.5) oznaczają:  container - to nasz dodatek który obsłuży dane,

78

 button - to JButton, który przed chwilą stworzyliśmy i jest on komponentem, do którego przypisane jest wyskakujące okienko.

 mode - to wybór trybu działania odpowiadający stałym statycznym klasy PopOutBean

(ViemMode, EditMode).

 editor – bieżący edytor (wpisujemy this)

W metodzie getTableCellEditorComponent zwracamy wartość metody getbutton() z bieżącego obiektu PopOutBean, aby poprawnie zwrócić komponent obsługujący edycję. Stworzenie nowego sposobu prezentacji dodatku wymaga tylko podstawowej wiedzy o klasach do edycji i prezentacji danych w komponencie JTable, co ogranicza się do wypełnienia trzech wyżej wypisanych metod (Listing 6.2.3 i Listing 6.2.4).

Powiązane dokumenty