/* * Copyright 2004-2009 the original author or authors. * * Licensed 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.compass.gps.device.jdbc; import java.lang.reflect.Array; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import javax.sql.DataSource; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.compass.core.CompassException; import org.compass.core.CompassSession; import org.compass.gps.CompassGpsException; import org.compass.gps.device.AbstractGpsDevice; import org.compass.gps.device.jdbc.dialect.DialectResolver; import org.compass.gps.device.jdbc.dialect.JdbcDialect; /** * A helper base class for Jdbc Gps Device. Provides supprot for * <code>DataSource</code> and * {@link org.compass.gps.device.jdbc.dialect.JdbcDialect}. Also * provides template like support for processing database indexing using the * <code>IndexExecution</code> object hint, and a set of callback methods: * {@link #processResultSet(Object, ResultSet, CompassSession)}, * {@link #processRow(Object, ResultSet, CompassSession)}, and * {@link #processRowValue(Object, ResultSet, CompassSession)}. One of the * callback mehtods should be overriden by the derived class otherwize the class * won't index anyhting. * * @author kimchy */ public abstract class AbstractJdbcGpsDevice extends AbstractGpsDevice implements JdbcGpsDevice { protected Log log = LogFactory.getLog(getClass()); /** * A hint object which provides the statement query to execute or the actual * <code>PreparedStatement</code>. It also provides a general data holder * called <code>description</code>. * * @author kimchy */ public static class IndexExecution { private Object description; private PreparedStatement statement; private String statementQuery; public IndexExecution(Object description) { this.description = description; } public IndexExecution(Object description, String statementQuery) { this.description = description; this.statementQuery = statementQuery; } public IndexExecution(Object description, PreparedStatement statement) { this.description = description; this.statement = statement; } public Object getDescription() { return description; } public PreparedStatement getStatement() { return statement; } public String getStatementQuery() { return statementQuery; } public void setStatementQuery(String statementQuery) { this.statementQuery = statementQuery; } public void setStatement(PreparedStatement statement) { this.statement = statement; } public void setDescription(Object description) { this.description = description; } } protected DataSource dataSource; protected JdbcDialect dialect; protected void doStart() throws CompassGpsException { if (dataSource == null) { throw new IllegalArgumentException("dataSource property must be set"); } try { this.dialect = new DialectResolver(true).getDialect(dataSource); } catch (Exception e) { log.warn("Failed to detect database dialect", e); throw new JdbcGpsDeviceException("Failed to detect database dialect", e); } } /** * If this variable is set to a non-zero value, it will be used for setting * the fetchSize property on statements used for query processing. */ private int fetchSize = 0; /** * Performs the indexing operation. * <p/> * Calls the abstract {@link #doGetIndexExecutions(Connection)} method with * an open connection to get the list of {@link IndexExecution} to perform. * <p/> * For each {@link IndexExecution}, executes the select query, and calls * the {@link #processResultSet(Object, ResultSet, CompassSession)} for the * returned <code>ResultSet</code>. */ protected void doIndex(CompassSession session) throws CompassGpsException { if (log.isInfoEnabled()) { log.info("{" + getName() + "}: Indexing the database with fetch size [" + fetchSize + "]"); } Connection connection = JdbcUtils.getConnection(dataSource); PreparedStatement ps = null; ResultSet rs = null; try { IndexExecution[] indexExecutions = doGetIndexExecutions(connection); for (IndexExecution indexExecution : indexExecutions) { if (!isRunning()) { return; } ps = indexExecution.getStatement(); if (ps == null) { if (log.isDebugEnabled()) { log.debug("{" + getName() + "} Executing select query [" + indexExecution.getStatementQuery() + "]"); } ps = connection.prepareStatement(indexExecution.getStatementQuery()); } if (getFetchSize() > 0) { ps.setFetchSize(getFetchSize()); } rs = ps.executeQuery(); processResultSet(indexExecution.getDescription(), rs, session); } } catch (CompassException e) { log.error("Failed to index database", e); throw e; } catch (Exception e) { log.error("Failed to index database", e); throw new JdbcGpsDeviceException("Failed to index database", e); } finally { JdbcUtils.closeResultSet(rs); JdbcUtils.closeStatement(ps); JdbcUtils.closeConnection(connection); } if (log.isInfoEnabled()) { log.info("{" + getName() + "}: Finished indexing the database"); } } /** * Called for each {@link IndexExecution} returned from the * {@link #doGetIndexExecutions(Connection)} with the <code>ResultSet</code>. * Can be override by derived classes, if not override, than iterates threw * the <code>ResultSet</code> and calls * {@link #processRow(Object, ResultSet, CompassSession)} for each row. */ protected void processResultSet(Object description, ResultSet rs, CompassSession session) throws SQLException, CompassException { while (rs.next()) { processRow(description, rs, session); } } /** * Called for each row in the <code>ResultSet</code> which maps to an * {@link IndexExecution}. Can be override by derived classes, if not * override, than calls * {@link #processRowValue(Object, ResultSet, CompassSession)} and uses it's * return value to save it in the <code>CompassSession</code>. The return * value can be an OSEM enables object, a <code>Resource</code>, or an * array of one of them. */ protected void processRow(Object description, ResultSet rs, CompassSession session) throws SQLException, CompassException { if (!isRunning()) { return; } Object value = processRowValue(description, rs, session); if (value != null) { if (value instanceof Object[]) { int length = Array.getLength(value); for (int i = 0; i < length; i++) { Object value1 = Array.get(value, i); session.create(value1); } } else { session.create(value); } } } /** * Called for each row in the <code>ResultSet</code> which maps to an * {@link IndexExecution}. Can be override by derived classes, and should * return the actual data to be saved using the <code>CompassSession</code>. * The return value can be either an OSEM enables object, a * <code>Resource</code>, or an array of one of them. */ protected Object processRowValue(Object description, ResultSet rs, CompassSession session) throws SQLException, CompassException { return null; } /** * Returns an array of the {@link IndexExecution} that should be executed * it's respective <code>ResultSet</code> should be indexed. */ protected abstract IndexExecution[] doGetIndexExecutions(Connection connection) throws SQLException, JdbcGpsDeviceException; public DataSource getDataSource() { return dataSource; } public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } public int getFetchSize() { return fetchSize; } public void setFetchSize(int fetchSize) { this.fetchSize = fetchSize; } public JdbcDialect getDialect() { return dialect; } public void setDialect(JdbcDialect dialect) { this.dialect = dialect; } }