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; } }