| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.empire.db; |
| |
| import java.lang.reflect.Constructor; |
| import java.lang.reflect.InvocationTargetException; |
| import java.sql.Connection; |
| import java.sql.ResultSet; |
| import java.sql.SQLException; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.apache.commons.beanutils.ConstructorUtils; |
| import org.apache.empire.commons.ObjectUtils; |
| import org.apache.empire.data.ColumnExpr; |
| import org.apache.empire.data.DataType; |
| import org.apache.empire.db.exceptions.EmpireSQLException; |
| import org.apache.empire.db.exceptions.QueryNoResultException; |
| import org.apache.empire.db.expr.join.DBJoinExpr; |
| import org.apache.empire.exceptions.BeanInstantiationException; |
| import org.apache.empire.exceptions.InvalidArgumentException; |
| import org.apache.empire.exceptions.MiscellaneousErrorException; |
| import org.apache.empire.exceptions.ObjectNotValidException; |
| import org.apache.empire.xml.XMLUtil; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| import org.w3c.dom.Document; |
| import org.w3c.dom.Element; |
| |
| |
| /** |
| * <P> |
| * This class is used to perform database queries from a DBCommand object and access the results.<BR> |
| * In oder to perform a query call the open() function or - for single row queries - call getRecordData();<BR> |
| * You can iterate through the rows using moveNext() or an iterator.<BR> |
| * <P> |
| * However take care: A reader must always be explicitly closed using the close() method!<BR> |
| * Otherwise you may lock the JDBC connection and run out of resources.<BR> |
| * Use <PRE>try { ... } finally { reader.close(); } </PRE> to make sure the reader is closed.<BR> |
| * <P> |
| * To access and work with the query result you can do one of the following:<BR> |
| * <ul> |
| * <li>access field values directly by using one of the get... functions (see {@link DBRecordData})</li> |
| * <li>get the rows as a list of Java Beans using by using {@link DBReader#getBeanList(Class, int)}</li> |
| * <li>get the rows as an XML-Document using {@link DBReader#getXmlDocument()} </li> |
| * <li>initialize a DBRecord with the current row data using {@link DBReader#initRecord(DBRowSet, DBRecord)}<br> |
| * This will allow you to modify and update the data. |
| * </li> |
| * </ul> |
| * |
| * |
| */ |
| public class DBReader extends DBRecordData |
| { |
| private final static long serialVersionUID = 1L; |
| |
| public abstract class DBReaderIterator implements Iterator<DBRecordData> |
| { |
| protected int curCount = 0; |
| protected int maxCount = 0; |
| |
| public DBReaderIterator(int maxCount) |
| { |
| if (maxCount < 0) |
| maxCount = 0x7FFFFFFF; // Highest positive number |
| // Set Maxcount |
| this.maxCount = maxCount; |
| } |
| |
| /** |
| * Implements the Iterator Interface Method remove not implemented and not applicable. |
| */ |
| @Override |
| public void remove() |
| { |
| log.error("DBReader.remove ist not implemented!"); |
| } |
| |
| /** |
| * Disposes the iterator. |
| */ |
| public void dispose() |
| { |
| curCount = maxCount = -1; |
| } |
| } |
| |
| /** |
| * This is an iterator for scrolling resultsets. |
| * This iterator has no such limitations as the forward iterator. |
| */ |
| public class DBReaderScrollableIterator extends DBReaderIterator |
| { |
| public DBReaderScrollableIterator(int maxCount) |
| { |
| super(maxCount); |
| } |
| |
| /** |
| * Implements the Iterator Interface. |
| * |
| * @return true if there is another record to read |
| */ |
| @Override |
| public boolean hasNext() |
| { |
| try |
| { // Check position |
| if (curCount >= maxCount) |
| return false; |
| // Check Recordset |
| if (rset == null || rset.isLast() || rset.isAfterLast()) |
| return false; |
| // there are more records |
| return true; |
| } catch (SQLException e) { |
| // Error |
| throw new EmpireSQLException(getDatabase(), e); |
| } |
| } |
| |
| /** |
| * Implements the Iterator Interface. |
| * |
| * @return the current Record interface |
| */ |
| @Override |
| public DBRecordData next() |
| { |
| if ((curCount < maxCount && moveNext())) |
| { |
| curCount++; |
| return DBReader.this; |
| } |
| // Past the end! |
| return null; |
| } |
| } |
| |
| /** |
| * This is an iterator for forward only resultsets. |
| * There is an important limitation on this iterator: After calling |
| * hasNext() the caller may not use any functions on the current item any more. i.e. |
| * Example: |
| * while (i.hasNext()) |
| * { |
| * DBRecordData r = i.next(); |
| * Object o = r.getValue(0); // ok |
| * |
| * bool last = i.hasNext(); // ok |
| * Object o = r.getValue(0); // Illegal call! |
| * } |
| */ |
| public class DBReaderForwardIterator extends DBReaderIterator |
| { |
| private boolean getCurrent = true; |
| private boolean hasCurrent = false; |
| |
| public DBReaderForwardIterator(int maxCount) |
| { |
| super(maxCount); |
| } |
| |
| /** |
| * Implements the Iterator Interface. |
| * |
| * @return true if there is another record to read |
| */ |
| @Override |
| public boolean hasNext() |
| { |
| // Check position |
| if (curCount >= maxCount) |
| return false; |
| if (rset == null) |
| throw new ObjectNotValidException(this); |
| // Check next Record |
| if (getCurrent == true) |
| { |
| getCurrent = false; |
| hasCurrent = moveNext(); |
| } |
| return hasCurrent; |
| } |
| |
| /** |
| * Implements the Iterator Interface. |
| * |
| * @return the current Record interface |
| */ |
| @Override |
| public DBRecordData next() |
| { |
| if (hasCurrent == false) |
| return null; // Past the end! |
| // next called without call to hasNext ? |
| if (getCurrent && !moveNext()) |
| { // No more records |
| hasCurrent = false; |
| getCurrent = false; |
| return null; |
| } |
| // Move forward |
| curCount++; |
| getCurrent = true; |
| return DBReader.this; |
| } |
| } |
| |
| // Logger |
| protected static final Logger log = LoggerFactory.getLogger(DBReader.class); |
| |
| private static boolean trackOpenResultSets = false; |
| |
| /** |
| * Support for finding code errors where a DBRecordSet is opened but not closed |
| */ |
| private static ThreadLocal<Map<DBReader, Exception>> threadLocalOpenResultSets = new ThreadLocal<Map<DBReader, Exception>>(); |
| |
| // Object references |
| private DBDatabase db = null; |
| private DBColumnExpr[] colList = null; |
| private ResultSet rset = null; |
| // the field index map |
| private Map<ColumnExpr, Integer> fieldIndexMap = null; |
| |
| /** |
| * Constructs a default DBReader object with the fieldIndexMap enabled. |
| */ |
| public DBReader() |
| { |
| // Default Constructor |
| this(true); |
| } |
| |
| /** |
| * Constructs an empty DBRecordSet object. |
| * @param useFieldIndexMap |
| */ |
| public DBReader(boolean useFieldIndexMap) |
| { |
| if (useFieldIndexMap) |
| fieldIndexMap = new HashMap<ColumnExpr, Integer>(); |
| } |
| |
| /** |
| * Returns the current DBDatabase object. |
| * |
| * @return the current DBDatabase object |
| */ |
| @Override |
| public DBDatabase getDatabase() |
| { |
| return db; |
| } |
| |
| public boolean getScrollable() |
| { |
| try |
| { |
| // Check Resultset |
| return (rset!=null && rset.getType()!=ResultSet.TYPE_FORWARD_ONLY); |
| } catch (SQLException e) |
| { |
| log.error("Cannot determine Resultset type", e); |
| return false; |
| } |
| } |
| |
| /** |
| * Returns the index value by a specified DBColumnExpr object. |
| * |
| * @return the index value |
| */ |
| @Override |
| public int getFieldIndex(ColumnExpr column) |
| { |
| if (fieldIndexMap==null) |
| return findFieldIndex(column); |
| // Use fieldIndexMap |
| Integer index = fieldIndexMap.get(column); |
| if (index==null) |
| { // add to field Index map |
| index = findFieldIndex(column); |
| fieldIndexMap.put(column, index); |
| } |
| return index; |
| } |
| |
| /** Get the column Expression at position */ |
| @Override |
| public DBColumnExpr getColumnExpr(int iColumn) |
| { |
| if (colList == null || iColumn < 0 || iColumn >= colList.length) |
| return null; // Index out of range |
| // return column Expression |
| return colList[iColumn]; |
| } |
| |
| /** |
| * Returns the index value by a specified column name. |
| * |
| * @param column the column name |
| * @return the index value |
| */ |
| @Override |
| public int getFieldIndex(String column) |
| { |
| if (colList != null) |
| { |
| for (int i = 0; i < colList.length; i++) |
| if (colList[i].getName().equalsIgnoreCase(column)) |
| return i; |
| } |
| // not found |
| return -1; |
| } |
| |
| /** |
| * Checks wehter a column value is null Unlike the base |
| * class implementation, this class directly check the value fromt the |
| * resultset. |
| * |
| * @param index index of the column |
| * @return true if the value is null or false otherwise |
| */ |
| @Override |
| public boolean isNull(int index) |
| { |
| if (index < 0 || index >= colList.length) |
| { // Index out of range |
| log.error("Index out of range: " + index); |
| return true; |
| } |
| try |
| { // Check Value on Resultset |
| rset.getObject(index + 1); |
| return rset.wasNull(); |
| } catch (Exception e) |
| { |
| log.error("isNullValue exception", e); |
| return super.isNull(index); |
| } |
| } |
| |
| /** |
| * Returns a data value identified by the column index. |
| * |
| * @param index index of the column |
| * @return the value |
| */ |
| @Override |
| public Object getValue(int index) |
| { |
| // Check params |
| if (index < 0 || index >= colList.length) |
| throw new InvalidArgumentException("index", index); |
| try |
| { // Get Value from Resultset |
| DataType dataType = colList[index].getDataType(); |
| return db.driver.getResultValue(rset, index + 1, dataType); |
| |
| } catch (SQLException e) |
| { // Operation failed |
| throw new EmpireSQLException(this, e); |
| } |
| } |
| |
| /** |
| * Checks if the rowset is open |
| * |
| * @return true if the rowset is open |
| */ |
| public boolean isOpen() |
| { |
| return (rset != null); |
| } |
| |
| /** |
| * Opens the reader by executing the given SQL command.<BR> |
| * After the reader is open, the reader's position is before the first record.<BR> |
| * Use moveNext or iterator() to step through the rows.<BR> |
| * Data of the current row can be accessed through the functions on the RecordData interface.<BR> |
| * <P> |
| * ATTENTION: After using the reader it must be closed using the close() method!<BR> |
| * Use <PRE>try { ... } finally { reader.close(); } </PRE> to make sure the reader is closed.<BR> |
| * <P> |
| * @param cmd the SQL-Command with cmd.getSelect() |
| * @param scrollable true if the reader should be scrollable or false if not |
| * @param conn a valid JDBC connection. |
| */ |
| public void open(DBCommandExpr cmd, boolean scrollable, Connection conn) |
| { |
| if (isOpen()) |
| close(); |
| // Get the query statement |
| String sqlCmd = cmd.getSelect(); |
| // Collect the query parameters |
| Object[] paramValues = cmd.getParamValues(); |
| List<Object[]> subqueryParamValues = (cmd instanceof DBCommand) ? findSubQueryParams((DBCommand)cmd) : null; |
| if (subqueryParamValues!=null && !subqueryParamValues.isEmpty()) |
| { // Check Count |
| if (paramValues!=null || subqueryParamValues.size()>1) |
| throw new MiscellaneousErrorException("More than one (sub)query is a parameterized query. Currently one one query is allowed to be parameterized!"); |
| // Use subquery params |
| paramValues = subqueryParamValues.get(0); |
| } |
| // Execute the query |
| DBDatabase queryDb = cmd.getDatabase(); |
| ResultSet queryRset = queryDb.executeQuery(sqlCmd, paramValues, scrollable, conn); |
| if (queryRset==null) |
| throw new QueryNoResultException(sqlCmd); |
| // init |
| init(queryDb, cmd.getSelectExprList(), queryRset); |
| } |
| |
| /** |
| * Opens the reader by executing the given SQL command.<BR> |
| * <P> |
| * see {@link DBReader#open(DBCommandExpr, boolean, Connection)} |
| * </P> |
| * @param cmd the SQL-Command with cmd.getSelect() |
| * @param conn a valid JDBC connection. |
| */ |
| public final void open(DBCommandExpr cmd, Connection conn) |
| { |
| open(cmd, false, conn); |
| } |
| |
| /** |
| * <P> |
| * Opens the reader by executing the given SQL command and moves to the first row.<BR> |
| * If true is returned data of the row can be accessed through the functions on the RecordData interface.<BR> |
| * This function is intended for single row queries and provided for convenience.<BR> |
| * However it behaves exacly as calling reader.open() and reader.moveNext()<BR> |
| * <P> |
| * ATTENTION: After using the reader it must be closed using the close() method!<BR> |
| * Use <PRE>try { ... } finally { reader.close(); } </PRE> to make sure the reader is closed.<BR> |
| * <P> |
| * @param cmd the SQL-Command with cmd.getSelect() |
| * @param conn a valid JDBC connection. |
| */ |
| public void getRecordData(DBCommandExpr cmd, Connection conn) |
| { // Open the record |
| open(cmd, conn); |
| // Get First Record |
| if (!moveNext()) |
| { // Close |
| throw new QueryNoResultException(cmd.getSelect()); |
| } |
| } |
| |
| /** |
| * Closes the DBRecordSet object, the Statement object and detach the columns.<BR> |
| * A reader must always be closed immediately after using it. |
| */ |
| @Override |
| public void close() |
| { |
| try |
| { // Dispose iterator |
| if (iterator != null) |
| { |
| iterator.dispose(); |
| iterator = null; |
| } |
| // Close Recordset |
| if (rset != null) |
| { |
| getDatabase().closeResultSet(rset); |
| // remove from tracking-list |
| endTrackingThisResultSet(); |
| } |
| // Detach columns |
| colList = null; |
| rset = null; |
| // clear FieldIndexMap |
| if (fieldIndexMap!=null) |
| fieldIndexMap.clear(); |
| // Done |
| } catch (Exception e) |
| { // What's wrong here? |
| log.warn(e.toString()); |
| } |
| } |
| |
| /** |
| * Moves the cursor down the given number of rows. |
| * |
| * @param count the number of rows to skip |
| * |
| * @return true if the reader is on a valid record or false otherwise |
| */ |
| public boolean skipRows(int count) |
| { |
| try |
| { // Check Recordset |
| if (rset == null) |
| throw new ObjectNotValidException(this); |
| // Forward only cursor? |
| int type = rset.getType(); |
| if (type == ResultSet.TYPE_FORWARD_ONLY) |
| { |
| if (count < 0) |
| throw new InvalidArgumentException("count", count); |
| // Move |
| for (; count > 0; count--) |
| { |
| if (!moveNext()) |
| return false; |
| } |
| return true; |
| } |
| // Scrollable Cursor |
| if (count > 0) |
| { // Move a single record first |
| if (rset.next() == false) |
| return false; |
| // Move relative |
| if (count > 1) |
| return rset.relative(count - 1); |
| } |
| else if (count < 0) |
| { // Move a single record first |
| if (rset.previous() == false) |
| return false; |
| // Move relative |
| if (count < -1) |
| return rset.relative(count + 1); |
| } |
| return true; |
| |
| } catch (SQLException e) { |
| // an error occurred |
| throw new EmpireSQLException(this, e); |
| } |
| } |
| |
| /** |
| * Moves the cursor down one row from its current position. |
| * |
| * @return true if the reader is on a valid record or false otherwise |
| */ |
| public boolean moveNext() |
| { |
| try |
| { // Check Recordset |
| if (rset == null) |
| throw new ObjectNotValidException(this); |
| // Move Next |
| if (rset.next() == false) |
| { // Close recordset automatically after last record |
| close(); |
| return false; |
| } |
| return true; |
| |
| } catch (SQLException e) { |
| // an error occurred |
| throw new EmpireSQLException(this, e); |
| } |
| } |
| |
| private DBReaderIterator iterator = null; // there can only be one! |
| |
| /** |
| * Returns an row iterator for this reader.<BR> |
| * There can only be one iterator at a time. |
| * <P> |
| * @param maxCount the maximum number of item that should be returned by this iterator |
| * @return the row iterator |
| */ |
| public Iterator<DBRecordData> iterator(int maxCount) |
| { |
| if (iterator == null && rset != null) |
| { |
| if (getScrollable()) |
| iterator = new DBReaderScrollableIterator(maxCount); |
| else |
| iterator = new DBReaderForwardIterator(maxCount); |
| } |
| return iterator; |
| } |
| |
| /** |
| * <PRE> |
| * Returns an row iterator for this reader. |
| * There can only be one iterator at a time. |
| * </PRE> |
| * @return the row iterator |
| */ |
| public final Iterator<DBRecordData> iterator() |
| { |
| return iterator(-1); |
| } |
| |
| /** |
| * <PRE> |
| * initializes a DBRecord object with the values of the current row. |
| * At least all primary key columns of the target rowset must be provided by this reader. |
| * This function is equivalent to calling rowset.initRecord(rec, reader) |
| * set also {@link DBRowSet#initRecord(DBRecord, DBRecordData)}); |
| * </PRE> |
| * @param rowset the rowset to which to attach |
| * @param rec the record which to initialize |
| */ |
| public void initRecord(DBRowSet rowset, DBRecord rec) |
| { |
| if (rowset==null) |
| throw new InvalidArgumentException("rowset", rowset); |
| // init Record |
| rowset.initRecord(rec, this); |
| } |
| |
| /** |
| * Returns the result of a query as a list of objects restricted |
| * to a maximum number of objects (unless maxCount is -1). |
| * |
| * @param c the collection to add the objects to |
| * @param t the class type of the objects in the list |
| * @param maxCount the maximum number of objects |
| * |
| * @return the list of T |
| */ |
| @SuppressWarnings("unchecked") |
| public <C extends Collection<T>, T> C getBeanList(C c, Class<T> t, int maxCount) |
| { |
| // Check Recordset |
| if (rset == null) |
| { // Resultset not available |
| throw new ObjectNotValidException(this); |
| } |
| // Query List |
| try |
| { |
| // Check whether we can use a constructor |
| Class<?>[] paramTypes = new Class[getFieldCount()]; |
| for (int i = 0; i < colList.length; i++) |
| paramTypes[i] = DBExpr.getValueClass(colList[i].getDataType()); |
| // Find Constructor |
| Constructor<?> ctor = findMatchingAccessibleConstructor(t, paramTypes); |
| Object[] args = (ctor!=null) ? new Object[getFieldCount()] : null; |
| |
| // Create a list of beans |
| while (moveNext() && maxCount != 0) |
| { // Create bean an init |
| if (ctor!=null) |
| { // Use Constructor |
| Class<?>[] ctorParamTypes = ctor.getParameterTypes(); |
| for (int i = 0; i < getFieldCount(); i++) |
| args[i] = ObjectUtils.convert(ctorParamTypes[i], getValue(i)); |
| T bean = (T)ctor.newInstance(args); |
| c.add(bean); |
| } |
| else |
| { // Use Property Setters |
| T bean = t.newInstance(); |
| getBeanProperties(bean); |
| c.add(bean); |
| } |
| // Decrease count |
| if (maxCount > 0) |
| maxCount--; |
| } |
| // done |
| return c; |
| } catch (InvocationTargetException e) { |
| throw new BeanInstantiationException(t, e); |
| } catch (IllegalAccessException e) { |
| throw new BeanInstantiationException(t, e); |
| } catch (InstantiationException e) { |
| throw new BeanInstantiationException(t, e); |
| } |
| } |
| |
| /** |
| * Returns the result of a query as a list of objects. |
| * |
| * @param t the class type of the objects in the list |
| * @param maxItems the maximum number of objects |
| * |
| * @return the list of T |
| */ |
| public final <T> ArrayList<T> getBeanList(Class<T> t, int maxItems) { |
| return getBeanList(new ArrayList<T>(), t, maxItems); |
| } |
| |
| /** |
| * Returns the result of a query as a list of objects. |
| * |
| * @param t the class type of the objects in the list |
| * |
| * @return the list of T |
| */ |
| public final <T> ArrayList<T> getBeanList(Class<T> t) { |
| return getBeanList(t, -1); |
| } |
| |
| /** |
| * Moves the cursor down one row from its current position. |
| * |
| * @return the number of column descriptions added to the Element |
| */ |
| @Override |
| public int addColumnDesc(Element parent) |
| { |
| if (colList == null) |
| throw new ObjectNotValidException(this); |
| // Add Field Description |
| for (int i = 0; i < colList.length; i++) |
| colList[i].addXml(parent, 0); |
| // return count |
| return colList.length; |
| } |
| |
| /** |
| * Adds all children to a parent. |
| * |
| * @param parent the parent element below which to search the child |
| * @return the number of row values added to the element |
| */ |
| @Override |
| public int addRowValues(Element parent) |
| { |
| if (rset == null) |
| throw new ObjectNotValidException(this); |
| // Add all children |
| for (int i = 0; i < colList.length; i++) |
| { // Read all |
| String name = colList[i].getName(); |
| String idColumnAttr = getXmlDictionary().getRowIdColumnAttribute(); |
| if (name.equalsIgnoreCase("id")) |
| { // Add Attribute |
| parent.setAttribute(idColumnAttr, getString(i)); |
| } |
| else |
| { // Add Element |
| String value = getString(i); |
| Element elem = XMLUtil.addElement(parent, name, value); |
| if (value == null) |
| elem.setAttribute("null", "yes"); // Null-Value |
| } |
| } |
| // return count |
| return colList.length; |
| } |
| |
| /** |
| * Adds all children to a parent. |
| * |
| * @param parent the parent element below which to search the child |
| * @return the number of rows added to the element |
| */ |
| public int addRows(Element parent) |
| { |
| int count = 0; |
| if (rset == null) |
| return 0; |
| // Add all rows |
| String rowElementName = getXmlDictionary().getRowElementName(); |
| while (moveNext()) |
| { |
| addRowValues(XMLUtil.addElement(parent, rowElementName)); |
| count++; |
| } |
| return count; |
| } |
| |
| /** |
| * returns the DBXmlDictionary that should used to generate XMLDocuments<BR> |
| * @return the DBXmlDictionary |
| */ |
| protected DBXmlDictionary getXmlDictionary() |
| { |
| return DBXmlDictionary.getInstance(); |
| } |
| |
| /** |
| * Returns a XML document with the field description an values of this record. |
| * |
| * @return the new XML Document object |
| */ |
| @Override |
| public Document getXmlDocument() |
| { |
| if (rset == null) |
| return null; |
| // Create Document |
| String rowsetElementName = getXmlDictionary().getRowSetElementName(); |
| Element root = XMLUtil.createDocument(rowsetElementName); |
| // Add Field Description |
| addColumnDesc(root); |
| // Add row rset |
| addRows(root); |
| // return Document |
| return root.getOwnerDocument(); |
| } |
| |
| /** returns the number of the elements of the colList array */ |
| @Override |
| public int getFieldCount() |
| { |
| return (colList != null) ? colList.length : 0; |
| } |
| |
| /** |
| * Initialize the reader from an open JDBC-ResultSet |
| * @param db the database |
| * @param colList the query column expressions |
| * @param rset the JDBC-ResultSet |
| */ |
| protected void init(DBDatabase db, DBColumnExpr[] colList, ResultSet rset) |
| { |
| this.db = db; |
| this.colList = colList; |
| this.rset = rset; |
| // add to tracking list (if enabled) |
| trackThisResultSet(); |
| } |
| |
| /** |
| * Access the column expression list |
| * @return the column expression list |
| */ |
| protected final DBColumnExpr[] getColumnExprList() |
| { |
| return colList; |
| } |
| |
| /** |
| * Access the JDBC-ResultSet |
| * @return the JDBC-ResultSet |
| */ |
| protected final ResultSet getResultSet() |
| { |
| return rset; |
| } |
| |
| /** |
| * finds the field Index of a given column expression |
| * Internally used as helper for getFieldIndex() |
| * @return the index value |
| */ |
| protected int findFieldIndex(ColumnExpr column) |
| { |
| if (colList == null) |
| return -1; |
| // First chance: Try to find an exact match |
| for (int i = 0; i < colList.length; i++) |
| { |
| if (colList[i].equals(column)) |
| return i; |
| } |
| // Second chance: Try Update Column |
| if (column instanceof DBColumn) |
| { |
| for (int i = 0; i < colList.length; i++) |
| { |
| DBColumn updColumn = colList[i].getUpdateColumn(); |
| if (updColumn!=null && updColumn.equals(column)) |
| return i; |
| } |
| } |
| // not found! |
| return -1; |
| } |
| |
| /** |
| * internal helper function to find parameterized subqueries |
| * @param cmd the command |
| * @return a list of parameter arrays, one for each subquery |
| */ |
| protected List<Object[]> findSubQueryParams(DBCommand cmd) |
| { |
| List<Object[]> subQueryParams = null; |
| List<DBJoinExpr> joins = cmd.getJoins(); |
| if (joins==null) |
| return null; // no joins |
| // check the joins |
| for (DBJoinExpr j : joins) |
| { |
| DBRowSet rsl = j.getLeft().getUpdateColumn().getRowSet(); |
| DBRowSet rsr = j.getRight().getUpdateColumn().getRowSet(); |
| if (rsl instanceof DBQuery) |
| { // the left join is a query |
| subQueryParams = addSubQueryParams((DBQuery)rsl, subQueryParams); |
| } |
| if (rsr instanceof DBQuery) |
| { // the right join is a query |
| subQueryParams = addSubQueryParams((DBQuery)rsr, subQueryParams); |
| } |
| } |
| return subQueryParams; |
| } |
| |
| /** |
| * Adds any subquery params to the supplied list |
| * @param query the subquery |
| * @param list the current list of parameters |
| * @return the new list of parameters |
| */ |
| private List<Object[]> addSubQueryParams(DBQuery query, List<Object[]> list) |
| { |
| DBCommandExpr sqcmd = query.getCommandExpr(); |
| Object[] params = query.getCommandExpr().getParamValues(); |
| if (params!=null && params.length>0) |
| { // add params |
| if (list== null) |
| list = new ArrayList<Object[]>(); |
| list.add(params); |
| } |
| // recurse |
| if (sqcmd instanceof DBCommand) |
| { // check this command too |
| List<Object[]> sqlist = findSubQueryParams((DBCommand)sqcmd); |
| if (sqlist!=null && !sqlist.isEmpty()) |
| { // make one list |
| if (list!= null) |
| list.addAll(sqlist); |
| else |
| list = sqlist; |
| } |
| } |
| return list; |
| } |
| |
| /** |
| * Support for finding code errors where a DBRecordSet is opened but not closed. |
| * |
| * @author bond |
| */ |
| protected synchronized void trackThisResultSet() |
| { |
| // check if enabled |
| if (trackOpenResultSets==false) |
| return; |
| // add this to the vector of open resultsets on this thread |
| Map<DBReader, Exception> openResultSets = threadLocalOpenResultSets.get(); |
| if (openResultSets == null) |
| { |
| // Lazy initialization of the |
| openResultSets = new HashMap<DBReader, Exception>(2); |
| threadLocalOpenResultSets.set(openResultSets); |
| } |
| |
| Exception stackException = openResultSets.get(this); |
| if (stackException != null) |
| { |
| log.error("DBRecordSet.addOpenResultSet called for an object which is already in the open list. This is the stack of the method opening the object which was not previously closed.", stackException); |
| // the code continues and overwrites the logged object with the new one |
| } |
| // get the current stack trace |
| openResultSets.put(this, new Exception()); |
| } |
| |
| /** |
| * Support for finding code errors where a DBRecordSet is opened but not closed. |
| * |
| * @author bond |
| */ |
| protected synchronized void endTrackingThisResultSet() |
| { |
| // check if enabled |
| if (trackOpenResultSets==false) |
| return; |
| // remove |
| Map<DBReader, Exception> openResultSets = threadLocalOpenResultSets.get(); |
| if (openResultSets.containsKey(this) == false) |
| { |
| log.error("DBRecordSet.removeOpenResultSet called for an object which is not in the open list. Here is the current stack.", new Exception()); |
| } |
| else |
| { |
| openResultSets.remove(this); |
| } |
| } |
| |
| /* |
| private void writeObject(ObjectOutputStream stream) throws IOException { |
| if (rset != null) { |
| throw new NotSerializableException(DBReader.class.getName() + " (due to attached ResultSet)"); |
| } |
| } |
| */ |
| |
| /** |
| * copied from org.apache.commons.beanutils.ConstructorUtils since it's private there |
| */ |
| protected static Constructor<?> findMatchingAccessibleConstructor(Class<?> clazz, Class<?>[] parameterTypes) |
| { |
| // See if we can find the method directly |
| // probably faster if it works |
| // (I am not sure whether it's a good idea to run into Exceptions) |
| // try { |
| // Constructor ctor = clazz.getConstructor(parameterTypes); |
| // try { |
| // // see comment in org.apache.commons.beanutils.ConstructorUtils |
| // ctor.setAccessible(true); |
| // } catch (SecurityException se) { /* ignore */ } |
| // return ctor; |
| // } catch (NoSuchMethodException e) { /* SWALLOW */ } |
| |
| // search through all constructors |
| int paramSize = parameterTypes.length; |
| Constructor<?>[] ctors = clazz.getConstructors(); |
| for (int i = 0, size = ctors.length; i < size; i++) |
| { // compare parameters |
| Class<?>[] ctorParams = ctors[i].getParameterTypes(); |
| int ctorParamSize = ctorParams.length; |
| if (ctorParamSize == paramSize) |
| { // Param Size matches |
| boolean match = true; |
| for (int n = 0; n < ctorParamSize; n++) |
| { |
| if (!ObjectUtils.isAssignmentCompatible(ctorParams[n], parameterTypes[n])) |
| { |
| match = false; |
| break; |
| } |
| } |
| if (match) { |
| // get accessible version of method |
| Constructor<?> ctor = ConstructorUtils.getAccessibleConstructor(ctors[i]); |
| if (ctor != null) { |
| try { |
| ctor.setAccessible(true); |
| } catch (SecurityException se) { /* ignore */ } |
| return ctor; |
| } |
| } |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Enables or disabled tracking of open ResultSets |
| * @param enable true to enable or false otherwise |
| * @return the previous state of the trackOpenResultSets |
| */ |
| public static synchronized boolean enableOpenResultSetTracking(boolean enable) |
| { |
| boolean prev = trackOpenResultSets; |
| trackOpenResultSets = enable; |
| return prev; |
| } |
| |
| /** |
| * <PRE> |
| * Call this if you want to check whether there are any unclosed resultsets |
| * It logs stack traces to help find piece of code |
| * where a DBReader was opened but not closed. |
| * </PRE> |
| */ |
| public static synchronized void checkOpenResultSets() |
| { |
| // check if enabled |
| if (trackOpenResultSets==false) |
| throw new MiscellaneousErrorException("Open-ResultSet-Tracking has not been enabled. Use DBReader.enableOpenResultSetTracking() to enable or disable."); |
| // Check map |
| Map<DBReader, Exception> openResultSets = threadLocalOpenResultSets.get(); |
| if (openResultSets != null && openResultSets.isEmpty() == false) |
| { |
| // we have found a(n) open result set(s). Now show the stack trace(s) |
| Object keySet[] = openResultSets.keySet().toArray(); |
| for (int i = 0; i < keySet.length; i++) |
| { |
| Exception stackException = openResultSets.get(keySet[i]); |
| log.error("A DBReader was not closed. Stack of opening code is ", stackException); |
| } |
| openResultSets.clear(); |
| } |
| } |
| |
| } |