Coverage Report - org.kuali.core.db.torque.KualiTorqueDataDumpTask
 
Classes in this File Line Coverage Branch Coverage Complexity
KualiTorqueDataDumpTask
0%
0/236
0%
0/50
2.586
 
 1  
 package org.kuali.core.db.torque;
 2  
 
 3  
 import static java.sql.Types.CLOB;
 4  
 import static java.sql.Types.DATE;
 5  
 import static java.sql.Types.TIMESTAMP;
 6  
 import static org.kuali.db.JDBCUtils.closeQuietly;
 7  
 
 8  
 import java.io.File;
 9  
 import java.io.FileNotFoundException;
 10  
 import java.io.FileOutputStream;
 11  
 import java.io.IOException;
 12  
 import java.io.OutputStream;
 13  
 import java.io.PrintWriter;
 14  
 import java.io.Reader;
 15  
 import java.io.Writer;
 16  
 import java.sql.Clob;
 17  
 import java.sql.Connection;
 18  
 import java.sql.DatabaseMetaData;
 19  
 import java.sql.ResultSet;
 20  
 import java.sql.ResultSetMetaData;
 21  
 import java.sql.SQLException;
 22  
 import java.sql.Statement;
 23  
 import java.sql.Timestamp;
 24  
 import java.text.SimpleDateFormat;
 25  
 import java.util.ArrayList;
 26  
 import java.util.Date;
 27  
 import java.util.List;
 28  
 import java.util.Set;
 29  
 import java.util.TreeSet;
 30  
 
 31  
 import org.apache.commons.io.FileUtils;
 32  
 import org.apache.commons.io.IOUtils;
 33  
 import org.apache.commons.lang.StringUtils;
 34  
 import org.apache.tools.ant.BuildException;
 35  
 import org.apache.tools.ant.Project;
 36  
 import org.apache.torque.engine.database.model.Column;
 37  
 import org.apache.torque.engine.database.model.Table;
 38  
 import org.apache.torque.engine.platform.Platform;
 39  
 import org.apache.torque.engine.platform.PlatformFactory;
 40  
 import org.apache.xerces.dom.DocumentImpl;
 41  
 import org.apache.xerces.dom.DocumentTypeImpl;
 42  
 import org.apache.xerces.util.XMLChar;
 43  
 import org.apache.xml.serialize.Method;
 44  
 import org.apache.xml.serialize.OutputFormat;
 45  
 import org.apache.xml.serialize.XMLSerializer;
 46  
 import org.w3c.dom.Element;
 47  
 
 48  
 /**
 49  
  * This task exports tables from a JDBC accessible database to XML
 50  
  */
 51  0
 public class KualiTorqueDataDumpTask extends DumpTask {
 52  0
     Utils utils = new Utils();
 53  0
     private static final String FS = System.getProperty("file.separator");
 54  
 
 55  
     private File buildDirectory;
 56  
 
 57  
     /**
 58  
      * The directory where XML files will be written
 59  
      */
 60  
     private File dataXMLDir;
 61  
 
 62  
     /**
 63  
      * The format to use for dates/timestamps
 64  
      */
 65  0
     private String dateFormat = "yyyyMMddHHmmss z";
 66  
 
 67  
     /**
 68  
      * The formatter that will do the formatting of dates;
 69  
      */
 70  
     private SimpleDateFormat dateFormatter;
 71  
 
 72  
     @Override
 73  
     protected void showConfiguration() {
 74  0
         super.showConfiguration();
 75  0
         log("Exporting to: " + getDataXMLDir().getAbsolutePath());
 76  0
         log("Date format: \"" + dateFormat + "\" - " + dateFormatter.format(new Date()));
 77  0
     }
 78  
 
 79  
     @Override
 80  
     protected void updateConfiguration(final Platform platform) {
 81  0
         super.updateConfiguration(platform);
 82  0
         dateFormatter = new SimpleDateFormat(dateFormat);
 83  0
     }
 84  
 
 85  
     /**
 86  
      * Dump the data to XML files
 87  
      */
 88  
     @Override
 89  
     public void execute() throws BuildException {
 90  
 
 91  
         try {
 92  0
             log("--------------------------------------");
 93  0
             log("Impex - Data Export");
 94  0
             log("--------------------------------------");
 95  0
             Platform platform = PlatformFactory.getPlatformFor(targetDatabase);
 96  0
             updateConfiguration(platform);
 97  0
             showConfiguration();
 98  
 
 99  
             // Generate the XML
 100  0
             generateXML(platform);
 101  0
         } catch (Exception e) {
 102  0
             throw new BuildException(e);
 103  0
         }
 104  0
     }
 105  
 
 106  
     /**
 107  
      * Generate a SQL statement that selects all data from the table
 108  
      */
 109  
     protected String getDataSelectStatement(final TableHelper helper, final String tableName) throws SQLException {
 110  0
         StringBuffer sb = new StringBuffer("SELECT * FROM ");
 111  0
         sb.append(tableName);
 112  0
         sb.append(" ORDER BY 'x'");
 113  0
         List<String> pkFields = helper.getPlatform().getPrimaryKeys(helper.getDbMetaData(), getSchema(), tableName);
 114  0
         for (String field : pkFields) {
 115  0
             sb.append(", ").append(field);
 116  
         }
 117  0
         return sb.toString();
 118  
     }
 119  
 
 120  
     /**
 121  
      * Generate an array of Column objects from the result set metadata
 122  
      */
 123  
     protected Column[] getColumns(final ResultSetMetaData md) throws SQLException {
 124  0
         Column[] columns = new Column[md.getColumnCount() + 1];
 125  0
         for (int i = 1; i <= md.getColumnCount(); i++) {
 126  0
             Column column = new Column();
 127  0
             column.setName(md.getColumnName(i));
 128  0
             column.setJdbcType(md.getColumnType(i));
 129  0
             columns[i] = column;
 130  
         }
 131  0
         return columns;
 132  
     }
 133  
 
 134  
     /**
 135  
      * Extract a column value from the result set, converting as needed
 136  
      */
 137  
     protected Object getColumnValue(final ResultSet rs, final int index, final Column column, final int rowCount,
 138  
             final String tableName) {
 139  
         // Extract a raw object
 140  0
         Object columnValue = null;
 141  
         try {
 142  0
             columnValue = rs.getObject(index);
 143  
 
 144  
             // If it is null we're done
 145  0
             if (columnValue == null) {
 146  0
                 return null;
 147  
             }
 148  
             // Handle special types
 149  0
             switch (column.getJdbcType()) {
 150  
             case (CLOB):
 151  
                 // Extract a CLOB
 152  0
                 return getClob((Clob) columnValue);
 153  
             case (DATE):
 154  
             case (TIMESTAMP):
 155  
                 // Extract dates and timestamps
 156  0
                 return getDate(rs, index);
 157  
             default:
 158  
                 // Otherwise return the raw object
 159  0
                 return columnValue;
 160  
             }
 161  0
         } catch (Exception e) {
 162  
             // Don't let an issue extracting a value from one column in one row
 163  
             // stop the process
 164  
             // Log the row/column and continue
 165  0
             log("Problem reading row " + rowCount + " column " + column.getName() + " from " + tableName,
 166  
                     Project.MSG_ERR);
 167  0
             log(e.getClass().getName() + " : " + e.getMessage(), Project.MSG_ERR);
 168  
 
 169  
         }
 170  0
         return null;
 171  
     }
 172  
 
 173  
     /**
 174  
      * Convert a JDBC Timestamp into a java.util.Date using the specified format
 175  
      */
 176  
     protected String getDate(final ResultSet rs, final int index) throws SQLException {
 177  0
         Timestamp date = rs.getTimestamp(index);
 178  0
         return dateFormatter.format(date);
 179  
     }
 180  
 
 181  
     /**
 182  
      * Convert a CLOB to a String
 183  
      */
 184  
     protected String getClob(final Clob clob) throws SQLException {
 185  0
         Reader r = null;
 186  0
         StringBuffer sb = new StringBuffer();
 187  
         try {
 188  0
             r = clob.getCharacterStream();
 189  0
             char[] buffer = new char[4096];
 190  
             int len;
 191  0
             while ((len = r.read(buffer)) != -1) {
 192  0
                 sb.append(buffer, 0, len);
 193  
             }
 194  0
         } catch (IOException e) {
 195  0
             throw new SQLException(e);
 196  
         } finally {
 197  0
             IOUtils.closeQuietly(r);
 198  0
         }
 199  0
         return sb.toString();
 200  
     }
 201  
 
 202  
     /**
 203  
      * Convert a row from the result set into an Element
 204  
      */
 205  
     protected Element getRow(final DocumentImpl doc, final String tableName, final ResultSetMetaData md,
 206  
             final ResultSet rs, final Column[] columns, final int rowCount) throws SQLException {
 207  
         // Generate a row object
 208  0
         Element row = doc.createElement(tableName);
 209  
 
 210  
         // Cycle through the columns
 211  0
         for (int i = 1; i <= md.getColumnCount(); i++) {
 212  
 
 213  
             // Extract a column value
 214  0
             Object columnValue = getColumnValue(rs, i, columns[i], rowCount, tableName);
 215  
 
 216  
             // Null values can be omitted from the XML
 217  0
             if (columnValue == null) {
 218  0
                 continue;
 219  
             }
 220  
 
 221  
             // Otherwise, escape the String and add it to the row Element
 222  0
             row.setAttribute(columns[i].getName(), xmlEscape(columnValue.toString()));
 223  
         }
 224  
 
 225  
         // Return an Element representing one row of data from the ResultSet
 226  0
         return row;
 227  
     }
 228  
 
 229  
     /**
 230  
      * Generate and return the dataset Element
 231  
      */
 232  
     protected Element getDatasetNode(final TableHelper helper, final DocumentImpl document, final String tableName)
 233  
             throws SQLException {
 234  0
         Element datasetNode = document.createElement("dataset");
 235  0
         Statement stmt = null;
 236  0
         ResultSet rs = null;
 237  
         try {
 238  
             // This query selects everything from the table
 239  0
             String query = getDataSelectStatement(helper, tableName);
 240  0
             stmt = helper.getConnection().createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
 241  0
             rs = stmt.executeQuery(query);
 242  0
             ResultSetMetaData md = rs.getMetaData();
 243  0
             Column[] columns = getColumns(md);
 244  0
             int count = 0;
 245  
             // Process the ResultSet
 246  0
             while (rs.next()) {
 247  0
                 count++;
 248  0
                 log("Processing row " + count + " of " + tableName, Project.MSG_DEBUG);
 249  0
                 Element row = getRow(document, tableName, md, rs, columns, count);
 250  0
                 datasetNode.appendChild(row);
 251  0
             }
 252  0
             helper.setRowCount(count);
 253  
             // Keep track of how many rows we found
 254  0
             if (count == 0) {
 255  0
                 log("No data found in table " + tableName, Project.MSG_DEBUG);
 256  0
                 return null;
 257  
             }
 258  0
         } catch (Exception e) {
 259  0
             throw new SQLException(e);
 260  
         } finally {
 261  
             // Clean up
 262  0
             closeQuietly(rs);
 263  0
             closeQuietly(stmt);
 264  0
         }
 265  0
         return datasetNode;
 266  
     }
 267  
 
 268  
     /**
 269  
      * Return the systemId to use
 270  
      */
 271  
     protected String getSystemId() {
 272  0
         if (antCompatibilityMode) {
 273  0
             return "data.dtd";
 274  
         } else {
 275  0
             return getArtifactId() + ".dtd";
 276  
         }
 277  
     }
 278  
 
 279  
     /**
 280  
      * Return the XML Document object that we will serialize to disk
 281  
      */
 282  
     protected DocumentImpl getDocument(final TableHelper helper, final String tableName) throws SQLException {
 283  
         // Generate the document type
 284  0
         DocumentTypeImpl docType = new DocumentTypeImpl(null, "dataset", null, getSystemId());
 285  
         // Generate an empty document
 286  0
         DocumentImpl doc = new DocumentImpl(docType);
 287  
         // Append a comment
 288  0
         doc.appendChild(doc.createComment(" " + getComment() + " "));
 289  
         // Extract the data from the table
 290  0
         Element datasetNode = getDatasetNode(helper, doc, tableName);
 291  0
         if (datasetNode == null) {
 292  
             // There was no data (zero rows), we are done
 293  0
             return null;
 294  
         }
 295  
         // Add the dataset to the document
 296  0
         doc.appendChild(datasetNode);
 297  
         // Return what we found
 298  0
         return doc;
 299  
     }
 300  
 
 301  
     /**
 302  
      * <code>
 303  
      * Convert a List<Table> into a List<String> of table names
 304  
      * </code>
 305  
      */
 306  
     protected List<String> getTableNamesFromTableObjects(final List<?> list) {
 307  0
         List<String> names = new ArrayList<String>();
 308  0
         for (Object object : list) {
 309  0
             Table table = (Table) object;
 310  0
             names.add(table.getName());
 311  0
         }
 312  0
         return names;
 313  
     }
 314  
 
 315  
     /**
 316  
      * Convert a List to a Set
 317  
      * 
 318  
      * @param list
 319  
      * @return
 320  
      */
 321  
     protected Set<String> getSet(final List<String> list) {
 322  0
         Set<String> set = new TreeSet<String>();
 323  0
         set.addAll(list);
 324  0
         return set;
 325  
     }
 326  
 
 327  
     /**
 328  
      * Generate XML from the data in the tables in the database
 329  
      */
 330  
     protected void generateXML(final Platform platform) throws Exception {
 331  0
         Connection connection = null;
 332  
 
 333  
         try {
 334  0
             connection = getConnection();
 335  
             // Get metadata about the database
 336  0
             DatabaseMetaData dbMetaData = connection.getMetaData();
 337  
             // Get the correct platform (oracle, mysql etc)
 338  
             // Get ALL the table names
 339  0
             Set<String> tableNames = getSet(getJDBCTableNames(dbMetaData));
 340  0
             log("Table Count: " + tableNames.size());
 341  0
             int completeSize = tableNames.size();
 342  
 
 343  0
             StringFilter filterer = new StringFilter(includePatterns, excludePatterns);
 344  0
             filterer.filter(tableNames.iterator());
 345  
 
 346  0
             int filteredSize = tableNames.size();
 347  
 
 348  0
             if (filteredSize != completeSize) {
 349  0
                 log("Filtered table count: " + tableNames.size());
 350  
             } else {
 351  0
                 log("No tables were filtered out.  Exporting all tables.");
 352  
             }
 353  
 
 354  0
             TableHelper helper = new TableHelper();
 355  0
             helper.setConnection(connection);
 356  0
             helper.setPlatform(platform);
 357  0
             helper.setDbMetaData(dbMetaData);
 358  0
             helper.setTableNames(tableNames);
 359  
 
 360  0
             processTables(helper);
 361  0
         } catch (Exception e) {
 362  0
             closeQuietly(connection);
 363  0
         }
 364  0
     }
 365  
 
 366  
     /**
 367  
      * Process the tables, keeping track of which tables had at least one row of data
 368  
      */
 369  
     protected void processTables(final TableHelper helper) throws IOException, SQLException {
 370  0
         long start = System.currentTimeMillis();
 371  0
         int exportCount = 0;
 372  0
         int skipCount = 0;
 373  0
         List<String> skippedTables = new ArrayList<String>();
 374  0
         List<String> exportedTables = new ArrayList<String>();
 375  0
         for (String tableName : helper.getTableNames()) {
 376  0
             boolean exported = processTable(helper, tableName);
 377  0
             if (exported) {
 378  0
                 exportedTables.add(tableName);
 379  0
                 exportCount++;
 380  
             } else {
 381  0
                 skippedTables.add(tableName);
 382  0
                 skipCount++;
 383  
             }
 384  0
         }
 385  0
         long elapsed = System.currentTimeMillis() - start;
 386  0
         log(utils.pad("Processed " + helper.getTableNames().size() + " tables", elapsed));
 387  0
         log("Exported data from " + exportCount + " tables to XML");
 388  0
         log("Skipped " + skipCount + " tables that had zero rows");
 389  0
         String base = buildDirectory.getCanonicalPath();
 390  0
         String skipped = base + FS + "/impex/skipped-tables.txt";
 391  0
         String exported = base + FS + "/impex/exported-tables.txt";
 392  0
         printTables("Skipped", skipped, skippedTables);
 393  0
         printTables("Exported", exported, exportedTables);
 394  0
     }
 395  
 
 396  
     protected void printTables(String msg, String filename, List<String> skippedTables) {
 397  0
         if (skippedTables.size() == 0) {
 398  0
             return;
 399  
         }
 400  0
         StringBuilder sb = new StringBuilder();
 401  0
         for (String skippedTable : skippedTables) {
 402  0
             sb.append(skippedTable + "\n");
 403  
         }
 404  0
         OutputStream out = null;
 405  
         try {
 406  0
             File file = new File(filename);
 407  0
             log(msg + " table list: " + file.getCanonicalPath());
 408  0
             out = FileUtils.openOutputStream(file);
 409  0
             out.write(sb.toString().getBytes());
 410  0
         } catch (IOException e) {
 411  0
             throw new RuntimeException(e);
 412  
         } finally {
 413  0
             IOUtils.closeQuietly(out);
 414  0
         }
 415  
 
 416  0
     }
 417  
 
 418  
     /**
 419  
      * Process one table. Only create an XML file if there is at least one row of data
 420  
      */
 421  
     protected boolean processTable(final TableHelper helper, final String tableName) throws SQLException, IOException {
 422  0
         log("Processing: " + tableName, Project.MSG_DEBUG);
 423  0
         long ts1 = System.currentTimeMillis();
 424  0
         DocumentImpl doc = getDocument(helper, tableName);
 425  0
         long ts2 = System.currentTimeMillis();
 426  0
         log(utils.pad("Extracting: " + tableName + " ", ts2 - ts1), Project.MSG_DEBUG);
 427  0
         boolean exported = false;
 428  0
         if (doc != null) {
 429  0
             serialize(tableName, doc);
 430  0
             exported = true;
 431  
         }
 432  0
         long ts3 = System.currentTimeMillis();
 433  0
         log(utils.pad("Serializing: " + tableName + " ", ts3 - ts2), Project.MSG_DEBUG);
 434  0
         if (!exported) {
 435  0
             log(utils.pad("Rows: " + StringUtils.leftPad(helper.getRowCount() + "", 5) + " " + tableName, (ts3 - ts1)),
 436  
                     Project.MSG_DEBUG);
 437  
         } else {
 438  0
             log(utils.pad("Rows: " + StringUtils.leftPad(helper.getRowCount() + "", 5) + " " + tableName, (ts3 - ts1)));
 439  
         }
 440  0
         return exported;
 441  
     }
 442  
 
 443  
     /**
 444  
      * This is where the XML will be written to
 445  
      */
 446  
     protected Writer getWriter(final String tableName) throws FileNotFoundException {
 447  0
         String filename = getDataXMLDir() + FS + tableName + ".xml";
 448  0
         log("filename:" + filename, Project.MSG_DEBUG);
 449  0
         return new PrintWriter(new FileOutputStream(filename));
 450  
     }
 451  
 
 452  
     /**
 453  
      * This is the XMLSerializer responsible for outputting the XML document
 454  
      */
 455  
     protected XMLSerializer getSerializer(final Writer out) {
 456  0
         return new XMLSerializer(out, new OutputFormat(Method.XML, getEncoding(), true));
 457  
     }
 458  
 
 459  
     /**
 460  
      * Serialize the document
 461  
      */
 462  
     protected void serialize(final String tableName, final DocumentImpl doc) throws IOException {
 463  0
         Writer out = null;
 464  
         try {
 465  0
             out = getWriter(tableName);
 466  0
             XMLSerializer serializer = getSerializer(out);
 467  0
             serializer.serialize(doc);
 468  0
             out.flush();
 469  0
         } catch (IOException e) {
 470  0
             throw e;
 471  
         } finally {
 472  0
             IOUtils.closeQuietly(out);
 473  0
         }
 474  0
     }
 475  
 
 476  
     /**
 477  
      * Escape characters that would cause issues for XML parsers
 478  
      */
 479  
     protected String xmlEscape(final String st) {
 480  0
         StringBuffer buff = new StringBuffer();
 481  0
         char[] block = st.toCharArray();
 482  0
         String stEntity = null;
 483  
         int i, last;
 484  
 
 485  0
         for (i = 0, last = 0; i < block.length; i++) {
 486  0
             if (XMLChar.isInvalid(block[i])) {
 487  0
                 stEntity = " ";
 488  
             }
 489  0
             if (stEntity != null) {
 490  0
                 buff.append(block, last, i - last);
 491  0
                 buff.append(stEntity);
 492  0
                 stEntity = null;
 493  0
                 last = i + 1;
 494  
             }
 495  
         }
 496  0
         if (last < block.length) {
 497  0
             buff.append(block, last, i - last);
 498  
         }
 499  0
         return buff.toString();
 500  
     }
 501  
 
 502  
     /**
 503  
      * Get the names of all the tables in our schema
 504  
      */
 505  
     public List<String> getJDBCTableNames(final DatabaseMetaData dbMeta) throws SQLException {
 506  
         // these are the entity types we want from the database
 507  0
         String[] types = { "TABLE" }; // JHK: removed views from list
 508  0
         List<String> tables = new ArrayList<String>();
 509  0
         ResultSet tableNames = null;
 510  
         try {
 511  
             // JHK: upper-cased schema name (required by Oracle)
 512  0
             tableNames = dbMeta.getTables(null, getSchema().toUpperCase(), null, types);
 513  0
             while (tableNames.next()) {
 514  0
                 String name = tableNames.getString(3);
 515  0
                 tables.add(name);
 516  0
             }
 517  
         } finally {
 518  0
             closeQuietly(tableNames);
 519  0
         }
 520  0
         return tables;
 521  
     }
 522  
 
 523  
     public File getDataXMLDir() {
 524  0
         return dataXMLDir;
 525  
     }
 526  
 
 527  
     public void setDataXMLDir(final File outputDirectory) {
 528  0
         this.dataXMLDir = outputDirectory;
 529  0
     }
 530  
 
 531  
     public String getDateFormat() {
 532  0
         return dateFormat;
 533  
     }
 534  
 
 535  
     public void setDateFormat(final String dateFormat) {
 536  0
         this.dateFormat = dateFormat;
 537  0
     }
 538  
 
 539  
     public File getBuildDirectory() {
 540  0
         return buildDirectory;
 541  
     }
 542  
 
 543  
     public void setBuildDirectory(File buildDirectory) {
 544  0
         this.buildDirectory = buildDirectory;
 545  0
     }
 546  
 }