org.unitils.dbunit.util.MultiSchemaXmlDataSetReader Maven / Gradle / Ivy
The newest version!
/*
* Copyright 2008, Unitils.org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.unitils.dbunit.util;
import static org.apache.commons.lang.StringUtils.isEmpty;
import static org.dbunit.dataset.ITable.NO_VALUE;
import static org.dbunit.dataset.datatype.DataType.UNKNOWN;
import static org.unitils.thirdparty.org.apache.commons.io.IOUtils.closeQuietly;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.xml.parsers.SAXParserFactory;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dbunit.dataset.Column;
import org.dbunit.dataset.ReplacementDataSet;
import org.unitils.core.UnitilsException;
import org.unitils.dbunit.datasetfactory.impl.DbUnitDataSet;
import org.unitils.dbunit.datasetfactory.impl.DbUnitTable;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
/**
* A reader for DbUnit xml datasets that creates a new ITable instance for each element (row).
*
* Following format is expected:
*
* <dataset>
* <first_table myColumn1="value1" myColumn2="value2" />
* <second_table myColumnA="A" />
* <first_table myColumn2="other value2" />
* <empty_table />
* </dataset>
*
*
* Elements for a table may occur more than once and anywhere in the data set. If multiple elements
* exist, they may specify different attributes (columns). Missing attributes (columns) will be treated as null values.
*
* Namespaces can be used to specify tables from different database schemas. The namespace URI should contain the name
* of the database schema:
*
* <dataset xmlns="SCHEMA_A" xmlns:b="SCHEMA_B">
* <first_table myColumn1="value1" myColumn2="value2" />
* <b:second_table myColumnA="A" />
* <first_table myColumn2="other value2" />
* <empty_table />
* </dataset>
*
*
* This example defines 2 schemas: SCHEMA_A and SCHEMA_B. The first schema is set as default schema (=default namespace).
* The 'first_table' table has no namespce and is therefore linked to SCHEMA_A. The 'second_table' table is prefixed
* with namespace b which is linked to SCHEMA_B. If no default namespace is defined, the schema that is
* passed as constructor argument is taken as default schema.
*
* @author Tim Ducheyne
* @author Filip Neven
*/
public class MultiSchemaXmlDataSetReader {
/* The logger instance for this class */
private static Log logger = LogFactory.getLog(MultiSchemaXmlDataSetReader.class);
/* The schema name to use when none is specified */
private String defaultSchemaName;
/**
* Creates a data set reader.
*
* @param defaultSchemaName The schema name to use when none is specified, not null
*/
public MultiSchemaXmlDataSetReader(String defaultSchemaName) {
this.defaultSchemaName = defaultSchemaName;
}
/**
* Parses the datasets from the given files.
* Each schema is given its own dataset and each row is given its own table.
*
* @param dataSetFiles The dataset files, not null
* @return The read data set, not null
*/
public MultiSchemaDataSet readDataSetXml(File... dataSetFiles) {
try {
DataSetContentHandler dataSetContentHandler = new DataSetContentHandler(defaultSchemaName);
XMLReader xmlReader = createXMLReader();
xmlReader.setContentHandler(dataSetContentHandler);
xmlReader.setErrorHandler(dataSetContentHandler);
for (File dataSetFile : dataSetFiles) {
InputStream dataSetInputStream = null;
try {
dataSetInputStream = new FileInputStream(dataSetFile);
xmlReader.parse(new InputSource(dataSetInputStream));
} finally {
closeQuietly(dataSetInputStream);
}
}
return dataSetContentHandler.getMultiSchemaDataSet();
} catch (Exception e) {
throw new UnitilsException("Unable to parse data set xml.", e);
}
}
/**
* Factory method for creating the SAX xml reader.
*
* @return the XML reader, not null
*/
protected XMLReader createXMLReader() {
try {
SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
saxParserFactory.setNamespaceAware(true);
// disable validation, so dataset can still be used when a DTD or XSD is missing
disableValidation(saxParserFactory);
return saxParserFactory.newSAXParser().getXMLReader();
} catch (Exception e) {
throw new UnitilsException("Unable to create SAX parser to read data set xml.", e);
}
}
/**
* Disables validation on the given sax parser factory.
*
* @param saxParserFactory The factory, not null
*/
protected void disableValidation(SAXParserFactory saxParserFactory) {
saxParserFactory.setValidating(false);
try {
saxParserFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
} catch (Exception e) {
logger.debug("Unable to set http://xml.org/sax/features/external-parameter-entities feature on SAX parser factory to false. Igoring exception: " + e.getMessage());
}
try {
saxParserFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
} catch (Exception e) {
logger.debug("Unable to set http://apache.org/xml/features/nonvalidating/load-external-dtd feature on SAX parser factory to false. Igoring exception: " + e.getMessage());
}
}
/**
* The xml content handler that is going to create the data sets.
*/
protected static class DataSetContentHandler extends DefaultHandler {
/* The schema name to use when none is specified */
protected String defaultSchemaName;
/* All created data sets per schema */
protected Map dbUnitDataSetsPerSchemaName = new LinkedHashMap();
/**
* Creates a data set SAX content handler
*
* @param defaultSchemaName The schema name to use when none is specified, not null
*/
public DataSetContentHandler(String defaultSchemaName) {
this.defaultSchemaName = defaultSchemaName;
}
/**
* Gets the result data set.
*
* @return the data set, not null
*/
public MultiSchemaDataSet getMultiSchemaDataSet() {
MultiSchemaDataSet multiSchemaDataSet = new MultiSchemaDataSet();
for (String schemaName : dbUnitDataSetsPerSchemaName.keySet()) {
DbUnitDataSet dataSet = dbUnitDataSetsPerSchemaName.get(schemaName);
// wrap data sets in replacement data sets, and replace [null] tokens by the null reference
ReplacementDataSet replacementDataSet = new ReplacementDataSet(dataSet);
replacementDataSet.addReplacementObject("[null]", null);
multiSchemaDataSet.setDataSetForSchema(schemaName, replacementDataSet);
}
return multiSchemaDataSet;
}
/**
* Processes an xml element. A new table is started for each element.
*
* @param uri the xml namespace uri (= schema name)
* @param localName the local xml name
* @param qName the element name (should be table name for table rows)
* @param attributes the attributes (should be table columns for table rows)
*/
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
// begin element of data set, if default namespace set, it will override the default schema
if ("dataset".equals(localName)) {
if (!isEmpty(uri)) {
defaultSchemaName = uri;
}
return;
}
// Begin new table for row
String schemaName = defaultSchemaName;
if (!isEmpty(uri)) {
schemaName = uri;
}
DbUnitDataSet dbUnitDataSet = dbUnitDataSetsPerSchemaName.get(schemaName);
if (dbUnitDataSet == null) {
dbUnitDataSet = new DbUnitDataSet();
dbUnitDataSetsPerSchemaName.put(schemaName, dbUnitDataSet);
}
DbUnitTable table = dbUnitDataSet.getDbUnitTable(localName);
if (table == null) {
table = new DbUnitTable(localName);
dbUnitDataSet.addTable(table);
}
addRow(attributes, table);
}
/**
* Gets column names and row values from the given attribute and adds a new row to the given table.
*
* @param table The table to add the row to, not null
* @param attributes the attributes, not null
*/
protected void addRow(Attributes attributes, DbUnitTable table) {
if (attributes.getLength() == 0) {
return;
}
for (int i = 0; i < attributes.getLength(); i++) {
Column column = new Column(attributes.getQName(i), UNKNOWN);
table.addColumn(column);
}
List