001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018:
019: package org.apache.tools.ant.taskdefs;
020:
021: import java.sql.Connection;
022: import java.sql.DatabaseMetaData;
023: import java.sql.Driver;
024: import java.sql.SQLException;
025: import java.util.Hashtable;
026: import java.util.Properties;
027: import java.util.Locale;
028:
029: import org.apache.tools.ant.AntClassLoader;
030: import org.apache.tools.ant.BuildException;
031: import org.apache.tools.ant.Project;
032: import org.apache.tools.ant.Task;
033: import org.apache.tools.ant.types.Path;
034: import org.apache.tools.ant.types.Reference;
035:
036: /**
037: * Handles JDBC configuration needed by SQL type tasks.
038: * <p>
039: * The following example class prints the contents of the first column of each row in TableName.
040: *</p>
041: *<code><pre>
042: package examples;
043: import java.sql.Connection;
044: import java.sql.ResultSet;
045: import java.sql.SQLException;
046: import java.sql.Statement;
047:
048: import org.apache.tools.ant.BuildException;
049: import org.apache.tools.ant.taskdefs.JDBCTask;
050:
051: public class SQLExampleTask extends JDBCTask {
052:
053: private String tableName;
054:
055: public void execute() throws BuildException {
056: Connection conn = getConnection();
057: Statement stmt=null;
058: try {
059: if (tableName == null) {
060: throw new BuildException("TableName must be specified",location);
061: }
062: String sql = "SELECT * FROM "+tableName;
063: stmt= conn.createStatement();
064: ResultSet rs = stmt.executeQuery(sql);
065: while (rs.next()) {
066: log(rs.getObject(1).toString());
067: }
068: } catch (SQLException e) {
069:
070: } finally {
071: if (stmt != null) {
072: try {stmt.close();}catch (SQLException ingore) {}
073: }
074: if (conn != null) {
075: try {conn.close();}catch (SQLException ingore) {}
076: }
077: }
078: }
079: public void setTableName(String tableName) {
080: this.tableName = tableName;
081: }
082:
083: }
084:
085:
086: </pre></code>
087:
088:
089:
090: * @since Ant 1.5
091: *
092: */
093:
094: public abstract class JDBCTask extends Task {
095:
096: /**
097: * Used for caching loaders / driver. This is to avoid
098: * getting an OutOfMemoryError when calling this task
099: * multiple times in a row.
100: */
101: private static Hashtable loaderMap = new Hashtable(3);
102:
103: private boolean caching = true;
104:
105: private Path classpath;
106:
107: private AntClassLoader loader;
108:
109: /**
110: * Autocommit flag. Default value is false
111: */
112: private boolean autocommit = false;
113:
114: /**
115: * DB driver.
116: */
117: private String driver = null;
118:
119: /**
120: * DB url.
121: */
122: private String url = null;
123:
124: /**
125: * User name.
126: */
127: private String userId = null;
128:
129: /**
130: * Password
131: */
132: private String password = null;
133:
134: /**
135: * RDBMS Product needed for this SQL.
136: **/
137: private String rdbms = null;
138:
139: /**
140: * RDBMS Version needed for this SQL.
141: **/
142: private String version = null;
143:
144: /**
145: * Sets the classpath for loading the driver.
146: * @param classpath The classpath to set
147: */
148: public void setClasspath(Path classpath) {
149: this .classpath = classpath;
150: }
151:
152: /**
153: * Caching loaders / driver. This is to avoid
154: * getting an OutOfMemoryError when calling this task
155: * multiple times in a row; default: true
156: * @param enable a <code>boolean</code> value
157: */
158: public void setCaching(boolean enable) {
159: caching = enable;
160: }
161:
162: /**
163: * Add a path to the classpath for loading the driver.
164: * @return a path to be configured
165: */
166: public Path createClasspath() {
167: if (this .classpath == null) {
168: this .classpath = new Path(getProject());
169: }
170: return this .classpath.createPath();
171: }
172:
173: /**
174: * Set the classpath for loading the driver
175: * using the classpath reference.
176: * @param r a reference to a classpath
177: */
178: public void setClasspathRef(Reference r) {
179: createClasspath().setRefid(r);
180: }
181:
182: /**
183: * Class name of the JDBC driver; required.
184: * @param driver The driver to set
185: */
186: public void setDriver(String driver) {
187: this .driver = driver.trim();
188: }
189:
190: /**
191: * Sets the database connection URL; required.
192: * @param url The url to set
193: */
194: public void setUrl(String url) {
195: this .url = url;
196: }
197:
198: /**
199: * Sets the password; required.
200: * @param password The password to set
201: */
202: public void setPassword(String password) {
203: this .password = password;
204: }
205:
206: /**
207: * Auto commit flag for database connection;
208: * optional, default false.
209: * @param autocommit The autocommit to set
210: */
211: public void setAutocommit(boolean autocommit) {
212: this .autocommit = autocommit;
213: }
214:
215: /**
216: * Execute task only if the lower case product name
217: * of the DB matches this
218: * @param rdbms The rdbms to set
219: */
220: public void setRdbms(String rdbms) {
221: this .rdbms = rdbms;
222: }
223:
224: /**
225: * Sets the version string, execute task only if
226: * rdbms version match; optional.
227: * @param version The version to set
228: */
229: public void setVersion(String version) {
230: this .version = version;
231: }
232:
233: /**
234: * Verify we are connected to the correct RDBMS
235: * @param conn the jdbc connection
236: * @return true if we are connected to the correct RDBMS
237: */
238: protected boolean isValidRdbms(Connection conn) {
239: if (rdbms == null && version == null) {
240: return true;
241: }
242:
243: try {
244: DatabaseMetaData dmd = conn.getMetaData();
245:
246: if (rdbms != null) {
247: String theVendor = dmd.getDatabaseProductName()
248: .toLowerCase();
249:
250: log("RDBMS = " + theVendor, Project.MSG_VERBOSE);
251: if (theVendor == null || theVendor.indexOf(rdbms) < 0) {
252: log("Not the required RDBMS: " + rdbms,
253: Project.MSG_VERBOSE);
254: return false;
255: }
256: }
257:
258: if (version != null) {
259: String theVersion = dmd.getDatabaseProductVersion()
260: .toLowerCase(Locale.ENGLISH);
261:
262: log("Version = " + theVersion, Project.MSG_VERBOSE);
263: if (theVersion == null
264: || !(theVersion.startsWith(version) || theVersion
265: .indexOf(" " + version) >= 0)) {
266: log(
267: "Not the required version: \"" + version
268: + "\"", Project.MSG_VERBOSE);
269: return false;
270: }
271: }
272: } catch (SQLException e) {
273: // Could not get the required information
274: log("Failed to obtain required RDBMS information",
275: Project.MSG_ERR);
276: return false;
277: }
278:
279: return true;
280: }
281:
282: /**
283: * Get the cache of loaders and drivers.
284: * @return a hashtable
285: */
286: protected static Hashtable getLoaderMap() {
287: return loaderMap;
288: }
289:
290: /**
291: * Get the classloader used to create a driver.
292: * @return the classloader
293: */
294: protected AntClassLoader getLoader() {
295: return loader;
296: }
297:
298: /**
299: * Creates a new Connection as using the driver, url, userid and password
300: * specified.
301: *
302: * The calling method is responsible for closing the connection.
303: *
304: * @return Connection the newly created connection.
305: * @throws BuildException if the UserId/Password/Url is not set or there
306: * is no suitable driver or the driver fails to load.
307: */
308: protected Connection getConnection() throws BuildException {
309: if (userId == null) {
310: throw new BuildException("UserId attribute must be set!",
311: getLocation());
312: }
313: if (password == null) {
314: throw new BuildException("Password attribute must be set!",
315: getLocation());
316: }
317: if (url == null) {
318: throw new BuildException("Url attribute must be set!",
319: getLocation());
320: }
321: try {
322:
323: log("connecting to " + getUrl(), Project.MSG_VERBOSE);
324: Properties info = new Properties();
325: info.put("user", getUserId());
326: info.put("password", getPassword());
327: Connection conn = getDriver().connect(getUrl(), info);
328:
329: if (conn == null) {
330: // Driver doesn't understand the URL
331: throw new SQLException("No suitable Driver for " + url);
332: }
333:
334: conn.setAutoCommit(autocommit);
335: return conn;
336: } catch (SQLException e) {
337: throw new BuildException(e, getLocation());
338: }
339:
340: }
341:
342: /**
343: * Gets an instance of the required driver.
344: * Uses the ant class loader and the optionally the provided classpath.
345: * @return Driver
346: * @throws BuildException
347: */
348: private Driver getDriver() throws BuildException {
349: if (driver == null) {
350: throw new BuildException("Driver attribute must be set!",
351: getLocation());
352: }
353:
354: Driver driverInstance = null;
355: try {
356: Class dc;
357: if (classpath != null) {
358: // check first that it is not already loaded otherwise
359: // consecutive runs seems to end into an OutOfMemoryError
360: // or it fails when there is a native library to load
361: // several times.
362: // this is far from being perfect but should work
363: // in most cases.
364: synchronized (loaderMap) {
365: if (caching) {
366: loader = (AntClassLoader) loaderMap.get(driver);
367: }
368: if (loader == null) {
369: log(
370: "Loading "
371: + driver
372: + " using AntClassLoader with classpath "
373: + classpath,
374: Project.MSG_VERBOSE);
375: loader = getProject().createClassLoader(
376: classpath);
377: if (caching) {
378: loaderMap.put(driver, loader);
379: }
380: } else {
381: log("Loading " + driver
382: + " using a cached AntClassLoader.",
383: Project.MSG_VERBOSE);
384: }
385: }
386: dc = loader.loadClass(driver);
387: } else {
388: log("Loading " + driver + " using system loader.",
389: Project.MSG_VERBOSE);
390: dc = Class.forName(driver);
391: }
392: driverInstance = (Driver) dc.newInstance();
393: } catch (ClassNotFoundException e) {
394: throw new BuildException("Class Not Found: JDBC driver "
395: + driver + " could not be loaded", e, getLocation());
396: } catch (IllegalAccessException e) {
397: throw new BuildException("Illegal Access: JDBC driver "
398: + driver + " could not be loaded", e, getLocation());
399: } catch (InstantiationException e) {
400: throw new BuildException(
401: "Instantiation Exception: JDBC driver " + driver
402: + " could not be loaded", e, getLocation());
403: }
404: return driverInstance;
405: }
406:
407: /**
408: * Set the caching attribute.
409: * @param value a <code>boolean</code> value
410: */
411: public void isCaching(boolean value) {
412: caching = value;
413: }
414:
415: /**
416: * Gets the classpath.
417: * @return Returns a Path
418: */
419: public Path getClasspath() {
420: return classpath;
421: }
422:
423: /**
424: * Gets the autocommit.
425: * @return Returns a boolean
426: */
427: public boolean isAutocommit() {
428: return autocommit;
429: }
430:
431: /**
432: * Gets the url.
433: * @return Returns a String
434: */
435: public String getUrl() {
436: return url;
437: }
438:
439: /**
440: * Gets the userId.
441: * @return Returns a String
442: */
443: public String getUserId() {
444: return userId;
445: }
446:
447: /**
448: * Set the user name for the connection; required.
449: * @param userId The userId to set
450: */
451: public void setUserid(String userId) {
452: this .userId = userId;
453: }
454:
455: /**
456: * Gets the password.
457: * @return Returns a String
458: */
459: public String getPassword() {
460: return password;
461: }
462:
463: /**
464: * Gets the rdbms.
465: * @return Returns a String
466: */
467: public String getRdbms() {
468: return rdbms;
469: }
470:
471: /**
472: * Gets the version.
473: * @return Returns a String
474: */
475: public String getVersion() {
476: return version;
477: }
478:
479: }
|