001: /*
002: * The Apache Software License, Version 1.1
003: *
004: * Copyright (c) 2002-2003 The Apache Software Foundation. All rights
005: * reserved.
006: *
007: * Redistribution and use in source and binary forms, with or without
008: * modification, are permitted provided that the following conditions
009: * are met:
010: *
011: * 1. Redistributions of source code must retain the above copyright
012: * notice, this list of conditions and the following disclaimer.
013: *
014: * 2. Redistributions in binary form must reproduce the above copyright
015: * notice, this list of conditions and the following disclaimer in
016: * the documentation and/or other materials provided with the
017: * distribution.
018: *
019: * 3. The end-user documentation included with the redistribution, if
020: * any, must include the following acknowlegement:
021: * "This product includes software developed by the
022: * Apache Software Foundation (http://www.apache.org/)."
023: * Alternately, this acknowlegement may appear in the software itself,
024: * if and wherever such third-party acknowlegements normally appear.
025: *
026: * 4. The names "Ant" and "Apache Software
027: * Foundation" must not be used to endorse or promote products derived
028: * from this software without prior written permission. For written
029: * permission, please contact apache@apache.org.
030: *
031: * 5. Products derived from this software may not be called "Apache"
032: * nor may "Apache" appear in their names without prior written
033: * permission of the Apache Group.
034: *
035: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
036: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
037: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
038: * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
039: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
040: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
041: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
042: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
043: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
044: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
045: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
046: * SUCH DAMAGE.
047: * ====================================================================
048: *
049: * This software consists of voluntary contributions made by many
050: * individuals on behalf of the Apache Software Foundation. For more
051: * information on the Apache Software Foundation, please see
052: * <http://www.apache.org/>.
053: */
054:
055: package org.tigris.scarab.migration;
056:
057: import java.sql.Connection;
058: import java.sql.DatabaseMetaData;
059: import java.sql.Driver;
060: import java.sql.SQLException;
061: import java.util.Hashtable;
062: import java.util.Properties;
063: import org.apache.tools.ant.AntClassLoader;
064: import org.apache.tools.ant.BuildException;
065: import org.apache.tools.ant.Project;
066: import org.apache.tools.ant.Task;
067: import org.apache.tools.ant.types.Path;
068: import org.apache.tools.ant.types.Reference;
069:
070: /**
071: * This is a copy of a class available in Ant 1.5, it should retain the
072: * copyright notice above and we should remove this class as soon as support
073: * for Ant 1.4 is removed.
074: */
075: public abstract class JDBCTask extends Task {
076:
077: /**
078: * Used for caching loaders / driver. This is to avoid
079: * getting an OutOfMemoryError when calling this task
080: * multiple times in a row.
081: */
082: private static Hashtable loaderMap = new Hashtable(3);
083:
084: private boolean caching = true;
085:
086: private Path classpath;
087:
088: private AntClassLoader loader;
089:
090: /**
091: * Autocommit flag. Default value is false
092: */
093: private boolean autocommit = false;
094:
095: /**
096: * DB driver.
097: */
098: private String driver = null;
099:
100: /**
101: * DB url.
102: */
103: private String url = null;
104:
105: /**
106: * User name.
107: */
108: private String userId = null;
109:
110: /**
111: * Password
112: */
113: private String password = null;
114:
115: /**
116: * RDBMS Product needed for this SQL.
117: **/
118: private String rdbms = null;
119:
120: /**
121: * RDBMS Version needed for this SQL.
122: **/
123: private String version = null;
124:
125: /**
126: * Sets the classpath for loading the driver.
127: * @param classpath The classpath to set
128: */
129: public void setClasspath(Path classpath) {
130: this .classpath = classpath;
131: }
132:
133: /**
134: * Caching loaders / driver. This is to avoid
135: * getting an OutOfMemoryError when calling this task
136: * multiple times in a row; default: true
137: * @param enable
138: */
139: public void setCaching(boolean enable) {
140: caching = enable;
141: }
142:
143: /**
144: * Add a path to the classpath for loading the driver.
145: */
146: public Path createClasspath() {
147: if (this .classpath == null) {
148: this .classpath = new Path(getProject());
149: }
150: return this .classpath.createPath();
151: }
152:
153: /**
154: * Set the classpath for loading the driver
155: * using the classpath reference.
156: */
157: public void setClasspathRef(Reference r) {
158: createClasspath().setRefid(r);
159: }
160:
161: /**
162: * Class name of the JDBC driver; required.
163: * @param driver The driver to set
164: */
165: public void setDriver(String driver) {
166: this .driver = driver;
167: }
168:
169: /**
170: * Sets the database connection URL; required.
171: * @param url The url to set
172: */
173: public void setUrl(String url) {
174: this .url = url;
175: }
176:
177: /**
178: * Sets the password; required.
179: * @param password The password to set
180: */
181: public void setPassword(String password) {
182: this .password = password;
183: }
184:
185: /**
186: * Auto commit flag for database connection;
187: * optional, default false.
188: * @param autocommit The autocommit to set
189: */
190: public void setAutocommit(boolean autocommit) {
191: this .autocommit = autocommit;
192: }
193:
194: /**
195: * Execute task only if the lower case product name
196: * of the DB matches this
197: * @param rdbms The rdbms to set
198: */
199: public void setRdbms(String rdbms) {
200: this .rdbms = rdbms;
201: }
202:
203: /**
204: * Sets the version string, execute task only if
205: * rdbms version match; optional.
206: * @param version The version to set
207: */
208: public void setVersion(String version) {
209: this .version = version;
210: }
211:
212: /**
213: * Verify we are connected to the correct RDBMS
214: */
215: protected boolean isValidRdbms(Connection conn) {
216: if (rdbms == null && version == null) {
217: return true;
218: }
219:
220: try {
221: DatabaseMetaData dmd = conn.getMetaData();
222:
223: if (rdbms != null) {
224: String theVendor = dmd.getDatabaseProductName()
225: .toLowerCase();
226:
227: log("RDBMS = " + theVendor, Project.MSG_VERBOSE);
228: if (theVendor == null || theVendor.indexOf(rdbms) < 0) {
229: log("Not the required RDBMS: " + rdbms,
230: Project.MSG_VERBOSE);
231: return false;
232: }
233: }
234:
235: if (version != null) {
236: // XXX maybe better toLowerCase(Locale.US)
237: String theVersion = dmd.getDatabaseProductVersion()
238: .toLowerCase();
239:
240: log("Version = " + theVersion, Project.MSG_VERBOSE);
241: if (theVersion == null
242: || !(theVersion.startsWith(version) || theVersion
243: .indexOf(" " + version) >= 0)) {
244: log(
245: "Not the required version: \"" + version
246: + "\"", Project.MSG_VERBOSE);
247: return false;
248: }
249: }
250: } catch (SQLException e) {
251: // Could not get the required information
252: log("Failed to obtain required RDBMS information",
253: Project.MSG_ERR);
254: return false;
255: }
256:
257: return true;
258: }
259:
260: protected static Hashtable getLoaderMap() {
261: return loaderMap;
262: }
263:
264: protected AntClassLoader getLoader() {
265: return loader;
266: }
267:
268: /**
269: * Creates a new Connection as using the driver, url, userid and password specified.
270: * The calling method is responsible for closing the connection.
271: * @return Connection the newly created connection.
272: * @throws BuildException if the UserId/Password/Url is not set or there is no suitable driver or the driver fails to load.
273: */
274: protected Connection getConnection() throws BuildException {
275: if (userId == null) {
276: throw new BuildException("User Id attribute must be set!",
277: getLocation());
278: }
279: if (password == null) {
280: throw new BuildException("Password attribute must be set!",
281: getLocation());
282: }
283: if (url == null) {
284: throw new BuildException("Url attribute must be set!",
285: getLocation());
286: }
287: try {
288:
289: log("connecting to " + getUrl(), Project.MSG_VERBOSE);
290: Properties info = new Properties();
291: info.put("user", getUserId());
292: info.put("password", getPassword());
293: Connection conn = getDriver().connect(getUrl(), info);
294:
295: if (conn == null) {
296: // Driver doesn't understand the URL
297: throw new SQLException("No suitable Driver for " + url);
298: }
299:
300: conn.setAutoCommit(autocommit);
301: return conn;
302: } catch (SQLException e) {
303: throw new BuildException(e, getLocation());
304: }
305:
306: }
307:
308: /**
309: * Gets an instance of the required driver.
310: * Uses the ant class loader and the optionally the provided classpath.
311: * @return Driver
312: * @throws BuildException
313: */
314: private Driver getDriver() throws BuildException {
315: if (driver == null) {
316: throw new BuildException("Driver attribute must be set!",
317: getLocation());
318: }
319:
320: Driver driverInstance = null;
321: try {
322: Class dc;
323: if (classpath != null) {
324: // check first that it is not already loaded otherwise
325: // consecutive runs seems to end into an OutOfMemoryError
326: // or it fails when there is a native library to load
327: // several times.
328: // this is far from being perfect but should work
329: // in most cases.
330: synchronized (loaderMap) {
331: if (caching) {
332: loader = (AntClassLoader) loaderMap.get(driver);
333: }
334: if (loader == null) {
335: log(
336: "Loading "
337: + driver
338: + " using AntClassLoader with classpath "
339: + classpath,
340: Project.MSG_VERBOSE);
341: // jdm - modified for usage with Ant 1.4
342: // loader = getProject().createClassLoader(classpath);
343: loader = new AntClassLoader(project, classpath);
344: if (caching) {
345: loaderMap.put(driver, loader);
346: }
347: } else {
348: log("Loading " + driver
349: + " using a cached AntClassLoader.",
350: Project.MSG_VERBOSE);
351: }
352: }
353: dc = loader.loadClass(driver);
354: } else {
355: log("Loading " + driver + " using system loader.",
356: Project.MSG_VERBOSE);
357: dc = Class.forName(driver);
358: }
359: driverInstance = (Driver) dc.newInstance();
360: } catch (ClassNotFoundException e) {
361: throw new BuildException("Class Not Found: JDBC driver "
362: + driver + " could not be loaded", getLocation());
363: } catch (IllegalAccessException e) {
364: throw new BuildException("Illegal Access: JDBC driver "
365: + driver + " could not be loaded", getLocation());
366: } catch (InstantiationException e) {
367: throw new BuildException(
368: "Instantiation Exception: JDBC driver " + driver
369: + " could not be loaded", getLocation());
370: }
371: return driverInstance;
372: }
373:
374: public void isCaching(boolean value) {
375: caching = value;
376: }
377:
378: /**
379: * Gets the classpath.
380: * @return Returns a Path
381: */
382: public Path getClasspath() {
383: return classpath;
384: }
385:
386: /**
387: * Gets the autocommit.
388: * @return Returns a boolean
389: */
390: public boolean isAutocommit() {
391: return autocommit;
392: }
393:
394: /**
395: * Gets the url.
396: * @return Returns a String
397: */
398: public String getUrl() {
399: return url;
400: }
401:
402: /**
403: * Gets the userId.
404: * @return Returns a String
405: */
406: public String getUserId() {
407: return userId;
408: }
409:
410: /**
411: * Set the user name for the connection; required.
412: * @param userId The userId to set
413: */
414: public void setUserid(String userId) {
415: this .userId = userId;
416: }
417:
418: /**
419: * Gets the password.
420: * @return Returns a String
421: */
422: public String getPassword() {
423: return password;
424: }
425:
426: /**
427: * Gets the rdbms.
428: * @return Returns a String
429: */
430: public String getRdbms() {
431: return rdbms;
432: }
433:
434: /**
435: * Gets the version.
436: * @return Returns a String
437: */
438: public String getVersion() {
439: return version;
440: }
441:
442: }
|