All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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 row = new ArrayList(10); for (String columnName : table.getColumnNames()) { Object value = NO_VALUE; if (attributes.getIndex(columnName) != -1) { value = attributes.getValue(columnName); } row.add(value); } table.addRow(row); } /** * Overridden to rethrow exception. * * @param e The exception */ @Override public void error(SAXParseException e) throws SAXException { throw e; } } }