blob: 1a0e138f9ecb8a6e352f5a711dfa1fcf10d73ffb [file] [log] [blame]
/*
* 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.tools.ant.taskdefs;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.Driver;
import java.sql.SQLException;
import java.util.Hashtable;
import java.util.Properties;
import java.util.Locale;
import org.apache.tools.ant.AntClassLoader;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.types.Path;
import org.apache.tools.ant.types.Reference;
/**
* Handles JDBC configuration needed by SQL type tasks.
* <p>
* The following example class prints the contents of the first column of each row in TableName.
*</p>
*<code><pre>
package examples;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.taskdefs.JDBCTask;
public class SQLExampleTask extends JDBCTask {
private String tableName;
public void execute() throws BuildException {
Connection conn = getConnection();
Statement stmt=null;
try {
if (tableName == null) {
throw new BuildException("TableName must be specified",location);
}
String sql = "SELECT * FROM "+tableName;
stmt= conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);
while (rs.next()) {
log(rs.getObject(1).toString());
}
} catch (SQLException e) {
} finally {
if (stmt != null) {
try {stmt.close();}catch (SQLException ingore) {}
}
if (conn != null) {
try {conn.close();}catch (SQLException ingore) {}
}
}
}
public void setTableName(String tableName) {
this.tableName = tableName;
}
}
</pre></code>
* @since Ant 1.5
*
*/
public abstract class JDBCTask extends Task {
private static final int HASH_TABLE_SIZE = 3;
/**
* Used for caching loaders / driver. This is to avoid
* getting an OutOfMemoryError when calling this task
* multiple times in a row.
*/
private static Hashtable loaderMap = new Hashtable(HASH_TABLE_SIZE);
private boolean caching = true;
private Path classpath;
private AntClassLoader loader;
/**
* Autocommit flag. Default value is false
*/
private boolean autocommit = false;
/**
* DB driver.
*/
private String driver = null;
/**
* DB url.
*/
private String url = null;
/**
* User name.
*/
private String userId = null;
/**
* Password
*/
private String password = null;
/**
* RDBMS Product needed for this SQL.
**/
private String rdbms = null;
/**
* RDBMS Version needed for this SQL.
**/
private String version = null;
/**
* Sets the classpath for loading the driver.
* @param classpath The classpath to set
*/
public void setClasspath(Path classpath) {
this.classpath = classpath;
}
/**
* Caching loaders / driver. This is to avoid
* getting an OutOfMemoryError when calling this task
* multiple times in a row; default: true
* @param enable a <code>boolean</code> value
*/
public void setCaching(boolean enable) {
caching = enable;
}
/**
* Add a path to the classpath for loading the driver.
* @return a path to be configured
*/
public Path createClasspath() {
if (this.classpath == null) {
this.classpath = new Path(getProject());
}
return this.classpath.createPath();
}
/**
* Set the classpath for loading the driver
* using the classpath reference.
* @param r a reference to a classpath
*/
public void setClasspathRef(Reference r) {
createClasspath().setRefid(r);
}
/**
* Class name of the JDBC driver; required.
* @param driver The driver to set
*/
public void setDriver(String driver) {
this.driver = driver.trim();
}
/**
* Sets the database connection URL; required.
* @param url The url to set
*/
public void setUrl(String url) {
this.url = url;
}
/**
* Sets the password; required.
* @param password The password to set
*/
public void setPassword(String password) {
this.password = password;
}
/**
* Auto commit flag for database connection;
* optional, default false.
* @param autocommit The autocommit to set
*/
public void setAutocommit(boolean autocommit) {
this.autocommit = autocommit;
}
/**
* Execute task only if the lower case product name
* of the DB matches this
* @param rdbms The rdbms to set
*/
public void setRdbms(String rdbms) {
this.rdbms = rdbms;
}
/**
* Sets the version string, execute task only if
* rdbms version match; optional.
* @param version The version to set
*/
public void setVersion(String version) {
this.version = version;
}
/**
* Verify we are connected to the correct RDBMS
* @param conn the jdbc connection
* @return true if we are 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(Locale.ENGLISH);
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;
}
/**
* Get the cache of loaders and drivers.
* @return a hashtable
*/
protected static Hashtable getLoaderMap() {
return loaderMap;
}
/**
* Get the classloader used to create a driver.
* @return the classloader
*/
protected AntClassLoader getLoader() {
return loader;
}
/**
* Creates a new Connection as using the driver, url, userid and password
* specified.
*
* The calling method is responsible for closing the connection.
*
* @return Connection the newly created connection.
* @throws BuildException if the UserId/Password/Url is not set or there
* is no suitable driver or the driver fails to load.
*/
protected Connection getConnection() throws BuildException {
if (userId == null) {
throw new BuildException("UserId attribute must be set!", getLocation());
}
if (password == null) {
throw new BuildException("Password attribute must be set!", getLocation());
}
if (url == null) {
throw new BuildException("Url attribute must be set!", getLocation());
}
try {
log("connecting to " + getUrl(), Project.MSG_VERBOSE);
Properties info = new Properties();
info.put("user", getUserId());
info.put("password", getPassword());
Connection conn = getDriver().connect(getUrl(), info);
if (conn == null) {
// Driver doesn't understand the URL
throw new SQLException("No suitable Driver for " + url);
}
conn.setAutoCommit(autocommit);
return conn;
} catch (SQLException e) {
throw new BuildException(e, getLocation());
}
}
/**
* Gets an instance of the required driver.
* Uses the ant class loader and the optionally the provided classpath.
* @return Driver
* @throws BuildException
*/
private Driver getDriver() throws BuildException {
if (driver == null) {
throw new BuildException("Driver attribute must be set!", getLocation());
}
Driver driverInstance = null;
try {
Class dc;
if (classpath != null) {
// check first that it is not already loaded otherwise
// consecutive runs seems to end into an OutOfMemoryError
// or it fails when there is a native library to load
// several times.
// this is far from being perfect but should work
// in most cases.
synchronized (loaderMap) {
if (caching) {
loader = (AntClassLoader) loaderMap.get(driver);
}
if (loader == null) {
log("Loading " + driver
+ " using AntClassLoader with classpath "
+ classpath, Project.MSG_VERBOSE);
loader = getProject().createClassLoader(classpath);
if (caching) {
loaderMap.put(driver, loader);
}
} else {
log("Loading " + driver
+ " using a cached AntClassLoader.",
Project.MSG_VERBOSE);
}
}
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",
e,
getLocation());
} catch (IllegalAccessException e) {
throw new BuildException(
"Illegal Access: JDBC driver " + driver + " could not be loaded",
e,
getLocation());
} catch (InstantiationException e) {
throw new BuildException(
"Instantiation Exception: JDBC driver " + driver + " could not be loaded",
e,
getLocation());
}
return driverInstance;
}
/**
* Set the caching attribute.
* @param value a <code>boolean</code> value
*/
public void isCaching(boolean value) {
caching = value;
}
/**
* Gets the classpath.
* @return Returns a Path
*/
public Path getClasspath() {
return classpath;
}
/**
* Gets the autocommit.
* @return Returns a boolean
*/
public boolean isAutocommit() {
return autocommit;
}
/**
* Gets the url.
* @return Returns a String
*/
public String getUrl() {
return url;
}
/**
* Gets the userId.
* @return Returns a String
*/
public String getUserId() {
return userId;
}
/**
* Set the user name for the connection; required.
* @param userId The userId to set
*/
public void setUserid(String userId) {
this.userId = userId;
}
/**
* Gets the password.
* @return Returns a String
*/
public String getPassword() {
return password;
}
/**
* Gets the rdbms.
* @return Returns a String
*/
public String getRdbms() {
return rdbms;
}
/**
* Gets the version.
* @return Returns a String
*/
public String getVersion() {
return version;
}
}