View Javadoc
1   package com.atlassian.dbexporter.exporter;
2   
3   import com.atlassian.dbexporter.Context;
4   import com.atlassian.dbexporter.EntityNameProcessor;
5   import com.atlassian.dbexporter.ImportExportErrorService;
6   import com.atlassian.dbexporter.Table;
7   import com.atlassian.dbexporter.jdbc.JdbcUtils;
8   import com.atlassian.dbexporter.node.NodeCreator;
9   import com.atlassian.dbexporter.progress.ProgressMonitor;
10  import com.google.common.base.Function;
11  import com.google.common.collect.Iterables;
12  
13  import java.math.BigDecimal;
14  import java.sql.Clob;
15  import java.sql.Connection;
16  import java.sql.ResultSet;
17  import java.sql.ResultSetMetaData;
18  import java.sql.SQLException;
19  import java.sql.Statement;
20  import java.sql.Timestamp;
21  import java.sql.Types;
22  
23  import static com.atlassian.dbexporter.jdbc.JdbcUtils.JdbcCallable;
24  import static com.atlassian.dbexporter.jdbc.JdbcUtils.closeQuietly;
25  import static com.atlassian.dbexporter.jdbc.JdbcUtils.createStatement;
26  import static com.atlassian.dbexporter.jdbc.JdbcUtils.metadata;
27  import static com.atlassian.dbexporter.jdbc.JdbcUtils.quote;
28  import static com.atlassian.dbexporter.jdbc.JdbcUtils.withConnection;
29  import static com.atlassian.dbexporter.jdbc.JdbcUtils.withNoAutoCommit;
30  import static com.atlassian.dbexporter.node.NodeBackup.ColumnDataNode;
31  import static com.atlassian.dbexporter.node.NodeBackup.RowDataNode;
32  import static com.atlassian.dbexporter.node.NodeBackup.TableDataNode;
33  import static com.atlassian.dbexporter.progress.ProgressMonitor.Task;
34  import static com.google.common.base.Preconditions.checkNotNull;
35  
36  public final class DataExporter implements Exporter {
37      private final ImportExportErrorService errorService;
38      private final String schema;
39  
40      public DataExporter(ImportExportErrorService errorService, String schema) {
41          this.errorService = checkNotNull(errorService);
42          this.schema = isBlank(schema) ? null : schema; // maybe null
43      }
44  
45      @Override
46      public void export(final NodeCreator node, final ExportConfiguration configuration, final Context context) {
47          final ProgressMonitor monitor = configuration.getProgressMonitor();
48          monitor.begin(Task.TABLES_DATA);
49          withConnection(errorService, configuration.getConnectionProvider(), new JdbcUtils.JdbcCallable<Void>() {
50              public Void call(Connection connection) {
51                  for (final String table : getTableNames(context)) {
52                      withNoAutoCommit(errorService, connection, new JdbcCallable<Void>() {
53                          @Override
54                          public Void call(Connection connection) {
55                              exportTable(table, connection, node, monitor, configuration.getEntityNameProcessor());
56                              return null;
57                          }
58                      });
59                  }
60                  node.closeEntity();
61                  return null;
62              }
63          });
64          monitor.end(Task.TABLES_DATA);
65      }
66  
67      /**
68       * Returns the names of the tables that must be included in the export.
69       *
70       * @return the table names
71       */
72      private Iterable<String> getTableNames(Context context) {
73          return Iterables.transform(context.getAll(Table.class), new Function<Table, String>() {
74              @Override
75              public String apply(Table t) {
76                  return t.getName();
77              }
78          });
79      }
80  
81      private NodeCreator exportTable(String table, Connection connection, NodeCreator node, ProgressMonitor monitor, EntityNameProcessor entityNameProcessor) {
82          monitor.begin(Task.TABLE_DATA, entityNameProcessor.tableName(table));
83          TableDataNode.add(node, entityNameProcessor.tableName(table));
84  
85          final Statement statement = createStatement(errorService, table, connection);
86          ResultSet result = null;
87          try {
88              result = executeQueryWithFetchSize(table, statement, "SELECT * FROM " + tableName(table, connection), 100);
89              final ResultSetMetaData meta = resultSetMetaData(table, result);
90  
91              // write column definitions
92              node = writeColumnDefinitions(table, node, meta, entityNameProcessor);
93              while (next(table, result)) {
94                  node = exportRow(table, node, result, monitor);
95              }
96          } finally {
97              closeQuietly(result, statement);
98          }
99  
100         monitor.end(Task.TABLE_DATA, entityNameProcessor.tableName(table));
101         return node.closeEntity();
102     }
103 
104     private String tableName(String table, Connection connection) {
105         final String quoted = quote(errorService, table, connection, table);
106         return schema != null ? schema + "." + quoted : quoted;
107     }
108 
109     private NodeCreator exportRow(String table, NodeCreator node, ResultSet result, ProgressMonitor monitor) {
110         monitor.begin(Task.TABLE_ROW);
111         final ResultSetMetaData metaData = resultSetMetaData(table, result);
112 
113         RowDataNode.add(node);
114 
115         for (int col = 1; col <= columnCount(table, metaData); col++) {
116             switch (columnType(table, metaData, col)) {
117                 case Types.TINYINT:
118                 case Types.SMALLINT:
119                 case Types.BIGINT:
120                 case Types.INTEGER:
121                     appendInteger(table, result, col, node);
122                     break;
123                 case Types.NUMERIC:
124                     // Oracle: either it's got a scale, or it's a DOUBLE PRECISION
125                     if (scale(table, metaData, col) > 0 || precision(table, metaData, col) == 126) {
126                         appendDouble(table, result, col, node);
127                     }
128                     // Oracle: NUMERIC with a precision of 1 indicates it is a boolean
129                     else if (precision(table, metaData, col) == 1) {
130                         appendBoolean(table, result, col, node);
131                     } else {
132                         appendInteger(table, result, col, node);
133                     }
134                     break;
135                 case Types.CHAR:
136                 case Types.NCHAR:
137                 case Types.VARCHAR:
138                 case Types.LONGVARCHAR:
139                 case Types.NVARCHAR:
140                 case Types.LONGNVARCHAR:
141                     final String s = getString(table, result, col);
142                     RowDataNode.append(node, wasNull(table, result) ? null : s);
143                     break;
144 
145                 case Types.BOOLEAN:
146                 case Types.BIT:
147                     appendBoolean(table, result, col, node);
148                     break;
149 
150                 case Types.DOUBLE:
151                 case Types.DECIMAL:
152                     appendDouble(table, result, col, node);
153                     break;
154 
155                 case Types.TIMESTAMP:
156                     final Timestamp t = getTimestamp(table, result, col);
157                     RowDataNode.append(node, wasNull(table, result) ? null : t);
158                     break;
159 
160                 case Types.CLOB:
161                 case Types.NCLOB:
162                     final String c = getClobAsString(table, result, col);
163                     RowDataNode.append(node, wasNull(table, result) ? null : c);
164                     break;
165 
166                 case Types.BLOB:
167                 case Types.BINARY:
168                 case Types.VARBINARY:
169                 case Types.LONGVARBINARY:
170                     final byte[] b = getBinary(table, result, col);
171                     RowDataNode.append(node, wasNull(table, result) ? null : b);
172                     break;
173 
174                 default:
175                     throw errorService.newImportExportException(table, String.format(
176                             "Cannot encode value for unsupported column type: \"%s\" (%d) of column %s.%s",
177                             columnTypeName(table, metaData, col),
178                             columnType(table, metaData, col),
179                             table,
180                             columnName(table, metaData, col)));
181             }
182         }
183 
184         monitor.end(Task.TABLE_ROW);
185         return node.closeEntity();
186     }
187 
188     private void appendBoolean(String table, ResultSet result, int col, NodeCreator node) {
189         final boolean b = getBoolean(table, result, col);
190         RowDataNode.append(node, wasNull(table, result) ? null : b);
191     }
192 
193     private void appendInteger(String table, ResultSet result, int col, NodeCreator node) {
194         final BigDecimal bd = getBigDecimal(table, result, col);
195         RowDataNode.append(node, wasNull(table, result) ? null : bd.toBigInteger());
196     }
197 
198     private void appendDouble(String table, ResultSet result, int col, NodeCreator node) {
199         final double d = getDouble(table, result, col);
200         RowDataNode.append(node, wasNull(table, result) ? null : BigDecimal.valueOf(d));
201     }
202 
203     private NodeCreator writeColumnDefinitions(String table, NodeCreator node, ResultSetMetaData metaData, EntityNameProcessor entityNameProcessor) {
204         for (int i = 1; i <= columnCount(table, metaData); i++) {
205             final String columnName = entityNameProcessor.columnName(columnName(table, metaData, i));
206             ColumnDataNode.add(node, columnName).closeEntity();
207         }
208         return node;
209     }
210 
211     private ResultSetMetaData resultSetMetaData(String table, ResultSet result) {
212         try {
213             return result.getMetaData();
214         } catch (SQLException e) {
215             throw errorService.newImportExportSqlException(table, "Could not get result set metadata", e);
216         }
217     }
218 
219     private int scale(String table, ResultSetMetaData metaData, int col) {
220         try {
221             return metaData.getScale(col);
222         } catch (SQLException e) {
223             throw errorService.newImportExportSqlException(table, "Could not get scale for col #" + col + " from result set meta data", e);
224         }
225     }
226 
227     private int precision(String table, ResultSetMetaData metaData, int col) {
228         try {
229             return metaData.getPrecision(col);
230         } catch (SQLException e) {
231             throw errorService.newImportExportSqlException(table, "Could not get scale for col #" + col + " from result set meta data", e);
232         }
233     }
234 
235     private int columnCount(String table, ResultSetMetaData metaData) {
236         try {
237             return metaData.getColumnCount();
238         } catch (SQLException e) {
239             throw errorService.newImportExportSqlException(table, "Could not get column count from result set metadata", e);
240         }
241     }
242 
243     private int columnType(String table, ResultSetMetaData metaData, int col) {
244         try {
245             return metaData.getColumnType(col);
246         } catch (SQLException e) {
247             throw errorService.newImportExportSqlException(table, "Could not get column type for col #" + col + " from result set meta data", e);
248         }
249     }
250 
251     private String columnTypeName(String table, ResultSetMetaData metaData, int col) {
252         try {
253             return metaData.getColumnTypeName(col);
254         } catch (SQLException e) {
255             throw errorService.newImportExportSqlException(table, "Could not get column type name for col #" + col + " from result set meta data", e);
256         }
257     }
258 
259     private String columnName(String table, ResultSetMetaData metaData, int i) {
260         try {
261             return metaData.getColumnName(i);
262         } catch (SQLException e) {
263             throw errorService.newImportExportSqlException(table, "Could not get column #" + i + " name from result set meta data", e);
264         }
265     }
266 
267     private String getString(String table, ResultSet result, int col) {
268         try {
269             return result.getString(col);
270         } catch (SQLException e) {
271             throw errorService.newImportExportSqlException(table, "Could not get string value for col #" + col, e);
272         }
273     }
274 
275     private boolean getBoolean(String table, ResultSet result, int col) {
276         try {
277             return result.getBoolean(col);
278         } catch (SQLException e) {
279             throw errorService.newImportExportSqlException(table, "Could not get boolean value for col #" + col, e);
280         }
281     }
282 
283     private BigDecimal getBigDecimal(String table, ResultSet result, int col) {
284         try {
285             return result.getBigDecimal(col);
286         } catch (SQLException e) {
287             throw errorService.newImportExportSqlException(table, "Could not get big decimal value for col #" + col, e);
288         }
289     }
290 
291     private double getDouble(String table, ResultSet result, int col) {
292         try {
293             return result.getDouble(col);
294         } catch (SQLException e) {
295             throw errorService.newImportExportSqlException(table, "Could not get double value for col #" + col, e);
296         }
297     }
298 
299     private Timestamp getTimestamp(String table, ResultSet result, int col) {
300         try {
301             return result.getTimestamp(col);
302         } catch (SQLException e) {
303             throw errorService.newImportExportSqlException(table, "Could not get timestamp value for col #" + col, e);
304         }
305     }
306 
307     private String getClobAsString(String table, ResultSet result, int col) {
308         try {
309             final Clob clob = result.getClob(col);
310             return clob == null ? null : clob.getSubString(1L, (int) clob.length());
311         } catch (SQLException e) {
312             throw errorService.newImportExportSqlException(table, "Could not get clob value for col #" + col, e);
313         }
314     }
315 
316     private byte[] getBinary(String table, ResultSet result, int col) {
317         try {
318             return result.getBytes(col);
319         } catch (SQLException e) {
320             throw errorService.newImportExportSqlException(table, "Could not get binary value for col #" + col, e);
321         }
322     }
323 
324     private boolean wasNull(String table, ResultSet result) {
325         try {
326             return result.wasNull();
327         } catch (SQLException e) {
328             throw errorService.newImportExportSqlException(table, "Could not figure out whether value was NULL", e);
329         }
330     }
331 
332     private boolean next(String table, ResultSet result) {
333         try {
334             return result.next();
335         } catch (SQLException e) {
336             throw errorService.newImportExportSqlException(table, "Could not get next for result set", e);
337         }
338     }
339 
340     private ResultSet getTablesResultSet(Connection connection) {
341         try {
342             return metadata(errorService, connection).getTables(null, schema, "%", new String[]{"TABLE"});
343         } catch (SQLException e) {
344             throw errorService.newImportExportSqlException(null, "Could not read tables in data exporter", e);
345         }
346     }
347 
348     private ResultSet executeQueryWithFetchSize(String table, Statement statement, String sql, int fetchSize) {
349         try {
350             statement.setFetchSize(fetchSize);
351             return statement.executeQuery(sql);
352         } catch (SQLException e) {
353             throw errorService.newImportExportSqlException(table, "Could not execute query '" + sql + "' with fetch size " + fetchSize, e);
354         }
355     }
356 
357     private static boolean isBlank(String str) {
358         int strLen;
359         if (str == null || (strLen = str.length()) == 0) {
360             return true;
361         }
362         for (int i = 0; i < strLen; i++) {
363             if (!Character.isWhitespace(str.charAt(i))) {
364                 return false;
365             }
366         }
367         return true;
368     }
369 }