Author: tomdz Date: Mon May 2 05:41:14 2011 New Revision: 1098483 URL: http://svn.apache.org/viewvc?rev=1098483&view=rev Log: Fix for DDLUTILS-245: writeDataToFile produce broken XML
Added: db/ddlutils/trunk/src/main/java/org/apache/ddlutils/io/ColumnXmlWriter.java db/ddlutils/trunk/src/main/java/org/apache/ddlutils/io/ModelXmlWriter.java db/ddlutils/trunk/src/main/java/org/apache/ddlutils/io/TableXmlWriter.java db/ddlutils/trunk/src/test/java/org/apache/ddlutils/util/ db/ddlutils/trunk/src/test/java/org/apache/ddlutils/util/DatabaseTestHelper.java - copied unchanged from r1002941, db/ddlutils/trunk/src/main/java/org/apache/ddlutils/util/DatabaseTestHelper.java Removed: db/ddlutils/trunk/src/main/java/org/apache/ddlutils/util/DatabaseTestHelper.java Modified: db/ddlutils/trunk/src/main/java/org/apache/ddlutils/dynabean/SqlDynaBean.java db/ddlutils/trunk/src/main/java/org/apache/ddlutils/io/DataReader.java db/ddlutils/trunk/src/main/java/org/apache/ddlutils/io/DataWriter.java db/ddlutils/trunk/src/main/java/org/apache/ddlutils/io/PrettyPrintingXmlWriter.java db/ddlutils/trunk/src/main/java/org/apache/ddlutils/io/XMLUtils.java db/ddlutils/trunk/src/test/java/org/apache/ddlutils/io/TestDataReaderAndWriter.java Modified: db/ddlutils/trunk/src/main/java/org/apache/ddlutils/dynabean/SqlDynaBean.java URL: http://svn.apache.org/viewvc/db/ddlutils/trunk/src/main/java/org/apache/ddlutils/dynabean/SqlDynaBean.java?rev=1098483&r1=1098482&r2=1098483&view=diff ============================================================================== --- db/ddlutils/trunk/src/main/java/org/apache/ddlutils/dynabean/SqlDynaBean.java (original) +++ db/ddlutils/trunk/src/main/java/org/apache/ddlutils/dynabean/SqlDynaBean.java Mon May 2 05:41:14 2011 @@ -67,4 +67,49 @@ public class SqlDynaBean extends BasicDy } return result.toString(); } + + /** + * {@inheritDoc} + */ + public int hashCode() + { + return toString().hashCode(); + } + + /** + * {@inheritDoc} + */ + public boolean equals(Object obj) + { + if (obj instanceof SqlDynaBean) + { + SqlDynaBean other = (SqlDynaBean)obj; + DynaClass dynaClass = getDynaClass(); + + if (dynaClass.equals(other.getDynaClass())) + { + DynaProperty[] props = dynaClass.getDynaProperties(); + + for (int idx = 0; idx < props.length; idx++) + { + Object value = get(props[idx].getName()); + Object otherValue = other.get(props[idx].getName()); + + if (value == null) + { + if (otherValue != null) + { + return false; + } + } + else + { + return value.equals(otherValue); + } + } + return true; + } + } + return false; + } } Added: db/ddlutils/trunk/src/main/java/org/apache/ddlutils/io/ColumnXmlWriter.java URL: http://svn.apache.org/viewvc/db/ddlutils/trunk/src/main/java/org/apache/ddlutils/io/ColumnXmlWriter.java?rev=1098483&view=auto ============================================================================== --- db/ddlutils/trunk/src/main/java/org/apache/ddlutils/io/ColumnXmlWriter.java (added) +++ db/ddlutils/trunk/src/main/java/org/apache/ddlutils/io/ColumnXmlWriter.java Mon May 2 05:41:14 2011 @@ -0,0 +1,176 @@ +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 org.apache.ddlutils.model.Column; + +/** + * Helper class for writing columns to XML. + */ +public class ColumnXmlWriter extends ModelXmlWriter +{ + private final int AS_TABLE_ATTRIBUTE = 0; + private final int AS_SUBTAG = 1; + private final int AS_COLUMN_ATTRIBUTE = 2; + private final int AS_VALUE = 3; + + private final String columnName; + private final String columnValue; + private final boolean nameBase64Encoded; + private final boolean valueBase64Encoded; + private final int columnFormattingMethod; + + /** + * Creates a new column writer. + * + * @param column The column, cannot be null + * @param value The value, cannot be null + */ + public ColumnXmlWriter(Column column, String value) + { + /* + * - attribute "column name"="column value" in the parent's (table) element + * iff the column name is a valid attribute name and is not "table-name" and not "column", + * and the value is a valid attribute value not longer than 255 characters + * - otherwise, writes a sub-element <column> with an attribute column-name that contains the name + * of the column, and the body of that sub-element contains the column value, + * iff the column name is a valid attribute value not longer than 255 characters. If the column + * value contains illegal characters, then the column sub element will have a "base64" attribute + * with the value "true" and the value will be base64 encoded + * - otherwise writes a sub-element <column> with a sub-element <column-name> whose + * body is the name of the column, and another sub-element <column-value> whose body contains + * the column value. If either the column name or value contain illegal characters, then the + * corresponding sub element will have a "base64" attribute with the value "true" and its body will + * be base64 encoded. + */ + if (XMLUtils.hasIllegalXMLCharacters(value)) + { + columnValue = XMLUtils.base64Encode(value); + valueBase64Encoded = true; + } + else + { + columnValue = value; + valueBase64Encoded = false; + } + + if (XMLUtils.hasIllegalXMLCharacters(column.getName())) { + columnName = XMLUtils.base64Encode(column.getName()); + nameBase64Encoded = true; + columnFormattingMethod = AS_VALUE; + } + else + { + columnName = column.getName(); + nameBase64Encoded = false; + if (columnName.length() > XMLUtils.MAX_NAME_LENGTH) + { + columnFormattingMethod = AS_VALUE; + } + else if ("table-name".equals(columnName) || + "column".equals(columnName) || + DatabaseIO.BASE64_ATTR_NAME.equals(columnName) || + !XMLUtils.isWellFormedXMLName(columnName)) + { + columnFormattingMethod = AS_COLUMN_ATTRIBUTE; + } + else if (valueBase64Encoded || (value.length() > XMLUtils.MAX_ATTRIBUTE_LENGTH)) + { + columnFormattingMethod = AS_SUBTAG; + } + else + { + columnFormattingMethod = AS_TABLE_ATTRIBUTE; + } + } + } + + /** + * Writes the column data as an attribute of the parent element if possible. + * Does nothing if the column name or value cannot be used in an attribute. + * + * @param writer The writer to write to + * @return <code>true</code> if something was written + */ + public boolean writeAttribute(DataWriter writer) + { + if (columnFormattingMethod == AS_TABLE_ATTRIBUTE) + { + writer.writeAttribute(null, columnName, columnValue); + return true; + } + else + { + return false; + } + } + + /** + * Writes any sub elements necessary for the column. If no sub elements + * are required, then this method does nothing. + * + * @param writer The writer to write to + * @return <code>true</code> if something was written + */ + public boolean writeSubElement(DataWriter writer) + { + if (columnFormattingMethod != AS_TABLE_ATTRIBUTE) + { + writer.printlnIfPrettyPrinting(); + writer.indentIfPrettyPrinting(2); + if (columnFormattingMethod == AS_SUBTAG) + { + writer.writeElementStart(null, columnName); + writeText(writer, columnValue, valueBase64Encoded); + } + else + { + writer.writeElementStart(null, "column"); + if (columnFormattingMethod == AS_COLUMN_ATTRIBUTE) + { + writer.writeAttribute(null, "column-name", columnName); + writeText(writer, columnValue, valueBase64Encoded); + } + else if (columnFormattingMethod == AS_VALUE) + { + writer.printlnIfPrettyPrinting(); + writer.indentIfPrettyPrinting(3); + writer.writeElementStart(null, "column-name"); + writeText(writer, columnName, nameBase64Encoded); + writer.writeElementEnd(); + + writer.printlnIfPrettyPrinting(); + writer.indentIfPrettyPrinting(3); + writer.writeElementStart(null, "column-value"); + writeText(writer, columnValue, valueBase64Encoded); + writer.writeElementEnd(); + writer.printlnIfPrettyPrinting(); + writer.indentIfPrettyPrinting(2); + } + } + writer.writeElementEnd(); + return true; + } + else + { + return false; + } + } +} Modified: db/ddlutils/trunk/src/main/java/org/apache/ddlutils/io/DataReader.java URL: http://svn.apache.org/viewvc/db/ddlutils/trunk/src/main/java/org/apache/ddlutils/io/DataReader.java?rev=1098483&r1=1098482&r2=1098483&view=diff ============================================================================== --- db/ddlutils/trunk/src/main/java/org/apache/ddlutils/io/DataReader.java (original) +++ db/ddlutils/trunk/src/main/java/org/apache/ddlutils/io/DataReader.java Mon May 2 05:41:14 2011 @@ -27,8 +27,11 @@ 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; @@ -311,48 +314,62 @@ public class DataReader */ private void readBean(XMLStreamReader xmlReader) throws XMLStreamException, DdlUtilsXMLException { - QName elemQName = xmlReader.getName(); - Table table = _model.findTable(elemQName.getLocalPart(), isCaseSensitive()); + 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 " + xmlReader.getLocation() + + _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."); - readOverElement(xmlReader); } else { DynaBean bean = _model.createDynaBeanFor(table); - - for (int idx = 0; idx < xmlReader.getAttributeCount(); idx++) + + for (int idx = 0; idx < table.getColumnCount(); idx++) { - QName attrQName = xmlReader.getAttributeName(idx); - Column column = table.findColumn(attrQName.getLocalPart(), isCaseSensitive()); + Column column = table.getColumn(idx); + String value = (String)attributes.get(isCaseSensitive() ? column.getName() : column.getName().toLowerCase()); - if (column == null) + if (value != 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)); + setColumnValue(bean, table, column, value); } } - 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. + * 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 bean The bean to fill - * @param table The table definition + * @param data Where to store the values */ - private void readColumnSubElements(XMLStreamReader xmlReader, DynaBean bean, Table table) throws XMLStreamException, DdlUtilsXMLException + private void readColumnSubElements(XMLStreamReader xmlReader, Map data) throws XMLStreamException, DdlUtilsXMLException { int eventType = XMLStreamReader.START_ELEMENT; @@ -361,7 +378,7 @@ public class DataReader eventType = xmlReader.next(); if (eventType == XMLStreamReader.START_ELEMENT) { - readColumnSubElement(xmlReader, bean, table); + readColumnSubElement(xmlReader, data); } } } @@ -370,48 +387,128 @@ public class DataReader * 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 + * @param data Where to store the values */ - private void readColumnSubElement(XMLStreamReader xmlReader, DynaBean bean, Table table) throws XMLStreamException, DdlUtilsXMLException + 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); + QName attrQName = xmlReader.getAttributeName(idx); + String value = xmlReader.getAttributeValue(idx); - if (DatabaseIO.BASE64_ATTR_NAME.equals(attrQName.getLocalPart()) && - "true".equalsIgnoreCase(xmlReader.getAttributeValue(idx))) + if (DatabaseIO.BASE64_ATTR_NAME.equals(attrQName.getLocalPart())) { - usesBase64 = true; - break; + if ("true".equalsIgnoreCase(value)) + { + usesBase64 = true; + } + } + else + { + attributes.put(attrQName.getLocalPart(), value); } } - Column column = table.findColumn(elemQName.getLocalPart(), isCaseSensitive()); + int eventType = XMLStreamReader.START_ELEMENT; + StringBuffer content = new StringBuffer(); - if (column == null) + while (eventType != XMLStreamReader.END_ELEMENT) { - _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."); + 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()); + } } - else + + String value = content.toString().trim(); + + if (usesBase64) { - String value = xmlReader.getElementText(); + value = new String(Base64.decodeBase64(value.getBytes())); + } - if (value != null) + String name = elemQName.getLocalPart(); + + if ("table-name".equals(name)) + { + data.put("table-name", value); + } + else + { + if ("column".equals(name)) { - value = value.trim(); + name = (String)attributes.get("column-name"); + } + if (attributes.containsKey("column-value")) + { + value = (String)attributes.get("column-value"); + } + data.put(name, value); + } + consumeRestOfElement(xmlReader); + } - if (usesBase64) + + /** + * 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)) { - value = new String(Base64.decodeBase64(value.getBytes())); + usesBase64 = true; } - setColumnValue(bean, table, column, value); + 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); } @@ -445,33 +542,6 @@ public class DataReader 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 Modified: db/ddlutils/trunk/src/main/java/org/apache/ddlutils/io/DataWriter.java URL: http://svn.apache.org/viewvc/db/ddlutils/trunk/src/main/java/org/apache/ddlutils/io/DataWriter.java?rev=1098483&r1=1098482&r2=1098483&view=diff ============================================================================== --- db/ddlutils/trunk/src/main/java/org/apache/ddlutils/io/DataWriter.java (original) +++ db/ddlutils/trunk/src/main/java/org/apache/ddlutils/io/DataWriter.java Mon May 2 05:41:14 2011 @@ -20,39 +20,25 @@ package org.apache.ddlutils.io; */ import java.io.OutputStream; -import java.io.UnsupportedEncodingException; import java.io.Writer; import java.util.ArrayList; import java.util.Collection; -import java.util.HashMap; import java.util.Iterator; import java.util.List; -import java.util.Map; - import org.apache.commons.beanutils.DynaBean; -import org.apache.commons.codec.binary.Base64; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.ddlutils.dynabean.SqlDynaBean; import org.apache.ddlutils.dynabean.SqlDynaClass; -import org.apache.ddlutils.io.converters.ConversionException; import org.apache.ddlutils.io.converters.SqlTypeConverter; import org.apache.ddlutils.model.Column; import org.apache.ddlutils.model.Table; /** * Writes dyna beans matching a specified database model into an XML file. - * - * TODO: Make names (tables, columns) XML-compliant - * - * @version $Revision: 289996 $ */ public class DataWriter extends PrettyPrintingXmlWriter { - /** String values with a size not bigger than this value will be written to attributes; - if their size is longer, then a sub element is generated instead. */ - private static final int MAX_ATTRIBUTE_LENGTH = 255; - /** Our log. */ private final Log _log = LogFactory.getLog(DataWriter.class); @@ -139,162 +125,36 @@ public class DataWriter extends PrettyPr */ public void write(SqlDynaBean bean) throws DataWriterException { - SqlDynaClass dynaClass = (SqlDynaClass)bean.getDynaClass(); - Table table = dynaClass.getTable(); - HashMap subElements = new HashMap(); + SqlDynaClass dynaClass = (SqlDynaClass)bean.getDynaClass(); + Table table = dynaClass.getTable(); + TableXmlWriter tableWriter = new TableXmlWriter(table); + List columnWriters = new ArrayList(); - try + for (int idx = 0; idx < table.getColumnCount(); idx++) { - indentIfPrettyPrinting(1); - writeElementStart(null, table.getName()); - for (int idx = 0; idx < table.getColumnCount(); idx++) - { - Column column = table.getColumn(idx); - Object value = bean.get(column.getName()); - SqlTypeConverter converter = _converterConf.getRegisteredConverter(table, column); - String valueAsText = null; + Column column = table.getColumn(idx); + Object value = bean.get(column.getName()); + SqlTypeConverter converter = _converterConf.getRegisteredConverter(table, column); + String valueAsText = null; - if (converter == null) - { - if (value != null) - { - valueAsText = value.toString(); - } - } - else - { - valueAsText = converter.convertToString(value, column.getTypeCode()); - } - if (valueAsText != null) - { - // we create an attribute only if the text is not too long - // and if it does not contain special characters - if ((valueAsText.length() > MAX_ATTRIBUTE_LENGTH) || analyzeText(valueAsText, null)) - { - // we defer writing the sub elements - subElements.put(column.getName(), valueAsText); - } - else - { - writeAttribute(null, column.getName(), valueAsText); - } - } - } - if (!subElements.isEmpty()) + if (converter == null) { - List cutPoints = new ArrayList(); - - for (Iterator it = subElements.entrySet().iterator(); it.hasNext();) + if (value != null) { - Map.Entry entry = (Map.Entry)it.next(); - String content = entry.getValue().toString(); - - printlnIfPrettyPrinting(); - indentIfPrettyPrinting(2); - writeElementStart(null, entry.getKey().toString()); - - // if the content contains special characters, we have to apply base64 encoding to it - // if the content is too short, then it has to contain special characters (otherwise - // it would have been written as an attribute already), otherwise we check - cutPoints.clear(); - - boolean writeBase64Encoded = analyzeText(content, cutPoints); - - if (writeBase64Encoded) - { - writeAttribute(null, DatabaseIO.BASE64_ATTR_NAME, "true"); - try { - writeCData(new String(Base64.encodeBase64(content.getBytes()), getEncoding())); - } - catch (UnsupportedEncodingException ex) { - throw new DataWriterException(ex); - } - } - else - { - if (cutPoints.isEmpty()) - { - writeCData(content); - } - else - { - int lastPos = 0; - - for (Iterator cutPointIt = cutPoints.iterator(); cutPointIt.hasNext();) - { - int curPos = ((Integer)cutPointIt.next()).intValue(); - - writeCData(content.substring(lastPos, curPos)); - lastPos = curPos; - } - if (lastPos < content.length()) - { - writeCData(content.substring(lastPos)); - } - } - } - - writeElementEnd(); + valueAsText = value.toString(); } - printlnIfPrettyPrinting(); - indentIfPrettyPrinting(1); } - writeElementEnd(); - printlnIfPrettyPrinting(); - } - catch (ConversionException ex) - { - throw new DataWriterException(ex); - } - } - - /** - * Determines whether the given string contains special characters that cannot - * be used in XML, and if not, finds the cut points where to split the text - * when writing it in a CDATA section. - * - * @param text The text - * @param cutPoints Will be filled with cut points to split the text when writing it - * in a CDATA section (only if the method returns <code>false</code>) - * @return <code>true</code> if the text contains special characters - */ - private boolean analyzeText(String text, List cutPoints) - { - List tmpCutPoints = cutPoints == null ? null : new ArrayList(); - int numChars = text.length(); - int numFoundCDataEndChars = 0; - - for (int charPos = 0; charPos < numChars; charPos++) - { - char c = text.charAt(charPos); - - if ((c < 0x0020) && (c != '\n') && (c != '\r') && (c != '\t')) + else { - return true; + valueAsText = converter.convertToString(value, column.getTypeCode()); } - else if (cutPoints != null) + if (valueAsText != null) { - if ((c == ']') && ((numFoundCDataEndChars == 0) || (numFoundCDataEndChars == 1))) - { - numFoundCDataEndChars++; - } - else if ((c == '>') && (numFoundCDataEndChars == 2)) - { - // we have to split the CDATA right here before the '>' (see DDLUTILS-174) - tmpCutPoints.add(new Integer(charPos)); - numFoundCDataEndChars = 0; - } - else - { - numFoundCDataEndChars = 0; - } + columnWriters.add(new ColumnXmlWriter(column, valueAsText)); } } - if (cutPoints != null) - { - cutPoints.addAll(tmpCutPoints); - } - return false; + + tableWriter.write(columnWriters, this); } /** Added: db/ddlutils/trunk/src/main/java/org/apache/ddlutils/io/ModelXmlWriter.java URL: http://svn.apache.org/viewvc/db/ddlutils/trunk/src/main/java/org/apache/ddlutils/io/ModelXmlWriter.java?rev=1098483&view=auto ============================================================================== --- db/ddlutils/trunk/src/main/java/org/apache/ddlutils/io/ModelXmlWriter.java (added) +++ db/ddlutils/trunk/src/main/java/org/apache/ddlutils/io/ModelXmlWriter.java Mon May 2 05:41:14 2011 @@ -0,0 +1,64 @@ +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.util.Iterator; +import java.util.List; + +/** + * Base class providing helper functions for writing model elements to XML. + */ +public abstract class ModelXmlWriter +{ + protected void writeText(DataWriter writer, String value, boolean isBase64Encoded) + { + if (isBase64Encoded) + { + writer.writeAttribute(null, "base64", "true"); + writer.writeCharacters(value); + } + else + { + List cutPoints = XMLUtils.findCDataCutPoints(value); + + // if the content contains special characters, we have to apply base64 encoding to it + if (cutPoints.isEmpty()) + { + writer.writeCharacters(value); + } + else + { + int lastPos = 0; + + for (Iterator cutPointIt = cutPoints.iterator(); cutPointIt.hasNext();) + { + int curPos = ((Integer)cutPointIt.next()).intValue(); + + writer.writeCData(value.substring(lastPos, curPos)); + lastPos = curPos; + } + if (lastPos < value.length()) + { + writer.writeCData(value.substring(lastPos)); + } + } + } + } +} Modified: db/ddlutils/trunk/src/main/java/org/apache/ddlutils/io/PrettyPrintingXmlWriter.java URL: http://svn.apache.org/viewvc/db/ddlutils/trunk/src/main/java/org/apache/ddlutils/io/PrettyPrintingXmlWriter.java?rev=1098483&r1=1098482&r2=1098483&view=diff ============================================================================== --- db/ddlutils/trunk/src/main/java/org/apache/ddlutils/io/PrettyPrintingXmlWriter.java (original) +++ db/ddlutils/trunk/src/main/java/org/apache/ddlutils/io/PrettyPrintingXmlWriter.java Mon May 2 05:41:14 2011 @@ -373,4 +373,24 @@ public class PrettyPrintingXmlWriter } } } + + /** + * Writes a text segment. + * + * @param data The data to write + */ + public void writeCharacters(String data) throws DdlUtilsXMLException + { + if (data != null) + { + try + { + _writer.writeCharacters(data); + } + catch (XMLStreamException ex) + { + throwException(ex); + } + } + } } Added: db/ddlutils/trunk/src/main/java/org/apache/ddlutils/io/TableXmlWriter.java URL: http://svn.apache.org/viewvc/db/ddlutils/trunk/src/main/java/org/apache/ddlutils/io/TableXmlWriter.java?rev=1098483&view=auto ============================================================================== --- db/ddlutils/trunk/src/main/java/org/apache/ddlutils/io/TableXmlWriter.java (added) +++ db/ddlutils/trunk/src/main/java/org/apache/ddlutils/io/TableXmlWriter.java Mon May 2 05:41:14 2011 @@ -0,0 +1,115 @@ +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.util.Iterator; +import java.util.List; +import org.apache.ddlutils.model.Table; + +/** + * Base interface for different strategies to write the XML for a data bean for a specific table. + */ +public class TableXmlWriter extends ModelXmlWriter +{ + private static final int AS_TAG_NAME = 0; + private static final int AS_ATTRIBUTE = 1; + private static final int AS_SUB_TAG = 2; + + private final String tableName; + private final int formattingMethod; + private final boolean base64Encoded; + + public TableXmlWriter(Table table) + { + if (XMLUtils.hasIllegalXMLCharacters(table.getName())) + { + tableName = XMLUtils.base64Encode(table.getName()); + formattingMethod = AS_SUB_TAG; + base64Encoded = true; + } + else + { + tableName = table.getName(); + base64Encoded = false; + if (tableName.length() > XMLUtils.MAX_NAME_LENGTH) + { + formattingMethod = AS_SUB_TAG; + } + else if ("table".equals(tableName) || !XMLUtils.isWellFormedXMLName(tableName)) + { + formattingMethod = AS_ATTRIBUTE; + } + else + { + formattingMethod = AS_TAG_NAME; + } + } + } + + /** + * Write the table data to XML to the given writer. + * + * @param columnXmlWriters A list of column xml writers for writing out the bean's values to XML + * @param writer The writer to write to + */ + public void write(List columnXmlWriters, DataWriter writer) + { + writer.indentIfPrettyPrinting(1); + if (formattingMethod == AS_TAG_NAME) + { + writer.writeElementStart(null, tableName); + } + else + { + writer.writeElementStart(null, "table"); + } + if (formattingMethod == AS_ATTRIBUTE) + { + writer.writeAttribute(null, "table-name", tableName); + } + for (Iterator it = columnXmlWriters.iterator(); it.hasNext();) + { + ((ColumnXmlWriter)it.next()).writeAttribute(writer); + } + + boolean hasSubTags = false; + + if (formattingMethod == AS_SUB_TAG) + { + writer.printlnIfPrettyPrinting(); + writer.indentIfPrettyPrinting(2); + writer.writeElementStart(null, "table-name"); + writeText(writer, tableName, base64Encoded); + writer.writeElementEnd(); + hasSubTags = true; + } + for (Iterator it = columnXmlWriters.iterator(); it.hasNext();) + { + hasSubTags = ((ColumnXmlWriter)it.next()).writeSubElement(writer) || hasSubTags; + } + if (hasSubTags) + { + writer.printlnIfPrettyPrinting(); + writer.indentIfPrettyPrinting(1); + } + writer.writeElementEnd(); + writer.printlnIfPrettyPrinting(); + } +} Modified: db/ddlutils/trunk/src/main/java/org/apache/ddlutils/io/XMLUtils.java URL: http://svn.apache.org/viewvc/db/ddlutils/trunk/src/main/java/org/apache/ddlutils/io/XMLUtils.java?rev=1098483&r1=1098482&r2=1098483&view=diff ============================================================================== --- db/ddlutils/trunk/src/main/java/org/apache/ddlutils/io/XMLUtils.java (original) +++ db/ddlutils/trunk/src/main/java/org/apache/ddlutils/io/XMLUtils.java Mon May 2 05:41:14 2011 @@ -19,9 +19,14 @@ package org.apache.ddlutils.io; * under the License. */ +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.List; +import org.apache.commons.codec.binary.Base64; + /** * <p>Contains basic utility methods for XML.</p> - * This class is borrowed from <a href='http://commons.apache.org/betwixt/'>Apache Commons Betwixt</a> + * Parts of this class are borrowed from <a href='http://commons.apache.org/betwixt/'>Apache Commons Betwixt</a> * whose class in turn is based on code in <a href='http://xerces.apache.org/xerces2-j/index.html'>Apache Xerces</a>. * <p>The code for {@link #isWellFormedXMLName} is based on code in * <code>org.apache.xerces.util.XMLChar</code> @@ -34,10 +39,14 @@ package org.apache.ddlutils.io; * @author Arnaud Le Hors, IBM * @author Rahul Srivastava, Sun Microsystems Inc. * @author Robert Burrell Donkin - * @version $Revision: $ */ public class XMLUtils { + /** Maximum length of attribute values that we want to generate. */ + public static final int MAX_ATTRIBUTE_LENGTH = 255; + /** Maximum length of a tag or attribute name that we want to generate. */ + public static final int MAX_NAME_LENGTH = 255; + /** Name start character mask. */ private static final int MASK_NAME_START = 0x01; /** Name character mask. */ @@ -295,4 +304,88 @@ public class XMLUtils { return (c < 0x10000) && ((CHARS[c] & MASK_NAME_START) != 0); } + + /** + * Determines whether the given string contains special characters that cannot + * be used in XML. + * + * @param text The text + * @return <code>true</code> if the text contains special characters + */ + public static boolean hasIllegalXMLCharacters(String text) + { + int numChars = text.length(); + + for (int charPos = 0; charPos < numChars; charPos++) + { + char c = text.charAt(charPos); + + if ((c != 0x9) && (c != 0xA) && (c != 0xD) && ((c < 0x20) || (c > 0xD7FF)) && ((c < 0xE000) || (c > 0xFFFD)) && ((c < 0x10000) || (c > 0x10FFFF))) + { + return true; + } + } + return false; + } + + /** + * Encodes the given value with Base64. + * + * @param value The value to encode + * @return The encoded value + */ + public static String base64Encode(String value) + { + try + { + return value == null ? null : new String(Base64.encodeBase64(value.getBytes("UTF-8")), "UTF-8"); + } + catch (UnsupportedEncodingException ex) + { + throw new IllegalStateException(ex); + } + } + + /** + * Determines whether the given string contains special characters that cannot + * be used in XML, and if not, finds the cut points where to split the text + * when writing it in a CDATA section. + * + * @param text The text + * @return <code>null</code> if the text contains special characters, or the list of cut points otherwise + */ + public static List findCDataCutPoints(String text) + { + List cutPoints = new ArrayList(); + int numChars = text.length(); + int numFoundCDataEndChars = 0; + + for (int charPos = 0; charPos < numChars; charPos++) + { + char c = text.charAt(charPos); + + if ((c != 0x9) && (c != 0xA) && (c != 0xD) && ((c < 0x20) || (c > 0xD7FF)) && ((c < 0xE000) || (c > 0xFFFD)) && ((c < 0x10000) || (c > 0x10FFFF))) + { + return null; + } + else + { + if ((c == ']') && ((numFoundCDataEndChars == 0) || (numFoundCDataEndChars == 1))) + { + numFoundCDataEndChars++; + } + else if ((c == '>') && (numFoundCDataEndChars == 2)) + { + // we have to split the CDATA right here before the '>' (see DDLUTILS-174) + cutPoints.add(new Integer(charPos)); + numFoundCDataEndChars = 0; + } + else + { + numFoundCDataEndChars = 0; + } + } + } + return cutPoints; + } }