Added reading and writing of onUpdate and onDelete attributes from/to XML

git-svn-id: https://svn.apache.org/repos/asf/db/ddlutils/trunk@573150 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/src/java/org/apache/ddlutils/io/DatabaseIO.java b/src/java/org/apache/ddlutils/io/DatabaseIO.java
index 5e296b9..8a09afc 100644
--- a/src/java/org/apache/ddlutils/io/DatabaseIO.java
+++ b/src/java/org/apache/ddlutils/io/DatabaseIO.java
@@ -35,7 +35,7 @@
 import javax.xml.stream.XMLStreamReader;
 
 import org.apache.commons.lang.StringUtils;
-import org.apache.ddlutils.DdlUtilsException;
+import org.apache.ddlutils.model.CascadeActionEnum;
 import org.apache.ddlutils.model.Column;
 import org.apache.ddlutils.model.Database;
 import org.apache.ddlutils.model.ForeignKey;
@@ -98,6 +98,10 @@
     public static final QName QNAME_ATTRIBUTE_LOCAL             = new QName(DDLUTILS_NAMESPACE, "local");
     /** Qualified name of the name attribute. */
     public static final QName QNAME_ATTRIBUTE_NAME              = new QName(DDLUTILS_NAMESPACE, "name");
+    /** Qualified name of the onDelete attribute. */
+    public static final QName QNAME_ATTRIBUTE_ON_DELETE         = new QName(DDLUTILS_NAMESPACE, "onDelete");
+    /** Qualified name of the onUpdate attribute. */
+    public static final QName QNAME_ATTRIBUTE_ON_UPDATE         = new QName(DDLUTILS_NAMESPACE, "onUpdate");
     /** Qualified name of the primaryKey attribute. */
     public static final QName QNAME_ATTRIBUTE_PRIMARY_KEY       = new QName(DDLUTILS_NAMESPACE, "primaryKey");
     /** Qualified name of the required attribute. */
@@ -160,7 +164,7 @@
      * @param filename The model file name
      * @return The database model
      */
-    public Database read(String filename) throws DdlUtilsException
+    public Database read(String filename) throws DdlUtilsXMLException
     {
         try
         {
@@ -168,7 +172,7 @@
         }
         catch (IOException ex)
         {
-            throw new DdlUtilsException(ex);
+            throw new DdlUtilsXMLException(ex);
         }
     }
 
@@ -178,7 +182,7 @@
      * @param file The model file
      * @return The database model
      */
-    public Database read(File file) throws DdlUtilsException
+    public Database read(File file) throws DdlUtilsXMLException
     {
         try
         {
@@ -186,7 +190,7 @@
         }
         catch (IOException ex)
         {
-            throw new DdlUtilsException(ex);
+            throw new DdlUtilsXMLException(ex);
         }
     }
 
@@ -196,7 +200,7 @@
      * @param reader The reader that returns the model XML
      * @return The database model
      */
-    public Database read(Reader reader) throws DdlUtilsException
+    public Database read(Reader reader) throws DdlUtilsXMLException
     {
         try
         {
@@ -204,7 +208,7 @@
         }
         catch (XMLStreamException ex)
         {
-            throw new DdlUtilsException(ex);
+            throw new DdlUtilsXMLException(ex);
         }
     }
 
@@ -214,7 +218,7 @@
      * @param source The input source
      * @return The database model
      */
-    public Database read(InputSource source) throws DdlUtilsException
+    public Database read(InputSource source) throws DdlUtilsXMLException
     {
         try
         {
@@ -222,7 +226,7 @@
         }
         catch (XMLStreamException ex)
         {
-            throw new DdlUtilsException(ex);
+            throw new DdlUtilsXMLException(ex);
         }
     }
 
@@ -246,7 +250,7 @@
      * @param xmlReader The reader
      * @return The database model
      */
-    private Database read(XMLStreamReader xmlReader) throws DdlUtilsException
+    private Database read(XMLStreamReader xmlReader) throws DdlUtilsXMLException
     {
         Database model = null;
 
@@ -266,11 +270,11 @@
         }
         catch (IOException ex)
         {
-            throw new DdlUtilsException(ex);
+            throw new DdlUtilsXMLException(ex);
         }
         catch (XMLStreamException ex)
         {
-            throw new DdlUtilsException(ex);
+            throw new DdlUtilsXMLException(ex);
         }
         if (model != null)
         {
@@ -483,6 +487,14 @@
             {
                 foreignKey.setName(xmlReader.getAttributeValue(idx));
             }
+            else if (isSameAs(attrQName, QNAME_ATTRIBUTE_ON_UPDATE))
+            {
+                foreignKey.setOnUpdate(getAttributeValueAsCascadeEnum(xmlReader, idx));
+            }
+            else if (isSameAs(attrQName, QNAME_ATTRIBUTE_ON_DELETE))
+            {
+                foreignKey.setOnDelete(getAttributeValueAsCascadeEnum(xmlReader, idx));
+            }
         }
         readReferenceElements(xmlReader, foreignKey);
         consumeRestOfElement(xmlReader);
@@ -707,7 +719,7 @@
      * @param attributeIdx The index of the attribute
      * @return The attribute's value as a boolean
      */
-    private boolean getAttributeValueAsBoolean(XMLStreamReader xmlReader, int attributeIdx) throws DdlUtilsException
+    private boolean getAttributeValueAsBoolean(XMLStreamReader xmlReader, int attributeIdx) throws DdlUtilsXMLException
     {
         String value = xmlReader.getAttributeValue(attributeIdx);
 
@@ -721,7 +733,30 @@
         }
         else
         {
-            throw new DdlUtilsException("Illegal boolean value '" + value +"' for attribute " + xmlReader.getAttributeLocalName(attributeIdx));
+            throw new DdlUtilsXMLException("Illegal boolean value '" + value +"' for attribute " + xmlReader.getAttributeLocalName(attributeIdx));
+        }
+    }
+
+    /**
+     * Returns the value of the indicated attribute of the current element as a boolean.
+     * If the value is not a valid boolean, then an exception is thrown.
+     * 
+     * @param xmlReader    The xml reader
+     * @param attributeIdx The index of the attribute
+     * @return The attribute's value as a boolean
+     */
+    private CascadeActionEnum getAttributeValueAsCascadeEnum(XMLStreamReader xmlReader, int attributeIdx) throws DdlUtilsXMLException
+    {
+        String            value     = xmlReader.getAttributeValue(attributeIdx);
+        CascadeActionEnum enumValue = value == null ? null : CascadeActionEnum.getEnum(value.toLowerCase());
+
+        if (enumValue == null)
+        {
+            throw new DdlUtilsXMLException("Illegal boolean value '" + value +"' for attribute " + xmlReader.getAttributeLocalName(attributeIdx));
+        }
+        else
+        {
+            return enumValue;
         }
     }
 
@@ -795,7 +830,7 @@
         }
         catch (Exception ex)
         {
-            throw new DdlUtilsException(ex);
+            throw new DdlUtilsXMLException(ex);
         }
     }
 
@@ -917,6 +952,14 @@
         writeElementStart(xmlWriter, QNAME_ELEMENT_FOREIGN_KEY);
         writeAttribute(xmlWriter, QNAME_ATTRIBUTE_FOREIGN_TABLE, foreignKey.getForeignTableName());
         writeAttribute(xmlWriter, QNAME_ATTRIBUTE_NAME,          foreignKey.getName());
+        if (foreignKey.getOnUpdate() != CascadeActionEnum.NONE)
+        {
+            writeAttribute(xmlWriter, QNAME_ATTRIBUTE_ON_UPDATE, foreignKey.getOnUpdate().getName());
+        }
+        if (foreignKey.getOnDelete() != CascadeActionEnum.NONE)
+        {
+            writeAttribute(xmlWriter, QNAME_ATTRIBUTE_ON_DELETE, foreignKey.getOnDelete().getName());
+        }
         if (foreignKey.getReferenceCount() > 0)
         {
             xmlWriter.printlnIfPrettyPrinting();
diff --git a/src/test/org/apache/ddlutils/io/TestDatabaseIO.java b/src/test/org/apache/ddlutils/io/TestDatabaseIO.java
index e84ddff..a35c7db 100644
--- a/src/test/org/apache/ddlutils/io/TestDatabaseIO.java
+++ b/src/test/org/apache/ddlutils/io/TestDatabaseIO.java
@@ -22,6 +22,7 @@
 import java.io.StringReader;

 import java.io.StringWriter;

 import java.sql.Types;

+import java.util.Iterator;

 

 import junit.framework.TestCase;

 

@@ -1037,6 +1038,344 @@
     }

 

     /**

+     * Tests a database model containing foreignkeys with onUpdate values.

+     */

+    public void testForeignkeysWithOnUpdate() throws Exception

+    {

+        StringBuffer modelXml = new StringBuffer();

+

+        modelXml.append("<database name='test'>\n");

+        modelXml.append("  <table name='SomeTable'\n");

+        modelXml.append("         description='Some table'>\n");

+        modelXml.append("    <column name='ID'\n");

+        modelXml.append("            type='VARCHAR'\n");

+        modelXml.append("            size='16'\n");

+        modelXml.append("            primaryKey='true'\n");

+        modelXml.append("            required='true'\n");

+        modelXml.append("            description='The primary key'/>\n");

+        modelXml.append("  </table>\n");

+        modelXml.append("  <table name='AnotherTable'\n");

+        modelXml.append("         description='And another table'>\n");

+        modelXml.append("    <column name='Some_ID'\n");

+        modelXml.append("            type='VARCHAR'\n");

+        modelXml.append("            size='16'\n");

+        modelXml.append("            description='The foreign key'/>\n");

+        for (Iterator it = CascadeActionEnum.iterator(); it.hasNext();)

+        {

+            CascadeActionEnum enumValue = (CascadeActionEnum)it.next();

+

+            modelXml.append("    <foreign-key name='foreignkey ");

+            modelXml.append(enumValue.getName());

+            modelXml.append("' foreignTable='SomeTable' onUpdate='");

+            modelXml.append(enumValue.getName());

+            modelXml.append("'>\n");

+            modelXml.append("       <reference local='Some_ID' foreign='ID'/>\n");

+            modelXml.append("    </foreign-key>\n");

+        }

+        modelXml.append("  </table>\n");

+        modelXml.append("</database>");

+

+        Database model = readModel(modelXml.toString());

+

+        assertEquals("test", model.getName());

+        assertEquals(2, model.getTableCount());

+

+        Table someTable = model.getTable(0);

+

+        assertEquals("SomeTable", "Some table", 1, 1, 0, 0, 0,

+                     someTable);

+        assertEquals("ID", Types.VARCHAR, 16, 0, null, "The primary key", null, true, true, false,

+                     someTable.getColumn(0));

+

+        Table anotherTable = model.getTable(1);

+

+        assertEquals("AnotherTable", "And another table", 1, 0, 0, CascadeActionEnum.getEnumList().size(), 0,

+                     anotherTable);

+        assertEquals("Some_ID", Types.VARCHAR, 16, 0, null, "The foreign key", null, false, false, false,

+                     anotherTable.getColumn(0));

+

+        int idx = 0;

+        

+        for (Iterator it = CascadeActionEnum.iterator(); it.hasNext(); idx++)

+        {

+            CascadeActionEnum enumValue = (CascadeActionEnum)it.next();

+            ForeignKey        fk        = anotherTable.getForeignKey(idx);

+

+            assertEquals("foreignkey " + enumValue.getName(), enumValue, CascadeActionEnum.NONE, someTable, 1, fk);

+            assertEquals(anotherTable.getColumn(0), someTable.getColumn(0), fk.getReference(0));

+        }

+

+        modelXml.setLength(0);

+        modelXml.append("<?xml version='1.0' encoding='UTF-8'?>\n");

+        modelXml.append("<database name=\"test\">\n");

+        modelXml.append("  <table name=\"SomeTable\" description=\"Some table\">\n");

+        modelXml.append("    <column name=\"ID\" primaryKey=\"true\" required=\"true\" type=\"VARCHAR\" size=\"16\" autoIncrement=\"false\" description=\"The primary key\" />\n");

+        modelXml.append("  </table>\n");

+        modelXml.append("  <table name=\"AnotherTable\" description=\"And another table\">\n");

+        modelXml.append("    <column name=\"Some_ID\" primaryKey=\"false\" required=\"false\" type=\"VARCHAR\" size=\"16\" autoIncrement=\"false\" description=\"The foreign key\" />\n");

+        for (Iterator it = CascadeActionEnum.iterator(); it.hasNext(); idx++)

+        {

+            CascadeActionEnum enumValue = (CascadeActionEnum)it.next();

+

+            modelXml.append("    <foreign-key foreignTable=\"SomeTable\" name=\"foreignkey ");

+            modelXml.append(enumValue.getName());

+            if (enumValue != CascadeActionEnum.NONE)

+            {

+                modelXml.append("\" onUpdate=\"");

+                modelXml.append(enumValue.getName());

+            }

+            modelXml.append("\">\n");

+            modelXml.append("      <reference local=\"Some_ID\" foreign=\"ID\" />\n");

+            modelXml.append("    </foreign-key>\n");

+        }

+        modelXml.append("  </table>\n");

+        modelXml.append("</database>\n");

+

+        assertEquals(modelXml.toString(), model);

+    }

+

+    /**

+     * Tests a database model containing foreignkeys with onDelete values.

+     */

+    public void testForeignkeysWithOnDelete() throws Exception

+    {

+        StringBuffer modelXml = new StringBuffer();

+

+        modelXml.append("<database name='test'>\n");

+        modelXml.append("  <table name='SomeTable'\n");

+        modelXml.append("         description='Some table'>\n");

+        modelXml.append("    <column name='ID'\n");

+        modelXml.append("            type='VARCHAR'\n");

+        modelXml.append("            size='16'\n");

+        modelXml.append("            primaryKey='true'\n");

+        modelXml.append("            required='true'\n");

+        modelXml.append("            description='The primary key'/>\n");

+        modelXml.append("  </table>\n");

+        modelXml.append("  <table name='AnotherTable'\n");

+        modelXml.append("         description='And another table'>\n");

+        modelXml.append("    <column name='Some_ID'\n");

+        modelXml.append("            type='VARCHAR'\n");

+        modelXml.append("            size='16'\n");

+        modelXml.append("            description='The foreign key'/>\n");

+        for (Iterator it = CascadeActionEnum.iterator(); it.hasNext();)

+        {

+            CascadeActionEnum enumValue = (CascadeActionEnum)it.next();

+

+            modelXml.append("    <foreign-key name='foreignkey ");

+            modelXml.append(enumValue.getName());

+            modelXml.append("' foreignTable='SomeTable' onDelete='");

+            modelXml.append(enumValue.getName());

+            modelXml.append("'>\n");

+            modelXml.append("       <reference local='Some_ID' foreign='ID'/>\n");

+            modelXml.append("    </foreign-key>\n");

+        }

+        modelXml.append("  </table>\n");

+        modelXml.append("</database>");

+

+        Database model = readModel(modelXml.toString());

+

+        assertEquals("test", model.getName());

+        assertEquals(2, model.getTableCount());

+

+        Table someTable = model.getTable(0);

+

+        assertEquals("SomeTable", "Some table", 1, 1, 0, 0, 0,

+                     someTable);

+        assertEquals("ID", Types.VARCHAR, 16, 0, null, "The primary key", null, true, true, false,

+                     someTable.getColumn(0));

+

+        Table anotherTable = model.getTable(1);

+

+        assertEquals("AnotherTable", "And another table", 1, 0, 0, CascadeActionEnum.getEnumList().size(), 0,

+                     anotherTable);

+        assertEquals("Some_ID", Types.VARCHAR, 16, 0, null, "The foreign key", null, false, false, false,

+                     anotherTable.getColumn(0));

+

+        int idx = 0;

+        

+        for (Iterator it = CascadeActionEnum.iterator(); it.hasNext(); idx++)

+        {

+            CascadeActionEnum enumValue = (CascadeActionEnum)it.next();

+            ForeignKey        fk        = anotherTable.getForeignKey(idx);

+

+            assertEquals("foreignkey " + enumValue.getName(), CascadeActionEnum.NONE, enumValue, someTable, 1, fk);

+            assertEquals(anotherTable.getColumn(0), someTable.getColumn(0), fk.getReference(0));

+        }

+

+        modelXml.setLength(0);

+        modelXml.append("<?xml version='1.0' encoding='UTF-8'?>\n");

+        modelXml.append("<database name=\"test\">\n");

+        modelXml.append("  <table name=\"SomeTable\" description=\"Some table\">\n");

+        modelXml.append("    <column name=\"ID\" primaryKey=\"true\" required=\"true\" type=\"VARCHAR\" size=\"16\" autoIncrement=\"false\" description=\"The primary key\" />\n");

+        modelXml.append("  </table>\n");

+        modelXml.append("  <table name=\"AnotherTable\" description=\"And another table\">\n");

+        modelXml.append("    <column name=\"Some_ID\" primaryKey=\"false\" required=\"false\" type=\"VARCHAR\" size=\"16\" autoIncrement=\"false\" description=\"The foreign key\" />\n");

+        for (Iterator it = CascadeActionEnum.iterator(); it.hasNext(); idx++)

+        {

+            CascadeActionEnum enumValue = (CascadeActionEnum)it.next();

+

+            modelXml.append("    <foreign-key foreignTable=\"SomeTable\" name=\"foreignkey ");

+            modelXml.append(enumValue.getName());

+            if (enumValue != CascadeActionEnum.NONE)

+            {

+                modelXml.append("\" onDelete=\"");

+                modelXml.append(enumValue.getName());

+            }

+            modelXml.append("\">\n");

+            modelXml.append("      <reference local=\"Some_ID\" foreign=\"ID\" />\n");

+            modelXml.append("    </foreign-key>\n");

+        }

+        modelXml.append("  </table>\n");

+        modelXml.append("</database>\n");

+

+        assertEquals(modelXml.toString(), model);

+    }

+

+    /**

+     * Tests a foreign key with an illegal onUpdate value.

+     */

+    public void testForeignKeyWithIllegalOnUpdateValue()

+    {

+        try

+        {

+            readModel(

+                "<database name='test'>\n" +

+                "  <table name='SomeTable'\n" +

+                "         description='Some table'>\n" +

+                "    <column name='ID'\n" +

+                "            type='VARCHAR'\n" +

+                "            size='16'\n" +

+                "            primaryKey='true'\n" +

+                "            required='true'\n" +

+                "            description='The primary key'/>\n" +

+                "  </table>\n" +

+                "  <table name='AnotherTable'\n" +

+                "         description='And another table'>\n" +

+                "    <column name='Some_ID'\n" +

+                "            type='VARCHAR'\n" +

+                "            size='16'\n" +

+                "            description='The foreign key'/>\n" +

+                "    <foreign-key foreignTable='SomeTable' onUpdate='illegal'>\n" +

+                "       <reference local='Some_ID' foreign='ID'/>\n" +

+                "    </foreign-key>\n" +

+                "  </table>\n" +

+                "</database>");

+

+            fail();

+        }

+        catch (DdlUtilsXMLException ex)

+        {}

+    }

+

+    /**

+     * Tests a foreign key with an empty onUpdate value.

+     */

+    public void testForeignKeyWithEmptyOnUpdateValue()

+    {

+        try

+        {

+            readModel(

+                "<database name='test'>\n" +

+                "  <table name='SomeTable'\n" +

+                "         description='Some table'>\n" +

+                "    <column name='ID'\n" +

+                "            type='VARCHAR'\n" +

+                "            size='16'\n" +

+                "            primaryKey='true'\n" +

+                "            required='true'\n" +

+                "            description='The primary key'/>\n" +

+                "  </table>\n" +

+                "  <table name='AnotherTable'\n" +

+                "         description='And another table'>\n" +

+                "    <column name='Some_ID'\n" +

+                "            type='VARCHAR'\n" +

+                "            size='16'\n" +

+                "            description='The foreign key'/>\n" +

+                "    <foreign-key foreignTable='SomeTable' onUpdate=''>\n" +

+                "       <reference local='Some_ID' foreign='ID'/>\n" +

+                "    </foreign-key>\n" +

+                "  </table>\n" +

+                "</database>");

+

+            fail();

+        }

+        catch (DdlUtilsXMLException ex)

+        {}

+    }

+

+    /**

+     * Tests a foreign key with an illegal onDelete value.

+     */

+    public void testForeignKeyWithIllegalOnDeleteValue()

+    {

+        try

+        {

+            readModel(

+                "<database name='test'>\n" +

+                "  <table name='SomeTable'\n" +

+                "         description='Some table'>\n" +

+                "    <column name='ID'\n" +

+                "            type='VARCHAR'\n" +

+                "            size='16'\n" +

+                "            primaryKey='true'\n" +

+                "            required='true'\n" +

+                "            description='The primary key'/>\n" +

+                "  </table>\n" +

+                "  <table name='AnotherTable'\n" +

+                "         description='And another table'>\n" +

+                "    <column name='Some_ID'\n" +

+                "            type='VARCHAR'\n" +

+                "            size='16'\n" +

+                "            description='The foreign key'/>\n" +

+                "    <foreign-key foreignTable='SomeTable' onDelete='illegal'>\n" +

+                "       <reference local='Some_ID' foreign='ID'/>\n" +

+                "    </foreign-key>\n" +

+                "  </table>\n" +

+                "</database>");

+

+            fail();

+        }

+        catch (DdlUtilsXMLException ex)

+        {}

+    }

+

+    /**

+     * Tests a foreign key with an empty onDelete value.

+     */

+    public void testForeignKeyWithEmptyOnDeleteValue()

+    {

+        try

+        {

+            readModel(

+                "<database name='test'>\n" +

+                "  <table name='SomeTable'\n" +

+                "         description='Some table'>\n" +

+                "    <column name='ID'\n" +

+                "            type='VARCHAR'\n" +

+                "            size='16'\n" +

+                "            primaryKey='true'\n" +

+                "            required='true'\n" +

+                "            description='The primary key'/>\n" +

+                "  </table>\n" +

+                "  <table name='AnotherTable'\n" +

+                "         description='And another table'>\n" +

+                "    <column name='Some_ID'\n" +

+                "            type='VARCHAR'\n" +

+                "            size='16'\n" +

+                "            description='The foreign key'/>\n" +

+                "    <foreign-key foreignTable='SomeTable' onDelete=''>\n" +

+                "       <reference local='Some_ID' foreign='ID'/>\n" +

+                "    </foreign-key>\n" +

+                "  </table>\n" +

+                "</database>");

+

+            fail();

+        }

+        catch (DdlUtilsXMLException ex)

+        {}

+    }

+

+    /**

      * Tests a foreign key referencing a non-existing table.

      */

     public void testForeignKeyReferencingUndefinedTable()