001: /*
002: * XmlRowDataConverter.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.sql.Types;
016:
017: import workbench.db.TableIdentifier;
018: import workbench.db.WbConnection;
019: import workbench.db.report.ReportColumn;
020: import workbench.db.report.ReportTable;
021: import workbench.db.report.TagWriter;
022: import workbench.resource.Settings;
023: import workbench.storage.RowData;
024: import workbench.util.SqlUtil;
025: import workbench.util.StrBuffer;
026: import workbench.util.StringUtil;
027:
028: /**
029: * Convert row data to our own XML format.
030: *
031: * @author support@sql-workbench.net
032: */
033: public class XmlRowDataConverter extends RowDataConverter {
034: public static final String LONG_ROW_TAG = "row-data";
035: public static final String LONG_COLUMN_TAG = "column-data";
036: public static final String SHORT_ROW_TAG = "rd";
037: public static final String SHORT_COLUMN_TAG = "cd";
038: public static final String COLUMN_DEF_TAG = ReportColumn.TAG_COLUMN_DEFINITION;
039: public static final String JAVA_CLASS_TAG = ReportColumn.TAG_COLUMN_JAVA_CLASS;
040: public static final String JAVA_TYPE_TAG = ReportColumn.TAG_COLUMN_JAVA_TYPE;
041: public static final String DBMS_TYPE_TAG = ReportColumn.TAG_COLUMN_DBMS_TYPE;
042: public static final String DATA_FORMAT_TAG = "data-format";
043: public static final String TABLE_NAME_TAG = ReportTable.TAG_TABLE_NAME;
044: public static final String TABLE_DEF_TAG = ReportTable.TAG_TABLE_DEF;
045: public static final String COLUMN_COUNT_TAG = "column-count";
046: public static final String COLUMN_NAME_TAG = ReportColumn.TAG_COLUMN_NAME;
047: public static final String ATTR_LONGVALUE = "longValue";
048: public static final String ATTR_NULL = "null";
049: public static final String ATTR_DATA_FILE = "dataFile";
050: public static final String KEY_FORMAT_LONG = "long";
051: public static final String KEY_FORMAT_SHORT = "short";
052: public static final String TAG_TAG_FORMAT = "wb-tag-format";
053:
054: private boolean useCData = false;
055: private boolean verboseFormat = true;
056: private String lineEnding = "\n";
057: private String coltag = LONG_COLUMN_TAG;
058: private String rowtag = LONG_ROW_TAG;
059: private String numAttrib = "row-num";
060: private String startColTag = " <" + coltag + " index=\"";
061: private String closeColTag = "</" + coltag + ">";
062: private String closeRowTag = "</" + rowtag + ">";
063: private String tableToUse = null;
064: private StrBuffer dbInfo;
065: private boolean writeClobFiles = false;
066: private boolean addColName = false;
067:
068: public XmlRowDataConverter() {
069: super ();
070: this .addColName = Settings.getInstance().getBoolProperty(
071: "workbench.export.xml.verbose.includecolname", false);
072: }
073:
074: public void setTableNameToUse(String name) {
075: this .tableToUse = name;
076: }
077:
078: public void setWriteClobToFile(boolean flag) {
079: this .writeClobFiles = flag;
080: }
081:
082: public void setOriginalConnection(WbConnection con) {
083: super .setOriginalConnection(con);
084: // This should be done before running the actual export
085: // in order to avoid concurrent statement execution during export.
086:
087: // getDatabaseInfoAsXml() indirectly runs some statements because
088: // it retrieves user and schema information from the database
089: // This method is called during initialization of the DataExporter
090: // and before the actual export is started.
091: StrBuffer indent = new StrBuffer(" ");
092: this .dbInfo = con.getDatabaseInfoAsXml(indent);
093: }
094:
095: public void setUseVerboseFormat(boolean flag) {
096: this .verboseFormat = flag;
097: if (flag) {
098: coltag = LONG_COLUMN_TAG;
099: rowtag = LONG_ROW_TAG;
100: startColTag = " <" + coltag + " index=\"";
101: } else {
102: coltag = SHORT_COLUMN_TAG;
103: rowtag = SHORT_ROW_TAG;
104: startColTag = "<" + coltag;
105: }
106: closeColTag = "</" + coltag + ">";
107: closeRowTag = "</" + rowtag + ">";
108: }
109:
110: public StrBuffer getStart() {
111: StrBuffer xml = new StrBuffer(250);
112: String enc = this .getEncoding();
113: xml.append("<?xml version=\"1.0\"");
114: if (enc != null)
115: xml.append(" encoding=\"" + enc + "\"");
116: xml.append("?>");
117: xml.append(this .lineEnding);
118: xml.append("<wb-export>");
119: xml.append(this .lineEnding);
120: xml.append(this .getMetaDataAsXml(" "));
121: xml.append(this .lineEnding);
122: if (this .verboseFormat)
123: xml.append(" ");
124: xml.append("<data>");
125: xml.append(this .lineEnding);
126: return xml;
127: }
128:
129: public StrBuffer getEnd(long totalRows) {
130: StrBuffer xml = new StrBuffer(100);
131: if (this .verboseFormat)
132: xml.append(" ");
133: xml.append("</data>");
134: xml.append(this .lineEnding);
135: xml.append("</wb-export>");
136: xml.append(this .lineEnding);
137: return xml;
138: }
139:
140: public void setUseCDATA(boolean flag) {
141: this .useCData = flag;
142: }
143:
144: public void setLineEnding(String ending) {
145: if (ending != null)
146: this .lineEnding = ending;
147: }
148:
149: public StrBuffer convertRowData(RowData row, long rowIndex) {
150: TagWriter tagWriter = new TagWriter();
151: StrBuffer indent = new StrBuffer(" ");
152: int colCount = this .metaData.getColumnCount();
153: StrBuffer xml = new StrBuffer(colCount * 100);
154:
155: if (this .verboseFormat) {
156: tagWriter.appendOpenTag(xml, indent, rowtag, numAttrib,
157: Long.toString(rowIndex + 1));
158: } else {
159: tagWriter.appendOpenTag(xml, null, rowtag);
160: }
161:
162: if (verboseFormat)
163: xml.append(this .lineEnding);
164:
165: for (int c = 0; c < colCount; c++) {
166: if (!this .includeColumnInExport(c))
167: continue;
168: Object data = row.getValue(c);
169: int type = this .metaData.getColumnType(c);
170: boolean isNull = (data == null);
171: boolean writeCloseTag = true;
172:
173: boolean externalFile = false;
174:
175: if (this .verboseFormat)
176: xml.append(indent);
177: xml.append(startColTag);
178: if (this .verboseFormat) {
179: xml.append(c);
180: xml.append('"');
181: if (addColName) {
182: xml.append(" name=\"" + metaData.getColumnName(c)
183: + "\"");
184: }
185: }
186:
187: if (isNull) {
188: xml.append(" null=\"true\"/");
189: writeCloseTag = false;
190: } else {
191: if (SqlUtil.isDateType(type)) {
192: java.util.Date d = (java.util.Date) data;
193: xml.append(" longValue=\"");
194: xml.append(Long.toString(d.getTime()));
195: xml.append('"');
196: } else if (writeClobFiles
197: && SqlUtil.isClobType(type, originalConnection
198: .getDbSettings())) {
199: externalFile = true;
200: File clobFile = createBlobFile(row, c, rowIndex);
201: String dataFile = clobFile.getName();
202: try {
203: writeClobFile((String) data, clobFile,
204: this .encoding);
205: } catch (Exception e) {
206: throw new RuntimeException(
207: "Error writing CLOB file", e);
208: }
209: xml.append(' ');
210: xml.append(ATTR_DATA_FILE);
211: xml.append("=\"");
212: xml.append(dataFile);
213: xml.append("\"/");
214: writeCloseTag = false;
215: } else if (SqlUtil.isBlobType(type)) {
216: externalFile = true;
217: File blobFile = createBlobFile(row, c, rowIndex);
218: String dataFile = blobFile.getName();
219: try {
220: writeBlobFile(data, blobFile);
221: } catch (Exception e) {
222: throw new RuntimeException(
223: "Error writing BLOB file", e);
224: }
225: xml.append(' ');
226: xml.append(ATTR_DATA_FILE);
227: xml.append("=\"");
228: xml.append(dataFile);
229: xml.append("\"/");
230: writeCloseTag = false;
231: }
232: }
233: xml.append('>');
234:
235: // Only write the tag content if the value is not null (we have already closed the tag)
236: // or if the value is not a blob value (which has already been written!)
237: if (!isNull && !externalFile) {
238: // String data needs to be escaped!
239: if (SqlUtil.isCharacterType(type)) {
240: if (this .useCData) {
241: xml.append(TagWriter.CDATA_START);
242: xml.append(this .getValueAsFormattedString(row,
243: c));
244: xml.append(TagWriter.CDATA_END);
245: } else {
246: writeEscapedXML(xml, this
247: .getValueAsFormattedString(row, c),
248: true);
249: }
250: } else {
251: xml.append(this .getValueAsFormattedString(row, c));
252: }
253: }
254: if (writeCloseTag)
255: xml.append(closeColTag);
256: if (this .verboseFormat)
257: xml.append(this .lineEnding);
258: }
259: if (this .verboseFormat)
260: xml.append(indent);
261: xml.append(closeRowTag);
262: xml.append(this .lineEnding);
263: return xml;
264: }
265:
266: private StrBuffer getMetaDataAsXml(String anIndent) {
267: TagWriter tagWriter = new TagWriter();
268: StrBuffer indent = new StrBuffer(anIndent);
269: StrBuffer indent2 = new StrBuffer(anIndent);
270: indent2.append(" ");
271:
272: int colCount = this .metaData.getColumnCount();
273: StrBuffer result = new StrBuffer(colCount * 50);
274: tagWriter.appendOpenTag(result, indent, "meta-data");
275: result.append(this .lineEnding);
276:
277: if (this .generatingSql != null) {
278: result.append(this .lineEnding);
279: tagWriter.appendOpenTag(result, indent2, "generating-sql");
280: result.append(this .lineEnding);
281: result.append(indent2);
282: result.append("<![CDATA[");
283: result.append(this .lineEnding);
284: result.append(indent2);
285: result.append(this .generatingSql);
286: result.append(this .lineEnding);
287: result.append(indent2);
288: result.append("]]>");
289: result.append(this .lineEnding);
290: tagWriter.appendCloseTag(result, indent2, "generating-sql");
291: result.append(this .lineEnding);
292: }
293:
294: if (this .dbInfo != null) {
295: result.append(this .dbInfo);
296: } else if (this .originalConnection != null) {
297: result.append(this .originalConnection
298: .getDatabaseInfoAsXml(indent2));
299: }
300:
301: //result.append(this.lineEnding);
302: result.append(indent2);
303: result.append('<');
304: result.append(TAG_TAG_FORMAT);
305: result.append('>');
306: result.append(this .verboseFormat ? KEY_FORMAT_LONG
307: : KEY_FORMAT_SHORT);
308: result.append("</");
309: result.append(TAG_TAG_FORMAT);
310: result.append('>');
311: result.append(this .lineEnding);
312: result.append(indent);
313: result.append("</meta-data>");
314: result.append(this .lineEnding);
315: result.append(this .lineEnding);
316:
317: result.append(indent);
318: result.append('<');
319: result.append(TABLE_DEF_TAG);
320: result.append('>');
321: result.append(this .lineEnding);
322:
323: result.append(indent);
324: result
325: .append(" <!-- The following information was retrieved from the JDBC driver's ResultSetMetaData -->");
326: result.append(this .lineEnding);
327:
328: result.append(indent);
329: result
330: .append(" <!-- column-name is retrieved from ResultSetMetaData.getColumnName() -->");
331: result.append(this .lineEnding);
332:
333: result.append(indent);
334: result
335: .append(" <!-- java-class is retrieved from ResultSetMetaData.getColumnClassName() -->");
336: result.append(this .lineEnding);
337:
338: result.append(indent);
339: result
340: .append(" <!-- java-sql-type-name is the constant's name from java.sql.Types -->");
341: result.append(this .lineEnding);
342:
343: result.append(indent);
344: result
345: .append(" <!-- java-sql-type is the constant's numeric value from java.sql.Types as returned from ResultSetMetaData.getColumnType() -->");
346: result.append(this .lineEnding);
347:
348: result.append(indent);
349: result
350: .append(" <!-- dbms-data-type is retrieved from ResultSetMetaData.getColumnTypeName() -->");
351: result.append(this .lineEnding);
352:
353: result.append(this .lineEnding);
354: result.append(indent);
355: result
356: .append(" <!-- For date and timestamp types, the internal long value obtained from java.util.Date.getTime()");
357: result.append(this .lineEnding);
358: result.append(indent);
359: result
360: .append(" is written as an attribute to the <column-data> tag. That value can be used");
361: result.append(this .lineEnding);
362: result.append(indent);
363: result
364: .append(" to create a java.util.Date() object directly, without the need to parse the actual tag content.");
365: result.append(this .lineEnding);
366: result.append(indent);
367: result
368: .append(" If Java is not used to parse this file, the date/time format used to write the data");
369: result.append(this .lineEnding);
370: result.append(indent);
371: result
372: .append(" is provided in the <data-format> tag of the column definition");
373: result.append(this .lineEnding);
374: result.append(indent);
375: result.append(" -->");
376: result.append(this .lineEnding);
377: result.append(this .lineEnding);
378:
379: result.append(indent);
380: result.append(" <");
381: result.append(TABLE_NAME_TAG);
382: result.append('>');
383: if (this .tableToUse != null) {
384: result.append(tableToUse);
385: } else {
386: TableIdentifier table = this .metaData.getUpdateTable();
387: if (table != null) {
388: result.append(table.getTableName());
389: }
390: }
391: result.append("</");
392: result.append(TABLE_NAME_TAG);
393: result.append(">");
394: result.append(this .lineEnding);
395:
396: result.append(indent);
397: result.append(" <column-count>");
398: result.append(colCount);
399: result.append("</column-count>");
400: result.append(this .lineEnding);
401: result.append(this .lineEnding);
402:
403: for (int i = 0; i < colCount; i++) {
404: if (!this .includeColumnInExport(i))
405: continue;
406: result.append(indent);
407: result.append(" <");
408: result.append(COLUMN_DEF_TAG);
409: result.append(" index=\"");
410: result.append(i);
411: result.append("\">");
412: result.append(this .lineEnding);
413:
414: result.append(indent);
415: appendTag(result, " ", COLUMN_NAME_TAG, StringUtil
416: .trimQuotes(this .metaData.getColumnName(i)));
417:
418: result.append(indent);
419: appendTag(result, " ", JAVA_CLASS_TAG,
420: getReadableClassName(this .metaData
421: .getColumnClassName(i)));
422:
423: result.append(indent);
424: appendTag(result, " ",
425: ReportColumn.TAG_COLUMN_JAVA_TYPE_NAME,
426: SqlUtil.getTypeName(this .metaData.getColumnType(i)));
427:
428: result.append(indent);
429: appendTag(result, " ", JAVA_TYPE_TAG, String
430: .valueOf(this .metaData.getColumnType(i)));
431:
432: result.append(indent);
433: appendTag(result, " ", DBMS_TYPE_TAG, this .metaData
434: .getDbmsTypeName(i));
435:
436: int type = this .metaData.getColumnType(i);
437: if (SqlUtil.isDateType(type)) {
438: if (type == Types.TIMESTAMP) {
439: result.append(indent);
440: result.append(" <data-format>");
441: result
442: .append(defaultTimestampFormatter
443: .toPattern());
444: } else {
445: result.append(indent);
446: result.append(" <data-format>");
447: result.append(defaultDateFormatter.toPattern());
448: }
449: result.append("</data-format>");
450: result.append(this .lineEnding);
451: }
452: result.append(indent);
453: result.append(" </");
454: result.append(COLUMN_DEF_TAG);
455: result.append(">");
456: result.append(this .lineEnding);
457: }
458: result.append(indent);
459: result.append("</");
460: result.append(TABLE_DEF_TAG);
461: result.append(">");
462: result.append(this .lineEnding);
463:
464: return result;
465: }
466:
467: private String getReadableClassName(String cls) {
468: if (cls.charAt(0) != '[')
469: return cls;
470:
471: String displayName = cls;
472: if (cls.charAt(0) == '[') {
473: if (cls.charAt(1) == 'B')
474: displayName = "byte[]";
475: else if (cls.charAt(1) == 'C')
476: displayName = "char[]";
477: else if (cls.charAt(1) == 'I')
478: displayName = "int[]";
479: else if (cls.charAt(1) == 'J')
480: displayName = "long[]";
481: else if (cls.charAt(1) == 'L') {
482: // a "class" starting with [L is a "real" Object not
483: // a native data type, so we'll extract the real class
484: // name, and make that array of that class
485: displayName = cls.substring(2, cls.length() - 1) + "[]";
486: }
487: }
488: return displayName;
489: }
490:
491: private void appendOpenTag(StrBuffer target, String indent,
492: String tag) {
493: target.append(indent);
494: target.append('<');
495: target.append(tag);
496: target.append('>');
497: }
498:
499: private void appendCloseTag(StrBuffer target, String tag) {
500: target.append("</");
501: target.append(tag);
502: target.append('>');
503: }
504:
505: private void appendTag(StrBuffer target, String indent, String tag,
506: String value) {
507: appendOpenTag(target, indent, tag);
508: target.append(value);
509: appendCloseTag(target, tag);
510: target.append(this.lineEnding);
511: }
512:
513: }
|