001: /*
002: * RowDataConverter.java
003: *
004: * This file is part of SQL Workbench/J, http://www.sql-workbench.net
005: *
006: * Copyright 2002-2008, Thomas Kellerer
007: * No part of this code maybe reused without the permission of the author
008: *
009: * To contact the author please send an email to: support@sql-workbench.net
010: *
011: */
012: package workbench.db.exporter;
013:
014: import java.io.File;
015: import java.io.IOException;
016: import java.io.OutputStream;
017: import java.io.Writer;
018: import java.sql.Clob;
019: import java.sql.SQLException;
020: import java.text.DecimalFormat;
021: import java.text.SimpleDateFormat;
022: import java.util.List;
023: import workbench.db.ColumnIdentifier;
024: import workbench.db.WbConnection;
025: import workbench.gui.components.BlobHandler;
026: import workbench.interfaces.DataFileWriter;
027: import workbench.interfaces.ErrorReporter;
028: import workbench.log.LogMgr;
029: import workbench.resource.Settings;
030: import workbench.storage.ColumnData;
031: import workbench.storage.ResultInfo;
032: import workbench.storage.RowData;
033: import workbench.util.DefaultOutputFactory;
034: import workbench.util.EncodingUtil;
035: import workbench.util.ExceptionUtil;
036: import workbench.util.FileUtil;
037: import workbench.util.OutputFactory;
038: import workbench.util.StrBuffer;
039: import workbench.util.StringUtil;
040: import workbench.util.WbFile;
041: import workbench.util.ZipOutputFactory;
042:
043: /**
044: * Interface for classes that can take objects of type {@link RowData}
045: * and convert them to e.g. text, XML, HTML
046: *
047: * @author support@sql-workbench.net
048: */
049: public abstract class RowDataConverter implements DataFileWriter {
050: public static final String BLOB_ARCHIVE_SUFFIX = "_lobs";
051: protected String encoding;
052: protected WbConnection originalConnection;
053: protected String generatingSql;
054: protected ResultInfo metaData;
055: protected boolean writeHeader = true;
056: private File outputFile;
057: private String baseFilename;
058: private String pageTitle;
059: private boolean[] columnsToExport = null;
060: protected List exportColumns = null;
061: protected ErrorReporter errorReporter;
062:
063: protected SimpleDateFormat defaultDateFormatter;
064: protected DecimalFormat defaultNumberFormatter;
065: protected SimpleDateFormat defaultTimestampFormatter;
066: protected boolean needsUpdateTable = false;
067: protected OutputFactory factory;
068: private boolean compressExternalFiles;
069: protected boolean useRowNumForBlobFile = true;
070: protected int[] blobNameCols = null;
071: protected List<String> blobIdColumns = null;
072:
073: protected String filenameColumn = null;
074: protected int filenameColumnIndex = -1;
075:
076: protected long currentRow = -1;
077: protected RowData currentRowData;
078:
079: protected boolean convertDateToTimestamp = false;
080:
081: /**
082: * The metadata for the result set that should be exported
083: */
084: public RowDataConverter() {
085: this .defaultDateFormatter = Settings.getInstance()
086: .getDefaultDateFormatter();
087: this .defaultTimestampFormatter = Settings.getInstance()
088: .getDefaultTimestampFormatter();
089: this .defaultNumberFormatter = Settings.getInstance()
090: .getDefaultDecimalFormatter();
091: }
092:
093: public void setWriteHeader(boolean writeHeader) {
094: this .writeHeader = writeHeader;
095: }
096:
097: public void setPageTitle(String title) {
098: this .pageTitle = title;
099: }
100:
101: public String getPageTitle() {
102: return getPageTitle(null);
103: }
104:
105: public String getPageTitle(String defaultTitle) {
106: if (StringUtil.isEmptyString(pageTitle)) {
107: return defaultTitle;
108: } else {
109: return pageTitle;
110: }
111: }
112:
113: /**
114: * Define the structure of the result to be exported.
115: */
116: public void setResultInfo(ResultInfo meta) {
117: this .metaData = meta;
118: this .useRowNumForBlobFile = true;
119:
120: if (this .blobIdColumns != null) {
121: int count = this .blobIdColumns.size();
122: int found = 0;
123: blobNameCols = new int[count];
124: int nameIndex = 0;
125: for (String col : blobIdColumns) {
126: int index = meta.findColumn(col);
127: blobNameCols[nameIndex] = index;
128: if (index > -1)
129: found++;
130: nameIndex++;
131: }
132: if (found == 0) {
133: this .blobNameCols = null;
134: this .useRowNumForBlobFile = true;
135: } else {
136: this .useRowNumForBlobFile = false;
137: }
138: }
139:
140: if (this .filenameColumn != null) {
141: this .filenameColumnIndex = meta.findColumn(filenameColumn);
142: }
143: }
144:
145: public void setFilenameColumn(String colname) {
146: if (StringUtil.isWhitespaceOrEmpty(colname)) {
147: this .filenameColumn = null;
148: } else {
149: this .filenameColumn = colname.trim();
150: }
151: this .filenameColumnIndex = -1;
152: }
153:
154: public ResultInfo getResultInfo() {
155: return this .metaData;
156: }
157:
158: void setBlobIdColumns(List<String> cols) {
159: blobIdColumns = cols;
160: }
161:
162: public void setOutputFile(File f) {
163: this .outputFile = f;
164: if (f != null) {
165: WbFile wf = new WbFile(f);
166: this .baseFilename = wf.getFileName();
167: }
168: }
169:
170: public void setCompressExternalFiles(boolean flag) {
171: this .compressExternalFiles = flag;
172: }
173:
174: private void initOutputFactory() {
175: if (this .compressExternalFiles) {
176: WbFile f = new WbFile(getOutputFile());
177: String fname = f.getFileName() + BLOB_ARCHIVE_SUFFIX
178: + ".zip";
179: File archive = new File(getBaseDir(), fname);
180: this .factory = new ZipOutputFactory(archive);
181: } else {
182: this .factory = new DefaultOutputFactory();
183: }
184: }
185:
186: public void exportFinished() throws IOException {
187: if (this .factory != null) {
188: this .factory.done();
189: // Make sure this instance of the factory it no re-used
190: // otherwise writting multiple blob archives does not work
191: // when exporting more than one table
192: this .factory = null;
193: }
194: }
195:
196: protected OutputStream createOutputStream(File output)
197: throws IOException {
198: if (this .factory == null)
199: initOutputFactory();
200: return this .factory.createOutputStream(output);
201: }
202:
203: /**
204: * Needed for the SqlLiteralFormatter
205: */
206: public File generateDataFileName(ColumnData data) {
207: StringBuilder fname = new StringBuilder(80);
208: if (this .currentRowData != null && currentRow != -1) {
209: int colIndex = this .metaData.findColumn(data
210: .getIdentifier().getColumnName());
211: return createBlobFile(currentRowData, colIndex, currentRow);
212: } else {
213: fname.append(StringUtil.makeFilename(data.getIdentifier()
214: .getColumnName()));
215: fname.append('_');
216: if (this .currentRow == -1) {
217: fname.append(data.getValue().hashCode());
218: } else {
219: fname.append("row_");
220: fname.append(currentRow);
221: }
222: fname.append(getFileExtension());
223: File f = new File(getBaseDir(), fname.toString());
224: return f;
225: }
226: }
227:
228: protected String getFileExtension() {
229: return Settings.getInstance().getProperty(
230: "workbench.export.default.blob.extension", ".data");
231: }
232:
233: protected String createFilename(RowData row, int colIndex,
234: long rowNum) {
235: String filename = null;
236: if (this .filenameColumnIndex > -1) {
237: Object value = row.getValue(filenameColumnIndex);
238: if (value != null) {
239: //filename = StringUtil.makeFilename(value.toString());
240: filename = value.toString();
241: }
242: }
243: return filename;
244: }
245:
246: public File createBlobFile(RowData row, int colIndex, long rowNum) {
247: String name = createFilename(row, colIndex, rowNum);
248: File f = null;
249: if (name != null) {
250: WbFile wf = new WbFile(name);
251: if (!wf.isAbsolute()) {
252: f = new WbFile(getBaseDir(), name);
253: }
254: } else {
255: StringBuilder fname = new StringBuilder(baseFilename
256: .length() + 25);
257:
258: if (this .factory == null)
259: initOutputFactory();
260:
261: if (!this .factory.isArchive()) {
262: fname.append(baseFilename);
263: fname.append('_');
264: }
265:
266: if (this .useRowNumForBlobFile || this .blobNameCols == null) {
267: fname.append("r");
268: fname.append(rowNum + 1);
269: fname.append("_c");
270: fname.append(colIndex + 1);
271: } else {
272: String col = this .metaData.getColumnName(colIndex);
273: fname.append(StringUtil.makeFilename(col));
274: fname.append("_#");
275: for (int i = 0; i < blobNameCols.length; i++) {
276: int c = blobNameCols[i];
277: if (c > -1) {
278: Object o = row.getValue(c);
279: if (i > 0)
280: fname.append('_');
281: if (o == null) {
282: fname.append("col#");
283: fname.append(i);
284: fname.append("NULL");
285: } else {
286: fname.append(StringUtil.makeFilename(o
287: .toString()));
288: }
289: }
290: }
291: }
292:
293: fname.append(getFileExtension());
294: f = new File(getBaseDir(), fname.toString());
295: }
296:
297: return f;
298: }
299:
300: public void writeClobFile(String value, File f, String encoding)
301: throws IOException {
302: if (value == null)
303: return;
304: Writer w = null;
305: try {
306: OutputStream out = this .createOutputStream(f);
307: w = EncodingUtil.createWriter(out, encoding);
308: w.write(value);
309: } finally {
310: FileUtil.closeQuitely(w);
311: }
312: }
313:
314: public void writeBlobFile(Object value, File f) throws IOException {
315: if (value == null)
316: return;
317:
318: try {
319: OutputStream out = this .createOutputStream(f);
320: BlobHandler.saveBlobToFile(value, out);
321: } catch (IOException io) {
322: LogMgr.logError("TextRowDataConverter.convertRowData",
323: "Error writing BLOB file: " + f.getName(), io);
324: throw io;
325: } catch (SQLException e) {
326: LogMgr.logError("TextRowDataConverter.convertRowData",
327: "Error writing BLOB file", e);
328: throw new IOException(ExceptionUtil.getDisplay(e));
329: }
330: }
331:
332: public File getBaseDir() {
333: if (this .outputFile == null)
334: return new File(".");
335: if (this .outputFile.isAbsolute())
336: return this .outputFile.getParentFile();
337: return new File(".");
338: }
339:
340: protected File getOutputFile() {
341: return this .outputFile;
342: }
343:
344: public void setErrorReporter(ErrorReporter reporter) {
345: this .errorReporter = reporter;
346: }
347:
348: public boolean includeColumnInExport(int col) {
349: if (this .columnsToExport == null)
350: return true;
351: return this .columnsToExport[col];
352: }
353:
354: /**
355: * The connection that was used to generate the source data.
356: */
357: public void setOriginalConnection(WbConnection conn) {
358: this .originalConnection = conn;
359: if (originalConnection != null) {
360: this .convertDateToTimestamp = this .originalConnection
361: .getDbSettings().getConvertDateInExport();
362: }
363: }
364:
365: /**
366: * The SQL statement that was used to generate the data.
367: */
368: public void setGeneratingSql(String sql) {
369: this .generatingSql = sql;
370: }
371:
372: /**
373: * Set the encoding for the output string.
374: * This might not be used by all implemented Converters
375: */
376: public void setEncoding(String enc) {
377: this .encoding = enc;
378: }
379:
380: public String getEncoding() {
381: return this .encoding;
382: }
383:
384: /**
385: * Returns the data for one specific row as a String in the
386: * correct format
387: */
388: public abstract StrBuffer convertRowData(RowData row, long rowIndex);
389:
390: /**
391: * Returns the String sequence needed in before the actual data part.
392: * (might be an empty string)
393: */
394: public abstract StrBuffer getStart();
395:
396: /**
397: * Returns the String sequence needed in before the actual data part.
398: * (might be an empty string)
399: */
400: public abstract StrBuffer getEnd(long totalRows);
401:
402: public boolean needsUpdateTable() {
403: return this .needsUpdateTable;
404: }
405:
406: public void setDefaultTimestampFormatter(SimpleDateFormat formatter) {
407: if (formatter == null)
408: return;
409: this .defaultTimestampFormatter = formatter;
410: }
411:
412: public void setDefaultDateFormatter(SimpleDateFormat formatter) {
413: if (formatter == null)
414: return;
415: this .defaultDateFormatter = formatter;
416: }
417:
418: public void setDefaultNumberFormatter(DecimalFormat formatter) {
419: this .defaultNumberFormatter = formatter;
420: }
421:
422: public void setDefaultDateFormat(String format) {
423: if (StringUtil.isEmptyString(format))
424: return;
425: SimpleDateFormat formatter = new SimpleDateFormat(format);
426: this .setDefaultDateFormatter(formatter);
427: }
428:
429: public void setDefaultTimestampFormat(String format) {
430: if (StringUtil.isEmptyString(format))
431: return;
432: SimpleDateFormat formatter = new SimpleDateFormat(format);
433: this .setDefaultTimestampFormatter(formatter);
434: }
435:
436: public void setDefaultNumberFormat(String aFormat) {
437: if (aFormat == null)
438: return;
439: try {
440: this .defaultNumberFormatter = new DecimalFormat(aFormat);
441: } catch (Exception e) {
442: this .defaultNumberFormatter = null;
443: LogMgr.logWarning(
444: "RowDataConverter.setDefaultDateFormat()",
445: "Could not create decimal formatter for format "
446: + aFormat);
447: }
448: }
449:
450: /**
451: * Set a list of columns that should be exported
452: * @param columns the list (of ColumnIdentifier objects) of columns to be exported.
453: * null means export all columns
454: */
455: public void setColumnsToExport(List<ColumnIdentifier> columns) {
456: this .exportColumns = columns;
457: if (columns == null) {
458: this .columnsToExport = null;
459: return;
460: }
461: int colCount = this .metaData.getColumnCount();
462: if (this .columnsToExport == null) {
463: this .columnsToExport = new boolean[colCount];
464: }
465: for (int i = 0; i < colCount; i++) {
466: this .columnsToExport[i] = columns.contains(this .metaData
467: .getColumn(i));
468: }
469: }
470:
471: /**
472: * Return the column's value as a formatted String.
473: * Especially for Date objects this is different then getValueAsString()
474: * as a default formatter can be defined.
475: * @param row The requested row
476: * @param col The column in aRow for which the value should be formatted
477: * @return The formatted value as a String
478: * @see #setDefaultDateFormatter(SimpleDateFormat)
479: * @see #setDefaultTimestampFormatter(SimpleDateFormat)
480: * @see #setDefaultNumberFormatter(DecimalFormat)
481: * @see #setDefaultDateFormat(String)
482: * @see #setDefaultTimestampFormat(String)
483: * @see #setDefaultNumberFormat(String)
484: */
485: public String getValueAsFormattedString(RowData row, int col)
486: throws IndexOutOfBoundsException {
487: Object value = row.getValue(col);
488: if (value == null) {
489: return null;
490: } else {
491: String result = null;
492: if (value instanceof java.sql.Timestamp
493: && this .defaultTimestampFormatter != null) {
494: result = this .defaultTimestampFormatter.format(value);
495: } else if (convertDateToTimestamp
496: && value instanceof java.util.Date) {
497: // sometimes the Oracle driver create a java.util.Date object, but
498: // DATE columns in Oracle do contain a time part and thus we need to
499: // format it correctly.
500: // Newer Oracle drivers (>= 10) support a property to treat
501: // DATE columns as Timestamp but for backward compatibility I'll leave
502: // this fix in here.
503: if (this .defaultTimestampFormatter == null) {
504: result = StringUtil.ISO_TIMESTAMP_FORMATTER
505: .format(value);
506: } else {
507: result = this .defaultTimestampFormatter
508: .format(value);
509: }
510: } else if (value instanceof java.util.Date
511: && this .defaultDateFormatter != null) {
512: result = this .defaultDateFormatter.format(value);
513: } else if (value instanceof Number
514: && this .defaultNumberFormatter != null) {
515: result = this .defaultNumberFormatter.format(value);
516: } else if (value instanceof Clob) {
517: try {
518: Clob lob = (Clob) value;
519: long len = lob.length();
520: return lob.getSubString(1, (int) len);
521: } catch (SQLException e) {
522: return "";
523: }
524: } else {
525: result = value.toString();
526: }
527: return result;
528: }
529: }
530:
531: protected void writeEscapedXML(StrBuffer out, String s,
532: boolean keepCR) {
533: if (s == null)
534: return;
535: for (int i = 0; i < s.length(); i++) {
536: char c = s.charAt(i);
537:
538: switch (c) {
539: case '&':
540: out.append("&");
541: break;
542: case '<':
543: out.append("<");
544: break;
545: case '>':
546: out.append(">");
547: break;
548: case '\r':
549: if (keepCR)
550: out.append(" ");
551: break;
552: case '\n':
553: out.append(" ");
554: break;
555: case (char) 0:
556: // ignore zero-byte
557: break;
558: default:
559: out.append(c);
560: }
561: }
562: }
563: }
|