/* * The Apache Software License, Version 1.1 * * Copyright (c) 1999 The Apache Software Foundation. All rights * reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The end-user documentation included with the redistribution, if * any, must include the following acknowlegement: * "This product includes software developed by the * Apache Software Foundation (http://www.apache.org/)." * Alternately, this acknowlegement may appear in the software itself, * if and wherever such third-party acknowlegements normally appear. * * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software * Foundation" must not be used to endorse or promote products derived * from this software without prior written permission. For written * permission, please contact apache@apache.org. * * 5. Products derived from this software may not be called "Apache" * nor may "Apache" appear in their names without prior written * permission of the Apache Group. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * ==================================================================== * * This software consists of voluntary contributions made by many * individuals on behalf of the Apache Software Foundation. For more * information on the Apache Software Foundation, please see * . */ package org.apache.tools.ant.taskdefs; import org.apache.tools.ant.*; import org.apache.tools.ant.types.*; import java.io.*; import java.util.Enumeration; import java.util.StringTokenizer; import java.util.Vector; import java.util.Properties; import java.util.zip.*; import java.sql.*; /** * Reads in a text file containing SQL statements seperated with semicolons * and executes it in a given db. * Both -- and // maybe used as comments. * * @author Jeff Martin */ public class SQLExec extends Task { private int goodSql = 0, totalSql = 0; private Path classpath; private AntClassLoader loader; /** * Database connection */ private Connection conn = null; /** * Autocommit flag. Default value is false */ private boolean autocommit=false; /** * SQL statement */ private Statement statement = null; /** * DB driver. */ private String driver = null; /** * DB url. */ private String url = null; /** * User name. */ private String userId = null; /** * Password */ private String password = null; /** * SQL input file */ private File srcFile = null; /** * SQL input command */ private String sqlCommand = ""; /** * SQL transactions to perform */ private Vector transactions = new Vector(); /** * Print SQL results. */ private boolean print = false; /** * Print header columns. */ private boolean showheaders = true; /** * Results Output file. */ private File output = null; /** * RDBMS Product needed for this SQL. **/ private String rdbms = null; /** * RDBMS Version needed for this SQL. **/ private String version = null; /** * Action to perform if an error is found **/ private String onError = "abort"; /** * Set the classpath for loading the driver. */ public void setClasspath(Path classpath) { if (this.classpath == null) { this.classpath = classpath; } else { this.classpath.append(classpath); } } /** * Create the classpath for loading the driver. */ public Path createClasspath() { if (this.classpath == null) { this.classpath = new Path(project); } return this.classpath.createPath(); } /** * Set the classpath for loading the driver using the classpath reference. */ public void setClasspathRef(Reference r) { createClasspath().setRefid(r); } /** * Set the name of the sql file to be run. */ public void setSrc(File srcFile) { this.srcFile = srcFile; } /** * Set the sql command to execute */ public void addText(String sql) { this.sqlCommand += sql; } /** * Set the sql command to execute */ public Transaction createTransaction() { Transaction t = new Transaction(); transactions.addElement(t); return t; } /** * Set the JDBC driver to be used. */ public void setDriver(String driver) { this.driver = driver; } /** * Set the DB connection url. */ public void setUrl(String url) { this.url = url; } /** * Set the user name for the DB connection. */ public void setUserid(String userId) { this.userId = userId; } /** * Set the password for the DB connection. */ public void setPassword(String password) { this.password = password; } /** * Set the autocommit flag for the DB connection. */ public void setAutocommit(boolean autocommit) { this.autocommit = autocommit; } /** * Set the print flag. */ public void setPrint(boolean print) { this.print = print; } /** * Set the showheaders flag. */ public void setShowheaders(boolean showheaders) { this.showheaders = showheaders; } /** * Set the output file. */ public void setOutput(File output) { this.output = output; } /** * Set the rdbms required */ public void setRdbms(String vendor) { this.rdbms = vendor.toLowerCase(); } /** * Set the version required */ public void setVersion(String version) { this.version = version.toLowerCase(); } /** * Set the action to perform onerror */ public void setOnerror(OnError action) { this.onError = action.getValue(); } /** * Load the sql file and then execute it */ public void execute() throws BuildException { sqlCommand = sqlCommand.trim(); if (srcFile == null && sqlCommand.length() == 0) { if (transactions.size() == 0) { throw new BuildException("Source file, transactions or sql statement must be set!", location); } } else { // Make a transaction group for the outer command Transaction t = createTransaction(); t.setSrc(srcFile); t.addText(sqlCommand); } if (driver == null) { throw new BuildException("Driver attribute must be set!", location); } if (userId == null) { throw new BuildException("User Id attribute must be set!", location); } if (password == null) { throw new BuildException("Password attribute must be set!", location); } if (url == null) { throw new BuildException("Url attribute must be set!", location); } if (srcFile != null && !srcFile.exists()) { throw new BuildException("Source file does not exist!", location); } Driver driverInstance = null; // Load the driver using the try { Class dc; if (classpath != null) { log("Loading " + driver + " using AntClassLoader with classpath " + classpath, Project.MSG_VERBOSE); loader = new AntClassLoader(project, classpath, false); dc = loader.loadClass(driver); } else { log("Loading " + driver + " using system loader.", Project.MSG_VERBOSE); dc = Class.forName(driver); } driverInstance = (Driver) dc.newInstance(); }catch(ClassNotFoundException e){ throw new BuildException("Class Not Found: JDBC driver " + driver + " could not be loaded", location); }catch(IllegalAccessException e){ throw new BuildException("Illegal Access: JDBC driver " + driver + " could not be loaded", location); }catch(InstantiationException e) { throw new BuildException("Instantiation Exception: JDBC driver " + driver + " could not be loaded", location); } try{ log("connecting to " + url, Project.MSG_VERBOSE ); Properties info = new Properties(); info.put("user", userId); info.put("password", password); conn = driverInstance.connect(url, info); if (conn == null) { // Driver doesn't understand the URL throw new SQLException("No suitable Driver for "+url); } if (!isValidRdbms(conn)) return; conn.setAutoCommit(autocommit); statement = conn.createStatement(); PrintStream out = System.out; try { if (output != null) { log("Opening PrintStream to output file " + output, Project.MSG_VERBOSE); out = new PrintStream(new BufferedOutputStream(new FileOutputStream(output))); } // Process all transactions for (Enumeration e = transactions.elements(); e.hasMoreElements();) { ((Transaction) e.nextElement()).runTransaction(out); if (!autocommit) { log("Commiting transaction", Project.MSG_VERBOSE); conn.commit(); } } } finally { if (out != null && out != System.out) { out.close(); } } } catch(IOException e){ if (!autocommit && conn != null && onError.equals("abort")) { try { conn.rollback(); } catch (SQLException ex) {} } throw new BuildException(e, location); } catch(SQLException e){ if (!autocommit && conn != null && onError.equals("abort")) { try { conn.rollback(); } catch (SQLException ex) {} } throw new BuildException(e, location); } finally { try { if (statement != null) { statement.close(); } if (conn != null) { conn.close(); } } catch (SQLException e) {} } log(goodSql + " of " + totalSql + " SQL statements executed successfully"); } protected void runStatements(Reader reader, PrintStream out) throws SQLException, IOException { String sql = ""; String line = ""; BufferedReader in = new BufferedReader(reader); try{ while ((line=in.readLine()) != null){ if (line.trim().startsWith("//")) continue; if (line.trim().startsWith("--")) continue; sql += " " + line; sql = sql.trim(); // SQL defines "--" as a comment to EOL // and in Oracle it may contain a hint // so we cannot just remove it, instead we must end it if (line.indexOf("--") >= 0) sql += "\n"; if (sql.endsWith(";")){ log("SQL: " + sql, Project.MSG_VERBOSE); execSQL(sql.substring(0, sql.length()-1), out); sql = ""; } } // Catch any statements not followed by ; if(!sql.equals("")){ execSQL(sql, out); } }catch(SQLException e){ throw e; } } /** * Verify if connected to the correct RDBMS **/ protected boolean isValidRdbms(Connection conn) { if (rdbms == null && version == null) return true; try { DatabaseMetaData dmd = conn.getMetaData(); if (rdbms != null) { String theVendor = dmd.getDatabaseProductName().toLowerCase(); log("RDBMS = " + theVendor, Project.MSG_VERBOSE); if (theVendor == null || theVendor.indexOf(rdbms) < 0) { log("Not the required RDBMS: "+rdbms, Project.MSG_VERBOSE); return false; } } if (version != null) { String theVersion = dmd.getDatabaseProductVersion().toLowerCase(); log("Version = " + theVersion, Project.MSG_VERBOSE); if (theVersion == null || !(theVersion.startsWith(version) || theVersion.indexOf(" " + version) >= 0)) { log("Not the required version: \""+ version +"\"", Project.MSG_VERBOSE); return false; } } } catch (SQLException e) { // Could not get the required information log("Failed to obtain required RDBMS information", Project.MSG_ERR); return false; } return true; } /** * Exec the sql statement. */ protected void execSQL(String sql, PrintStream out) throws SQLException { // Check and ignore empty statements if ("".equals(sql.trim())) return; try { totalSql++; if (!statement.execute(sql)) { log(statement.getUpdateCount()+" rows affected", Project.MSG_VERBOSE); } if (print) { printResults(out); } SQLWarning warning = conn.getWarnings(); while(warning!=null){ log(warning + " sql warning", Project.MSG_VERBOSE); warning=warning.getNextWarning(); } conn.clearWarnings(); goodSql++; } catch (SQLException e) { log("Failed to execute: " + sql, Project.MSG_ERR); if (!onError.equals("continue")) throw e; log(e.toString(), Project.MSG_ERR); } } /** * print any results in the statement. */ protected void printResults(PrintStream out) throws java.sql.SQLException { ResultSet rs = null; do { rs = statement.getResultSet(); if (rs != null) { log("Processing new result set.", Project.MSG_VERBOSE); ResultSetMetaData md = rs.getMetaData(); int columnCount = md.getColumnCount(); StringBuffer line = new StringBuffer(); if (showheaders) { for (int col = 1; col < columnCount; col++) { line.append(md.getColumnName(col)); line.append(","); } line.append(md.getColumnName(columnCount)); out.println(line); line.setLength(0); } while (rs.next()) { boolean first = true; for (int col = 1; col <= columnCount; col++) { String columnValue = rs.getString(col); if (columnValue != null) { columnValue = columnValue.trim(); } if (first) { first = false; } else { line.append(","); } line.append(columnValue); } out.println(line); line.setLength(0); } } } while (statement.getMoreResults()); out.println(); } /** * Enumerated attribute with the values "continue", "stop" and "abort" * for the onerror attribute. */ public static class OnError extends EnumeratedAttribute { public String[] getValues() { return new String[] {"continue", "stop", "abort"}; } } /** * Contains the definition of a new transaction element. * Transactions allow several files or blocks of statements * to be executed using the same JDBC connection and commit * operation in between. */ public class Transaction { private File tSrcFile = null; private String tSqlCommand = ""; public void setSrc(File src) { this.tSrcFile = src; } public void addText(String sql) { this.tSqlCommand += sql; } private void runTransaction(PrintStream out) throws IOException, SQLException { if (tSqlCommand.length() != 0) { log("Executing commands", Project.MSG_INFO); runStatements(new StringReader(tSqlCommand), out); } if (tSrcFile != null) { log("Executing file: " + tSrcFile.getAbsolutePath(), Project.MSG_INFO); runStatements(new FileReader(tSrcFile), out); } } } }