/* * 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.cocoon.components.language.markup.xsp; import org.apache.avalon.framework.logger.AbstractLogEnabled; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.CallableStatement; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.util.ArrayList; /** * This is base class for all EsqlQueries * * @author <a href="mailto:tcurdt@apache.org">Torsten Curdt</a> * @version CVS $Id$ */ public abstract class AbstractEsqlQuery extends AbstractLogEnabled { private int maxRows = -1; private int skipRows = 0; private int rowCount = -1; private int position = -1; private String query = null; private Connection connection = null; private ResultSetMetaData resultSetMetaData = null; private PreparedStatement preparedStatement = null; private ResultSet resultSet = null; private boolean hasResultSet = false; private boolean keepgoing = true; private int queryResultsCount = 0; private int updateResultsCount = 0; private int updateCount = -2; private ArrayList groups = null; private int groupLevel = -1; private int changeLevel = -1; /** * Constructor * * @param connection * @param query - The SQL query string */ protected AbstractEsqlQuery(Connection connection, String query) { this.connection = connection; this.query = query; } /** * Only newInstance may use this contructor * @param resultSet */ protected AbstractEsqlQuery(final ResultSet resultSet) { this.connection = null; this.query = null; this.resultSet = resultSet; this.hasResultSet = (resultSet != null); } /** * Create a EsqlQuery of the same type * @param resultSet */ public abstract AbstractEsqlQuery newInstance(final ResultSet resultSet); /** * Return the query string ("select * from bla") * * NOTE: Might want to be overridden by indiviual EsqlQuery implementations * e.g. for database specific LIMIT features. Be aware that there a two different * limit approaches: * <pre> * retrieve query * time time * +---------+ ........... * |JDBC | : : * |ResultSet| : : * |.........|-+ :_________:_ * | | | skip/max+1 |JDBC | | skip/max+1 * | | | window |ResultSet| | window * |.........| | |_________| | * | |-+ : :_| * | | : : * +---------+ :.........: * </pre> * With the "retrieve time" limit the JDBC ResultSet includes ALL of the rows of the * query. * With the "query time" limit only a small window of rows are in the actuall JDBC * ResultSet. In order to know whether there are more rows available (without an additional * query) we need to have at least one more row in the JDBC ResultSet. So we ask for getMaxRows()+1 * * @throws SQLException */ public String getQueryString() throws SQLException { return (query); } /** * NOTE: Might want to be overridden by indiviual EsqlQuery implementations * * @throws SQLException */ public PreparedStatement prepareStatement() throws SQLException { preparedStatement = connection.prepareStatement(getQueryString()); return (preparedStatement); } /** * NOTE: Might want to be overridden by indiviual EsqlQuery implementations * * @throws SQLException */ public CallableStatement prepareCall() throws SQLException { preparedStatement = connection.prepareCall(getQueryString()); return ((CallableStatement) preparedStatement); } /** * Gets the total number of rows of a the query WITHOUT the * limits of skip/max rows. * * NOTE: Might want to be overridden by indiviual EsqlQuery implementations * * @return total number of rows * @throws SQLException */ public int getRowCount() throws SQLException { if (rowCount < 0) { String lowerQuery = query.toLowerCase(); int from = lowerQuery.indexOf(" from "); int groupby = lowerQuery.indexOf(" group by "); int orderby = lowerQuery.indexOf(" order by "); int min = Math.min(groupby, orderby); String countQuery; if (min > -1) { countQuery = "select count(*)" + String.valueOf(query).substring(from, min); } else { countQuery = "select count(*)" + String.valueOf(query).substring(from); } if (getLogger().isDebugEnabled()) getLogger().debug("executing [" + String.valueOf(query) + "]"); ResultSet rs = preparedStatement.executeQuery(countQuery); try { if (rs.first()) { rowCount = rs.getInt(1); if (getLogger().isDebugEnabled()) getLogger().debug("count = " + rowCount); } } finally { rs.close(); } } return (rowCount); } /** * Move to the first row. * * NOTE: Might want to be overridden by indiviual EsqlQuery implementations * * @throws SQLException */ public void getResultRows() throws SQLException { if (skipRows > 0) { while (resultSet.next()) { position++; if (position >= skipRows) { break; } } } } /** * Clean up all database resources used by the query. In particular, * close result sets and statements. * */ public void cleanUp() { this.resultSetMetaData = null; if (this.resultSet != null){ try { this.resultSet.close(); this.resultSet = null; } catch (SQLException e) { // should never happen! (only cause: access error) } } if (this.preparedStatement != null){ try { this.preparedStatement.close(); this.preparedStatement = null; } catch (SQLException e) { // should never happen! (only cause: access error) } } } /* ************** FINAL methods *********************** */ protected final void setPosition(int p) { position = p; } protected final PreparedStatement setPreparedStatement(final PreparedStatement ps) { preparedStatement = ps; return (preparedStatement); } public final Connection getConnection() { return (connection); } public final int getSkipRows() { return (skipRows); } public final void setSkipRows(int i) { skipRows = i; } public final int getMaxRows() { return (maxRows); } public final void setMaxRows(int i) { maxRows = i; } public final ResultSetMetaData getResultSetMetaData() { return (resultSetMetaData); } public final PreparedStatement getPreparedStatement() { return (preparedStatement); } public final CallableStatement getCallableStatement() { return ((CallableStatement) preparedStatement); } public final ResultSet getResultSet() { return (resultSet); } public final boolean nextRow() throws SQLException { position++; return (resultSet.next()); } public final int getCurrentRow() { return (position); } public final boolean execute(int resultSetFromObject) throws SQLException { if (preparedStatement != null) { hasResultSet = preparedStatement.execute(); if (hasResultSet) { resultSet = (ResultSet) ((CallableStatement) preparedStatement).getObject(resultSetFromObject); queryResultsCount++; return (true); } else { updateResultsCount++; updateCount = preparedStatement.getUpdateCount(); return (updateCount > -1); } } else { return (false); } } public final boolean execute() throws SQLException { if (preparedStatement != null) { hasResultSet = preparedStatement.execute(); if (hasResultSet) { resultSet = preparedStatement.getResultSet(); resultSetMetaData = resultSet.getMetaData(); queryResultsCount++; return (true); } else { updateResultsCount++; updateCount = preparedStatement.getUpdateCount(); return (updateCount > -1); } } else { return (false); } } public final boolean executeQuery() throws SQLException { if (preparedStatement != null) { resultSet = preparedStatement.executeQuery(); if (resultSet != null) { resultSetMetaData = resultSet.getMetaData(); queryResultsCount++; hasResultSet = true; return (true); } else { return (false); } } else { return (false); } } /** * Try to get the next ResultSet * * @return whether there is one or not * @throws SQLException */ public final boolean getMoreResults() throws SQLException { if (preparedStatement != null) { hasResultSet = preparedStatement.getMoreResults(); if (hasResultSet) { resultSet = preparedStatement.getResultSet(); resultSetMetaData = resultSet.getMetaData(); queryResultsCount++; return (true); } else { updateResultsCount++; updateCount = preparedStatement.getUpdateCount(); return (updateCount > -1); } } else { return (false); } } public final boolean hasResultSet() { return (hasResultSet); } /** * Returns the how many rows where updated on last update */ public final int getUpdateCount() { return (updateCount); } /** * Returns the number of query results */ public final int getQueryResultsCount() { return (queryResultsCount); } /** * Returns the number of update results */ public final int getUpdateResultsCount() { return (updateResultsCount); } public final boolean keepGoing() { return (keepgoing); } public final void setKeepGoing(boolean still) { keepgoing = still; } /* ************************ GROUPING ************************ */ public final void incGroupLevel() { groupLevel++; } public final void decGroupLevel() { groupLevel--; } public final boolean groupLevelExists() { return (groups != null && groups.size() >= groupLevel + 1 && groups.get(groupLevel) != null); } public final void setGroupingVar(String key) throws SQLException { if (groups == null) groups = new ArrayList(groupLevel); groups.ensureCapacity(groupLevel); groups.add(groupLevel, new EsqlGroup(key, getResultSet().getObject(key)) ); } public final boolean hasGroupingVarChanged() throws SQLException { if (changeLevel != -1) { if (changeLevel < groupLevel) { return (true); } else { changeLevel = -1; return (true); } } else { boolean result = false; // need to check the complete hierarchy of nested groups for changes for (int i = 0; i <= groupLevel; i++) { Object tmp = getResultSet().getObject(((EsqlGroup) groups.get(i)).var); if (!tmp.equals(((EsqlGroup) groups.get(i)).value)) { ((EsqlGroup) groups.get(i)).value = tmp; result = true; if (changeLevel == -1 && groupLevel != i) changeLevel = i; } } return (result); } } final static class EsqlGroup { public String var = null; public Object value = null; EsqlGroup(String var, Object value) { this.var = var; this.value = value; } } }