1 package org.apache.torque.task;
2
3 /* ====================================================================
4 * The Apache Software License, Version 1.1
5 *
6 * Copyright (c) 2001-2003 The Apache Software Foundation. All rights
7 * reserved.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 *
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 *
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in
18 * the documentation and/or other materials provided with the
19 * distribution.
20 *
21 * 3. The end-user documentation included with the redistribution,
22 * if any, must include the following acknowledgment:
23 * "This product includes software developed by the
24 * Apache Software Foundation (http://www.apache.org/)."
25 * Alternately, this acknowledgment may appear in the software itself,
26 * if and wherever such third-party acknowledgments normally appear.
27 *
28 * 4. The names "Apache" and "Apache Software Foundation" and
29 * "Apache Turbine" must not be used to endorse or promote products
30 * derived from this software without prior written permission. For
31 * written permission, please contact apache@apache.org.
32 *
33 * 5. Products derived from this software may not be called "Apache",
34 * "Apache Turbine", nor may "Apache" appear in their name, without
35 * prior written permission of the Apache Software Foundation.
36 *
37 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
38 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
39 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
40 * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
41 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
42 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
43 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
44 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
45 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
46 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
47 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
48 * SUCH DAMAGE.
49 * ====================================================================
50 *
51 * This software consists of voluntary contributions made by many
52 * individuals on behalf of the Apache Software Foundation. For more
53 * information on the Apache Software Foundation, please see
54 * <http://www.apache.org/>.
55 */
56
57 import java.io.BufferedOutputStream;
58 import java.io.BufferedReader;
59 import java.io.IOException;
60 import java.io.InputStreamReader;
61 import java.io.File;
62 import java.io.FileInputStream;
63 import java.io.FileOutputStream;
64 import java.io.FileReader;
65 import java.io.PrintStream;
66 import java.io.StringReader;
67 import java.io.Reader;
68 import java.util.List;
69 import java.util.ArrayList;
70 import java.util.Iterator;
71 import java.util.HashMap;
72 import java.util.Map;
73 import java.util.Properties;
74 import java.sql.Connection;
75 import java.sql.DatabaseMetaData;
76 import java.sql.Driver;
77 import java.sql.ResultSet;
78 import java.sql.ResultSetMetaData;
79 import java.sql.SQLException;
80 import java.sql.SQLWarning;
81 import java.sql.Statement;
82 import org.apache.commons.lang.StringUtils;
83 import org.apache.tools.ant.AntClassLoader;
84 import org.apache.tools.ant.BuildException;
85 import org.apache.tools.ant.Project;
86 import org.apache.tools.ant.ProjectHelper;
87 import org.apache.tools.ant.Task;
88 import org.apache.tools.ant.types.EnumeratedAttribute;
89 import org.apache.tools.ant.types.Path;
90 import org.apache.tools.ant.types.Reference;
91
92 /***
93 * This task uses an SQL -> Database map in the form of a properties
94 * file to insert each SQL file listed into its designated database.
95 *
96 * @author <a href="mailto:jeff@custommonkey.org">Jeff Martin</a>
97 * @author <a href="mailto:gholam@xtra.co.nz">Michael McCallum</A>
98 * @author <a href="mailto:tim.stephenson@sybase.com">Tim Stephenson</A>
99 * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</A>
100 * @author <a href="mailto:mpoeschl@marmot.at">Martin Poeschl</a>
101 * @version $Id: TorqueSQLExec.java,v 1.2 2003/03/21 17:31:08 mpoeschl Exp $
102 */
103 public class TorqueSQLExec extends Task
104 {
105 private int goodSql = 0;
106 private int totalSql = 0;
107 private Path classpath;
108 private AntClassLoader loader;
109
110 /***
111 *
112 */
113 public static class DelimiterType extends EnumeratedAttribute
114 {
115 public static final String NORMAL = "normal";
116 public static final String ROW = "row";
117
118 public String[] getValues()
119 {
120 return new String[] {NORMAL, ROW};
121 }
122 }
123
124 /*** Database connection */
125 private Connection conn = null;
126
127 /*** Autocommit flag. Default value is false */
128 private boolean autocommit = false;
129
130 /*** SQL statement */
131 private Statement statement = null;
132
133 /*** DB driver. */
134 private String driver = null;
135
136 /*** DB url. */
137 private String url = null;
138
139 /*** User name. */
140 private String userId = null;
141
142 /*** Password */
143 private String password = null;
144
145 /*** SQL input command */
146 private String sqlCommand = "";
147
148 /*** SQL Statement delimiter */
149 private String delimiter = ";";
150
151 /***
152 * The delimiter type indicating whether the delimiter will
153 * only be recognized on a line by itself
154 */
155 private String delimiterType = DelimiterType.NORMAL;
156
157 /*** Print SQL results. */
158 private boolean print = false;
159
160 /*** Print header columns. */
161 private boolean showheaders = true;
162
163 /*** Results Output file. */
164 private File output = null;
165
166 /*** RDBMS Product needed for this SQL. */
167 private String rdbms = null;
168
169 /*** RDBMS Version needed for this SQL. */
170 private String version = null;
171
172 /*** Action to perform if an error is found */
173 private String onError = "abort";
174
175 /*** Encoding to use when reading SQL statements from a file */
176 private String encoding = null;
177
178 /*** Src directory for the files listed in the sqldbmap. */
179 private String srcDir;
180
181 /*** Properties file that maps an individual SQL file to a database. */
182 private File sqldbmap;
183
184 /***
185 * Set the sqldbmap properties file.
186 *
187 * @param sqldbmap filename for the sqldbmap
188 */
189 public void setSqlDbMap(String sqldbmap)
190 {
191 this.sqldbmap = project.resolveFile(sqldbmap);
192 }
193
194 /***
195 * Get the sqldbmap properties file.
196 *
197 * @return filename for the sqldbmap
198 */
199 public File getSqlDbMap()
200 {
201 return sqldbmap;
202 }
203
204 /***
205 * Set the src directory for the sql files listed in the sqldbmap file.
206 *
207 * @param srcDir sql source directory
208 */
209 public void setSrcDir(String srcDir)
210 {
211 this.srcDir = project.resolveFile(srcDir).toString();
212 }
213
214 /***
215 * Get the src directory for the sql files listed in the sqldbmap file.
216 *
217 * @return sql source directory
218 */
219 public String getSrcDir()
220 {
221 return srcDir;
222 }
223
224 /***
225 * Set the classpath for loading the driver.
226 *
227 * @param classpath the classpath
228 */
229 public void setClasspath(Path classpath)
230 {
231 if (this.classpath == null)
232 {
233 this.classpath = classpath;
234 }
235 else
236 {
237 this.classpath.append(classpath);
238 }
239 }
240
241 /***
242 * Create the classpath for loading the driver.
243 *
244 * @return the classpath
245 */
246 public Path createClasspath()
247 {
248 if (this.classpath == null)
249 {
250 this.classpath = new Path(project);
251 }
252 return this.classpath.createPath();
253 }
254
255 /***
256 * Set the classpath for loading the driver using the classpath reference.
257 *
258 * @param r reference to the classpath
259 */
260 public void setClasspathRef(Reference r)
261 {
262 createClasspath().setRefid(r);
263 }
264
265 /***
266 * Set the sql command to execute
267 *
268 * @param sql sql command to execute
269 */
270 public void addText(String sql)
271 {
272 this.sqlCommand += sql;
273 }
274
275 /***
276 * Set the JDBC driver to be used.
277 *
278 * @param driver driver class name
279 */
280 public void setDriver(String driver)
281 {
282 this.driver = driver;
283 }
284
285 /***
286 * Set the DB connection url.
287 *
288 * @param url connection url
289 */
290 public void setUrl(String url)
291 {
292 this.url = url;
293 }
294
295 /***
296 * Set the user name for the DB connection.
297 *
298 * @param userId database user
299 */
300 public void setUserid(String userId)
301 {
302 this.userId = userId;
303 }
304
305 /***
306 * Set the file encoding to use on the sql files read in
307 *
308 * @param encoding the encoding to use on the files
309 */
310 public void setEncoding(String encoding)
311 {
312 this.encoding = encoding;
313 }
314
315 /***
316 * Set the password for the DB connection.
317 *
318 * @param password database password
319 */
320 public void setPassword(String password)
321 {
322 this.password = password;
323 }
324
325 /***
326 * Set the autocommit flag for the DB connection.
327 *
328 * @param autocommit the autocommit flag
329 */
330 public void setAutocommit(boolean autocommit)
331 {
332 this.autocommit = autocommit;
333 }
334
335 /***
336 * Set the statement delimiter.
337 *
338 * <p>For example, set this to "go" and delimitertype to "ROW" for
339 * Sybase ASE or MS SQL Server.</p>
340 *
341 * @param delimiter
342 */
343 public void setDelimiter(String delimiter)
344 {
345 this.delimiter = delimiter;
346 }
347
348 /***
349 * Set the Delimiter type for this sql task. The delimiter type takes two
350 * values - normal and row. Normal means that any occurence of the delimiter
351 * terminate the SQL command whereas with row, only a line containing just
352 * the delimiter is recognized as the end of the command.
353 *
354 * @param delimiterType
355 */
356 public void setDelimiterType(DelimiterType delimiterType)
357 {
358 this.delimiterType = delimiterType.getValue();
359 }
360
361 /***
362 * Set the print flag.
363 *
364 * @param print
365 */
366 public void setPrint(boolean print)
367 {
368 this.print = print;
369 }
370
371 /***
372 * Set the showheaders flag.
373 *
374 * @param showheaders
375 */
376 public void setShowheaders(boolean showheaders)
377 {
378 this.showheaders = showheaders;
379 }
380
381 /***
382 * Set the output file.
383 *
384 * @param output
385 */
386 public void setOutput(File output)
387 {
388 this.output = output;
389 }
390
391 /***
392 * Set the rdbms required
393 *
394 * @param vendor
395 */
396 public void setRdbms(String vendor)
397 {
398 this.rdbms = vendor.toLowerCase();
399 }
400
401 /***
402 * Set the version required
403 *
404 * @param version
405 */
406 public void setVersion(String version)
407 {
408 this.version = version.toLowerCase();
409 }
410
411 /***
412 * Set the action to perform onerror
413 *
414 * @param action
415 */
416 public void setOnerror(OnError action)
417 {
418 this.onError = action.getValue();
419 }
420
421 /***
422 * Load the sql file and then execute it
423 *
424 * @throws BuildException
425 */
426 public void execute() throws BuildException
427 {
428 sqlCommand = sqlCommand.trim();
429
430 if (sqldbmap == null || getSqlDbMap().exists() == false)
431 {
432 throw new BuildException("You haven't provided an sqldbmap, or "
433 + "the one you specified doesn't exist: " + sqldbmap);
434 }
435
436 if (driver == null)
437 {
438 throw new BuildException("Driver attribute must be set!", location);
439 }
440 if (userId == null)
441 {
442 throw new BuildException("User Id attribute must be set!",
443 location);
444 }
445 if (password == null)
446 {
447 throw new BuildException("Password attribute must be set!",
448 location);
449 }
450 if (url == null)
451 {
452 throw new BuildException("Url attribute must be set!", location);
453 }
454
455 Properties map = new Properties();
456
457 try
458 {
459 FileInputStream fis = new FileInputStream(getSqlDbMap());
460 map.load(fis);
461 fis.close();
462 }
463 catch (IOException ioe)
464 {
465 throw new BuildException("Cannot open and process the sqldbmap!");
466 }
467
468 Map databases = new HashMap();
469
470 Iterator eachFileName = map.keySet().iterator();
471 while (eachFileName.hasNext())
472 {
473 String sqlfile = (String) eachFileName.next();
474 String database = map.getProperty(sqlfile);
475
476 List files = (List) databases.get(database);
477
478 if (files == null)
479 {
480 files = new ArrayList();
481 databases.put(database, files);
482 }
483
484 // We want to make sure that the base schemas
485 // are inserted first.
486 if (sqlfile.indexOf("schema.sql") != -1)
487 {
488 files.add(0, sqlfile);
489 }
490 else
491 {
492 files.add(sqlfile);
493 }
494 }
495
496 Iterator eachDatabase = databases.keySet().iterator();
497 while (eachDatabase.hasNext())
498 {
499 String db = (String) eachDatabase.next();
500 List transactions = new ArrayList();
501 eachFileName = ((List) databases.get(db)).iterator();
502 while (eachFileName.hasNext())
503 {
504 String fileName = (String) eachFileName.next();
505 File file = new File(srcDir, fileName);
506
507 if (file.exists())
508 {
509 Transaction transaction = new Transaction();
510 transaction.setSrc(file);
511 transactions.add(transaction);
512 }
513 else
514 {
515 super.log("File '" + fileName
516 + "' in sqldbmap does not exist, so skipping it.");
517 }
518 }
519
520 insertDatabaseSqlFiles(url, db, transactions);
521 }
522 }
523
524 /***
525 * Take the base url, the target database and insert a set of SQL
526 * files into the target database.
527 *
528 * @param url
529 * @param database
530 * @param transactions
531 */
532 private void insertDatabaseSqlFiles(String url, String database,
533 List transactions)
534 {
535 url = StringUtils.replace(url, "@DB@", database);
536 System.out.println("Our new url -> " + url);
537
538 Driver driverInstance = null;
539 try
540 {
541 Class dc;
542 if (classpath != null)
543 {
544 log("Loading " + driver
545 + " using AntClassLoader with classpath " + classpath,
546 Project.MSG_VERBOSE);
547
548 loader = new AntClassLoader(project, classpath);
549 dc = loader.loadClass(driver);
550 }
551 else
552 {
553 log("Loading " + driver + " using system loader.",
554 Project.MSG_VERBOSE);
555 dc = Class.forName(driver);
556 }
557 driverInstance = (Driver) dc.newInstance();
558 }
559 catch (ClassNotFoundException e)
560 {
561 throw new BuildException("Class Not Found: JDBC driver " + driver
562 + " could not be loaded", location);
563 }
564 catch (IllegalAccessException e)
565 {
566 throw new BuildException("Illegal Access: JDBC driver " + driver
567 + " could not be loaded", location);
568 }
569 catch (InstantiationException e)
570 {
571 throw new BuildException("Instantiation Exception: JDBC driver "
572 + driver + " could not be loaded", location);
573 }
574
575 try
576 {
577 log("connecting to " + url, Project.MSG_VERBOSE);
578 Properties info = new Properties();
579 info.put("user", userId);
580 info.put("password", password);
581 conn = driverInstance.connect(url, info);
582
583 if (conn == null)
584 {
585 // Driver doesn't understand the URL
586 throw new SQLException("No suitable Driver for " + url);
587 }
588
589 if (!isValidRdbms(conn))
590 {
591 return;
592 }
593
594 conn.setAutoCommit(autocommit);
595 statement = conn.createStatement();
596 PrintStream out = System.out;
597 try
598 {
599 if (output != null)
600 {
601 log("Opening PrintStream to output file " + output,
602 Project.MSG_VERBOSE);
603 out = new PrintStream(new BufferedOutputStream(
604 new FileOutputStream(output)));
605 }
606
607 // Process all transactions
608 for (Iterator it = transactions.iterator(); it.hasNext();)
609 {
610 ((Transaction) it.next()).runTransaction(out);
611 if (!autocommit)
612 {
613 log("Commiting transaction", Project.MSG_VERBOSE);
614 conn.commit();
615 }
616 }
617 }
618 finally
619 {
620 if (out != null && out != System.out)
621 {
622 out.close();
623 }
624 }
625 }
626 catch (IOException e)
627 {
628 if (!autocommit && conn != null && onError.equals("abort"))
629 {
630 try
631 {
632 conn.rollback();
633 }
634 catch (SQLException ex)
635 {
636 // do nothing.
637 }
638 }
639 throw new BuildException(e, location);
640 }
641 catch (SQLException e)
642 {
643 if (!autocommit && conn != null && onError.equals("abort"))
644 {
645 try
646 {
647 conn.rollback();
648 }
649 catch (SQLException ex)
650 {
651 // do nothing.
652 }
653 }
654 throw new BuildException(e, location);
655 }
656 finally
657 {
658 try
659 {
660 if (statement != null)
661 {
662 statement.close();
663 }
664 if (conn != null)
665 {
666 conn.close();
667 }
668 }
669 catch (SQLException e)
670 {
671 }
672 }
673
674 log(goodSql + " of " + totalSql
675 + " SQL statements executed successfully");
676 }
677
678 /***
679 * Read the statements from the .sql file and execute them.
680 * Lines starting with '//', '--' or 'REM ' are ignored.
681 *
682 * @param reader
683 * @param out
684 * @throws SQLException
685 * @throws IOException
686 */
687 protected void runStatements(Reader reader, PrintStream out)
688 throws SQLException, IOException
689 {
690 String sql = "";
691 String line = "";
692
693 BufferedReader in = new BufferedReader(reader);
694
695 try
696 {
697 while ((line = in.readLine()) != null)
698 {
699 line = line.trim();
700 line = ProjectHelper.replaceProperties(project, line,
701 project.getProperties());
702 if (line.startsWith("//") || line.startsWith("--"))
703 {
704 continue;
705 }
706 if (line.length() > 4
707 && line.substring(0, 4).equalsIgnoreCase("REM "))
708 {
709 continue;
710 }
711
712 sql += " " + line;
713 sql = sql.trim();
714
715 // SQL defines "--" as a comment to EOL
716 // and in Oracle it may contain a hint
717 // so we cannot just remove it, instead we must end it
718 if (line.indexOf("--") >= 0)
719 {
720 sql += "\n";
721 }
722
723 if (delimiterType.equals(DelimiterType.NORMAL)
724 && sql.endsWith(delimiter)
725 || delimiterType.equals(DelimiterType.ROW)
726 && line.equals(delimiter))
727 {
728 log("SQL: " + sql, Project.MSG_VERBOSE);
729 execSQL(sql.substring(0, sql.length() - delimiter.length()),
730 out);
731 sql = "";
732 }
733 }
734
735 // Catch any statements not followed by ;
736 if (!sql.equals(""))
737 {
738 execSQL(sql, out);
739 }
740 }
741 catch (SQLException e)
742 {
743 throw e;
744 }
745 }
746
747 /***
748 * Verify if connected to the correct RDBMS
749 *
750 * @param conn
751 */
752 protected boolean isValidRdbms(Connection conn)
753 {
754 if (rdbms == null && version == null)
755 {
756 return true;
757 }
758
759 try
760 {
761 DatabaseMetaData dmd = conn.getMetaData();
762
763 if (rdbms != null)
764 {
765 String theVendor = dmd.getDatabaseProductName().toLowerCase();
766
767 log("RDBMS = " + theVendor, Project.MSG_VERBOSE);
768 if (theVendor == null || theVendor.indexOf(rdbms) < 0)
769 {
770 log("Not the required RDBMS: "
771 + rdbms, Project.MSG_VERBOSE);
772 return false;
773 }
774 }
775
776 if (version != null)
777 {
778 String theVersion = dmd.getDatabaseProductVersion()
779 .toLowerCase();
780
781 log("Version = " + theVersion, Project.MSG_VERBOSE);
782 if (theVersion == null || !(theVersion.startsWith(version)
783 || theVersion.indexOf(" " + version) >= 0))
784 {
785 log("Not the required version: \"" + version + "\"",
786 Project.MSG_VERBOSE);
787 return false;
788 }
789 }
790 }
791 catch (SQLException e)
792 {
793 // Could not get the required information
794 log("Failed to obtain required RDBMS information", Project.MSG_ERR);
795 return false;
796 }
797
798 return true;
799 }
800
801 /***
802 * Exec the sql statement.
803 *
804 * @param sql
805 * @param out
806 * @throws SQLException
807 */
808 protected void execSQL(String sql, PrintStream out) throws SQLException
809 {
810 // Check and ignore empty statements
811 if ("".equals(sql.trim()))
812 {
813 return;
814 }
815
816 try
817 {
818 totalSql++;
819 if (!statement.execute(sql))
820 {
821 log(statement.getUpdateCount() + " rows affected",
822 Project.MSG_VERBOSE);
823 }
824 else
825 {
826 if (print)
827 {
828 printResults(out);
829 }
830 }
831
832 SQLWarning warning = conn.getWarnings();
833 while (warning != null)
834 {
835 log(warning + " sql warning", Project.MSG_VERBOSE);
836 warning = warning.getNextWarning();
837 }
838 conn.clearWarnings();
839 goodSql++;
840 }
841 catch (SQLException e)
842 {
843 log("Failed to execute: " + sql, Project.MSG_ERR);
844 if (!onError.equals("continue"))
845 {
846 throw e;
847 }
848 log(e.toString(), Project.MSG_ERR);
849 }
850 }
851
852 /***
853 * print any results in the statement.
854 *
855 * @param out
856 * @throws SQLException
857 */
858 protected void printResults(PrintStream out) throws java.sql.SQLException
859 {
860 ResultSet rs = null;
861 do
862 {
863 rs = statement.getResultSet();
864 if (rs != null)
865 {
866 log("Processing new result set.", Project.MSG_VERBOSE);
867 ResultSetMetaData md = rs.getMetaData();
868 int columnCount = md.getColumnCount();
869 StringBuffer line = new StringBuffer();
870 if (showheaders)
871 {
872 for (int col = 1; col < columnCount; col++)
873 {
874 line.append(md.getColumnName(col));
875 line.append(",");
876 }
877 line.append(md.getColumnName(columnCount));
878 out.println(line);
879 line.setLength(0);
880 }
881 while (rs.next())
882 {
883 boolean first = true;
884 for (int col = 1; col <= columnCount; col++)
885 {
886 String columnValue = rs.getString(col);
887 if (columnValue != null)
888 {
889 columnValue = columnValue.trim();
890 }
891
892 if (first)
893 {
894 first = false;
895 }
896 else
897 {
898 line.append(",");
899 }
900 line.append(columnValue);
901 }
902 out.println(line);
903 line.setLength(0);
904 }
905 }
906 }
907 while (statement.getMoreResults());
908 out.println();
909 }
910
911 /***
912 * Enumerated attribute with the values "continue", "stop" and "abort"
913 * for the onerror attribute.
914 */
915 public static class OnError extends EnumeratedAttribute
916 {
917 public String[] getValues()
918 {
919 return new String[] {"continue", "stop", "abort"};
920 }
921 }
922
923 /***
924 * Contains the definition of a new transaction element.
925 * Transactions allow several files or blocks of statements
926 * to be executed using the same JDBC connection and commit
927 * operation in between.
928 */
929 public class Transaction
930 {
931 private File tSrcFile = null;
932 private String tSqlCommand = "";
933
934 public void setSrc(File src)
935 {
936 this.tSrcFile = src;
937 }
938
939 public void addText(String sql)
940 {
941 this.tSqlCommand += sql;
942 }
943
944 private void runTransaction(PrintStream out)
945 throws IOException, SQLException
946 {
947 if (tSqlCommand.length() != 0)
948 {
949 log("Executing commands", Project.MSG_INFO);
950 runStatements(new StringReader(tSqlCommand), out);
951 }
952
953 if (tSrcFile != null)
954 {
955 log("Executing file: " + tSrcFile.getAbsolutePath(),
956 Project.MSG_INFO);
957 Reader reader = (encoding == null) ? new FileReader(tSrcFile)
958 : new InputStreamReader(new FileInputStream(tSrcFile),
959 encoding);
960 runStatements(reader, out);
961 reader.close();
962 }
963 }
964 }
965 }
This page was automatically generated by Maven