package javacodebook.xml.processing.dom.create;
import java.io.*;
import java.util.*;
import java.sql.*;
import javax.servlet.*;
import javax.servlet.http.*;
import org.w3c.dom.*;
// das sind Xerces-spezifischen Klassen die benötig werden, da
// deren Funktionsumfang vom W3C noch nicht spezifiziert ist.
import org.apache.xerces.dom.DocumentImpl;
import org.apache.xml.serialize.OutputFormat;
import org.apache.xml.serialize.XMLSerializer;
/**
* RDB2XMLConverter ist ein Servlet, welches in generischer Weise SQL-Datenbankanfragen in
* XML-Datenstöme verwandeln kann. Dabei werden Metainformationen wie Feld- und Tabellenamen und Typen
* mitgeliefert.
* Das Servlet unterstützt die HTTP-Get Methode. Dabei benötigt es einen Parameter namens 'sql'
* mit der gewünschten SQL-Anfrage. Die Parameter 'driver', 'url', 'user' und 'pwd' sind
* optional und können die Datenbankverbindung beschreiben auf die das SQL abgesetzt werden soll.
* Zu beachten ist, dass dabei die entsprechende Treiberklasse im Klassenpfad der Servlet-Engine
* zu finden sein muss.
*
* Dieses Servlet funktioniert nur, solange Spaltennamen nach well-formed XML benannt sind. Dies kann bei Bedarf
* sehr leicht geändert werden, indem der Spaltenname nicht über den Elementnamen, sondern über ein zu-
* sätzliches Attribut modelliert wird. Der Elementname könnte dann generisch, z.B. 'column' genannt werden.
*
*/
public class RDB2XMLConverter
extends HttpServlet {
private static final String CONTENT_TYPE = "text/xml";
private Connection connection = null;
private String defaultDriver;
private String defaultUrl;
private String defaultUser;
private String defaultPwd;
public void init(ServletConfig config) {
defaultDriver = config.getInitParameter("defaultDriver");
defaultUrl = config.getInitParameter("defaultUrl");
defaultUser = config.getInitParameter("defaultUser");
defaultPwd = config.getInitParameter("defaultPwd");
}
/**
* Die doGet-Methode ist überschreiben, damit das Servlet http-Get unterstützt.
* In der doGet-Methode wird der http-Parameter 'sql' ausgelesen. Der ausgelesene
* String wird als SQL entweder an die Default-Datenbank oder an eine andere Datenbank,
* die über zusätzliche http-Parameter beschrieben wurde, abgesetzt.
*
* @param request
* @param response
* @throws ServletException
* @throws IOException
*/
public void doGet(HttpServletRequest request, HttpServletResponse response) throws
ServletException, IOException {
response.setContentType(CONTENT_TYPE);
PrintWriter out = response.getWriter();
String sql = request.getParameter("sql");
// falls kein http-Parameter namens 'sql' übergeben wird, soll eine Fehlermeldung
// zurück gegeben werden.
if (sql == null || sql.length() == 0) {
out.println(
"please provide http-get-parameter named \'sql\'");
return;
}
try {
// eine Datenbankverbindung wird aufgebaut
Connection con = getConnection(request);
Document doc = null;
// da kein Connectionpool implementiert ist, sollte an dieser Stelle vorsichtshalber
// sichergestellt sein, dass nicht mehr als ein Thread die Connection verwendet.
synchronized (con) {
doc = createDocument(con, sql);
}
// Auf Basis des Document-Objektes wird ein OutputFormat-Objekt erzeugt. Hierbei muss das richige
// Encoding gewählt werden. Der letze boolsche Wert im Konstruktor gibt an, ob das
// XML eingerückt ausgegeben werden soll oder nicht.
OutputFormat format = new OutputFormat(doc, "ISO-8859-1", true);
// Es wird ein XMLSerializer auf Basis des Ausgabestroms zum Client
// und des OutputFormat-Objekts instanziiert.
XMLSerializer serial = new XMLSerializer(out, format);
// Nun kann das Dokument zum Client geschrieben werden.
serial.serialize(doc);
out.flush();
out.close();
}
catch (Exception e) {
//Im Ausnahmefall wird eine Fehlermeldung zurück gegeben.
out.println("" + e + "");
}
}
/**
* Diese Methode liefert auf Basis der Parameter, die in dem HttpServletRequest-Objekt
* gekapselt sind, eine neue oder die schon bestehende Datenbankverbindung. An dieser
* Stelle würde in einer professionellen Anwendung ein Connectionpool eingesetzt
* werden.
* Falls keine Parameter 'url', 'driver', 'user' und 'pwd' übergeben wurden, wird die
* Default-Datenbankverbindung genommen, die über die web.xml konfiguriert wurde.
*
* @param request
* @return java.sql.Connection
* @throws Exception
*/
private Connection getConnection(HttpServletRequest request) throws Exception {
String driver = null, url = null, user = null, pwd = null;
Connection con = null;
// wenn der Parameter 'url' übergeben wurde, soll das SQL auf einer anderem als der
// Default-Datenbank ausgeführt werden, also werden die anderen Parameter auch
// noch ausgelesen.
if ( (url = request.getParameter("url")) != null && url.length() != 0) {
driver = request.getParameter("driver");
user = request.getParameter("user");
pwd = request.getParameter("pwd");
// eine neue Verbindung wird mit den entsprechenden Parametern geholt.
return getConnection(driver, url, user, pwd);
}
else {
// ansonsten wird eine Datenbankverbindung mit den default Werten erzeugt
// oder die schon vorhandene Verdinung zurück gegeben.
if (connection == null) {
connection = getConnection(defaultDriver, defaultUrl, defaultUser, defaultPwd);
}
return connection;
}
}
/**
* Diese Methode dient zur Erzeugung einer neuen Datenbankverbindung auf Basis
* der übergebenen Parameter.
*
* @param driver
* @param url
* @param user
* @param pwd
* @return java.sql.Connection
* @throws Exception
*/
private Connection getConnection(String driver, String url, String user,
String pwd) throws Exception {
Class.forName(driver);
Connection con = DriverManager.getConnection(url, user, pwd);
return con;
}
/**
* In dieser Methode wird zunächst eine Datenbankabfrage durchgeführt und dann
* das erhaltene Resulat in ein XML-Dokument umgewandelt. Diese Umwandlung geschieht
* immer nach dem gleichen Schema.
*
* @param con
* @param sql
* @return org.w3c.Document
* @throws Exception
*/
private Document createDocument(Connection con, String sql) throws Exception {
// Es wird ein Statement-Objekt zum Absetzen der Anfrage benötigt.
Statement stmt = con.createStatement();
// Nun wird die Anfrage ausgeführt und Ergebnisse in form eines ResultSet-Objekts
// zurückgegeben.
ResultSet rs = stmt.executeQuery(sql);
// Über das ResultSet kann ein ResultSetMetaData-Objekt erzeugt werden was dazu
// dient allerlei Metainformation über das Ergebnis und die Datenbank zu erhalten,
// wie z.B. Spalten- und Tabellennamen.
ResultSetMetaData rsmd = rs.getMetaData();
// Als Rückgabewert wird ein neues DocumentImpl-Objekt benötigt welches
// das W3C-Document-Interface implementiert. Der W3C-Standard definiert nicht
// wie man Objekte erzeugen soll, die das Document-Interface implementieren.
// Deswegen benutzen wir hier die Xerces-spezifischen Objekte. Die folgende
// Zeile müsste also ersetzt werden, sollte man sich in Zukunft für
// einen anderen Parser entscheiden.
Document doc = new DocumentImpl();
// Als Root-Element wird ein Element namens ResultSet erzeugt
Element root = doc.createElement("ResultSet");
// Ein Kommentar soll eingefügt werden.
Comment comment=doc.createComment("Das ResultSet Element kapselt das Resultat der SQL-Abfrage.");
// Der Kommentar und das Root-Element muss an das Document-Objekt angehängt werden.
doc.appendChild(comment);
doc.appendChild(root);
// Es wird ein Attr-Objekt erzeugt, welches ein Attribut namens 'onSql' repräsentiert
Attr sqlAttr = doc.createAttribute("onSql");
// Der Wert diese Attributes wird mit dem SQL belegt, das durch das
// XML-Dokument beantworted werden soll.
sqlAttr.setNodeValue(sql);
// Nun muss das Attr-Objekt noch an das Root-Element angehängt werden.
root.setAttributeNode(sqlAttr);
// In den folgenden Schleifen wird die Anzahl der Spalten benötigt, die
// das Ergebnis der Anfrage hat.
int columnCount = rsmd.getColumnCount();
while (rs.next()) {
// Für jeden Datensatz des Ergebnisses wird ein Element namens 'RowSet' erzeugt.
Element row = doc.createElement("RowSet");
for (int i = 1; i < columnCount; i++) {
// für jeden Wert in jedem der Datensätze der Ergenismenge soll ein Element
// mit dem Namen der betreffenden Spalte erzeugt werden.
Element column = doc.createElement(rsmd.getColumnName(i));
Object object = rs.getObject(i);
Text textNode = null;
// Falls der Wert null war, wird an das Element der Text 'null' gehängt.
if (object == null) {
textNode = doc.createTextNode("null");
}
// Ansonsten wird verschiedene Metainformation als Attribut gesetzt.
// Die hier angewandte Methode zum setzten von Attributen
// ist wesentlich komfortabler als die oben angewandte.
else {
column.setAttribute("type", rsmd.getColumnTypeName(i));
column.setAttribute("length",
new Integer(rsmd.getPrecision(i)).toString());
column.setAttribute("table", rsmd.getTableName(i));
column.setAttribute("java-type", object.getClass().getName());
// Für den eigentlichen Wert muss nun ein Text-Knoten
// erstellt werden.
textNode = doc.createTextNode(object.toString());
}
// Der Text-Knoten muss als Unterknoten an das Column-Element angehängt werden.
column.appendChild(textNode);
// Das Column-Element muss wiederum an das RowSet-Element angehängt werden.
row.appendChild(column);
}
// Letztendlich muss auch noch jedes entstandene RowSet-Element and
// das Root-Element angehängt werden.
root.appendChild(row);
}
// Das zusammengesetzte Dokument wird zurückgegeben.
return doc;
}
}