/*
Copyright (C) 2003 EBI, GRL
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
*/
package org.ensembl.mart.lib;
import java.io.OutputStream;
import java.io.PrintStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Class for interaction between UI and Mart Database. Manages mySQL database
* connections, and executes Querys.
*
* @author <a href="mailto:craig@ebi.ac.uk">Craig Melsopp</a>
* @author <a href="mailto:dlondon@ebi.ac.uk">Darin London</a>
*/
//TODO: implement broad(transcript based) versus narrow(gene based) filtering of resultsets
public class Engine {
private static Logger logger = Logger.getLogger(Engine.class.getName());
/**
* Attempts to load the database drivers normally shipped with
* martlib distribution.
*/
private static void loadFallbackDatabaseDrivers() {
String[] driverNames = new String[] { "org.gjt.mm.mysql.Driver" };
for (int i = 0; i < driverNames.length; i++) {
try {
Class.forName(driverNames[i]).newInstance();
} catch (Exception e) {
if (logger.isLoggable(Level.WARNING))
logger.warning("Failed to load driver" + driverNames[i]);
throw new RuntimeException(e.getMessage(), e);
}
}
}
/**
* Load drivers normally distributed with mart lib. These will be
* available if no other drivers are previously loaded.
*/
{
loadFallbackDatabaseDrivers();
}
public Engine() {
}
public void countFocus(OutputStream os, Query oquery) throws InvalidQueryException, SQLException {
PrintStream pstream = new PrintStream(os, true); //autoflush true
//ensure that we are using a copy of the Query
Query query = new Query(oquery);
//TODO: this may be removed
//remove any attributes present, so that they do not affect the count
if (query.getAttributes().length > 0)
query.removeAllAttributes();
//process any unprocessed filters
Hashtable needsHandler = new Hashtable();
Filter[] filters = query.getFilters();
for (int i = 0, n = filters.length; i < n; i++) {
Filter filter = filters[i];
if (filter instanceof IDListFilter) {
IDListFilter idfilter = (IDListFilter) filter;
if (idfilter.getHandler() != null) {
String handler = idfilter.getHandler();
if (!needsHandler.containsKey(handler))
needsHandler.put(handler, new ArrayList());
List unhandledFilters = (ArrayList) needsHandler.get(handler);
if (!unhandledFilters.contains(filter))
unhandledFilters.add(filter);
needsHandler.put(handler, unhandledFilters);
}
}
}
for (Iterator iter = needsHandler.keySet().iterator(); iter.hasNext();) {
String handler = (String) iter.next();
List unprocessedFilters = (ArrayList) needsHandler.get(handler);
UnprocessedFilterHandler idhandler = UnprocessedFilterHandlerFactory.getInstance(handler);
query = idhandler.ModifyQuery(this, unprocessedFilters, query);
}
DetailedDataSource dsource = query.getDataSource();
QueryCompiler csql = new QueryCompiler(query,dsource);
String fcountSQL = csql.toFocusCountSQL();
writeSQLResults(pstream, query, fcountSQL);
}
private void writeSQLResults(PrintStream pstream, Query query, String sql) throws InvalidQueryException {
DetailedDataSource dsource = query.getDataSource();
if (dsource == null)
throw new InvalidQueryException("Query must have a DataSource to execute against\n");
Connection conn = null;
try {
conn = dsource.getConnection();
PreparedStatement ps = conn.prepareStatement(sql);
int p = 1;
for (int i = 0, n = query.getFilters().length; i < n; ++i) {
Filter f = query.getFilters()[i];
String value = f.getValue();
if (value != null) {
logger.fine("SQL (prepared statement value) : " + p + " = " + value);
ps.setString(p++, value);
}
}
ResultSet rs = ps.executeQuery();
if (rs.next())
pstream.print(rs.getString(1) + "\n");
else
pstream.print("0\n");
} catch (SQLException e) {
if (logger.isLoggable(Level.WARNING))
logger.warning(e.getMessage());
throw new InvalidQueryException(e);
} finally {
DetailedDataSource.close(conn);
}
}
/**
* Checks for DomainSpecificFilters in the Query, and uses the DSFilterHandler
* system to modify the Query accordingly, if present.
* Constructs a QueryRunner object for the given Query, and format using
* a QueryRunnerFactory. Uses the QueryRunner to execute the Query
* with the mySQL connection of this Engine, and write the results to
* a specified OutputStream.
*
* @param query - A Query Object
* @param formatspec - A FormatSpec Object
* @param os - An OutputStream
* @throws FormatException - unsupported Format supplied to the QueryRunnerFactory
* @throws SequenceException - general Exception thrown for a variety of reasons that the SeqQueryRunners cannot write out sequence data
* @throws InvalidQueryException - general Exception thrown when invalid query parameters have been presented, and the resulting SQL will not work.
* @see Query
* @see FormatSpec
* @see QueryRunnerFactory
* @see QueryRunner
* @see UnprocessedFilterHandler
* @see UnprocessedFilterHandlerFactory
*/
public void execute(Query query, FormatSpec formatspec, OutputStream os)
throws SequenceException, FormatException, InvalidQueryException, SQLException {
if (query.hasLimit())
execute(query, formatspec, os, query.getLimit());
else
execute(query, formatspec, os, 0);
}
/**
* Checks for DomainSpecificFilters in the Query, and uses the DSFilterHandler
* system to modify the Query accordingly, if present.
* Constructs a QueryRunner object for the given Query, and format using
* a QueryRunnerFactory. Applies a limit clause to the SQL.
* Uses the QueryRunner to execute the Query with the mySQL connection of
* this Engine, and write the results to a specified OutputStream.
*
* @param query A Query Object
* @param formatspec A FormatSpec Object
* @param os An OutputStream
* @param limit limits the number of records returned by the query
* @throws SequenceException
* @throws FormatException
* @throws InvalidQueryException
* @see Query
* @see FormatSpec
* @see QueryRunnerFactory
* @see QueryRunner
* @see UnprocessedFilterHandler
* @see UnprocessedFilterHandlerFactory
*/
public void execute(Query query, FormatSpec formatspec, OutputStream os, int limit)
throws SequenceException, FormatException, InvalidQueryException, SQLException {
execute(query, formatspec, os, limit, false);
}
/**
* allows the client to add a limit to the number of rows returned for a query.
* @param query
* @param formatspec
* @param os
* @param limit
* @param isSubQuery
* @throws SequenceException
* @throws FormatException
* @throws InvalidQueryException
* @throws SQLException
* @see Query
* @see FormatSpec
* @see QueryRunnerFactory
* @see QueryRunner
* @see UnprocessedFilterHandler
* @see UnprocessedFilterHandlerFactory
*/
public void execute(Query query, FormatSpec formatspec, OutputStream os, int limit, boolean isSubQuery)
throws SequenceException, FormatException, InvalidQueryException, SQLException {
//must initialize query for sequence queries specially
if (query.getType() == Query.SEQUENCE) {
//make a copy to prevent changes being filtered back to the client
query = new Query(query);
query.initializeForSequence();
}
//process any unprocessed filters
Hashtable needsHandler = new Hashtable();
Filter[] filters = query.getFilters();
for (int i = 0, n = filters.length; i < n; i++) {
Filter filter = filters[i];
if (filter instanceof IDListFilter) {
IDListFilter idfilter = (IDListFilter) filter;
if (idfilter.getHandler() != null) {
String handler = idfilter.getHandler();
if (!needsHandler.containsKey(handler))
needsHandler.put(handler, new ArrayList());
List unhandledFilters = (ArrayList) needsHandler.get(handler);
if (!unhandledFilters.contains(filter))
unhandledFilters.add(filter);
needsHandler.put(handler, unhandledFilters);
}
}
}
for (Iterator iter = needsHandler.keySet().iterator(); iter.hasNext();) {
String handler = (String) iter.next();
List unprocessedFilters = (ArrayList) needsHandler.get(handler);
UnprocessedFilterHandler idhandler = UnprocessedFilterHandlerFactory.getInstance(handler);
query = idhandler.ModifyQuery(this, unprocessedFilters, query);
}
logger.fine(query.toString());
QueryRunner qr = QueryRunnerFactory.getInstance(query, formatspec, os);
qr.execute(limit, isSubQuery);
}
public String sql(Query query) {
throw new RuntimeException();
}
}