org.apache.ddlutils.io.DatabaseDataIO.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.ddlutils.io.DatabaseDataIO.java

Source

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.read(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.read(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.read(input);
        } catch (Exception ex) {
            throw new DdlUtilsException(ex);
        }
    }
}