blob: 887fb7af64dde385f66cb8979e7293e0b7190ff6 [file] [log] [blame]
package org.apache.ddlutils.io;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.commons.collections.map.ListOrderedMap;
import org.apache.ddlutils.DdlUtilsException;
import org.apache.ddlutils.Platform;
import org.apache.ddlutils.model.Column;
import org.apache.ddlutils.model.Database;
import org.apache.ddlutils.model.Table;
/**
* Provides basic live database data <-> XML functionality.
*
* @version $Revision: $
*/
public class DatabaseDataIO
{
/** The converters to use for converting between data and its XML representation. */
private ArrayList _converters = new ArrayList();
/** Whether we should continue when an error was detected. */
private boolean _failOnError = true;
/** Whether foreign key order shall be followed when inserting data into the database. */
private boolean _ensureFKOrder = true;
/** Whether we should use batch mode. */
private boolean _useBatchMode;
/** The maximum number of objects to insert in one batch. */
private Integer _batchSize;
/** Whether DdlUtils should search for the schema of the tables. @deprecated */
private boolean _determineSchema;
/** The schema pattern for finding tables when reading data from a live database. @deprecated */
private String _schemaPattern;
/**
* Registers a converter.
*
* @param converterRegistration The registration info
*/
public void registerConverter(DataConverterRegistration converterRegistration)
{
_converters.add(converterRegistration);
}
/**
* Determines whether data io is stopped when an error happens.
*
* @return Whether io is stopped when an error was detected (true by default)
*/
public boolean isFailOnError()
{
return _failOnError;
}
/**
* Specifies whether data io shall be stopped when an error happens.
*
* @param failOnError Whether io should stop when an error was detected
*/
public void setFailOnError(boolean failOnError)
{
_failOnError = failOnError;
}
/**
* Determines whether batch mode is used for inserting data into the database.
*
* @return <code>true</code> if batch mode is used
*/
public boolean getUseBatchMode()
{
return _useBatchMode;
}
/**
* Specifies whether batch mode should be used for inserting data into the database.
*
* @param useBatchMode <code>true</code> if batch mode shall be used
*/
public void setUseBatchMode(boolean useBatchMode)
{
_useBatchMode = useBatchMode;
}
/**
* Returns the batch size override.
*
* @return The batch size if different from the default, <code>null</code> otherwise
*/
public Integer getBatchSize()
{
return _batchSize;
}
/**
* Sets the batch size to be used by this object.
*
* @param batchSize The batch size if different from the default, or <code>null</code> if
* the default shall be used
*/
public void setBatchSize(Integer batchSize)
{
_batchSize = batchSize;
}
/**
* Determines whether the sink delays the insertion of beans so that the beans referenced by it
* via foreignkeys are already inserted into the database.
*
* @return <code>true</code> if beans are inserted after its foreignkey-references
*/
public boolean isEnsureFKOrder()
{
return _ensureFKOrder;
}
/**
* Specifies whether the sink shall delay the insertion of beans so that the beans referenced by it
* via foreignkeys are already inserted into the database.<br/>
* Note that you should careful with setting <code>haltOnErrors</code> to false as this might
* result in beans not inserted at all. The sink will then throw an appropriate exception at the end
* of the insertion process (method {@link DataSink#end()}).
*
* @param ensureFKOrder <code>true</code> if beans shall be inserted after its foreignkey-references
*/
public void setEnsureFKOrder(boolean ensureFKOrder)
{
_ensureFKOrder = ensureFKOrder;
}
/**
* Specifies whether DdlUtils should try to find the schema of the tables when reading data
* from a live database.
*
* @param determineSchema Whether to try to find the table's schemas
* @deprecated Will be removed once proper schema support is in place
*/
public void setDetermineSchema(boolean determineSchema)
{
_determineSchema = determineSchema;
}
/**
* Sets the schema pattern to find the schemas of tables when reading data from a live database.
*
* @param schemaPattern The schema pattern
* @deprecated Will be removed once proper schema support is in place
*/
public void setSchemaPattern(String schemaPattern)
{
_schemaPattern = schemaPattern;
}
/**
* Registers the converters at the given configuration.
*
* @param converterConf The converter configuration
*/
private void registerConverters(ConverterConfiguration converterConf) throws DdlUtilsException
{
for (Iterator it = _converters.iterator(); it.hasNext();)
{
DataConverterRegistration registrationInfo = (DataConverterRegistration)it.next();
if (registrationInfo.getTypeCode() != Integer.MIN_VALUE)
{
converterConf.registerConverter(registrationInfo.getTypeCode(),
registrationInfo.getConverter());
}
else
{
if ((registrationInfo.getTable() == null) || (registrationInfo.getColumn() == null))
{
throw new DdlUtilsException("Please specify either the jdbc type or a table/column pair for which the converter shall be defined");
}
converterConf.registerConverter(registrationInfo.getTable(),
registrationInfo.getColumn(),
registrationInfo.getConverter());
}
}
}
/**
* Returns a data writer instance configured to write to the indicated file
* in the specified encoding.
*
* @param path The path to the output XML data file
* @param xmlEncoding The encoding to use for writing the XML
* @return The writer
*/
public DataWriter getConfiguredDataWriter(String path, String xmlEncoding) throws DdlUtilsException
{
try
{
DataWriter writer = new DataWriter(new FileOutputStream(path), xmlEncoding);
registerConverters(writer.getConverterConfiguration());
return writer;
}
catch (IOException ex)
{
throw new DdlUtilsException(ex);
}
}
/**
* Returns a data writer instance configured to write to the given output stream
* in the specified encoding.
*
* @param output The output stream
* @param xmlEncoding The encoding to use for writing the XML
* @return The writer
*/
public DataWriter getConfiguredDataWriter(OutputStream output, String xmlEncoding) throws DdlUtilsException
{
DataWriter writer = new DataWriter(output, xmlEncoding);
registerConverters(writer.getConverterConfiguration());
return writer;
}
/**
* Returns a data writer instance configured to write to the given output writer
* in the specified encoding.
*
* @param output The output writer; needs to be configured with the specified encoding
* @param xmlEncoding The encoding to use for writing the XML
* @return The writer
*/
public DataWriter getConfiguredDataWriter(Writer output, String xmlEncoding) throws DdlUtilsException
{
DataWriter writer = new DataWriter(output, xmlEncoding);
registerConverters(writer.getConverterConfiguration());
return writer;
}
/**
* Writes the data contained in the database to which the given platform is connected, as XML
* to the given output stream (which won't be closed by this method).
*
* @param platform The platform; needs to be connected to a live database
* @param path The path of the output file
* @param xmlEncoding The encoding to use for the XML
*/
public void writeDataToXML(Platform platform, String path, String xmlEncoding) throws DdlUtilsException
{
writeDataToXML(platform, getConfiguredDataWriter(path, xmlEncoding));
}
/**
* Writes the data contained in the database to which the given platform is connected, as XML
* to the given output stream (which won't be closed by this method).
*
* @param platform The platform; needs to be connected to a live database
* @param model The model for which to retrieve and write the data
* @param path The path of the output file
* @param xmlEncoding The encoding to use for the XML
*/
public void writeDataToXML(Platform platform, Database model, String path, String xmlEncoding)
{
writeDataToXML(platform, model, getConfiguredDataWriter(path, xmlEncoding));
}
/**
* Writes the data contained in the database to which the given platform is connected, as XML
* to the given output stream (which won't be closed by this method).
*
* @param platform The platform; needs to be connected to a live database
* @param output The output stream
* @param xmlEncoding The encoding to use for the XML
*/
public void writeDataToXML(Platform platform, OutputStream output, String xmlEncoding)
{
writeDataToXML(platform, getConfiguredDataWriter(output, xmlEncoding));
}
/**
* Writes the data contained in the database to which the given platform is connected, as XML
* to the given output stream (which won't be closed by this method).
*
* @param platform The platform; needs to be connected to a live database
* @param model The model for which to retrieve and write the data
* @param output The output stream
* @param xmlEncoding The encoding to use for the XML
*/
public void writeDataToXML(Platform platform, Database model, OutputStream output, String xmlEncoding)
{
writeDataToXML(platform, model, getConfiguredDataWriter(output, xmlEncoding));
}
/**
* Writes the data contained in the database to which the given platform is connected, as XML
* to the given output writer (which won't be closed by this method).
*
* @param platform The platform; needs to be connected to a live database
* @param output The output writer (which needs to be openend with the specified encoding)
* @param xmlEncoding The encoding to use for the XML
*/
public void writeDataToXML(Platform platform, Writer output, String xmlEncoding)
{
writeDataToXML(platform, getConfiguredDataWriter(output, xmlEncoding));
}
/**
* Writes the data contained in the database to which the given platform is connected, as XML
* to the given output writer (which won't be closed by this method).
*
* @param platform The platform; needs to be connected to a live database
* @param model The model for which to retrieve and write the data
* @param output The output writer (which needs to be openend with the specified encoding)
* @param xmlEncoding The encoding to use for the XML
*/
public void writeDataToXML(Platform platform, Database model, Writer output, String xmlEncoding)
{
writeDataToXML(platform, model, getConfiguredDataWriter(output, xmlEncoding));
}
/**
* Writes the data contained in the database to which the given platform is connected, as XML
* to the given data writer.
*
* @param platform The platform; needs to be connected to a live database
* @param writer The data writer
*/
public void writeDataToXML(Platform platform, DataWriter writer)
{
writeDataToXML(platform, platform.readModelFromDatabase("unnamed"), writer);
}
/**
* Writes the data contained in the database to which the given platform is connected, as XML
* to the given data writer.
*
* @param platform The platform; needs to be connected to a live database
* @param model The model for which to retrieve and write the data
* @param writer The data writer
*/
public void writeDataToXML(Platform platform, Database model, DataWriter writer)
{
registerConverters(writer.getConverterConfiguration());
// TODO: An advanced algorithm could be employed here that writes individual
// objects related by foreign keys, in the correct order
List tables = sortTables(model.getTables());
writer.writeDocumentStart();
for (Iterator it = tables.iterator(); it.hasNext();)
{
writeDataForTableToXML(platform, model, (Table)it.next(), writer);
}
writer.writeDocumentEnd();
}
/**
* Sorts the given table according to their foreign key order.
*
* @param tables The tables
* @return The sorted tables
*/
private List sortTables(Table[] tables)
{
ArrayList result = new ArrayList();
HashSet processed = new HashSet();
ListOrderedMap pending = new ListOrderedMap();
for (int idx = 0; idx < tables.length; idx++)
{
Table table = tables[idx];
if (table.getForeignKeyCount() == 0)
{
result.add(table);
processed.add(table);
}
else
{
HashSet waitedFor = new HashSet();
for (int fkIdx = 0; fkIdx < table.getForeignKeyCount(); fkIdx++)
{
Table waitedForTable = table.getForeignKey(fkIdx).getForeignTable();
if (!table.equals(waitedForTable))
{
waitedFor.add(waitedForTable);
}
}
pending.put(table, waitedFor);
}
}
HashSet newProcessed = new HashSet();
while (!processed.isEmpty() && !pending.isEmpty())
{
newProcessed.clear();
for (Iterator it = pending.entrySet().iterator(); it.hasNext();)
{
Map.Entry entry = (Map.Entry)it.next();
Table table = (Table)entry.getKey();
HashSet waitedFor = (HashSet)entry.getValue();
waitedFor.removeAll(processed);
if (waitedFor.isEmpty())
{
it.remove();
result.add(table);
newProcessed.add(table);
}
}
processed.clear();
HashSet tmp = processed;
processed = newProcessed;
newProcessed = tmp;
}
// the remaining are within circular dependencies
for (Iterator it = pending.keySet().iterator(); it.hasNext();)
{
result.add(it.next());
}
return result;
}
/**
* Writes the data contained in a single table to XML.
*
* @param platform The platform
* @param model The database model
* @param table The table
* @param writer The data writer
*/
private void writeDataForTableToXML(Platform platform, Database model, Table table, DataWriter writer)
{
Table[] tables = { table };
StringBuffer query = new StringBuffer();
query.append("SELECT ");
Connection connection = null;
String schema = null;
if (_determineSchema)
{
try
{
// TODO: Remove this once we have full support for schemas
connection = platform.borrowConnection();
schema = platform.getModelReader().determineSchemaOf(connection, _schemaPattern, tables[0]);
}
catch (SQLException ex)
{
// ignored
}
finally
{
if (connection != null)
{
try
{
connection.close();
}
catch (SQLException ex)
{
// ignored
}
}
}
}
Column[] columns = tables[0].getColumns();
for (int columnIdx = 0; columnIdx < columns.length; columnIdx++)
{
if (columnIdx > 0)
{
query.append(",");
}
if (platform.isDelimitedIdentifierModeOn())
{
query.append(platform.getPlatformInfo().getDelimiterToken());
}
query.append(columns[columnIdx].getName());
if (platform.isDelimitedIdentifierModeOn())
{
query.append(platform.getPlatformInfo().getDelimiterToken());
}
}
query.append(" FROM ");
if (platform.isDelimitedIdentifierModeOn())
{
query.append(platform.getPlatformInfo().getDelimiterToken());
}
if (schema != null)
{
query.append(schema);
query.append(".");
}
query.append(tables[0].getName());
if (platform.isDelimitedIdentifierModeOn())
{
query.append(platform.getPlatformInfo().getDelimiterToken());
}
writer.write(platform.query(model, query.toString(), tables));
}
/**
* Returns a data reader instance configured for the given platform (which needs to
* be connected to a live database) and model.
*
* @param platform The database
* @param model The model
* @return The data reader
*/
public DataReader getConfiguredDataReader(Platform platform, Database model) throws DdlUtilsException
{
DataToDatabaseSink sink = new DataToDatabaseSink(platform, model);
DataReader reader = new DataReader();
sink.setHaltOnErrors(_failOnError);
sink.setEnsureForeignKeyOrder(_ensureFKOrder);
sink.setUseBatchMode(_useBatchMode);
if (_batchSize != null)
{
sink.setBatchSize(_batchSize.intValue());
}
reader.setModel(model);
reader.setSink(sink);
registerConverters(reader.getConverterConfiguration());
return reader;
}
/**
* Reads the data from the specified files and writes it to the database to which the given
* platform is connected.
*
* @param platform The platform, must be connected to a live database
* @param files The XML data files
*/
public void writeDataToDatabase(Platform platform, String[] files) throws DdlUtilsException
{
writeDataToDatabase(platform, platform.readModelFromDatabase("unnamed"), files);
}
/**
* Reads the data from the given input streams and writes it to the database to which the given
* platform is connected.
*
* @param platform The platform, must be connected to a live database
* @param inputs The input streams for the XML data
*/
public void writeDataToDatabase(Platform platform, InputStream[] inputs) throws DdlUtilsException
{
writeDataToDatabase(platform, platform.readModelFromDatabase("unnamed"), inputs);
}
/**
* Reads the data from the given input readers and writes it to the database to which the given
* platform is connected.
*
* @param platform The platform, must be connected to a live database
* @param inputs The input readers for the XML data
*/
public void writeDataToDatabase(Platform platform, Reader[] inputs) throws DdlUtilsException
{
writeDataToDatabase(platform, platform.readModelFromDatabase("unnamed"), inputs);
}
/**
* Reads the data from the indicated files and writes it to the database to which the given
* platform is connected. Only data that matches the given model will be written.
*
* @param platform The platform, must be connected to a live database
* @param model The model to which to constrain the written data
* @param files The XML data files
*/
public void writeDataToDatabase(Platform platform, Database model, String[] files) throws DdlUtilsException
{
DataReader dataReader = getConfiguredDataReader(platform, model);
dataReader.getSink().start();
for (int idx = 0; (files != null) && (idx < files.length); idx++)
{
writeDataToDatabase(dataReader, files[idx]);
}
dataReader.getSink().end();
}
/**
* Reads the data from the given input streams and writes it to the database to which the given
* platform is connected. Only data that matches the given model will be written.
*
* @param platform The platform, must be connected to a live database
* @param model The model to which to constrain the written data
* @param inputs The input streams for the XML data
*/
public void writeDataToDatabase(Platform platform, Database model, InputStream[] inputs) throws DdlUtilsException
{
DataReader dataReader = getConfiguredDataReader(platform, model);
dataReader.getSink().start();
for (int idx = 0; (inputs != null) && (idx < inputs.length); idx++)
{
writeDataToDatabase(dataReader, inputs[idx]);
}
dataReader.getSink().end();
}
/**
* Reads the data from the given input readers and writes it to the database to which the given
* platform is connected. Only data that matches the given model will be written.
*
* @param platform The platform, must be connected to a live database
* @param model The model to which to constrain the written data
* @param inputs The input readers for the XML data
*/
public void writeDataToDatabase(Platform platform, Database model, Reader[] inputs) throws DdlUtilsException
{
DataReader dataReader = getConfiguredDataReader(platform, model);
dataReader.getSink().start();
for (int idx = 0; (inputs != null) && (idx < inputs.length); idx++)
{
writeDataToDatabase(dataReader, inputs[idx]);
}
dataReader.getSink().end();
}
/**
* Reads the data from the specified files and writes it to the database via the given data reader.
* Note that the sink that the data reader is configured with, won't be started or ended by
* this method. This has to be done by the code using this method.
*
* @param dataReader The data reader
* @param files The XML data files
*/
public void writeDataToDatabase(DataReader dataReader, String[] files) throws DdlUtilsException
{
for (int idx = 0; (files != null) && (idx < files.length); idx++)
{
writeDataToDatabase(dataReader, files[idx]);
}
}
/**
* Reads the data from the given input stream and writes it to the database via the given data reader.
* Note that the input stream won't be closed by this method. Note also that the sink that the data
* reader is configured with, won't be started or ended by this method. This has to be done by the
* code using this method.
*
* @param dataReader The data reader
* @param inputs The input streams for the XML data
*/
public void writeDataToDatabase(DataReader dataReader, InputStream[] inputs) throws DdlUtilsException
{
for (int idx = 0; (inputs != null) && (idx < inputs.length); idx++)
{
writeDataToDatabase(dataReader, inputs[idx]);
}
}
/**
* Reads the data from the given input stream and writes it to the database via the given data reader.
* Note that the input stream won't be closed by this method. Note also that the sink that the data
* reader is configured with, won't be started or ended by this method. This has to be done by the
* code using this method.
*
* @param dataReader The data reader
* @param inputs The input readers for the XML data
*/
public void writeDataToDatabase(DataReader dataReader, Reader[] inputs) throws DdlUtilsException
{
for (int idx = 0; (inputs != null) && (idx < inputs.length); idx++)
{
writeDataToDatabase(dataReader, inputs[idx]);
}
}
/**
* Reads the data from the indicated XML file and writes it to the database via the given data reader.
* Note that the sink that the data reader is configured with, won't be started or ended by this method.
* This has to be done by the code using this method.
*
* @param dataReader The data reader
* @param path The path to the XML data file
*/
public void writeDataToDatabase(DataReader dataReader, String path) throws DdlUtilsException
{
try
{
dataReader.parse(path);
}
catch (Exception ex)
{
throw new DdlUtilsException(ex);
}
}
/**
* Reads the data from the given input stream and writes it to the database via the given data reader.
* Note that the input stream won't be closed by this method. Note also that the sink that the data
* reader is configured with, won't be started or ended by this method. This has to be done by the
* code using this method.
*
* @param dataReader The data reader
* @param input The input stream for the XML data
*/
public void writeDataToDatabase(DataReader dataReader, InputStream input) throws DdlUtilsException
{
try
{
dataReader.parse(input);
}
catch (Exception ex)
{
throw new DdlUtilsException(ex);
}
}
/**
* Reads the data from the given input stream and writes it to the database via the given data reader.
* Note that the input stream won't be closed by this method. Note also that the sink that the data
* reader is configured with, won't be started or ended by this method. This has to be done by the
* code using this method.
*
* @param dataReader The data reader
* @param input The input reader for the XML data
*/
public void writeDataToDatabase(DataReader dataReader, Reader input) throws DdlUtilsException
{
try
{
dataReader.parse(input);
}
catch (Exception ex)
{
throw new DdlUtilsException(ex);
}
}
}