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

Java tutorial

Introduction

Here is the source code for org.apache.ddlutils.io.DataReader.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.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

import javax.xml.namespace.QName;
import javax.xml.stream.Location;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;

import org.apache.commons.beanutils.DynaBean;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ddlutils.io.converters.SqlTypeConverter;
import org.apache.ddlutils.model.Column;
import org.apache.ddlutils.model.Database;
import org.apache.ddlutils.model.Table;
import org.xml.sax.InputSource;

/**
 * Reads data XML into dyna beans matching a specified database model. Note that
 * the data sink won't be started or ended by the data reader, this has to be done
 * in the code that uses the data reader. 
 * 
 * @version $Revision: $
 */
public class DataReader {
    /** Our log. */
    private final Log _log = LogFactory.getLog(DataReader.class);

    /** The database model. */
    private Database _model;
    /** The object to receive the read beans. */
    private DataSink _sink;
    /** The converters. */
    private ConverterConfiguration _converterConf = new ConverterConfiguration();
    /** Whether to be case sensitive or not. */
    private boolean _caseSensitive = false;

    /**
     * Returns the converter configuration of this data reader.
     * 
     * @return The converter configuration
     */
    public ConverterConfiguration getConverterConfiguration() {
        return _converterConf;
    }

    /**
     * Returns the database model.
     *
     * @return The model
     */
    public Database getModel() {
        return _model;
    }

    /**
     * Sets the database model.
     *
     * @param model The model
     */
    public void setModel(Database model) {
        _model = model;
    }

    /**
     * Returns the data sink.
     *
     * @return The sink
     */
    public DataSink getSink() {
        return _sink;
    }

    /**
     * Sets the data sink.
     *
     * @param sink The sink
     */
    public void setSink(DataSink sink) {
        _sink = sink;
    }

    /**
     * Determines whether this rules object matches case sensitively.
     *
     * @return <code>true</code> if the case of the pattern matters
     */
    public boolean isCaseSensitive() {
        return _caseSensitive;
    }

    /**
     * Specifies whether this rules object shall match case sensitively.
     *
     * @param beCaseSensitive <code>true</code> if the case of the pattern shall matter
     */
    public void setCaseSensitive(boolean beCaseSensitive) {
        _caseSensitive = beCaseSensitive;
    }

    /**
     * Creates a new, initialized XML input factory object.
     * 
     * @return The factory object
     */
    private XMLInputFactory getXMLInputFactory() {
        XMLInputFactory factory = XMLInputFactory.newInstance();

        factory.setProperty("javax.xml.stream.isCoalescing", Boolean.TRUE);
        factory.setProperty("javax.xml.stream.isNamespaceAware", Boolean.FALSE);
        return factory;
    }

    /**
     * Reads the data contained in the specified file.
     * 
     * @param filename The data file name
     */
    public void read(String filename) throws DdlUtilsXMLException {
        read(new File(filename));
    }

    /**
     * Reads the data contained in the specified file.
     * 
     * @param file The data file
     */
    public void read(File file) throws DdlUtilsXMLException {
        FileInputStream input = null;

        try {
            input = new FileInputStream(file);
            read(input);
        } catch (IOException ex) {
            throw new DdlUtilsXMLException(ex);
        } finally {
            if (input != null) {
                try {
                    input.close();
                } catch (IOException ex) {
                    _log.warn("Error while trying to close the input stream for " + file, ex);
                }
            }
        }
    }

    /**
     * Reads the data given by the reader.
     * 
     * @param reader The reader that returns the data XML
     */
    public void read(Reader reader) throws DdlUtilsXMLException {
        BufferedReader bufferedReader;

        if (reader instanceof BufferedReader) {
            bufferedReader = (BufferedReader) reader;
        } else {
            bufferedReader = new BufferedReader(reader);
        }
        try {
            read(getXMLInputFactory().createXMLStreamReader(bufferedReader));
        } catch (XMLStreamException ex) {
            throw new DdlUtilsXMLException(ex);
        }
    }

    /**
     * Reads the data given by the input stream.
     * 
     * @param input The input stream that returns the data XML
     */
    public void read(InputStream input) throws DdlUtilsXMLException {
        BufferedInputStream bufferedInput;

        if (input instanceof BufferedInputStream) {
            bufferedInput = (BufferedInputStream) input;
        } else {
            bufferedInput = new BufferedInputStream(input);
        }
        try {
            read(getXMLInputFactory().createXMLStreamReader(bufferedInput));
        } catch (XMLStreamException ex) {
            throw new DdlUtilsXMLException(ex);
        }
    }

    /**
     * Reads the data from the given input source.
     *
     * @param source The input source
     */
    public void read(InputSource source) throws DdlUtilsXMLException {
        read(source.getCharacterStream());
    }

    /**
     * Reads the data from the given XML stream reader.
     * 
     * @param xmlReader The reader
     */
    private void read(XMLStreamReader xmlReader) throws DdlUtilsXMLException {
        try {
            while (xmlReader.getEventType() != XMLStreamReader.START_ELEMENT) {
                if (xmlReader.next() == XMLStreamReader.END_DOCUMENT) {
                    return;
                }
            }
            readDocument(xmlReader);
        } catch (XMLStreamException ex) {
            throw new DdlUtilsXMLException(ex);
        }
    }

    // TODO: add debug level logging (or trace ?)

    /**
     * Reads the xml document from the given xml stream reader.
     * 
     * @param xmlReader The reader
     */
    private void readDocument(XMLStreamReader xmlReader) throws XMLStreamException, DdlUtilsXMLException {
        // we ignore the top-level tag since we don't know about its name
        int eventType = XMLStreamReader.START_ELEMENT;

        while (eventType != XMLStreamReader.END_ELEMENT) {
            eventType = xmlReader.next();
            if (eventType == XMLStreamReader.START_ELEMENT) {
                readBean(xmlReader);
            }
        }
    }

    /**
     * Reads a bean from the given xml stream reader.
     * 
     * @param xmlReader The reader
     */
    private void readBean(XMLStreamReader xmlReader) throws XMLStreamException, DdlUtilsXMLException {
        QName elemQName = xmlReader.getName();
        Location location = xmlReader.getLocation();
        Map attributes = new HashMap();
        String tableName = null;

        for (int idx = 0; idx < xmlReader.getAttributeCount(); idx++) {
            QName attrQName = xmlReader.getAttributeName(idx);

            attributes.put(isCaseSensitive() ? attrQName.getLocalPart() : attrQName.getLocalPart().toLowerCase(),
                    xmlReader.getAttributeValue(idx));
        }
        readColumnSubElements(xmlReader, attributes);

        if ("table".equals(elemQName.getLocalPart())) {
            tableName = (String) attributes.get("table-name");
        } else {
            tableName = elemQName.getLocalPart();
        }

        Table table = _model.findTable(tableName, isCaseSensitive());

        if (table == null) {
            _log.warn("Data XML contains an element " + elemQName + " at location " + location
                    + " but there is no table defined with this name. This element will be ignored.");
        } else {
            DynaBean bean = _model.createDynaBeanFor(table);

            for (int idx = 0; idx < table.getColumnCount(); idx++) {
                Column column = table.getColumn(idx);
                String value = (String) attributes
                        .get(isCaseSensitive() ? column.getName() : column.getName().toLowerCase());

                if (value != null) {
                    setColumnValue(bean, table, column, value);
                }
            }
            getSink().addBean(bean);
            consumeRestOfElement(xmlReader);
        }
    }

    /**
     * Reads all relevant sub elements that match the columns specified by the given table object from the xml reader into the given bean.
     *  
     * @param xmlReader The reader
     * @param data      Where to store the values
     */
    private void readColumnSubElements(XMLStreamReader xmlReader, Map data)
            throws XMLStreamException, DdlUtilsXMLException {
        int eventType = XMLStreamReader.START_ELEMENT;

        while (eventType != XMLStreamReader.END_ELEMENT) {
            eventType = xmlReader.next();
            if (eventType == XMLStreamReader.START_ELEMENT) {
                readColumnSubElement(xmlReader, data);
            }
        }
    }

    /**
     * Reads the next column sub element that matches a column specified by the given table object from the xml reader into the given bean.
     *  
     * @param xmlReader The reader
     * @param data      Where to store the values
     */
    private void readColumnSubElement(XMLStreamReader xmlReader, Map data)
            throws XMLStreamException, DdlUtilsXMLException {
        QName elemQName = xmlReader.getName();
        Map attributes = new HashMap();
        boolean usesBase64 = false;

        for (int idx = 0; idx < xmlReader.getAttributeCount(); idx++) {
            QName attrQName = xmlReader.getAttributeName(idx);
            String value = xmlReader.getAttributeValue(idx);

            if (DatabaseIO.BASE64_ATTR_NAME.equals(attrQName.getLocalPart())) {
                if ("true".equalsIgnoreCase(value)) {
                    usesBase64 = true;
                }
            } else {
                attributes.put(attrQName.getLocalPart(), value);
            }
        }

        int eventType = XMLStreamReader.START_ELEMENT;
        StringBuffer content = new StringBuffer();

        while (eventType != XMLStreamReader.END_ELEMENT) {
            eventType = xmlReader.next();
            if (eventType == XMLStreamReader.START_ELEMENT) {
                readColumnDataSubElement(xmlReader, attributes);
            } else if ((eventType == XMLStreamReader.CHARACTERS) || (eventType == XMLStreamReader.CDATA)
                    || (eventType == XMLStreamReader.SPACE) || (eventType == XMLStreamReader.ENTITY_REFERENCE)) {
                content.append(xmlReader.getText());
            }
        }

        String value = content.toString().trim();

        if (usesBase64) {
            value = new String(Base64.decodeBase64(value.getBytes()));
        }

        String name = elemQName.getLocalPart();

        if ("table-name".equals(name)) {
            data.put("table-name", value);
        } else {
            if ("column".equals(name)) {
                name = (String) attributes.get("column-name");
            }
            if (attributes.containsKey("column-value")) {
                value = (String) attributes.get("column-value");
            }
            data.put(name, value);
        }
        consumeRestOfElement(xmlReader);
    }

    /**
     * Reads the next column-name or column-value sub element.
     *  
     * @param xmlReader The reader
     * @param data      Where to store the values
     */
    private void readColumnDataSubElement(XMLStreamReader xmlReader, Map data)
            throws XMLStreamException, DdlUtilsXMLException {
        QName elemQName = xmlReader.getName();
        boolean usesBase64 = false;

        for (int idx = 0; idx < xmlReader.getAttributeCount(); idx++) {
            QName attrQName = xmlReader.getAttributeName(idx);
            String value = xmlReader.getAttributeValue(idx);

            if (DatabaseIO.BASE64_ATTR_NAME.equals(attrQName.getLocalPart())) {
                if ("true".equalsIgnoreCase(value)) {
                    usesBase64 = true;
                }
                break;
            }
        }

        String value = xmlReader.getElementText();

        if (value != null) {
            value = value.toString().trim();

            if (usesBase64) {
                value = new String(Base64.decodeBase64(value.getBytes()));
            }
        }

        String name = elemQName.getLocalPart();

        if ("column-name".equals(name)) {
            data.put("column-name", value);
        } else if ("column-value".equals(name)) {
            data.put("column-value", value);
        }
        consumeRestOfElement(xmlReader);
    }

    /**
     * Converts the column value read from the XML stream to an object and sets it at the given bean.
     * 
     * @param bean   The bean
     * @param table  The table definition
     * @param column The column definition
     * @param value  The value as a string
     */
    private void setColumnValue(DynaBean bean, Table table, Column column, String value)
            throws DdlUtilsXMLException {
        SqlTypeConverter converter = _converterConf.getRegisteredConverter(table, column);
        Object propValue = (converter != null ? converter.convertFromString(value, column.getTypeCode()) : value);

        try {
            PropertyUtils.setProperty(bean, column.getName(), propValue);
        } catch (NoSuchMethodException ex) {
            throw new DdlUtilsXMLException("Undefined column " + column.getName());
        } catch (IllegalAccessException ex) {
            throw new DdlUtilsXMLException("Could not set bean property for column " + column.getName(), ex);
        } catch (InvocationTargetException ex) {
            throw new DdlUtilsXMLException("Could not set bean property for column " + column.getName(), ex);
        }
    }

    /**
     * Consumes the rest of the current element. This assumes that the current XML stream
     * event type is not START_ELEMENT.
     * 
     * @param reader The xml reader
     */
    private void consumeRestOfElement(XMLStreamReader reader) throws XMLStreamException {
        int eventType = reader.getEventType();

        while ((eventType != XMLStreamReader.END_ELEMENT) && (eventType != XMLStreamReader.END_DOCUMENT)) {
            eventType = reader.next();
        }
    }
}