Author: tomdz Date: Tue Oct 10 13:29:40 2006 New Revision: 462544 URL: http://svn.apache.org/viewvc?view=rev&rev=462544 Log: Fixed DDLUTILS-63: values that contain characters that cannot be put into an XML file, are now encoded in Base64 and a sub element will always be used (instead of an attribute) which has the attribute base64="true"
Added: db/ddlutils/trunk/lib/stax-api-1.0.1.jar (with props) db/ddlutils/trunk/lib/wstx-asl-3.0.2.jar (with props) db/ddlutils/trunk/src/test/org/apache/ddlutils/io/TestDataReaderAndWriter.java - copied, changed from r454479, db/ddlutils/trunk/src/test/org/apache/ddlutils/io/TestDataReader.java Removed: db/ddlutils/trunk/lib/stax-1.1.2-dev.jar db/ddlutils/trunk/lib/stax-api-1.0.jar db/ddlutils/trunk/src/test/org/apache/ddlutils/io/TestDataReader.java Modified: db/ddlutils/trunk/.classpath db/ddlutils/trunk/src/java/org/apache/ddlutils/io/DataWriter.java db/ddlutils/trunk/src/java/org/apache/ddlutils/io/DatabaseIO.java db/ddlutils/trunk/src/java/org/apache/ddlutils/io/SetColumnPropertyFromSubElementRule.java db/ddlutils/trunk/src/test/org/apache/ddlutils/RunAllTests.java Modified: db/ddlutils/trunk/.classpath URL: http://svn.apache.org/viewvc/db/ddlutils/trunk/.classpath?view=diff&rev=462544&r1=462543&r2=462544 ============================================================================== --- db/ddlutils/trunk/.classpath (original) +++ db/ddlutils/trunk/.classpath Tue Oct 10 13:29:40 2006 @@ -9,12 +9,12 @@ <classpathentry path="lib/commons-dbcp-1.2.1.jar" exported="true" kind="lib"/> <classpathentry path="lib/commons-digester-1.7.jar" exported="true" kind="lib"/> <classpathentry path="lib/commons-beanutils-1.7.0.jar" exported="true" kind="lib"/> - <classpathentry path="lib/stax-api-1.0.jar" exported="true" kind="lib"/> <classpathentry path="lib/commons-betwixt-0.8-dev.jar" exported="true" kind="lib"/> <classpathentry path="lib/commons-lang-2.1.jar" exported="true" kind="lib"/> <classpathentry path="lib/commons-codec-1.3.jar" exported="true" kind="lib"/> <classpathentry path="lib/jakarta-oro-2.0.8.jar" exported="true" kind="lib"/> <classpathentry path="lib/build-only/ant-1.6.5.jar" kind="lib"/> <classpathentry path="lib/build-only/junit-3.8.2.jar" kind="lib"/> + <classpathentry path="lib/stax-api-1.0.1.jar" kind="lib"/> <classpathentry path="target/classes" kind="output"/> </classpath> Added: db/ddlutils/trunk/lib/stax-api-1.0.1.jar URL: http://svn.apache.org/viewvc/db/ddlutils/trunk/lib/stax-api-1.0.1.jar?view=auto&rev=462544 ============================================================================== Binary file - no diff available. Propchange: db/ddlutils/trunk/lib/stax-api-1.0.1.jar ------------------------------------------------------------------------------ svn:mime-type = application/octet-stream Added: db/ddlutils/trunk/lib/wstx-asl-3.0.2.jar URL: http://svn.apache.org/viewvc/db/ddlutils/trunk/lib/wstx-asl-3.0.2.jar?view=auto&rev=462544 ============================================================================== Binary file - no diff available. Propchange: db/ddlutils/trunk/lib/wstx-asl-3.0.2.jar ------------------------------------------------------------------------------ svn:mime-type = application/octet-stream Modified: db/ddlutils/trunk/src/java/org/apache/ddlutils/io/DataWriter.java URL: http://svn.apache.org/viewvc/db/ddlutils/trunk/src/java/org/apache/ddlutils/io/DataWriter.java?view=diff&rev=462544&r1=462543&r2=462544 ============================================================================== --- db/ddlutils/trunk/src/java/org/apache/ddlutils/io/DataWriter.java (original) +++ db/ddlutils/trunk/src/java/org/apache/ddlutils/io/DataWriter.java Tue Oct 10 13:29:40 2006 @@ -18,6 +18,7 @@ import java.io.OutputStream; import java.io.PrintWriter; +import java.io.Writer; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; @@ -28,6 +29,7 @@ import javax.xml.stream.XMLStreamWriter; 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; @@ -108,6 +110,29 @@ } /** + * Creates a data writer instance using the specified writer. Note that the writer + * needs to be configured using the specified encoding. + * + * @param output The target to write the data XML to + * @param encoding The encoding of the writer + */ + public DataWriter(Writer output, String encoding) throws DataWriterException + { + _output = new PrintWriter(output); + _encoding = encoding; + try + { + XMLOutputFactory factory = XMLOutputFactory.newInstance(); + + _writer = factory.createXMLStreamWriter(_output); + } + catch (XMLStreamException ex) + { + throw new DataWriterException(ex); + } + } + + /** * Determines whether the output shall be pretty-printed. * * @return <code>true</code> if the output is pretty-printed @@ -252,7 +277,9 @@ } if (valueAsText != null) { - if (valueAsText.length() > MAX_ATTRIBUTE_LENGTH) + // 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) || containsSpecialCharacters(valueAsText)) { // we defer writing the sub elements subElements.put(column.getName(), valueAsText); @@ -267,12 +294,25 @@ { for (Iterator it = subElements.entrySet().iterator(); it.hasNext();) { - Map.Entry entry = (Map.Entry)it.next(); - + Map.Entry entry = (Map.Entry)it.next(); + String content = entry.getValue().toString(); + printlnIfPrettyPrinting(); indentIfPrettyPrinting(2); _writer.writeStartElement(entry.getKey().toString()); - _writer.writeCData(entry.getValue().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 we check + if ((content.length() <= MAX_ATTRIBUTE_LENGTH) || containsSpecialCharacters(content)) + { + _writer.writeAttribute(DatabaseIO.BASE64_ATTR_NAME, "true"); + _writer.writeCData(new String(Base64.encodeBase64(content.getBytes()))); + } + else + { + _writer.writeCData(content); + } + _writer.writeEndElement(); } printlnIfPrettyPrinting(); @@ -289,6 +329,29 @@ { throw new DataWriterException(ex); } + } + + /** + * 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 + */ + private boolean containsSpecialCharacters(String text) + { + int numChars = text.length(); + + for (int charPos = 0; charPos < numChars; charPos++) + { + char c = text.charAt(charPos); + + if ((c < 0x0020) && (c != '\n') && (c != '\r') && (c != '\t')) + { + return true; + } + } + return false; } /** Modified: db/ddlutils/trunk/src/java/org/apache/ddlutils/io/DatabaseIO.java URL: http://svn.apache.org/viewvc/db/ddlutils/trunk/src/java/org/apache/ddlutils/io/DatabaseIO.java?view=diff&rev=462544&r1=462543&r2=462544 ============================================================================== --- db/ddlutils/trunk/src/java/org/apache/ddlutils/io/DatabaseIO.java (original) +++ db/ddlutils/trunk/src/java/org/apache/ddlutils/io/DatabaseIO.java Tue Oct 10 13:29:40 2006 @@ -44,6 +44,10 @@ */ public class DatabaseIO { + /** The name of the XML attribute use to denote that teh content of a data XML + element uses Base64 encoding. */ + public static final String BASE64_ATTR_NAME = "base64"; + /** Whether to validate the XML. */ private boolean _validateXml = true; /** Whether to use the internal dtd that comes with DdlUtils. */ Modified: db/ddlutils/trunk/src/java/org/apache/ddlutils/io/SetColumnPropertyFromSubElementRule.java URL: http://svn.apache.org/viewvc/db/ddlutils/trunk/src/java/org/apache/ddlutils/io/SetColumnPropertyFromSubElementRule.java?view=diff&rev=462544&r1=462543&r2=462544 ============================================================================== --- db/ddlutils/trunk/src/java/org/apache/ddlutils/io/SetColumnPropertyFromSubElementRule.java (original) +++ db/ddlutils/trunk/src/java/org/apache/ddlutils/io/SetColumnPropertyFromSubElementRule.java Tue Oct 10 13:29:40 2006 @@ -17,9 +17,11 @@ */ import org.apache.commons.beanutils.PropertyUtils; +import org.apache.commons.codec.binary.Base64; import org.apache.commons.digester.Rule; import org.apache.ddlutils.io.converters.SqlTypeConverter; import org.apache.ddlutils.model.Column; +import org.xml.sax.Attributes; /** * A digester rule for setting a bean property that corresponds to a column @@ -34,6 +36,8 @@ private Column _column; /** The converter for generating the property value from a string. */ private SqlTypeConverter _converter; + /** Whether the element's content uses Base64. */ + private boolean _usesBase64 = false; /** * Creates a new creation rule that sets the property corresponding to the given column. @@ -50,9 +54,45 @@ /** * [EMAIL PROTECTED] */ + public void begin(Attributes attributes) throws Exception + { + for (int idx = 0; idx < attributes.getLength(); idx++) + { + String attrName = attributes.getLocalName(idx); + + if ("".equals(attrName)) + { + attrName = attributes.getQName(idx); + } + if (DatabaseIO.BASE64_ATTR_NAME.equals(attrName) && + "true".equalsIgnoreCase(attributes.getValue(idx))) + { + _usesBase64 = true; + break; + } + } + } + + /** + * [EMAIL PROTECTED] + */ + public void end() throws Exception + { + _usesBase64 = false; + } + + /** + * [EMAIL PROTECTED] + */ public void body(String text) throws Exception { String attrValue = text.trim(); + + if (_usesBase64 && (attrValue != null)) + { + attrValue = new String(Base64.decodeBase64(attrValue.getBytes())); + } + Object propValue = (_converter != null ? _converter.convertFromString(attrValue, _column.getTypeCode()) : attrValue); if (digester.getLogger().isDebugEnabled()) Modified: db/ddlutils/trunk/src/test/org/apache/ddlutils/RunAllTests.java URL: http://svn.apache.org/viewvc/db/ddlutils/trunk/src/test/org/apache/ddlutils/RunAllTests.java?view=diff&rev=462544&r1=462543&r2=462544 ============================================================================== --- db/ddlutils/trunk/src/test/org/apache/ddlutils/RunAllTests.java (original) +++ db/ddlutils/trunk/src/test/org/apache/ddlutils/RunAllTests.java Tue Oct 10 13:29:40 2006 @@ -21,7 +21,7 @@ import org.apache.ddlutils.dynabean.TestDynaSqlQueries; import org.apache.ddlutils.io.TestAlteration; import org.apache.ddlutils.io.TestConstraints; -import org.apache.ddlutils.io.TestDataReader; +import org.apache.ddlutils.io.TestDataReaderAndWriter; import org.apache.ddlutils.io.TestDatabaseIO; import org.apache.ddlutils.io.TestDatatypes; import org.apache.ddlutils.io.converters.TestDateConverter; @@ -93,7 +93,7 @@ suite.addTestSuite(TestSqlBuilder.class); suite.addTestSuite(TestPlatformUtils.class); suite.addTestSuite(TestDatabaseIO.class); - suite.addTestSuite(TestDataReader.class); + suite.addTestSuite(TestDataReaderAndWriter.class); suite.addTestSuite(TestDateConverter.class); suite.addTestSuite(TestTimeConverter.class); suite.addTestSuite(TestAxionPlatform.class); Copied: db/ddlutils/trunk/src/test/org/apache/ddlutils/io/TestDataReaderAndWriter.java (from r454479, db/ddlutils/trunk/src/test/org/apache/ddlutils/io/TestDataReader.java) URL: http://svn.apache.org/viewvc/db/ddlutils/trunk/src/test/org/apache/ddlutils/io/TestDataReaderAndWriter.java?view=diff&rev=462544&p1=db/ddlutils/trunk/src/test/org/apache/ddlutils/io/TestDataReader.java&r1=454479&p2=db/ddlutils/trunk/src/test/org/apache/ddlutils/io/TestDataReaderAndWriter.java&r2=462544 ============================================================================== --- db/ddlutils/trunk/src/test/org/apache/ddlutils/io/TestDataReader.java (original) +++ db/ddlutils/trunk/src/test/org/apache/ddlutils/io/TestDataReaderAndWriter.java Tue Oct 10 13:29:40 2006 @@ -17,78 +17,77 @@ */ import java.io.StringReader; +import java.io.StringWriter; import java.util.ArrayList; import junit.framework.TestCase; import org.apache.commons.beanutils.DynaBean; +import org.apache.ddlutils.dynabean.SqlDynaBean; import org.apache.ddlutils.model.Database; /** - * Tests the [EMAIL PROTECTED] org.apache.ddlutils.io.DataReader} class. + * Tests the [EMAIL PROTECTED] org.apache.ddlutils.io.DataReader} and [EMAIL PROTECTED] org.apache.ddlutils.io.DataWriter} classes. * * @author Thomas Dudziak * @version $Revision: 289996 $ */ -public class TestDataReader extends TestCase +public class TestDataReaderAndWriter extends TestCase { - /** The tested XML database schema. */ - private static final String TEST_SCHEMA = - "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n"+ - "<database name=\"bookstore\">\n"+ - " <table name=\"author\">\n"+ - " <column name=\"author_id\" type=\"INTEGER\" primaryKey=\"true\" required=\"true\"/>\n"+ - " <column name=\"name\" type=\"VARCHAR\" size=\"50\" required=\"true\"/>\n"+ - " <column name=\"organisation\" type=\"VARCHAR\" size=\"50\" required=\"false\"/>\n"+ - " </table>\n"+ - " <table name=\"book\">\n"+ - " <column name=\"book_id\" type=\"INTEGER\" required=\"true\" primaryKey=\"true\" autoIncrement=\"true\"/>\n"+ - " <column name=\"isbn\" type=\"VARCHAR\" size=\"15\" required=\"true\"/>\n"+ - " <column name=\"author_id\" type=\"INTEGER\" required=\"true\"/>\n"+ - " <column name=\"title\" type=\"VARCHAR\" size=\"255\" defaultValue=\"N/A\" required=\"true\"/>\n"+ - " <column name=\"issue_date\" type=\"DATE\" required=\"false\"/>\n"+ - " <foreign-key foreignTable=\"author\">\n"+ - " <reference local=\"author_id\" foreign=\"author_id\"/>\n"+ - " </foreign-key>\n"+ - " <index name=\"book_isbn\">\n"+ - " <index-column name=\"isbn\"/>\n"+ - " </index>\n"+ - " </table>\n"+ - "</database>"; - - /** The test data. */ - private static final String TEST_DATA = - "<data>\n"+ - " <author author_id=\"1\" name=\"Ernest Hemingway\"/>\n"+ - " <author author_id=\"2\" name=\"William Shakespeare\"/>\n"+ - " <book book_id=\"1\" author_id=\"1\">\n"+ - " <isbn>0684830493</isbn>\n"+ - " <title>Old Man And The Sea</title>\n"+ - " <issue_date>1952</issue_date>\n"+ - " </book>\n"+ - " <book book_id=\"2\" author_id=\"2\">\n"+ - " <isbn>0198321465</isbn>\n"+ - " <title>Macbeth</title>\n"+ - " <issue_date>1606</issue_date>\n"+ - " </book>\n"+ - " <book book_id=\"3\" author_id=\"2\">\n"+ - " <isbn>0140707026</isbn>\n"+ - " <title>A Midsummer Night's Dream</title>\n"+ - " <issue_date>1595</issue_date>\n"+ - " </book>\n"+ - "</data>"; - /** * Tests reading the data from XML. */ public void testRead() throws Exception { + final String testSchemaXml = + "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n"+ + "<database name=\"bookstore\">\n"+ + " <table name=\"author\">\n"+ + " <column name=\"author_id\" type=\"INTEGER\" primaryKey=\"true\" required=\"true\"/>\n"+ + " <column name=\"name\" type=\"VARCHAR\" size=\"50\" required=\"true\"/>\n"+ + " <column name=\"organisation\" type=\"VARCHAR\" size=\"50\" required=\"false\"/>\n"+ + " </table>\n"+ + " <table name=\"book\">\n"+ + " <column name=\"book_id\" type=\"INTEGER\" required=\"true\" primaryKey=\"true\" autoIncrement=\"true\"/>\n"+ + " <column name=\"isbn\" type=\"VARCHAR\" size=\"15\" required=\"true\"/>\n"+ + " <column name=\"author_id\" type=\"INTEGER\" required=\"true\"/>\n"+ + " <column name=\"title\" type=\"VARCHAR\" size=\"255\" defaultValue=\"N/A\" required=\"true\"/>\n"+ + " <column name=\"issue_date\" type=\"DATE\" required=\"false\"/>\n"+ + " <foreign-key foreignTable=\"author\">\n"+ + " <reference local=\"author_id\" foreign=\"author_id\"/>\n"+ + " </foreign-key>\n"+ + " <index name=\"book_isbn\">\n"+ + " <index-column name=\"isbn\"/>\n"+ + " </index>\n"+ + " </table>\n"+ + "</database>"; + final String testDataXml = + "<data>\n"+ + " <author author_id=\"1\" name=\"Ernest Hemingway\"/>\n"+ + " <author author_id=\"2\" name=\"William Shakespeare\"/>\n"+ + " <book book_id=\"1\" author_id=\"1\">\n"+ + " <isbn>0684830493</isbn>\n"+ + " <title>Old Man And The Sea</title>\n"+ + " <issue_date>1952</issue_date>\n"+ + " </book>\n"+ + " <book book_id=\"2\" author_id=\"2\">\n"+ + " <isbn>0198321465</isbn>\n"+ + " <title>Macbeth</title>\n"+ + " <issue_date>1606</issue_date>\n"+ + " </book>\n"+ + " <book book_id=\"3\" author_id=\"2\">\n"+ + " <isbn>0140707026</isbn>\n"+ + " <title>A Midsummer Night's Dream</title>\n"+ + " <issue_date>1595</issue_date>\n"+ + " </book>\n"+ + "</data>"; + DatabaseIO modelReader = new DatabaseIO(); modelReader.setUseInternalDtd(true); modelReader.setValidateXml(false); - Database model = modelReader.read(new StringReader(TEST_SCHEMA)); + Database model = modelReader.read(new StringReader(testSchemaXml)); final ArrayList readObjects = new ArrayList(); DataReader dataReader = new DataReader(); @@ -105,7 +104,7 @@ public void end() throws DataSinkException {} }); - dataReader.parse(new StringReader(TEST_DATA)); + dataReader.parse(new StringReader(testDataXml)); assertEquals(5, readObjects.size()); @@ -163,5 +162,68 @@ obj5.get("title").toString()); assertEquals("1595-01-01", obj5.get("issue_date").toString()); // parsed as a java.sql.Date + } + + /** + * Tests special characters in the data XML (for DDLUTILS-63). + */ + public void testSpecialCharacters() throws Exception + { + final String testSchemaXml = + "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n"+ + "<database name=\"test\">\n"+ + " <table name=\"test\">\n"+ + " <column name=\"id\" type=\"INTEGER\" primaryKey=\"true\" required=\"true\"/>\n"+ + " <column name=\"value\" type=\"VARCHAR\" size=\"50\" required=\"true\"/>\n"+ + " </table>\n"+ + "</database>"; + final String testedValue = "Some Special Characters: \u0001\u0009\u0010"; + + DatabaseIO modelIO = new DatabaseIO(); + + modelIO.setUseInternalDtd(true); + modelIO.setValidateXml(false); + + Database model = modelIO.read(new StringReader(testSchemaXml)); + StringWriter output = new StringWriter(); + DataWriter dataWriter = new DataWriter(output, "UTF-8"); + SqlDynaBean bean = (SqlDynaBean)model.createDynaBeanFor(model.getTable(0)); + + bean.set("id", new Integer(1)); + bean.set("value", testedValue); + dataWriter.writeDocumentStart(); + dataWriter.write(bean); + dataWriter.writeDocumentEnd(); + + String dataXml = output.toString(); + + final ArrayList readObjects = new ArrayList(); + DataReader dataReader = new DataReader(); + + dataReader.setModel(model); + dataReader.setSink(new DataSink() { + public void start() throws DataSinkException + {} + + public void addBean(DynaBean bean) throws DataSinkException + { + readObjects.add(bean); + } + + public void end() throws DataSinkException + {} + }); + dataReader.parse(new StringReader(dataXml)); + + assertEquals(1, readObjects.size()); + + DynaBean obj = (DynaBean)readObjects.get(0); + + assertEquals("test", + obj.getDynaClass().getName()); + assertEquals("1", + obj.get("id").toString()); + assertEquals(testedValue, + obj.get("value").toString()); } }