blob: c8cd65ca82f2359248501dbe888a444047dcd20d [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.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.lang.reflect.InvocationTargetException;
import javax.xml.namespace.QName;
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
{
try
{
read(new FileReader(filename));
}
catch (IOException ex)
{
throw new DdlUtilsXMLException(ex);
}
}
/**
* Reads the data contained in the specified file.
*
* @param file The data file
*/
public void read(File file) throws DdlUtilsXMLException
{
try
{
read(new FileReader(file));
}
catch (IOException ex)
{
throw new DdlUtilsXMLException(ex);
}
}
/**
* Reads the data given by the reader.
*
* @param reader The reader that returns the data XML
*/
public void read(Reader reader) throws DdlUtilsXMLException
{
try
{
read(getXMLInputFactory().createXMLStreamReader(reader));
}
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
{
try
{
read(getXMLInputFactory().createXMLStreamReader(input));
}
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();
Table table = _model.findTable(elemQName.getLocalPart(), isCaseSensitive());
if (table == null)
{
_log.warn("Data XML contains an element " + elemQName + " at location " + xmlReader.getLocation() +
" but there is no table defined with this name. This element will be ignored.");
readOverElement(xmlReader);
}
else
{
DynaBean bean = _model.createDynaBeanFor(table);
for (int idx = 0; idx < xmlReader.getAttributeCount(); idx++)
{
QName attrQName = xmlReader.getAttributeName(idx);
Column column = table.findColumn(attrQName.getLocalPart(), isCaseSensitive());
if (column == null)
{
_log.warn("Data XML contains an attribute " + attrQName + " at location " + xmlReader.getLocation() +
" but there is no column defined in table " + table.getName() + " with this name. This attribute will be ignored.");
}
else
{
setColumnValue(bean, table, column, xmlReader.getAttributeValue(idx));
}
}
readColumnSubElements(xmlReader, bean, table);
getSink().addBean(bean);
consumeRestOfElement(xmlReader);
}
}
/**
* Reads all column 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 bean The bean to fill
* @param table The table definition
*/
private void readColumnSubElements(XMLStreamReader xmlReader, DynaBean bean, Table table) throws XMLStreamException, DdlUtilsXMLException
{
int eventType = XMLStreamReader.START_ELEMENT;
while (eventType != XMLStreamReader.END_ELEMENT)
{
eventType = xmlReader.next();
if (eventType == XMLStreamReader.START_ELEMENT)
{
readColumnSubElement(xmlReader, bean, table);
}
}
}
/**
* 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 bean The bean to fill
* @param table The table definition
*/
private void readColumnSubElement(XMLStreamReader xmlReader, DynaBean bean, Table table) throws XMLStreamException, DdlUtilsXMLException
{
QName elemQName = xmlReader.getName();
boolean usesBase64 = false;
for (int idx = 0; idx < xmlReader.getAttributeCount(); idx++)
{
QName attrQName = xmlReader.getAttributeName(idx);
if (DatabaseIO.BASE64_ATTR_NAME.equals(attrQName.getLocalPart()) &&
"true".equalsIgnoreCase(xmlReader.getAttributeValue(idx)))
{
usesBase64 = true;
break;
}
}
Column column = table.findColumn(elemQName.getLocalPart(), isCaseSensitive());
if (column == null)
{
_log.warn("Data XML contains an element " + elemQName + " at location " + xmlReader.getLocation() +
" but there is no column defined in table " + table.getName() + " with this name. This element will be ignored.");
}
else
{
String value = xmlReader.getElementText();
if (value != null)
{
value = value.trim();
if (usesBase64)
{
value = new String(Base64.decodeBase64(value.getBytes()));
}
setColumnValue(bean, table, column, 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);
}
}
// TODO: move these two into a helper class:
/**
* Reads over the current element. This assumes that the current XML stream event type is
* START_ELEMENT.
*
* @param reader The xml reader
*/
private void readOverElement(XMLStreamReader reader) throws XMLStreamException
{
int depth = 1;
while (depth > 0)
{
int eventType = reader.next();
if (eventType == XMLStreamReader.START_ELEMENT)
{
depth++;
}
else if (eventType == XMLStreamReader.END_ELEMENT)
{
depth--;
}
}
}
/**
* 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();
}
}
}