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