All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.org.jafer.databeans.JDBC Maven / Gradle / Ivy

The newest version!
/**
 * JAFER Toolkit Project.
 * Copyright (C) 2002, JAFER Toolkit Project, Oxford University.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

/**
 *  Title: JAFER Toolkit
 *  Description:
 *  Copyright: Copyright (c) 2002
 *
 *@author     Antony Corfield; Matthew Dovey; Colin Tatham
 *@version    1.0
 */

package org.jafer.databeans;

import java.io.StringWriter;
import java.net.URL;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Hashtable;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.sql.DataSource;
import javax.xml.transform.TransformerException;

import org.apache.xpath.CachedXPathAPI;
import org.apache.xpath.NodeSet;
import org.jafer.exception.JaferException;
import org.jafer.interfaces.Databean;
import org.jafer.interfaces.Present;
import org.jafer.interfaces.Search;
import org.jafer.interfaces.Z3950Connection;
import org.jafer.record.Field;
import org.jafer.util.Config;
import org.jafer.util.xml.DOMFactory;
import org.jafer.util.xml.XMLSerializer;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import z3950.v3.RPNQuery;

/**
 * 

* Superclass with behaviour common to subclasses. Specific subclasses * configured to make use of specific JDBC drivers should be instantiated.
* No caching available, apart from the ResultSet object (which may have a * Forward Only cursor).
*

* XML configuration files:
config.xml
Database * name, username, password for JDBC driver to use.
Toggle to return * results with initial search (piggyback) on/off.
List of XML templates * for creating records of different formats, with locations of the files.
* Mappings of Z39.50 Use attributes to SQL database tables and columns. (Also * prefix/append values to add wildcards to SQL query for each.)
Mappings * for translation of Relation attribute values to SQL operators.
* OAITemplate.xml
Template record conforming to OAI schema. Data * from database is inserted in place of nodes, from the * column specified.
MODSTemplate.xml
Template record * conforming to MODS schema. Data from database is inserted in place of * nodes, from the column specified.
*

*

* (Column names used in building the SQL query are taken from the XML record * template in use, and are expected to be of the form tableName.columnName.) *

* query.xsl
The incoming query is translated from an XML format * to SQL via this stylesheet. */ public abstract class JDBC extends Databean implements Z3950Connection, Present, Search { public static Logger logger; private URL queryXSLT; private Document configDocument; private Document recordTemplate, recordDocument; private Hashtable paramMap, recordSchemas; protected Node query; protected DataSource dataSource; protected Connection connection; protected ResultSet resultSet; private String hostName, currentDatabase, recordSchema, queryString; protected String username, password, primaryTable, primaryKey, foreignKey; private int port, recordCursor; protected int nResults; private static CachedXPathAPI cachedXPath = new CachedXPathAPI(); public final static String CONFIG_FILE = "org/jafer/conf/jdbcConfig/config.xml"; public final static String QUERY_XSLT = "org/jafer/conf/jdbcConfig/query.xsl"; /** * Stores a reference to exception that occured in the last search or null * if no errors occured */ private JaferException searchException = null; /** * Loads configuration info from config files. * * @see initialise() */ public JDBC() { /** * @todo move initialise() methods out of constructor, to prevent * unecessary processing when called by JDBCFactory getDatabean() * for interface probing. */ /** @todo handle exceptions, rather than passing... */ /** @todo logging in rest of Class... */ logger = Logger.getLogger("org.jafer.zserver"); try { initialise(); } catch (JaferException ex) { logger.log(Level.SEVERE, "Error in configuring JDBC object", ex); throw new RuntimeException("Error in configuring JDBC object", ex); } } public void setHost(String hostName) { /** @todo method used by JDBCFactory using setting in server.xml. */ this.hostName = hostName; } public String getHost() { return hostName; } public void setPort(int port) { /** @todo method used by JDBCFactory using setting in server.xml. */ this.port = port; } public int getPort() { return port; } public void setRecordCursor(int nRecord) throws JaferException { if (nRecord > 0 && nRecord <= getNumberOfResults()) recordCursor = nRecord; else throw new JaferException("Record cursor cannot be set to a value higher than the number of results returned, or zero"); } public int getRecordCursor() { return recordCursor; } public void setCheckRecordFormat(boolean checkRecordFormat) { throw new java.lang.UnsupportedOperationException("Method setCheckRecordFormat() not yet implemented."); } public boolean isCheckRecordFormat() { throw new java.lang.UnsupportedOperationException("Method isCheckRecordFormat() not yet implemented."); } public void setElementSpec(String elementSpec) { throw new java.lang.UnsupportedOperationException("Method setElementSpec() not yet implemented."); } public String getElementSpec() { throw new java.lang.UnsupportedOperationException("Method getElementSpec() not yet implemented."); } /** * Sets record format to be produced, using name of XML Schema for record * format.
If a suitable template is not available, uses default * record format set in config.xml, or last schema set successfully. */ public void setRecordSchema(String schema) { if (schema.equals(getRecordSchema())) return; if (recordSchemas.containsKey(schema)) { recordSchema = schema; setRecordTemplate(getRecordSchema()); } else logger.log(Level.WARNING, "Record Schema requested is not available: " + schema); } public String getRecordSchema() { return recordSchema; } /** * Creates record from data in database, following the layout of the chosen * record template. */ public Field getCurrentRecord() throws JaferException { Field field = null; if (resultSet == null) try { search(); } catch (JaferException ex) { ex.printStackTrace(); } catch (SQLException ex) { ex.printStackTrace(); } if (getRecordSchema() == null) throw new JaferException("Record schema not set"); try { field = createRecord(); } catch (JaferException ex1) { ex1.printStackTrace(); } catch (SQLException ex1) { ex1.printStackTrace(); } return field; // } // catch (SQLException ex) { // throw new JaferException("Error executing query on database", ex); // } } public String getCurrentDatabase() { return currentDatabase; } public void setSearchProfile(String searchProfile) { throw new java.lang.UnsupportedOperationException("Method setSearchProfile() not yet implemented."); } public String getSearchProfile() { throw new java.lang.UnsupportedOperationException("Method getSearchProfile() not yet implemented."); } public void setResultSetName(String resultSetName) { throw new java.lang.UnsupportedOperationException("Method setResultSetName() not yet implemented."); } public String getResultSetName() { throw new java.lang.UnsupportedOperationException("Method getResultSetName() not yet implemented."); } /** * Method doesn't do anything. (Single database name is loaded from XML * configuration file: config.xml) * * @param database (not used) */ public void setDatabases(String database) { } /** * Method doesn't do anything. (Single database name is loaded from XML * configuration file: config.xml) * * @param databases (not used) */ public void setDatabases(String[] databases) { } /** * Returns an array containing the name of the single database that can be * searched. Database name is loaded from XML configuration file: * config.xml) * * @returns name of database that can be searched. */ public String[] getDatabases() { return new String[] { this.currentDatabase }; } public void setParseQuery(boolean parseQuery) { throw new java.lang.UnsupportedOperationException("Method setParseQuery() not yet implemented."); } public boolean isParseQuery() { throw new java.lang.UnsupportedOperationException("Method isParseQuery() not yet implemented."); } public int submitQuery(Object query) throws JaferException { // reset the last search exception setSearchException(null); if (query instanceof Node) return submitQuery((Node) query); else if (query instanceof RPNQuery) return submitQuery((RPNQuery) query); else { // store search exception for caller to check against setSearchException(new JaferException("Only queries of type Node or RPNQuery accepted. (See www.jafer.org)")); throw getSearchException(); } } public int submitQuery(RPNQuery query) throws JaferException { try { org.jafer.query.RPNQuery rpnQuery = new org.jafer.query.RPNQuery(query); return submitQuery(rpnQuery.toJaferQuery().getQuery()); } catch (JaferException exc) { // store search exception for caller to check against setSearchException(exc); throw getSearchException(); } } public int submitQuery(Node query) throws JaferException { // reset the last search exception setSearchException(null); try { /** * Subclasses will need to override this if ResultSet in use is * scrollable, and a ResultSet is to be re-used for Present * operation. */ /** @todo more specific error message: when connection isn't made... */ if (dataSource == null) configureDataSource(); this.query = query; setQueryString("select count(*)"); System.out.println(getQueryString()); ResultSet results = getStatement().executeQuery(getQueryString()); logger.log(Level.FINE, "submitQuery(): " + getQueryString()); results.next(); nResults = results.getInt(1); return nResults; } catch (SQLException ex) { // throw new JaferException("Error in database connection, or in // generation/execution of SQL statement", ex); // store search exception for caller to check against setSearchException(new JaferException(ex.getMessage())); throw getSearchException(); } catch (JaferException exc) { // store search exception for caller to check against setSearchException(exc); throw getSearchException(); } } public void saveQuery(String filePath) throws JaferException { if (getQuery() instanceof Node) XMLSerializer.out((Node) getQuery(), "xml", filePath); /** @todo more? */ } public int getNumberOfResults() { return nResults; } public int getNumberOfResults(String databaseName) { /** @todo do something else here... */ return nResults; } public Object getQuery() { return query; } /** * Executes search on database using current query. */ protected void search() throws SQLException, JaferException { setQueryString(""); System.out.print(getQueryString()); resultSet = getStatement().executeQuery(getQueryString()); logger.log(Level.FINE, "search(): " + getQueryString()); } /** * Gets a conection from the DataSource in use. */ protected Connection getConnection() throws SQLException { /** @todo test to see if timed out? */ if (connection != null) return connection; connection = dataSource.getConnection(); return connection; } /** * Gets a new Statement, which can be modified in subclasses. */ protected Statement getStatement() throws SQLException { /** * Subclasses will need to override if a scrollable ResultSet is not * available: */ return getConnection().createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE); } /** * Transforms XML query to SQL string via stylesheet. */ protected void setQueryString(String selectPhrase) throws JaferException { StringWriter writer = new StringWriter(); paramMap.put("selectStatement", selectPhrase); paramMap.put("primaryKey", primaryKey); paramMap.put("primaryTable", primaryTable); paramMap.put("foreignKey", foreignKey); Element root = query.getOwnerDocument().createElement("root"); /** @todo handle cases where ownerDocument is null... */ root.appendChild(query); try { XMLSerializer.transformOutput(root, queryXSLT, paramMap, writer); } catch (JaferException ex) { throw new JaferException(ex.getMessage(), ex); // throw new JaferException("Error in transformation of XML query to // SQL string.", ex); } queryString = writer.toString(); } protected String getQueryString() { return queryString; } private Field createRecord() throws SQLException, JaferException { Node templateNode = recordTemplate.getDocumentElement(); Node fieldNode = processTemplate(templateNode); Element rootNode = recordDocument.createElement("record"); rootNode.appendChild(fieldNode); try { recordSchema = getAttributeValue(templateNode, "xmlns"); rootNode.setAttribute("schema", recordSchema); rootNode.setAttribute("syntax", Config.getRecordSyntax(recordSchema)); // rootNode.appendChild(recordDocument.importNode(getQuery(), // true)); } catch (NullPointerException ex) { throw new JaferException("No \"xmlns\" attribute found in record template: " + templateNode.getNodeName()); } rootNode.setAttribute("dbName", getCurrentDatabase()); rootNode.setAttribute("number", Integer.toString(getRecordCursor())); return new Field(rootNode, fieldNode); } private Node processTemplate(Node template) throws SQLException, JaferException { Node fieldNode = processNode(template).item(0); ((Element) fieldNode).removeAttribute("xmlns:jafer"); return fieldNode; } private NodeSet processNode(Node node) throws SQLException, JaferException { Node child, newNode; NodeList list = node.getChildNodes(); NodeSet childSet = new NodeSet(); for (int i = 0; i < list.getLength(); i++) { child = list.item(i); if (child.getNodeType() != Node.TEXT_NODE) childSet.addNodes(processNode(child)); } if (node.getNodeName().equals("jafer:insertElement")) return processJaferElement(node, childSet); else if (node.getNodeName().equals("jafer:insertData")) return processJaferData(node); else { newNode = recordDocument.importNode(node, false); for (int i = 0; i < childSet.getLength(); i++) { newNode.appendChild(childSet.item(i)); } return new NodeSet(newNode); } } private NodeSet processJaferElement(Node jaferNode, NodeSet childSet) throws JaferException, SQLException { /** * @todo use String[] instead of Vector? Throw exception if no column * name found, and handle in processNode()? */ NodeSet set = new NodeSet(); Vector data; Node newNode, textNode, copy; newNode = recordDocument.createElement(getAttributeValue(jaferNode, "name")); NamedNodeMap map = jaferNode.getAttributes(); for (int j = 0; j < map.getLength(); j++) { if (map.item(j).getNodeName() != "column" && map.item(j).getNodeName() != "name") ((Element) newNode).setAttribute(map.item(j).getNodeName(), map.item(j).getNodeValue()); } for (int k = 0; k < childSet.getLength(); k++) newNode.appendChild(childSet.item(k)); String columnName = getAttributeValue(jaferNode, "column"); if (columnName != null && !columnName.equals("")) { data = processInsert(columnName); String text; for (int i = 0; i < data.size(); i++) { copy = newNode.cloneNode(true); text = (data.get(i) == null) ? "" : String.valueOf(data.get(i)); if (copy.getNodeName().equalsIgnoreCase("fixfield")) // fixfields // need // to be // enclosed // in // quotes textNode = recordDocument.createTextNode("\"" + text + "\""); else textNode = recordDocument.createTextNode(text); copy.appendChild(textNode); set.addElement(copy); } } else set.addElement(newNode); return set; } private NodeSet processJaferData(Node jaferNode) throws JaferException, SQLException { /** * @todo use String[] instead of Vector? Throw exception if no column * name found, and handle in processNode() */ Vector data; String columnName, fieldData = "", delimiter = ""; Node textNode; columnName = getAttributeValue(jaferNode, "column"); if (columnName != null && !columnName.equals("")) { data = processInsert(columnName); if (getAttributeValue(jaferNode, "delimiter") != null) delimiter = getAttributeValue(jaferNode, "delimiter"); for (int i = 0; i < data.size(); i++) { if (data.get(i) != null && !data.get(i).equals("")) { if (!fieldData.equals("")) fieldData += delimiter; fieldData += String.valueOf(data.get(i)); } } } else logger.log(Level.WARNING, "No column name found in record template for insertData element."); // throw new JaferException("No column name found in record template for // insertData element."); textNode = recordDocument.createTextNode(fieldData); return new NodeSet(textNode); } protected Vector processInsert(String columnName) throws SQLException, JaferException { /** @todo use String[] instead of Vector? */ Vector data = new Vector(); String tableName, pKey, sql; ResultSet results; /** @todo optimize: re-use ResultSet, need to clear when updating cursor */ if (columnName != null && !columnName.equals("") && alignCursor()) {// i.e. no value in column attribute pKey = resultSet.getString(1); if (columnName.indexOf('.') < 0) throw new JaferException("Column name must be specified in record template as tablename.columnname"); tableName = columnName.substring(0, columnName.indexOf('.')); if (tableName.equalsIgnoreCase(primaryTable)) sql = "select " + columnName + " as '" + columnName + "' from " + tableName + " where " + primaryKey + " = " + pKey; else sql = "select " + columnName + " as '" + columnName + "' from " + tableName + " where " + foreignKey + " = " + pKey; try { results = getStatement().executeQuery(sql); } catch (SQLException ex) { /** @todo more detail in message: */ logger.log(Level.WARNING, "Error in retrieving data from database, column skipped: " + columnName, ex); return data; } while (results.next()) data.add(results.getString(columnName)); } return data; } protected boolean alignCursor() throws SQLException, JaferException { // subclass needs to override this if absolute() not supported by jdbc // driver in use. return resultSet.absolute(getRecordCursor()); } // CONFIGURATION methods: // /** * Loads XML config file, sets available schemas (and default schema).
* Loads query transform stylesheet.
Loads database name, user name * and password for JDBC conection from XML config file.
*/ private void initialise() throws JaferException { URL configFile = this.getClass().getClassLoader().getResource(CONFIG_FILE); configDocument = DOMFactory.parse(configFile); queryXSLT = this.getClass().getClassLoader().getResource(QUERY_XSLT); paramMap = new Hashtable(); recordDocument = DOMFactory.newDocument(); setDatabaseConfiguration(); recordSchemas = new Hashtable(); loadRecordSchemas(); } private void setDatabaseConfiguration() throws JaferException { currentDatabase = getXMLConfigValue("config/database/@name"); username = getXMLConfigValue("config/database/@username"); password = getXMLConfigValue("config/database/@password"); /** * @todo identify primary table if more than one table listed in xml * config... */ primaryTable = getXMLConfigValue("config/database/tables/table/@name"); primaryKey = getXMLConfigValue("config/database/tables/table/@primaryKey"); foreignKey = getXMLConfigValue("config/database/tables/table/@foreignKey"); } abstract protected void configureDataSource() throws JaferException; private void loadRecordSchemas() throws JaferException { Node child; String schema, location; NodeList list = selectNodeList(configDocument, "config/recordTemplates/template"); for (int i = 0; i < list.getLength(); i++) { child = list.item(i); schema = getAttributeValue(child, "schema"); location = getAttributeValue(child, "location"); recordSchemas.put(schema, location); } } private void setRecordTemplate(String schema) { String filePath = (String) recordSchemas.get(schema); URL template = this.getClass().getClassLoader().getResource(filePath); try { recordTemplate = DOMFactory.parse(template); } catch (JaferException ex) { logger.log(Level.WARNING, "Problem loading XML record template for schema: " + schema); } } // UTILITY methods: // protected String getXMLConfigValue(String XPath) throws JaferException { if (selectNode(configDocument, XPath) != null) return selectNode(configDocument, XPath).getNodeValue(); return ""; } private Node selectNode(Node sourceNode, String XPath) throws JaferException { Node node; try { node = cachedXPath.selectSingleNode(sourceNode, XPath); } catch (TransformerException ex) { throw new JaferException("Error in accessing XML configuration information"); } return node; } private NodeList selectNodeList(Node sourceNode, String XPath) throws JaferException { NodeList list; try { list = cachedXPath.selectNodeList(sourceNode, XPath); } catch (TransformerException ex) { throw new JaferException("Error in accessing XML configuration information"); } return list; } private String getAttributeValue(Node node, String attName) { if (node.getAttributes().getNamedItem(attName) != null) return node.getAttributes().getNamedItem(attName).getNodeValue(); return null; } /* * (non-Javadoc) * * @see org.jafer.interfaces.Search#getSearchDiagnostic(java.lang.String) */ public JaferException getSearchException(String database) throws JaferException { // make sure database name specified and this client has a // JaferException // lined up to report if the database name matches if (database != null && getSearchException() != null) { // get the JaferException for the database, empty arry if no errors // found JaferException[] errors = getSearchException(new String[] { database }); if (errors.length != 0) { // return the first JaferException return errors[0]; } } return null; } /* * (non-Javadoc) * * @see org.jafer.interfaces.Search#getSearchDiagnostics(java.lang.String[]) */ public JaferException[] getSearchException(String[] databases) throws JaferException { // create empty array for success condition JaferException[] errors = new JaferException[0]; // make sure database names are specified and this client has a // JaferException lined up to report if the database name matches if (databases != null && getSearchException() != null) { // make sure this abstract client has defined databases if it // doesn't there is never a way to return JaferException and hence // it is // not fully configured if (this.getCurrentDatabase() == null) { throw new JaferException("Configuration Error: Client does not have any defined databases to match against"); } // loop round all the supplied database names looking for one // that matches a name in this abstract clients database name list for (int index = 0; index < databases.length; index++) { // do we get a name match if (databases[index] != null && databases[index].equalsIgnoreCase(this.getCurrentDatabase())) { errors = new JaferException[] { getSearchException() }; // break the loop as well as can only have one exception break; } } } return errors; } /** * This method returns the JaferException from the last search. * * @return JaferException instance or null if no errors were found */ protected JaferException getSearchException() { return this.searchException; } /** * This method sets the last exception that occurred during a search * * @param exc The exception that occurred */ protected void setSearchException(JaferException exc) { this.searchException = exc; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy