Java tutorial
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(); } } }