1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 package org.dbunit.dataset.xml;
22
23 import java.io.IOException;
24 import java.io.StringReader;
25 import java.util.ArrayList;
26 import java.util.Iterator;
27 import java.util.List;
28
29 import javax.xml.parsers.ParserConfigurationException;
30 import javax.xml.parsers.SAXParserFactory;
31
32 import org.dbunit.dataset.Column;
33 import org.dbunit.dataset.DataSetException;
34 import org.dbunit.dataset.DefaultTableMetaData;
35 import org.dbunit.dataset.IDataSet;
36 import org.dbunit.dataset.ITableMetaData;
37 import org.dbunit.dataset.NoSuchColumnException;
38 import org.dbunit.dataset.OrderedTableNameMap;
39 import org.dbunit.dataset.datatype.DataType;
40 import org.dbunit.dataset.stream.BufferedConsumer;
41 import org.dbunit.dataset.stream.DefaultConsumer;
42 import org.dbunit.dataset.stream.IDataSetConsumer;
43 import org.dbunit.dataset.stream.IDataSetProducer;
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
46 import org.xml.sax.Attributes;
47 import org.xml.sax.ContentHandler;
48 import org.xml.sax.EntityResolver;
49 import org.xml.sax.InputSource;
50 import org.xml.sax.SAXException;
51 import org.xml.sax.SAXParseException;
52 import org.xml.sax.XMLReader;
53 import org.xml.sax.helpers.DefaultHandler;
54
55
56
57
58
59
60
61 public class FlatXmlProducer extends DefaultHandler implements IDataSetProducer, ContentHandler
62 {
63
64
65
66
67 private static final Logger logger = LoggerFactory.getLogger(FlatXmlProducer.class);
68
69 private static final IDataSetConsumer EMPTY_CONSUMER = new DefaultConsumer();
70 private static final String DATASET = "dataset";
71
72 private final InputSource _inputSource;
73 private final EntityResolver _resolver;
74 private boolean _validating = false;
75
76
77
78
79 private IDataSet _metaDataSet;
80
81
82
83
84 private FlatDtdHandler _dtdHandler;
85
86
87
88
89 private int _lineNumber = 0;
90
91
92
93 private int _lineNumberGlobal = 0;
94
95
96
97
98 private boolean _columnSensing = false;
99 private boolean _caseSensitiveTableNames;
100
101
102
103
104 private IDataSetConsumer _consumer = EMPTY_CONSUMER;
105
106
107
108 private OrderedTableNameMap _orderedTableNameMap;
109
110
111 public FlatXmlProducer(InputSource xmlSource)
112 {
113 this(xmlSource, true);
114 }
115
116 public FlatXmlProducer(InputSource xmlSource, boolean dtdMetadata)
117 {
118 this(xmlSource, dtdMetadata, false);
119 }
120
121 public FlatXmlProducer(InputSource xmlSource, IDataSet metaDataSet)
122 {
123 _inputSource = xmlSource;
124 _metaDataSet = metaDataSet;
125 _resolver = this;
126 _caseSensitiveTableNames = metaDataSet.isCaseSensitiveTableNames();
127 initialize(false);
128 }
129
130 public FlatXmlProducer(InputSource xmlSource, EntityResolver resolver)
131 {
132 _inputSource = xmlSource;
133 _resolver = resolver;
134 initialize(true);
135 }
136
137
138
139
140
141
142 public FlatXmlProducer(InputSource xmlSource, boolean dtdMetadata, boolean columnSensing)
143 {
144 this(xmlSource, dtdMetadata, columnSensing, false);
145 }
146
147
148
149
150
151
152
153
154 public FlatXmlProducer(InputSource xmlSource, boolean dtdMetadata, boolean columnSensing, boolean caseSensitiveTableNames)
155 {
156 _inputSource = xmlSource;
157 _columnSensing = columnSensing;
158 _caseSensitiveTableNames = caseSensitiveTableNames;
159 _resolver = this;
160 initialize(dtdMetadata);
161 }
162
163
164
165 private void initialize(boolean dtdMetadata)
166 {
167 if (dtdMetadata)
168 {
169 this._dtdHandler = new FlatDtdHandler(this);
170 }
171 }
172
173
174
175
176
177 public boolean isCaseSensitiveTableNames() {
178 return _caseSensitiveTableNames;
179 }
180
181 private ITableMetaData createTableMetaData(String tableName, Attributes attributes) throws DataSetException
182 {
183 if (logger.isDebugEnabled())
184 logger.debug("createTableMetaData(tableName={}, attributes={}) - start", tableName, attributes);
185
186
187 if (_metaDataSet != null)
188 {
189 return _metaDataSet.getTableMetaData(tableName);
190 }
191
192
193 Column[] columns = new Column[attributes.getLength()];
194 for (int i = 0; i < attributes.getLength(); i++)
195 {
196 columns[i] = new Column(attributes.getQName(i), DataType.UNKNOWN);
197 }
198
199 return new DefaultTableMetaData(tableName, columns);
200 }
201
202
203
204
205
206
207
208
209 private ITableMetaData mergeTableMetaData(List columnsToMerge, ITableMetaData originalMetaData) throws DataSetException
210 {
211 Column[] columns = new Column[originalMetaData.getColumns().length + columnsToMerge.size()];
212 System.arraycopy(originalMetaData.getColumns(), 0, columns, 0, originalMetaData.getColumns().length);
213
214 for (int i = 0; i < columnsToMerge.size(); i++)
215 {
216 Column column = (Column)columnsToMerge.get(i);
217 columns[columns.length - columnsToMerge.size() + i] = column;
218 }
219
220 return new DefaultTableMetaData(originalMetaData.getTableName(), columns);
221 }
222
223
224
225
226
227 private ITableMetaData getActiveMetaData()
228 {
229 if(_orderedTableNameMap != null)
230 {
231 String lastTableName = _orderedTableNameMap.getLastTableName();
232 if(lastTableName != null)
233 {
234 return (ITableMetaData) _orderedTableNameMap.get(lastTableName);
235 }
236 else
237 {
238 return null;
239 }
240 }
241 else
242 {
243 return null;
244 }
245
246 }
247
248
249
250
251
252
253 private boolean isNewTable(String tableName)
254 {
255 return !_orderedTableNameMap.isLastTable(tableName);
256 }
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274 protected void handleMissingColumns(Attributes attributes)
275 throws DataSetException
276 {
277 List columnsToMerge = new ArrayList();
278
279 ITableMetaData activeMetaData = getActiveMetaData();
280
281 int attributeLength = attributes.getLength();
282 for (int i = 0 ; i < attributeLength; i++)
283 {
284 try {
285 activeMetaData.getColumnIndex(attributes.getQName(i));
286 }
287 catch (NoSuchColumnException e) {
288 columnsToMerge.add(new Column(attributes.getQName(i), DataType.UNKNOWN));
289 }
290 }
291
292 if (!columnsToMerge.isEmpty())
293 {
294 if (_columnSensing)
295 {
296 logger.debug("Column sensing enabled. Will create a new metaData with potentially new columns if needed");
297 activeMetaData = mergeTableMetaData(columnsToMerge, activeMetaData);
298 _orderedTableNameMap.update(activeMetaData.getTableName(), activeMetaData);
299
300 _consumer.startTable(activeMetaData);
301 }
302 else
303 {
304 final StringBuilder extraColumnNames = new StringBuilder();
305 for (Iterator i = columnsToMerge.iterator(); i.hasNext();) {
306 Column col = (Column) i.next();
307 extraColumnNames.append(extraColumnNames.length() > 0 ? "," : "").append(col.getColumnName());
308 }
309 String msg = "Extra columns (" + extraColumnNames.toString() + ") on line " + (_lineNumber + 1)
310 + " for table " + activeMetaData.getTableName() + " (global line number is "
311 + _lineNumberGlobal + "). Those columns will be ignored.";
312 msg += "\n\tPlease add the extra columns to line 1,"
313 + " or use a DTD to make sure the value of those columns are populated"
314 + " or specify 'columnSensing=true' for your FlatXmlProducer.";
315 msg += "\n\tSee FAQ for more details.";
316 logger.warn(msg);
317 }
318 }
319 }
320
321 public void setColumnSensing(boolean columnSensing)
322 {
323 _columnSensing = columnSensing;
324 }
325
326 public void setValidating(boolean validating)
327 {
328 _validating = validating;
329 }
330
331
332
333
334 public void setConsumer(IDataSetConsumer consumer) throws DataSetException
335 {
336 logger.debug("setConsumer(consumer) - start");
337
338 if(this._columnSensing) {
339 _consumer = new BufferedConsumer(consumer);
340 }
341 else {
342 _consumer = consumer;
343 }
344 }
345
346 public void produce() throws DataSetException
347 {
348 logger.debug("produce() - start");
349
350 try
351 {
352 SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
353 saxParserFactory.setValidating(_validating);
354 XMLReader xmlReader = saxParserFactory.newSAXParser().getXMLReader();
355
356 if(_dtdHandler != null)
357 {
358 FlatDtdHandler.setLexicalHandler(xmlReader, _dtdHandler);
359 FlatDtdHandler.setDeclHandler(xmlReader, _dtdHandler);
360 }
361
362 xmlReader.setContentHandler(this);
363 xmlReader.setErrorHandler(this);
364 xmlReader.setEntityResolver(_resolver);
365 xmlReader.parse(_inputSource);
366 }
367 catch (ParserConfigurationException e)
368 {
369 throw new DataSetException(e);
370 }
371 catch (SAXException e)
372 {
373 DataSetException exceptionToRethrow = XmlProducer.buildException(e);
374 throw exceptionToRethrow;
375 }
376 catch (IOException e)
377 {
378 throw new DataSetException(e);
379 }
380 }
381
382
383
384
385 public InputSource resolveEntity(String publicId, String systemId)
386 throws SAXException
387 {
388 logger.debug("resolveEntity(publicId={}, systemId={}) - start", publicId, systemId);
389
390
391 if (_dtdHandler == null || !_dtdHandler.isDtdPresent())
392 {
393 return new InputSource(new StringReader(""));
394 }
395 return null;
396 }
397
398
399
400
401 public void error(SAXParseException e) throws SAXException
402 {
403 throw e;
404
405 }
406
407
408
409
410 public void startElement(String uri, String localName, String qName,
411 Attributes attributes) throws SAXException
412 {
413 if (logger.isDebugEnabled())
414 logger.debug("startElement(uri={}, localName={}, qName={}, attributes={}) - start",
415 new Object[] { uri, localName, qName, attributes });
416
417 try
418 {
419 ITableMetaData activeMetaData = getActiveMetaData();
420
421 if (activeMetaData == null && qName.equals(DATASET))
422 {
423 _consumer.startDataSet();
424 _orderedTableNameMap = new OrderedTableNameMap(_caseSensitiveTableNames);
425 return;
426 }
427
428
429 if (isNewTable(qName))
430 {
431
432 if (activeMetaData != null)
433 {
434 _consumer.endTable();
435 }
436
437
438 if(_orderedTableNameMap.containsTable(qName))
439 {
440 activeMetaData = (ITableMetaData)_orderedTableNameMap.get(qName);
441 _orderedTableNameMap.setLastTable(qName);
442 }
443 else
444 {
445 activeMetaData = createTableMetaData(qName, attributes);
446 _orderedTableNameMap.add(activeMetaData.getTableName(), activeMetaData);
447 }
448
449
450 _consumer.startTable(activeMetaData);
451 _lineNumber = 0;
452 }
453
454
455 int attributesLength = attributes.getLength();
456 if (attributesLength > 0)
457 {
458
459 if (_dtdHandler == null || !_dtdHandler.isDtdPresent())
460 {
461 handleMissingColumns(attributes);
462
463 activeMetaData = getActiveMetaData();
464 }
465
466 _lineNumber++;
467 _lineNumberGlobal++;
468 Column[] columns = activeMetaData.getColumns();
469 Object[] rowValues = new Object[columns.length];
470 for (int i = 0; i < attributesLength; i++)
471 {
472 determineAndSetRowValue(attributes, activeMetaData,
473 rowValues, i);
474 }
475 _consumer.row(rowValues);
476 }
477 }
478 catch (DataSetException e)
479 {
480 throw new SAXException(e);
481 }
482 }
483
484 protected void determineAndSetRowValue(Attributes attributes,
485 ITableMetaData activeMetaData, Object[] rowValues, int i)
486 throws DataSetException, NoSuchColumnException
487 {
488 String attributeQName = attributes.getQName(i);
489 String attributeValue = attributes.getValue(i);
490 try
491 {
492 int colIndex = activeMetaData.getColumnIndex(attributeQName);
493 rowValues[colIndex] = attributeValue;
494 }
495 catch (NoSuchColumnException e)
496 {
497
498
499
500 if (_columnSensing)
501 {
502 throw e;
503 }
504 }
505 }
506
507 public void endElement(String uri, String localName, String qName) throws SAXException
508 {
509 if (logger.isDebugEnabled())
510 logger.debug("endElement(uri={}, localName={}, qName={}) - start",
511 new Object[]{ uri, localName, qName });
512
513
514 if (qName.equals(DATASET))
515 {
516 try
517 {
518
519 if (getActiveMetaData() != null)
520 {
521 _consumer.endTable();
522 }
523
524
525 _consumer.endDataSet();
526 }
527 catch (DataSetException e)
528 {
529 throw new SAXException(e);
530 }
531 }
532 }
533
534 private static class FlatDtdHandler extends FlatDtdProducer
535 {
536
537
538
539 private final Logger logger = LoggerFactory.getLogger(FlatDtdHandler.class);
540
541 private boolean _dtdPresent = false;
542 private FlatXmlProducer xmlProducer;
543
544 public FlatDtdHandler(FlatXmlProducer xmlProducer)
545 {
546 this.xmlProducer = xmlProducer;
547 }
548
549 public boolean isDtdPresent()
550 {
551 return _dtdPresent;
552 }
553
554
555
556
557 public void startDTD(String name, String publicId, String systemId)
558 throws SAXException
559 {
560 if (logger.isDebugEnabled())
561 logger.debug("startDTD(name={}, publicId={}, systemId={}) - start",
562 new Object[] { name, publicId, systemId });
563
564 _dtdPresent = true;
565 try
566 {
567
568 FlatDtdDataSet metaDataSet = new FlatDtdDataSet();
569 this.setConsumer(metaDataSet);
570
571 xmlProducer._metaDataSet = metaDataSet;
572
573 super.startDTD(name, publicId, systemId);
574 }
575 catch (DataSetException e)
576 {
577 throw new SAXException(e);
578 }
579 }
580 }
581 }