http://git-wip-us.apache.org/repos/asf/cayenne/blob/c58b6f40/cayenne-server/src/main/java/org/apache/cayenne/map/Attribute.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/map/Attribute.java 
b/cayenne-server/src/main/java/org/apache/cayenne/map/Attribute.java
index 34b4596..8c8a48e 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/map/Attribute.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/map/Attribute.java
@@ -21,6 +21,7 @@ package org.apache.cayenne.map;
 
 import java.io.Serializable;
 
+import org.apache.cayenne.configuration.ConfigurationNodeVisitor;
 import org.apache.cayenne.util.CayenneMapEntry;
 import org.apache.cayenne.util.ToStringBuilder;
 import org.apache.cayenne.util.XMLEncoder;
@@ -54,7 +55,8 @@ public abstract class Attribute implements CayenneMapEntry, 
XMLSerializable, Ser
         return new ToStringBuilder(this).append("name", getName()).toString();
     }
 
-    public abstract void encodeAsXML(XMLEncoder encoder);
+    @Override
+    public abstract void encodeAsXML(XMLEncoder encoder, 
ConfigurationNodeVisitor delegate);
 
     /**
      * Returns parent entity that holds this attribute.

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c58b6f40/cayenne-server/src/main/java/org/apache/cayenne/map/CallbackMap.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/map/CallbackMap.java 
b/cayenne-server/src/main/java/org/apache/cayenne/map/CallbackMap.java
index 9cc4b18..b355f43 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/map/CallbackMap.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/map/CallbackMap.java
@@ -106,11 +106,7 @@ public class CallbackMap implements Serializable {
             XMLEncoder encoder) {
 
         for (String methodName : descriptor.getCallbackMethods()) {
-            encoder.print("<");
-            encoder.print(stringCallbackName);
-            encoder.print(" method-name=\"");
-            encoder.print(methodName);
-            encoder.println("\"/>");
+            encoder.start(stringCallbackName).attribute("method-name", 
methodName).end();
         }
     }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c58b6f40/cayenne-server/src/main/java/org/apache/cayenne/map/DataMap.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/map/DataMap.java 
b/cayenne-server/src/main/java/org/apache/cayenne/map/DataMap.java
index b3002df..1cffde2 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/map/DataMap.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/map/DataMap.java
@@ -19,7 +19,6 @@
 
 package org.apache.cayenne.map;
 
-import org.apache.cayenne.CayenneRuntimeException;
 import org.apache.cayenne.ObjectId;
 import org.apache.cayenne.Persistent;
 import org.apache.cayenne.configuration.ConfigurationNode;
@@ -120,7 +119,7 @@ public class DataMap implements Serializable, 
ConfigurationNode, XMLSerializable
         * The namespace in which the data map XML file will be created. This is
         * also the URI to locate a copy of the schema document.
         */
-       public static final String SCHEMA_XSD = 
"http://cayenne.apache.org/schema/9/modelMap";;
+       public static final String SCHEMA_XSD = 
"http://cayenne.apache.org/schema/10/modelMap";;
 
        protected String name;
        protected String location;
@@ -147,8 +146,7 @@ public class DataMap implements Serializable, 
ConfigurationNode, XMLSerializable
        private SortedMap<String, SQLResult> results;
 
        /**
-        * @deprecated since 4.0 unused as listeners are no longer tied to a
-        *             DataMap.
+        * @deprecated since 4.0 unused as listeners are no longer tied to a 
DataMap.
         */
        private List<EntityListener> defaultEntityListeners;
 
@@ -177,13 +175,13 @@ public class DataMap implements Serializable, 
ConfigurationNode, XMLSerializable
        }
 
        public DataMap(String mapName, Map<String, Object> properties) {
-               embeddablesMap = new TreeMap<String, Embeddable>();
-               objEntityMap = new TreeMap<String, ObjEntity>();
-               dbEntityMap = new TreeMap<String, DbEntity>();
-               procedureMap = new TreeMap<String, Procedure>();
+               embeddablesMap = new TreeMap<>();
+               objEntityMap = new TreeMap<>();
+               dbEntityMap = new TreeMap<>();
+               procedureMap = new TreeMap<>();
                queryDescriptorMap = new TreeMap<>();
                defaultEntityListeners = new ArrayList<>(3);
-               results = new TreeMap<String, SQLResult>();
+               results = new TreeMap<>();
                setName(mapName);
                initWithProperties(properties);
        }
@@ -265,8 +263,7 @@ public class DataMap implements Serializable, 
ConfigurationNode, XMLSerializable
                                : ObjEntity.LOCK_TYPE_NONE;
 
                this.defaultPackage = (packageName != null) ? 
packageName.toString() : null;
-               this.quotingSQLIdentifiers = (quoteSqlIdentifier != null) ? 
"true".equalsIgnoreCase(quoteSqlIdentifier
-                               .toString()) : false;
+               this.quotingSQLIdentifiers = (quoteSqlIdentifier != null) ? 
"true".equalsIgnoreCase(quoteSqlIdentifier.toString()) : false;
                this.defaultSchema = (schema != null) ? schema.toString() : 
null;
                this.defaultCatalog = (catalog != null) ? catalog.toString() : 
null;
                this.defaultSuperclass = (superclass != null) ? 
superclass.toString() : null;
@@ -310,96 +307,58 @@ public class DataMap implements Serializable, 
ConfigurationNode, XMLSerializable
         * 
         * @since 1.1
         */
-       public void encodeAsXML(XMLEncoder encoder) {
-               encoder.println("<data-map 
xmlns=\"http://cayenne.apache.org/schema/9/modelMap\"";);
-
-               encoder.indent(1);
-               encoder.println(" 
xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"";);
-               encoder.println(" xsi:schemaLocation=\"" + SCHEMA_XSD + " " + 
SCHEMA_XSD + ".xsd\"");
-
-               encoder.printProjectVersion();
-               encoder.println(">");
-
-               // properties
-               if (defaultLockType == ObjEntity.LOCK_TYPE_OPTIMISTIC) {
-                       encoder.printProperty(DEFAULT_LOCK_TYPE_PROPERTY, 
"optimistic");
-               }
-
-               if (!Util.isEmptyString(defaultPackage)) {
-                       encoder.printProperty(DEFAULT_PACKAGE_PROPERTY, 
defaultPackage);
-               }
-
-               if (!Util.isEmptyString(defaultCatalog)) {
-                       encoder.printProperty(DEFAULT_CATALOG_PROPERTY, 
defaultCatalog);
-               }
-
-               if (!Util.isEmptyString(defaultSchema)) {
-                       encoder.printProperty(DEFAULT_SCHEMA_PROPERTY, 
defaultSchema);
-               }
-
-               if (!Util.isEmptyString(defaultSuperclass)) {
-                       encoder.printProperty(DEFAULT_SUPERCLASS_PROPERTY, 
defaultSuperclass);
-               }
-
-               if (quotingSQLIdentifiers) {
-                       
encoder.printProperty(DEFAULT_QUOTE_SQL_IDENTIFIERS_PROPERTY, 
quotingSQLIdentifiers);
-               }
-
-               if (clientSupported) {
-                       encoder.printProperty(CLIENT_SUPPORTED_PROPERTY, 
"true");
-               }
-
-               if (!Util.isEmptyString(defaultClientPackage)) {
-                       encoder.printProperty(DEFAULT_CLIENT_PACKAGE_PROPERTY, 
defaultClientPackage);
-               }
-
-               if (!Util.isEmptyString(defaultClientSuperclass)) {
-                       
encoder.printProperty(DEFAULT_CLIENT_SUPERCLASS_PROPERTY, 
defaultClientSuperclass);
-               }
-
-               // embeddables
-               encoder.print(getEmbeddableMap());
-
-               // procedures
-               encoder.print(getProcedureMap());
-
-               // DbEntities
-               for (DbEntity dbe : getDbEntityMap().values()) {
-                       dbe.encodeAsXML(encoder);
-               }
-
-               // others...
-               encoder.print(getObjEntityMap());
-               encodeDBRelationshipsAsXML(getDbEntityMap(), encoder);
-               encodeOBJRelationshipsAsXML(getObjEntityMap(), encoder);
-
-               for (QueryDescriptor query : getQueryDescriptors()) {
-                       query.encodeAsXML(encoder);
-               }
-
-               encoder.indent(-1);
-               encoder.println("</data-map>");
+       public void encodeAsXML(XMLEncoder encoder, ConfigurationNodeVisitor 
delegate) {
+               encoder.start("data-map")
+                               .attribute("xmlns", SCHEMA_XSD)
+                               .attribute("xmlns:xsi", 
"http://www.w3.org/2001/XMLSchema-instance";, true)
+                               .attribute("xsi:schemaLocation", SCHEMA_XSD + " 
" + SCHEMA_XSD + ".xsd", true)
+                               .projectVersion()
+                               // properties
+                               .property(DEFAULT_LOCK_TYPE_PROPERTY, 
defaultLockType)
+                               .property(DEFAULT_PACKAGE_PROPERTY, 
defaultPackage)
+                               .property(DEFAULT_CATALOG_PROPERTY, 
defaultCatalog)
+                               .property(DEFAULT_SCHEMA_PROPERTY, 
defaultSchema)
+                               .property(DEFAULT_SUPERCLASS_PROPERTY, 
defaultSuperclass)
+                               
.property(DEFAULT_QUOTE_SQL_IDENTIFIERS_PROPERTY, quotingSQLIdentifiers)
+                               .property(CLIENT_SUPPORTED_PROPERTY, 
clientSupported)
+                               .property(DEFAULT_CLIENT_PACKAGE_PROPERTY, 
defaultClientPackage)
+                               .property(DEFAULT_CLIENT_SUPERCLASS_PROPERTY, 
defaultClientSuperclass)
+                               // elements
+                               .nested(getEmbeddableMap(), delegate)
+                               .nested(getProcedureMap(), delegate)
+                               .nested(getDbEntityMap(), delegate)
+                               .nested(getObjEntityMap(), delegate);
+
+               // and finally relationships
+               encodeDbRelationshipsAsXML(encoder, delegate);
+               encodeObjRelationshipsAsXML(encoder, delegate);
+
+               // descriptors at the end just to keep logic from older versions
+               encoder.nested(getQueryDescriptors(), delegate);
+
+               delegate.visitDataMap(this);
+               encoder.end();
        }
 
        // stores relationships for the map of entities
-       private final void encodeDBRelationshipsAsXML(Map<String, DbEntity> 
entityMap, XMLEncoder encoder) {
-               for (Entity entity : entityMap.values()) {
+       private void encodeDbRelationshipsAsXML(XMLEncoder encoder, 
ConfigurationNodeVisitor delegate) {
+               for (Entity entity : getDbEntityMap().values()) {
                        for (Relationship relationship : 
entity.getRelationships()) {
                                // filter out synthetic
                                if (!relationship.isRuntime()) {
-                                       relationship.encodeAsXML(encoder);
+                                       relationship.encodeAsXML(encoder, 
delegate);
                                }
                        }
                }
        }
 
        // stores relationships for the map of entities
-       private final void encodeOBJRelationshipsAsXML(Map<String, ObjEntity> 
entityMap, XMLEncoder encoder) {
-               for (ObjEntity entity : entityMap.values()) {
+       private void encodeObjRelationshipsAsXML(XMLEncoder encoder, 
ConfigurationNodeVisitor delegate) {
+               for (ObjEntity entity : getObjEntityMap().values()) {
                        for (Relationship relationship : 
entity.getDeclaredRelationships()) {
                                // filter out synthetic
                                if (!relationship.isRuntime()) {
-                                       relationship.encodeAsXML(encoder);
+                                       relationship.encodeAsXML(encoder, 
delegate);
                                }
                        }
                }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c58b6f40/cayenne-server/src/main/java/org/apache/cayenne/map/DbAttribute.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/map/DbAttribute.java 
b/cayenne-server/src/main/java/org/apache/cayenne/map/DbAttribute.java
index 796d69c..37bbc60 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/map/DbAttribute.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/map/DbAttribute.java
@@ -24,7 +24,6 @@ import 
org.apache.cayenne.configuration.ConfigurationNodeVisitor;
 import org.apache.cayenne.dba.TypesMapping;
 import org.apache.cayenne.map.event.AttributeEvent;
 import org.apache.cayenne.map.event.DbAttributeListener;
-import org.apache.cayenne.util.Util;
 import org.apache.cayenne.util.XMLEncoder;
 
 /**
@@ -99,49 +98,40 @@ public class DbAttribute extends Attribute implements 
ConfigurationNode {
      * @since 1.1
      */
     @Override
-    public void encodeAsXML(XMLEncoder encoder) {
+    public void encodeAsXML(XMLEncoder encoder, ConfigurationNodeVisitor 
delegate) {
 
-        encoder.print("<db-attribute name=\"");
-        encoder.print(Util.encodeXmlAttribute(getName()));
-        encoder.print('\"');
+        encoder.start("db-attribute").attribute("name", getName());
 
         String type = TypesMapping.getSqlNameByType(getType());
-        if (type != null) {
-            encoder.print(" type=\"" + type + '\"');
-        }
+        encoder.attribute("type", type);
 
         if (isPrimaryKey()) {
-            encoder.print(" isPrimaryKey=\"true\"");
+            encoder.attribute("isPrimaryKey", true);
 
             // only allow generated if an attribute is a PK.
             if (isGenerated()) {
-                encoder.print(" isGenerated=\"true\"");
+                encoder.attribute("isGenerated", true);
             }
         }
 
         if (isMandatory()) {
-            encoder.print(" isMandatory=\"true\"");
+            encoder.attribute("isMandatory", true);
         }
 
         if (getMaxLength() > 0) {
-            encoder.print(" length=\"");
-            encoder.print(getMaxLength());
-            encoder.print('\"');
+            encoder.attribute("length", getMaxLength());
         }
 
         if (getScale() > 0) {
-            encoder.print(" scale=\"");
-            encoder.print(getScale());
-            encoder.print('\"');
+            encoder.attribute("scale", getScale());
         }
 
         if (getAttributePrecision() > 0) {
-            encoder.print(" attributePrecision=\"");
-            encoder.print(getAttributePrecision());
-            encoder.print('\"');
+            encoder.attribute("attributePrecision", getAttributePrecision());
         }
 
-        encoder.println("/>");
+        delegate.visitDbAttribute(this);
+        encoder.end();
     }
 
     public String getAliasedName(String alias) {

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c58b6f40/cayenne-server/src/main/java/org/apache/cayenne/map/DbEntity.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/map/DbEntity.java 
b/cayenne-server/src/main/java/org/apache/cayenne/map/DbEntity.java
index 3c3af26..d3dda7e 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/map/DbEntity.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/map/DbEntity.java
@@ -34,7 +34,6 @@ import org.apache.cayenne.map.event.EntityEvent;
 import org.apache.cayenne.map.event.MapEvent;
 import org.apache.cayenne.map.event.RelationshipEvent;
 import org.apache.cayenne.util.CayenneMapEntry;
-import org.apache.cayenne.util.Util;
 import org.apache.cayenne.util.XMLEncoder;
 import org.apache.commons.collections.Transformer;
 
@@ -114,40 +113,30 @@ public class DbEntity extends Entity implements 
ConfigurationNode, DbEntityListe
      * @since 1.1
      */
     @Override
-    public void encodeAsXML(XMLEncoder encoder) {
-        encoder.print("<db-entity name=\"");
-        encoder.print(Util.encodeXmlAttribute(getName()));
-        encoder.print('\"');
+    public void encodeAsXML(XMLEncoder encoder, ConfigurationNodeVisitor 
delegate) {
+        encoder.start("db-entity").attribute("name", getName());
 
         if (getSchema() != null && getSchema().trim().length() > 0) {
-            encoder.print(" schema=\"");
-            encoder.print(Util.encodeXmlAttribute(getSchema().trim()));
-            encoder.print('\"');
+            encoder.attribute("schema", getSchema().trim());
         }
-
         if (getCatalog() != null && getCatalog().trim().length() > 0) {
-            encoder.print(" catalog=\"");
-            encoder.print(Util.encodeXmlAttribute(getCatalog().trim()));
-            encoder.print('\"');
+            encoder.attribute("catalog", getCatalog().trim());
         }
 
-        encoder.println('>');
-
-        encoder.indent(1);
-        encoder.print(getAttributeMap());
+        encoder.nested(getAttributeMap(), delegate);
 
         if (getPrimaryKeyGenerator() != null) {
-            getPrimaryKeyGenerator().encodeAsXML(encoder);
+            getPrimaryKeyGenerator().encodeAsXML(encoder, delegate);
         }
 
         if (getQualifier() != null) {
-            encoder.print("<qualifier>");
-            getQualifier().encodeAsXML(encoder);
-            encoder.println("</qualifier>");
+            encoder.start("qualifier");
+            getQualifier().encodeAsXML(encoder, delegate);
+            encoder.end();
         }
 
-        encoder.indent(-1);
-        encoder.println("</db-entity>");
+        delegate.visitDbEntity(this);
+        encoder.end();
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c58b6f40/cayenne-server/src/main/java/org/apache/cayenne/map/DbJoin.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/map/DbJoin.java 
b/cayenne-server/src/main/java/org/apache/cayenne/map/DbJoin.java
index ecb9ab8..d0de023 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/map/DbJoin.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/map/DbJoin.java
@@ -22,6 +22,7 @@ package org.apache.cayenne.map;
 import java.io.Serializable;
 
 import org.apache.cayenne.CayenneRuntimeException;
+import org.apache.cayenne.configuration.ConfigurationNodeVisitor;
 import org.apache.cayenne.util.ToStringBuilder;
 import org.apache.cayenne.util.XMLEncoder;
 import org.apache.cayenne.util.XMLSerializable;
@@ -95,23 +96,12 @@ public class DbJoin implements XMLSerializable, 
Serializable {
     /**
      * Prints itself as XML to the provided XMLEncoder.
      */
-    public void encodeAsXML(XMLEncoder encoder) {
-        encoder.print("<db-attribute-pair");
-
-        // sanity check
-        if (getSourceName() != null) {
-            encoder.print(" source=\"");
-            encoder.print(getSourceName());
-            encoder.print("\"");
-        }
-
-        if (getTargetName() != null) {
-            encoder.print(" target=\"");
-            encoder.print(getTargetName());
-            encoder.print("\"");
-        }
-
-        encoder.println("/>");
+    @Override
+    public void encodeAsXML(XMLEncoder encoder, ConfigurationNodeVisitor 
delegate) {
+        encoder.start("db-attribute-pair")
+                .attribute("source", getSourceName())
+                .attribute("target", getTargetName())
+                .end();
     }
 
     public DbRelationship getRelationship() {

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c58b6f40/cayenne-server/src/main/java/org/apache/cayenne/map/DbKeyGenerator.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/map/DbKeyGenerator.java 
b/cayenne-server/src/main/java/org/apache/cayenne/map/DbKeyGenerator.java
index f6cd94e..d91e787 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/map/DbKeyGenerator.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/map/DbKeyGenerator.java
@@ -22,6 +22,7 @@ package org.apache.cayenne.map;
 
 import java.io.Serializable;
 
+import org.apache.cayenne.configuration.ConfigurationNodeVisitor;
 import org.apache.cayenne.util.CayenneMapEntry;
 import org.apache.cayenne.util.XMLEncoder;
 import org.apache.cayenne.util.XMLSerializable;
@@ -77,32 +78,22 @@ public class DbKeyGenerator implements CayenneMapEntry, 
XMLSerializable, Seriali
      * 
      * @since 1.1
      */
-    public void encodeAsXML(XMLEncoder encoder) {
+    @Override
+    public void encodeAsXML(XMLEncoder encoder, ConfigurationNodeVisitor 
delegate) {
         if (getGeneratorType() == null) {
             return;
         }
 
-        encoder.println("<db-key-generator>");
-        encoder.indent(1);
-
-        encoder.print("<db-generator-type>");
-        encoder.print(getGeneratorType());
-        encoder.println("</db-generator-type>");
+        encoder.start("db-key-generator")
+                .start("db-generator-type").cdata(getGeneratorType()).end();
 
         if (getGeneratorName() != null) {
-            encoder.print("<db-generator-name>");
-            encoder.print(getGeneratorName());
-            encoder.println("</db-generator-name>");
+            encoder.start("db-generator-name").cdata(getGeneratorName()).end();
         }
-
         if (getKeyCacheSize() != null) {
-            encoder.print("<db-key-cache-size>");
-            encoder.print(String.valueOf(getKeyCacheSize()));
-            encoder.println("</db-key-cache-size>");
+            
encoder.start("db-key-cache-size").cdata(String.valueOf(getKeyCacheSize())).end();
         }
-
-        encoder.indent(-1);
-        encoder.println("</db-key-generator>");
+        encoder.end();
     }
 
     public DbEntity getDbEntity() {
@@ -117,8 +108,8 @@ public class DbKeyGenerator implements CayenneMapEntry, 
XMLSerializable, Seriali
         this.generatorType = generatorType;
         if (this.generatorType != null) {
             this.generatorType = this.generatorType.trim().toUpperCase();
-            if (!(ORACLE_TYPE.equals(this.generatorType) || 
NAMED_SEQUENCE_TABLE_TYPE
-                    .equals(this.generatorType)))
+            if (!(ORACLE_TYPE.equals(this.generatorType)
+                    || NAMED_SEQUENCE_TABLE_TYPE.equals(this.generatorType)))
                 this.generatorType = null;
         }
     }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c58b6f40/cayenne-server/src/main/java/org/apache/cayenne/map/DbRelationship.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/map/DbRelationship.java 
b/cayenne-server/src/main/java/org/apache/cayenne/map/DbRelationship.java
index 0d58cd0..53d4c73 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/map/DbRelationship.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/map/DbRelationship.java
@@ -74,30 +74,22 @@ public class DbRelationship extends Relationship implements 
ConfigurationNode {
      * 
      * @since 1.1
      */
-    public void encodeAsXML(XMLEncoder encoder) {
-        encoder.print("<db-relationship name=\"");
-        encoder.print(Util.encodeXmlAttribute(getName()));
-        encoder.print("\" source=\"");
-        encoder.print(Util.encodeXmlAttribute(getSourceEntity().getName()));
+    public void encodeAsXML(XMLEncoder encoder, ConfigurationNodeVisitor 
delegate) {
+        encoder.start("db-relationship")
+                .attribute("name", getName())
+                .attribute("source", getSourceEntity().getName());
 
         if (getTargetEntityName() != null && getTargetEntity() != null) {
-            encoder.print("\" target=\"");
-            encoder.print(Util.encodeXmlAttribute(getTargetEntityName()));
+            encoder.attribute("target", getTargetEntityName());
         }
 
-        if (isToDependentPK() && isValidForDepPk()) {
-            encoder.print("\" toDependentPK=\"true");
-        }
-
-        encoder.print("\" toMany=\"");
-        encoder.print(isToMany());
-        encoder.println("\">");
+        encoder.attribute("toDependentPK", isToDependentPK() && 
isValidForDepPk());
+        encoder.attribute("toMany", isToMany());
 
-        encoder.indent(1);
-        encoder.print(getJoins());
-        encoder.indent(-1);
+        encoder.nested(getJoins(), delegate);
 
-        encoder.println("</db-relationship>");
+        delegate.visitDbRelationship(this);
+        encoder.end();
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c58b6f40/cayenne-server/src/main/java/org/apache/cayenne/map/EJBQLQueryDescriptor.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/map/EJBQLQueryDescriptor.java 
b/cayenne-server/src/main/java/org/apache/cayenne/map/EJBQLQueryDescriptor.java
index d3adde5..ed7f2e0 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/map/EJBQLQueryDescriptor.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/map/EJBQLQueryDescriptor.java
@@ -18,6 +18,7 @@
  ****************************************************************/
 package org.apache.cayenne.map;
 
+import org.apache.cayenne.configuration.ConfigurationNodeVisitor;
 import org.apache.cayenne.query.EJBQLQuery;
 import org.apache.cayenne.util.XMLEncoder;
 
@@ -59,25 +60,17 @@ public class EJBQLQueryDescriptor extends QueryDescriptor {
     }
 
     @Override
-    public void encodeAsXML(XMLEncoder encoder) {
-        encoder.print("<query name=\"");
-        encoder.print(getName());
-        encoder.print("\" type=\"");
-        encoder.print(type);
-        encoder.println("\">");
-
-        encoder.indent(1);
+    public void encodeAsXML(XMLEncoder encoder, ConfigurationNodeVisitor 
delegate) {
+        encoder.start("query").attribute("name", getName()).attribute("type", 
type);
 
         // print properties
         encodeProperties(encoder);
 
         if (ejbql != null) {
-            encoder.print("<ejbql><![CDATA[");
-            encoder.print(ejbql);
-            encoder.println("]]></ejbql>");
+            encoder.start("ejbql").cdata(ejbql, true).end();
         }
 
-        encoder.indent(-1);
-        encoder.println("</query>");
+        delegate.visitQuery(this);
+        encoder.end();
     }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c58b6f40/cayenne-server/src/main/java/org/apache/cayenne/map/Embeddable.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/map/Embeddable.java 
b/cayenne-server/src/main/java/org/apache/cayenne/map/Embeddable.java
index 744a5cc..d6a354e 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/map/Embeddable.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/map/Embeddable.java
@@ -168,18 +168,12 @@ public class Embeddable implements ConfigurationNode, 
XMLSerializable, Serializa
        /**
         * {@link XMLSerializable} implementation that generates XML for 
embeddable.
         */
-       public void encodeAsXML(XMLEncoder encoder) {
-               encoder.print("<embeddable");
-               if (getClassName() != null) {
-                       encoder.print(" className=\"");
-                       encoder.print(getClassName());
-                       encoder.print("\"");
-               }
-               encoder.println(">");
-
-               encoder.indent(1);
-               encoder.print(attributes);
-               encoder.indent(-1);
-               encoder.println("</embeddable>");
+       @Override
+       public void encodeAsXML(XMLEncoder encoder, ConfigurationNodeVisitor 
delegate) {
+               encoder.start("embeddable")
+                               .attribute("className", getClassName())
+                               .nested(attributes, delegate);
+               delegate.visitEmbeddable(this);
+               encoder.end();
        }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c58b6f40/cayenne-server/src/main/java/org/apache/cayenne/map/EmbeddableAttribute.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/map/EmbeddableAttribute.java 
b/cayenne-server/src/main/java/org/apache/cayenne/map/EmbeddableAttribute.java
index 27a8da7..197b7dc 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/map/EmbeddableAttribute.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/map/EmbeddableAttribute.java
@@ -22,7 +22,6 @@ import java.io.Serializable;
 
 import org.apache.cayenne.configuration.ConfigurationNode;
 import org.apache.cayenne.configuration.ConfigurationNodeVisitor;
-import org.apache.cayenne.util.Util;
 import org.apache.cayenne.util.XMLEncoder;
 import org.apache.cayenne.util.XMLSerializable;
 
@@ -55,23 +54,14 @@ public class EmbeddableAttribute implements 
ConfigurationNode, XMLSerializable,
         return visitor.visitEmbeddableAttribute(this);
     }
 
-    public void encodeAsXML(XMLEncoder encoder) {
-        encoder.print("<embeddable-attribute name=\"" + getName() + '\"');
-
-        if (getType() != null) {
-            encoder.print(" type=\"");
-            encoder.print(getType());
-            encoder.print('\"');
-        }
-
-        // If this obj attribute is mapped to db attribute
-        if (dbAttributeName != null) {
-            encoder.print(" db-attribute-name=\"");
-            encoder.print(Util.encodeXmlAttribute(dbAttributeName));
-            encoder.print('\"');
-        }
-
-        encoder.println("/>");
+    @Override
+    public void encodeAsXML(XMLEncoder encoder, ConfigurationNodeVisitor 
delegate) {
+        encoder.start("embeddable-attribute")
+                .attribute("name", getName())
+                .attribute("type", getType())
+                .attribute("db-attribute-name", dbAttributeName);
+        delegate.visitEmbeddableAttribute(this);
+        encoder.end();
     }
 
     public String getDbAttributeName() {

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c58b6f40/cayenne-server/src/main/java/org/apache/cayenne/map/EmbeddedAttribute.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/map/EmbeddedAttribute.java 
b/cayenne-server/src/main/java/org/apache/cayenne/map/EmbeddedAttribute.java
index 1eeaa2e..3be89e5 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/map/EmbeddedAttribute.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/map/EmbeddedAttribute.java
@@ -19,6 +19,7 @@
 package org.apache.cayenne.map;
 
 import org.apache.cayenne.CayenneRuntimeException;
+import org.apache.cayenne.configuration.ConfigurationNodeVisitor;
 import org.apache.cayenne.util.Util;
 import org.apache.cayenne.util.XMLEncoder;
 
@@ -55,33 +56,19 @@ public class EmbeddedAttribute extends ObjAttribute {
     }
 
     @Override
-    public void encodeAsXML(XMLEncoder encoder) {
-        encoder.print("<embedded-attribute name=\"" + getName() + '\"');
-        if (getType() != null) {
-            encoder.print(" type=\"");
-            encoder.print(getType());
-            encoder.print('\"');
-        }
-
-        if (attributeOverrides.isEmpty()) {
-            encoder.println("/>");
-            return;
-        }
-
-        encoder.println('>');
-
-        encoder.indent(1);
+    public void encodeAsXML(XMLEncoder encoder, ConfigurationNodeVisitor 
delegate) {
+        encoder.start("embedded-attribute")
+                .attribute("name", getName())
+                .attribute("type", getType());
 
         for (Map.Entry<String, String> e : attributeOverrides.entrySet()) {
-            encoder.print("<embeddable-attribute-override name=\"");
-            encoder.print(e.getKey());
-            encoder.print("\" db-attribute-path=\"");
-            encoder.print(e.getValue());
-            encoder.println("\"/>");
+            encoder.start("embeddable-attribute-override")
+                    .attribute("name", e.getKey())
+                    .attribute("db-attribute-path", e.getValue())
+                    .end();
         }
 
-        encoder.indent(-1);
-        encoder.println("</embedded-attribute>");
+        encoder.end();
     }
 
     public Map<String, String> getAttributeOverrides() {

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c58b6f40/cayenne-server/src/main/java/org/apache/cayenne/map/EntityListener.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/map/EntityListener.java 
b/cayenne-server/src/main/java/org/apache/cayenne/map/EntityListener.java
index 84e172d..f9486bc 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/map/EntityListener.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/map/EntityListener.java
@@ -20,6 +20,7 @@ package org.apache.cayenne.map;
 
 import java.io.Serializable;
 
+import org.apache.cayenne.configuration.ConfigurationNodeVisitor;
 import org.apache.cayenne.util.XMLEncoder;
 import org.apache.cayenne.util.XMLSerializable;
 
@@ -63,7 +64,8 @@ public class EntityListener implements Serializable, 
XMLSerializable {
         return callbacks;
     }
 
-    public void encodeAsXML(XMLEncoder encoder) {
+    @Override
+    public void encodeAsXML(XMLEncoder encoder, ConfigurationNodeVisitor 
delegate) {
         encoder.print("<entity-listener class=\"");
         encoder.print(className);
         encoder.println("\">");

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c58b6f40/cayenne-server/src/main/java/org/apache/cayenne/map/MapLoader.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/map/MapLoader.java 
b/cayenne-server/src/main/java/org/apache/cayenne/map/MapLoader.java
deleted file mode 100644
index 2ea7a6f..0000000
--- a/cayenne-server/src/main/java/org/apache/cayenne/map/MapLoader.java
+++ /dev/null
@@ -1,1261 +0,0 @@
-/*****************************************************************
- *   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.
- ****************************************************************/
-
-package org.apache.cayenne.map;
-
-import org.apache.cayenne.CayenneRuntimeException;
-import org.apache.cayenne.dba.TypesMapping;
-import org.apache.cayenne.exp.ExpressionFactory;
-import org.apache.cayenne.util.Util;
-import org.xml.sax.Attributes;
-import org.xml.sax.InputSource;
-import org.xml.sax.SAXException;
-import org.xml.sax.XMLReader;
-import org.xml.sax.helpers.DefaultHandler;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.TreeMap;
-
-/**
- * Default MapLoader. Its responsibilities include reading DataMaps from XML
- * files and saving DataMap objects back to XML.
- */
-public class MapLoader extends DefaultHandler {
-
-       // TODO: andrus, 7/17/2006 - move upgrade logic out of here
-       final static String _1_2_PACKAGE_PREFIX = "org.objectstyle.cayenne.";
-       final static String _2_0_PACKAGE_PREFIX = "org.apache.cayenne.";
-
-       public static final String DATA_MAP_TAG = "data-map";
-
-       public static final String PROPERTY_TAG = "property";
-
-       /**
-        * @since 3.0
-        */
-       public static final String EMBEDDABLE_TAG = "embeddable";
-
-       /**
-        * @since 3.0
-        */
-       public static final String EMBEDDABLE_ATTRIBUTE_TAG = 
"embeddable-attribute";
-
-       /**
-        * @since 3.0
-        */
-       public static final String EMBEDDED_ATTRIBUTE_TAG = 
"embedded-attribute";
-
-       /**
-        * @since 3.0
-        */
-       public static final String EMBEDDABLE_ATTRIBUTE_OVERRIDE_TAG = 
"embeddable-attribute-override";
-
-       public static final String DB_ENTITY_TAG = "db-entity";
-       public static final String OBJ_ENTITY_TAG = "obj-entity";
-       public static final String DB_ATTRIBUTE_TAG = "db-attribute";
-       public static final String OBJ_ATTRIBUTE_TAG = "obj-attribute";
-       public static final String OBJ_ATTRIBUTE_OVERRIDE_TAG = 
"attribute-override";
-       public static final String OBJ_RELATIONSHIP_TAG = "obj-relationship";
-       public static final String DB_RELATIONSHIP_TAG = "db-relationship";
-       public static final String DB_RELATIONSHIP_REF_TAG = 
"db-relationship-ref";
-       public static final String DB_ATTRIBUTE_PAIR_TAG = "db-attribute-pair";
-       public static final String PROCEDURE_TAG = "procedure";
-       public static final String PROCEDURE_PARAMETER_TAG = 
"procedure-parameter";
-
-       // lifecycle listeners and callbacks related
-       public static final String POST_ADD_TAG = "post-add";
-       public static final String PRE_PERSIST_TAG = "pre-persist";
-       public static final String POST_PERSIST_TAG = "post-persist";
-       public static final String PRE_UPDATE_TAG = "pre-update";
-       public static final String POST_UPDATE_TAG = "post-update";
-       public static final String PRE_REMOVE_TAG = "pre-remove";
-       public static final String POST_REMOVE_TAG = "post-remove";
-       public static final String POST_LOAD_TAG = "post-load";
-
-       // Query-related
-       public static final String QUERY_TAG = "query";
-
-       public static final String QUERY_SQL_TAG = "sql";
-       public static final String QUERY_EJBQL_TAG = "ejbql";
-       public static final String QUERY_QUALIFIER_TAG = "qualifier";
-       public static final String QUERY_ORDERING_TAG = "ordering";
-       public static final String QUERY_PREFETCH_TAG = "prefetch";
-
-       public static final String TRUE = "true";
-       public static final String FALSE = "false";
-
-       public static final String DB_KEY_GENERATOR_TAG = "db-key-generator";
-       public static final String DB_GENERATOR_TYPE_TAG = "db-generator-type";
-       public static final String DB_GENERATOR_NAME_TAG = "db-generator-name";
-       public static final String DB_KEY_CACHE_SIZE_TAG = "db-key-cache-size";
-
-       /**
-        * @since 3.0
-        */
-       public static final String OBJ_ENTITY_ROOT = "obj-entity";
-
-       /**
-        * @since 3.0
-        */
-       public static final String DB_ENTITY_ROOT = "db-entity";
-
-       /**
-        * @since 3.0
-        */
-       public static final String PROCEDURE_ROOT = "procedure";
-
-       /**
-        * @since 3.0
-        */
-       public static final String DATA_MAP_ROOT = "data-map";
-
-       /**
-        * @since 3.0
-        */
-       public static final String JAVA_CLASS_ROOT = "java-class";
-
-       private static final String DATA_MAP_LOCATION_SUFFIX = ".map.xml";
-
-       // Reading from XML
-       private String mapVersion;
-       private DataMap dataMap;
-       private DbEntity dbEntity;
-       private ObjEntity objEntity;
-       private Embeddable embeddable;
-       private EmbeddedAttribute embeddedAttribute;
-       private DbRelationship dbRelationship;
-       private ObjRelationship objRelationship;
-       private DbAttribute attrib;
-       private Procedure procedure;
-       private QueryDescriptorLoader queryBuilder;
-       private String sqlKey;
-
-       private String descending;
-       private String ignoreCase;
-
-       private Map<String, StartClosure> startTagOpMap;
-       private Map<String, EndClosure> endTagOpMap;
-       private String currentTag;
-       private Attributes currentAttributes;
-       private StringBuilder charactersBuffer;
-       private Map<String, Object> mapProperties;
-
-       public MapLoader() {
-               // compile tag processors.
-               startTagOpMap = new HashMap<>(40);
-               endTagOpMap = new HashMap<>(40);
-
-               startTagOpMap.put(DATA_MAP_TAG, new StartClosure() {
-
-                       @Override
-                       void execute(Attributes attributes) throws SAXException 
{
-                               processStartDataMap(attributes);
-                       }
-               });
-               
-               startTagOpMap.put(DB_ENTITY_TAG, new StartClosure() {
-
-                       @Override
-                       void execute(Attributes attributes) throws SAXException 
{
-                               processStartDbEntity(attributes);
-                       }
-               });
-
-               startTagOpMap.put(DB_ATTRIBUTE_TAG, new StartClosure() {
-
-                       @Override
-                       void execute(Attributes attributes) throws SAXException 
{
-                               processStartDbAttribute(attributes);
-                       }
-               });
-
-               startTagOpMap.put(OBJ_ENTITY_TAG, new StartClosure() {
-
-                       @Override
-                       void execute(Attributes attributes) throws SAXException 
{
-                               processStartObjEntity(attributes);
-                       }
-               });
-
-               startTagOpMap.put(OBJ_ATTRIBUTE_TAG, new StartClosure() {
-
-                       @Override
-                       void execute(Attributes attributes) throws SAXException 
{
-                               processStartObjAttribute(attributes);
-                       }
-               });
-
-               startTagOpMap.put(OBJ_ATTRIBUTE_OVERRIDE_TAG, new 
StartClosure() {
-
-                       @Override
-                       void execute(Attributes attributes) throws SAXException 
{
-                               processStartAttributeOverride(attributes);
-                       }
-               });
-
-               startTagOpMap.put(EMBEDDABLE_TAG, new StartClosure() {
-
-                       @Override
-                       void execute(Attributes attributes) throws SAXException 
{
-                               processStartEmbeddable(attributes);
-                       }
-               });
-
-               startTagOpMap.put(EMBEDDABLE_ATTRIBUTE_TAG, new StartClosure() {
-
-                       @Override
-                       void execute(Attributes attributes) throws SAXException 
{
-                               processStartEmbeddableAttribute(attributes);
-                       }
-               });
-
-               startTagOpMap.put(EMBEDDABLE_ATTRIBUTE_OVERRIDE_TAG, new 
StartClosure() {
-
-                       @Override
-                       void execute(Attributes attributes) throws SAXException 
{
-                               
processStartEmbeddableAttributeOverride(attributes);
-                       }
-               });
-
-               startTagOpMap.put(EMBEDDED_ATTRIBUTE_TAG, new StartClosure() {
-
-                       @Override
-                       void execute(Attributes attributes) throws SAXException 
{
-                               processStartEmbeddedAttribute(attributes);
-                       }
-               });
-
-               startTagOpMap.put(DB_RELATIONSHIP_TAG, new StartClosure() {
-
-                       @Override
-                       void execute(Attributes attributes) throws SAXException 
{
-                               processStartDbRelationship(attributes);
-                       }
-               });
-
-               startTagOpMap.put(DB_ATTRIBUTE_PAIR_TAG, new StartClosure() {
-
-                       @Override
-                       void execute(Attributes attributes) throws SAXException 
{
-                               processStartDbAttributePair(attributes);
-                       }
-               });
-
-               startTagOpMap.put(OBJ_RELATIONSHIP_TAG, new StartClosure() {
-
-                       @Override
-                       void execute(Attributes attributes) throws SAXException 
{
-                               processStartObjRelationship(attributes);
-                       }
-               });
-
-               startTagOpMap.put(DB_RELATIONSHIP_REF_TAG, new StartClosure() {
-
-                       @Override
-                       void execute(Attributes attributes) throws SAXException 
{
-                               processStartDbRelationshipRef(attributes);
-                       }
-               });
-
-               startTagOpMap.put(PROCEDURE_PARAMETER_TAG, new StartClosure() {
-
-                       @Override
-                       void execute(Attributes attributes) throws SAXException 
{
-                               processStartProcedureParameter(attributes);
-                       }
-               });
-
-               startTagOpMap.put(PROCEDURE_TAG, new StartClosure() {
-
-                       @Override
-                       void execute(Attributes attributes) throws SAXException 
{
-                               processStartProcedure(attributes);
-                       }
-               });
-
-               startTagOpMap.put(QUERY_EJBQL_TAG, new StartClosure() {
-
-                       @Override
-                       void execute(Attributes attributes) throws SAXException 
{
-                               charactersBuffer = new StringBuilder();
-                       }
-               });
-
-               startTagOpMap.put(QUERY_TAG, new StartClosure() {
-
-                       @Override
-                       void execute(Attributes attributes) throws SAXException 
{
-                               processStartQuery(attributes);
-                       }
-               });
-
-               startTagOpMap.put(QUERY_SQL_TAG, new StartClosure() {
-
-                       @Override
-                       void execute(Attributes attributes) throws SAXException 
{
-                               charactersBuffer = new StringBuilder();
-                               processStartQuerySQL(attributes);
-                       }
-               });
-
-               startTagOpMap.put(QUERY_ORDERING_TAG, new StartClosure() {
-
-                       @Override
-                       void execute(Attributes attributes) throws SAXException 
{
-                               charactersBuffer = new StringBuilder();
-                               processStartQueryOrdering(attributes);
-                       }
-               });
-
-               startTagOpMap.put(DB_KEY_GENERATOR_TAG, new StartClosure() {
-
-                       @Override
-                       void execute(Attributes attributes) throws SAXException 
{
-                               processStartDbKeyGenerator(attributes);
-                       }
-               });
-
-               startTagOpMap.put(PROPERTY_TAG, new StartClosure() {
-
-                       @Override
-                       void execute(Attributes attributes) throws SAXException 
{
-                               // properties can belong to query or DataMap
-                               if (queryBuilder != null) {
-                                       processStartQueryProperty(attributes);
-                               } else {
-                                       processStartDataMapProperty(attributes);
-                               }
-                       }
-               });
-
-               startTagOpMap.put(POST_ADD_TAG, new StartClosure() {
-
-                       @Override
-                       void execute(Attributes attributes) throws SAXException 
{
-                               processStartPostAdd(attributes);
-                       }
-               });
-
-               startTagOpMap.put(PRE_PERSIST_TAG, new StartClosure() {
-
-                       @Override
-                       void execute(Attributes attributes) throws SAXException 
{
-                               processStartPrePersist(attributes);
-                       }
-               });
-
-               startTagOpMap.put(POST_PERSIST_TAG, new StartClosure() {
-
-                       @Override
-                       void execute(Attributes attributes) throws SAXException 
{
-                               processStartPostPersist(attributes);
-                       }
-               });
-
-               startTagOpMap.put(PRE_UPDATE_TAG, new StartClosure() {
-
-                       @Override
-                       void execute(Attributes attributes) throws SAXException 
{
-                               processStartPreUpdate(attributes);
-                       }
-               });
-
-               startTagOpMap.put(POST_UPDATE_TAG, new StartClosure() {
-
-                       @Override
-                       void execute(Attributes attributes) throws SAXException 
{
-                               processStartPostUpdate(attributes);
-                       }
-               });
-
-               startTagOpMap.put(PRE_REMOVE_TAG, new StartClosure() {
-
-                       @Override
-                       void execute(Attributes attributes) throws SAXException 
{
-                               processStartPreRemove(attributes);
-                       }
-               });
-
-               startTagOpMap.put(POST_REMOVE_TAG, new StartClosure() {
-
-                       @Override
-                       void execute(Attributes attributes) throws SAXException 
{
-                               processStartPostRemove(attributes);
-                       }
-               });
-
-               startTagOpMap.put(POST_LOAD_TAG, new StartClosure() {
-
-                       @Override
-                       void execute(Attributes attributes) throws SAXException 
{
-                               processStartPostLoad(attributes);
-                       }
-               });
-
-               StartClosure resetBuffer = new StartClosure() {
-
-                       @Override
-                       void execute(Attributes attributes) throws SAXException 
{
-                               charactersBuffer = new StringBuilder();
-                       }
-               };
-
-               startTagOpMap.put(QUERY_PREFETCH_TAG, resetBuffer);
-               startTagOpMap.put(QUERY_QUALIFIER_TAG, resetBuffer);
-               startTagOpMap.put(DB_GENERATOR_TYPE_TAG, resetBuffer);
-               startTagOpMap.put(DB_GENERATOR_NAME_TAG, resetBuffer);
-               startTagOpMap.put(DB_KEY_CACHE_SIZE_TAG, resetBuffer);
-
-               endTagOpMap.put(DATA_MAP_TAG, new EndClosure() {
-
-                       @Override
-                       void execute() throws SAXException {
-                               processEndDataMap();
-                       }
-               });
-               endTagOpMap.put(DB_ENTITY_TAG, new EndClosure() {
-
-                       @Override
-                       void execute() throws SAXException {
-                               processEndDbEntity();
-                       }
-               });
-               endTagOpMap.put(OBJ_ENTITY_TAG, new EndClosure() {
-
-                       @Override
-                       void execute() throws SAXException {
-                               processEndObjEntity();
-                       }
-               });
-               endTagOpMap.put(EMBEDDABLE_TAG, new EndClosure() {
-
-                       @Override
-                       void execute() throws SAXException {
-                               processEndEmbeddable();
-                       }
-               });
-               endTagOpMap.put(EMBEDDABLE_ATTRIBUTE_TAG, new EndClosure() {
-
-                       @Override
-                       void execute() throws SAXException {
-                               processEndEmbeddedAttribute();
-                       }
-               });
-
-               endTagOpMap.put(DB_ATTRIBUTE_TAG, new EndClosure() {
-
-                       @Override
-                       void execute() throws SAXException {
-                               processEndDbAttribute();
-                       }
-               });
-
-               endTagOpMap.put(DB_RELATIONSHIP_TAG, new EndClosure() {
-
-                       @Override
-                       void execute() throws SAXException {
-                               processEndDbRelationship();
-                       }
-               });
-               endTagOpMap.put(OBJ_RELATIONSHIP_TAG, new EndClosure() {
-
-                       @Override
-                       void execute() throws SAXException {
-                               processEndObjRelationship();
-                       }
-               });
-               endTagOpMap.put(DB_GENERATOR_TYPE_TAG, new EndClosure() {
-
-                       @Override
-                       void execute() throws SAXException {
-                               processEndDbGeneratorType();
-                       }
-               });
-               endTagOpMap.put(DB_GENERATOR_NAME_TAG, new EndClosure() {
-
-                       @Override
-                       void execute() throws SAXException {
-                               processEndDbGeneratorName();
-                       }
-               });
-               endTagOpMap.put(DB_KEY_CACHE_SIZE_TAG, new EndClosure() {
-
-                       @Override
-                       void execute() throws SAXException {
-                               processEndDbKeyCacheSize();
-                       }
-               });
-               endTagOpMap.put(PROCEDURE_PARAMETER_TAG, new EndClosure() {
-
-                       @Override
-                       void execute() throws SAXException {
-                               processEndProcedureParameter();
-                       }
-               });
-               endTagOpMap.put(PROCEDURE_TAG, new EndClosure() {
-
-                       @Override
-                       void execute() throws SAXException {
-                               processEndProcedure();
-                       }
-               });
-               endTagOpMap.put(QUERY_TAG, new EndClosure() {
-
-                       @Override
-                       void execute() throws SAXException {
-                               processEndQuery();
-                       }
-               });
-               endTagOpMap.put(QUERY_SQL_TAG, new EndClosure() {
-
-                       @Override
-                       void execute() throws SAXException {
-                               processEndQuerySQL();
-                       }
-               });
-
-               endTagOpMap.put(QUERY_EJBQL_TAG, new EndClosure() {
-
-                       @Override
-                       void execute() throws SAXException {
-                               processEndEjbqlQuery();
-                       }
-               });
-
-               endTagOpMap.put(QUERY_QUALIFIER_TAG, new EndClosure() {
-
-                       @Override
-                       void execute() throws SAXException {
-                               processEndQualifier();
-                       }
-               });
-               endTagOpMap.put(QUERY_ORDERING_TAG, new EndClosure() {
-
-                       @Override
-                       void execute() throws SAXException {
-                               processEndQueryOrdering();
-                       }
-               });
-               endTagOpMap.put(QUERY_PREFETCH_TAG, new EndClosure() {
-
-                       @Override
-                       void execute() throws SAXException {
-                               processEndQueryPrefetch();
-                       }
-               });
-       }
-       
-       private void processStartDataMap(Attributes attributes) {
-               this.mapVersion = attributes.getValue("", "project-version");
-       }
-
-       private void processStartPostAdd(Attributes attributes) {
-               String methodName = attributes.getValue("", "method-name");
-               if (objEntity != null) {
-                       // new callback tags - children of "obj-entity"
-                       
objEntity.getCallbackMap().getPostAdd().addCallbackMethod(methodName);
-               }
-       }
-
-       private void processStartPrePersist(Attributes attributes) {
-
-               // 3.0 -> 3.0.0.1 upgrade hack... treat pre-persist as post-add
-               // only 3.0 used "pre-persist" in a "post-add" sense
-               if ("3.0".equals(mapVersion)) {
-                       processStartPostAdd(attributes);
-               } else {
-
-                       String methodName = attributes.getValue("", 
"method-name");
-
-                       if (objEntity != null) {
-                               // new callback tags - children of "obj-entity"
-                               
objEntity.getCallbackMap().getPrePersist().addCallbackMethod(methodName);
-                       }
-               }
-       }
-
-       private void processStartPostPersist(Attributes attributes) {
-               String methodName = attributes.getValue("", "method-name");
-               if (objEntity != null) {
-                       
objEntity.getCallbackMap().getPostPersist().addCallbackMethod(methodName);
-               }
-       }
-
-       private void processStartPreUpdate(Attributes attributes) {
-               String methodName = attributes.getValue("", "method-name");
-               if (objEntity != null) {
-                       
objEntity.getCallbackMap().getPreUpdate().addCallbackMethod(methodName);
-               }
-       }
-
-       private void processStartPostUpdate(Attributes attributes) {
-               String methodName = attributes.getValue("", "method-name");
-               if (objEntity != null) {
-                       
objEntity.getCallbackMap().getPostUpdate().addCallbackMethod(methodName);
-               }
-       }
-
-       private void processStartPreRemove(Attributes attributes) {
-               String methodName = attributes.getValue("", "method-name");
-               if (objEntity != null) {
-                       
objEntity.getCallbackMap().getPreRemove().addCallbackMethod(methodName);
-               }
-       }
-
-       private void processStartPostRemove(Attributes attributes) {
-               String methodName = attributes.getValue("", "method-name");
-               if (objEntity != null) {
-                       
objEntity.getCallbackMap().getPostRemove().addCallbackMethod(methodName);
-               }
-       }
-
-       private void processStartPostLoad(Attributes attributes) {
-               String methodName = attributes.getValue("", "method-name");
-               if (objEntity != null) {
-                       
objEntity.getCallbackMap().getPostLoad().addCallbackMethod(methodName);
-               }
-       }
-
-       /**
-        * Loads a DataMap from XML input source.
-        */
-       public synchronized DataMap loadDataMap(InputSource src) throws 
CayenneRuntimeException {
-               if (src == null) {
-                       throw new NullPointerException("Null InputSource.");
-               }
-
-               try {
-                       String mapName = mapNameFromLocation(src.getSystemId());
-                       dataMap = new DataMap(mapName);
-                       XMLReader parser = Util.createXmlReader();
-
-                       parser.setContentHandler(this);
-                       parser.setErrorHandler(this);
-                       parser.parse(src);
-               } catch (SAXException e) {
-                       dataMap = null;
-                       throw new CayenneRuntimeException("Wrong DataMap 
format, last processed tag: "
-                                       + constructCurrentStateString(), 
Util.unwindException(e));
-               } catch (Exception e) {
-                       dataMap = null;
-                       throw new CayenneRuntimeException("Error loading 
DataMap, last processed tag: "
-                                       + constructCurrentStateString(), 
Util.unwindException(e));
-               }
-               return dataMap;
-       }
-
-       /**
-        * Constructs error message for displaying as exception message
-        */
-       private Appendable constructCurrentStateString() {
-               StringBuilder sb = new StringBuilder();
-               sb.append("<").append(currentTag);
-
-               if (currentAttributes != null) {
-                       for (int i = 0; i < currentAttributes.getLength(); i++) 
{
-                               sb.append(" 
").append(currentAttributes.getLocalName(i)).append("=").append("\"")
-                                               
.append(currentAttributes.getValue(i)).append("\"");
-                       }
-               }
-               sb.append(">");
-
-               return sb;
-       }
-
-       /**
-        * Helper method to guess the map name from its location.
-        */
-       protected String mapNameFromLocation(String location) {
-               if (location == null) {
-                       return "Untitled";
-               }
-
-               int lastSlash = location.lastIndexOf('/');
-               if (lastSlash < 0) {
-                       lastSlash = location.lastIndexOf('\\');
-               }
-
-               if (lastSlash >= 0 && lastSlash + 1 < location.length()) {
-                       location = location.substring(lastSlash + 1);
-               }
-
-               if (location.endsWith(DATA_MAP_LOCATION_SUFFIX)) {
-                       location = location.substring(0, location.length() - 
DATA_MAP_LOCATION_SUFFIX.length());
-               }
-
-               return location;
-       }
-
-       @Override
-       public void startElement(String namespaceUri, String localName, String 
qName, Attributes attributes)
-                       throws SAXException {
-
-               rememberCurrentState(localName, attributes);
-
-               StartClosure op = startTagOpMap.get(localName);
-               if (op != null) {
-                       op.execute(attributes);
-               }
-       }
-
-       @Override
-       public void endElement(String namespaceURI, String localName, String 
qName) throws SAXException {
-
-               EndClosure op = endTagOpMap.get(localName);
-               if (op != null) {
-                       op.execute();
-               }
-
-               resetCurrentState();
-               charactersBuffer = null;
-       }
-
-       private void processStartEmbeddable(Attributes atts) {
-               embeddable = new Embeddable(atts.getValue("", "className"));
-               dataMap.addEmbeddable(embeddable);
-       }
-
-       private void processStartEmbeddableAttribute(Attributes atts) {
-               String name = atts.getValue("", "name");
-               String type = atts.getValue("", "type");
-               String dbName = atts.getValue("", "db-attribute-name");
-
-               EmbeddableAttribute ea = new EmbeddableAttribute(name);
-               ea.setType(type);
-               ea.setDbAttributeName(dbName);
-               embeddable.addAttribute(ea);
-       }
-
-       private void processStartEmbeddedAttribute(Attributes atts) {
-
-               String name = atts.getValue("", "name");
-               String type = atts.getValue("", "type");
-
-               embeddedAttribute = new EmbeddedAttribute(name);
-               embeddedAttribute.setType(type);
-               objEntity.addAttribute(embeddedAttribute);
-       }
-
-       private void processStartEmbeddableAttributeOverride(Attributes atts) {
-               String name = atts.getValue("", "name");
-               String dbName = atts.getValue("", "db-attribute-path");
-               embeddedAttribute.addAttributeOverride(name, dbName);
-       }
-
-       private void processStartDbEntity(Attributes atts) {
-               String name = atts.getValue("", "name");
-
-               dbEntity = new DbEntity(name);
-               dbEntity.setSchema(atts.getValue("", "schema"));
-               dbEntity.setCatalog(atts.getValue("", "catalog"));
-
-               dataMap.addDbEntity(dbEntity);
-       }
-
-       private void processStartDbAttribute(Attributes atts) {
-               String name = atts.getValue("", "name");
-               String type = atts.getValue("", "type");
-
-               attrib = new DbAttribute(name);
-               attrib.setType(TypesMapping.getSqlTypeByName(type));
-               dbEntity.addAttribute(attrib);
-
-               String length = atts.getValue("", "length");
-               if (length != null) {
-                       attrib.setMaxLength(Integer.parseInt(length));
-               }
-
-               // this is an obsolete 1.2 'precision' attribute that really 
meant
-               // 'scale'
-               String pseudoPrecision = atts.getValue("", "precision");
-               if (pseudoPrecision != null) {
-                       attrib.setScale(Integer.parseInt(pseudoPrecision));
-               }
-
-               String precision = atts.getValue("", "attributePrecision");
-               if (precision != null) {
-                       
attrib.setAttributePrecision(Integer.parseInt(precision));
-               }
-
-               String scale = atts.getValue("", "scale");
-               if (scale != null) {
-                       attrib.setScale(Integer.parseInt(scale));
-               }
-
-               attrib.setPrimaryKey(TRUE.equalsIgnoreCase(atts.getValue("", 
"isPrimaryKey")));
-               attrib.setMandatory(TRUE.equalsIgnoreCase(atts.getValue("", 
"isMandatory")));
-               attrib.setGenerated(TRUE.equalsIgnoreCase(atts.getValue("", 
"isGenerated")));
-       }
-
-       private void processStartDbKeyGenerator(Attributes atts) {
-               DbKeyGenerator pkGenerator = new DbKeyGenerator();
-               dbEntity.setPrimaryKeyGenerator(pkGenerator);
-       }
-
-       private void processStartQuerySQL(Attributes atts) {
-               this.sqlKey = convertClassNameFromV1_2(atts.getValue("", 
"adapter-class"));
-       }
-
-       private void processStartObjEntity(Attributes atts) {
-               objEntity = new ObjEntity(atts.getValue("", "name"));
-               objEntity.setClassName(atts.getValue("", "className"));
-               objEntity.setClientClassName(atts.getValue("", 
"clientClassName"));
-
-               String isAbstract = atts.getValue("", "abstract");
-               objEntity.setAbstract(TRUE.equalsIgnoreCase(isAbstract));
-
-               String readOnly = atts.getValue("", "readOnly");
-               objEntity.setReadOnly(TRUE.equalsIgnoreCase(readOnly));
-
-               String serverOnly = atts.getValue("", "serverOnly");
-               objEntity.setServerOnly(TRUE.equalsIgnoreCase(serverOnly));
-
-               String excludeSuperclassListeners = atts.getValue("", 
"exclude-superclass-listeners");
-               
objEntity.setExcludingSuperclassListeners(TRUE.equalsIgnoreCase(excludeSuperclassListeners));
-
-               String excludeDefaultListeners = atts.getValue("", 
"exclude-default-listeners");
-               
objEntity.setExcludingDefaultListeners(TRUE.equalsIgnoreCase(excludeDefaultListeners));
-
-               String lockType = atts.getValue("", "lock-type");
-               if ("optimistic".equals(lockType)) {
-                       
objEntity.setDeclaredLockType(ObjEntity.LOCK_TYPE_OPTIMISTIC);
-               }
-
-               String superEntityName = atts.getValue("", "superEntityName");
-               if (superEntityName != null) {
-                       objEntity.setSuperEntityName(superEntityName);
-               } else {
-                       objEntity.setSuperClassName(atts.getValue("", 
"superClassName"));
-                       objEntity.setClientSuperClassName(atts.getValue("", 
"clientSuperClassName"));
-               }
-
-               objEntity.setDbEntityName(atts.getValue("", "dbEntityName"));
-
-               dataMap.addObjEntity(objEntity);
-       }
-
-       private void processStartObjAttribute(Attributes atts) {
-               String name = atts.getValue("", "name");
-               String type = atts.getValue("", "type");
-
-               String lock = atts.getValue("", "lock");
-
-               ObjAttribute oa = new ObjAttribute(name);
-               oa.setType(type);
-               oa.setUsedForLocking(TRUE.equalsIgnoreCase(lock));
-               objEntity.addAttribute(oa);
-               String dbPath = atts.getValue("", "db-attribute-path");
-               if (dbPath == null) {
-                       dbPath = atts.getValue("", "db-attribute-name");
-               }
-               oa.setDbAttributePath(dbPath);
-       }
-
-       private void processStartAttributeOverride(Attributes atts) {
-               String name = atts.getValue("", "name");
-               String dbPath = atts.getValue("", "db-attribute-path");
-
-               objEntity.addAttributeOverride(name, dbPath);
-       }
-
-       private void processStartDbRelationship(Attributes atts) throws 
SAXException {
-               String name = atts.getValue("", "name");
-               if (name == null) {
-                       throw new 
SAXException("MapLoader::processStartDbRelationship()," + " Unable to parse 
name. Attributes:\n"
-                                       + printAttributes(atts));
-               }
-
-               String sourceName = atts.getValue("", "source");
-               if (sourceName == null) {
-                       throw new 
SAXException("MapLoader::processStartDbRelationship() - null source entity");
-               }
-
-               DbEntity source = dataMap.getDbEntity(sourceName);
-               if (source == null) {
-                       return;
-               }
-
-               String toManyString = atts.getValue("", "toMany");
-               boolean toMany = toManyString != null && 
toManyString.equalsIgnoreCase(TRUE);
-
-               String toDependPkString = atts.getValue("", "toDependentPK");
-               boolean toDependentPK = toDependPkString != null && 
toDependPkString.equalsIgnoreCase(TRUE);
-
-               dbRelationship = new DbRelationship(name);
-               dbRelationship.setSourceEntity(source);
-               dbRelationship.setTargetEntityName(atts.getValue("", "target"));
-               dbRelationship.setToMany(toMany);
-               dbRelationship.setToDependentPK(toDependentPK);
-
-               source.addRelationship(dbRelationship);
-       }
-
-       private void processStartDbRelationshipRef(Attributes atts) throws 
SAXException {
-               // db-relationship-ref element is deprecated and is supported 
for
-               // backwards
-               // compatibility only
-
-               String name = atts.getValue("", "name");
-               if (name == null) {
-                       throw new 
SAXException("MapLoader::processStartDbRelationshipRef()" + ", Null 
DbRelationship name for "
-                                       + objRelationship.getName());
-               }
-
-               String path = objRelationship.getDbRelationshipPath();
-               path = (path != null) ? path + "." + name : name;
-               objRelationship.setDbRelationshipPath(path);
-       }
-
-       private void processStartDbAttributePair(Attributes atts) {
-               DbJoin join = new DbJoin(dbRelationship);
-               join.setSourceName(atts.getValue("", "source"));
-               join.setTargetName(atts.getValue("", "target"));
-               dbRelationship.addJoin(join);
-       }
-
-       private void processStartObjRelationship(Attributes atts) throws 
SAXException {
-               String name = atts.getValue("", "name");
-               if (null == name) {
-                       throw new 
SAXException("MapLoader::processStartObjRelationship(),"
-                                       + " Unable to parse target. 
Attributes:\n" + printAttributes(atts));
-               }
-
-               String collectionType = atts.getValue("", "collection-type");
-               String mapKey = atts.getValue("", "map-key");
-
-               String sourceName = atts.getValue("", "source");
-               if (sourceName == null) {
-                       throw new 
SAXException("MapLoader::processStartObjRelationship(),"
-                                       + " Unable to parse source. 
Attributes:\n" + printAttributes(atts));
-               }
-
-               ObjEntity source = dataMap.getObjEntity(sourceName);
-               if (source == null) {
-                       throw new 
SAXException("MapLoader::processStartObjRelationship()," + " Unable to find 
source " + sourceName);
-               }
-
-               String deleteRuleName = atts.getValue("", "deleteRule");
-               int deleteRule = (deleteRuleName != null) ? 
DeleteRule.deleteRuleForName(deleteRuleName) : DeleteRule.NO_ACTION;
-
-               objRelationship = new ObjRelationship(name);
-               objRelationship.setSourceEntity(source);
-               objRelationship.setTargetEntityName(atts.getValue("", 
"target"));
-               objRelationship.setDeleteRule(deleteRule);
-               
objRelationship.setUsedForLocking(TRUE.equalsIgnoreCase(atts.getValue("", 
"lock")));
-               
objRelationship.setDeferredDbRelationshipPath((atts.getValue("", 
"db-relationship-path")));
-               objRelationship.setCollectionType(collectionType);
-               objRelationship.setMapKey(mapKey);
-               source.addRelationship(objRelationship);
-       }
-
-       private void processStartProcedure(Attributes attributes) throws 
SAXException {
-
-               String name = attributes.getValue("", "name");
-               if (null == name) {
-                       throw new 
SAXException("MapLoader::processStartProcedure()," + " no procedure name.");
-               }
-
-               String schema = attributes.getValue("", "schema");
-               String catalog = attributes.getValue("", "catalog");
-               String returningValue = attributes.getValue("", 
"returningValue");
-
-               procedure = new Procedure(name);
-               procedure.setReturningValue(returningValue != null && 
returningValue.equalsIgnoreCase(TRUE));
-               procedure.setSchema(schema);
-               procedure.setCatalog(catalog);
-               dataMap.addProcedure(procedure);
-       }
-
-       private void processStartProcedureParameter(Attributes attributes) 
throws SAXException {
-
-               String name = attributes.getValue("", "name");
-               if (name == null) {
-                       throw new 
SAXException("MapLoader::processStartProcedureParameter()," + " no procedure 
parameter name.");
-               }
-
-               ProcedureParameter parameter = new ProcedureParameter(name);
-
-               String type = attributes.getValue("", "type");
-               if (type != null) {
-                       parameter.setType(TypesMapping.getSqlTypeByName(type));
-               }
-
-               String length = attributes.getValue("", "length");
-               if (length != null) {
-                       parameter.setMaxLength(Integer.parseInt(length));
-               }
-
-               String precision = attributes.getValue("", "precision");
-               if (precision != null) {
-                       parameter.setPrecision(Integer.parseInt(precision));
-               }
-
-               String direction = attributes.getValue("", "direction");
-               if ("in".equals(direction)) {
-                       parameter.setDirection(ProcedureParameter.IN_PARAMETER);
-               } else if ("out".equals(direction)) {
-                       
parameter.setDirection(ProcedureParameter.OUT_PARAMETER);
-               } else if ("in_out".equals(direction)) {
-                       
parameter.setDirection(ProcedureParameter.IN_OUT_PARAMETER);
-               }
-
-               procedure.addCallParameter(parameter);
-       }
-
-       private void processStartQuery(Attributes attributes) throws 
SAXException {
-               String name = attributes.getValue("", "name");
-               if (null == name) {
-                       throw new SAXException("MapLoader::processStartQuery(), 
no query name.");
-               }
-
-               this.queryBuilder = new QueryDescriptorLoader();
-
-               String type = attributes.getValue("", "type");
-               // Legacy format support (v7 and older)
-               if(type == null) {
-                       queryBuilder.setLegacyFactory(attributes.getValue("", 
"factory"));
-               } else {
-                       queryBuilder.setQueryType(type);
-               }
-
-               String rootType = attributes.getValue("", "root");
-               String rootName = attributes.getValue("", "root-name");
-               String resultEntity = attributes.getValue("", "result-entity");
-
-               queryBuilder.setName(name);
-               queryBuilder.setRoot(dataMap, rootType, rootName);
-
-               // TODO: Andrus, 2/13/2006 'result-type' is only used in 
ProcedureQuery
-               // and is
-               // deprecated in 1.2
-               if (!Util.isEmptyString(resultEntity)) {
-                       queryBuilder.setResultEntity(resultEntity);
-               }
-       }
-
-       private void processStartQueryProperty(Attributes attributes) throws 
SAXException {
-               String name = attributes.getValue("", "name");
-               if (null == name) {
-                       throw new 
SAXException("MapLoader::processStartQueryProperty(), no property name.");
-               }
-
-               String value = attributes.getValue("", "value");
-               if (null == value) {
-                       throw new 
SAXException("MapLoader::processStartQueryProperty(), no property value.");
-               }
-
-               queryBuilder.addProperty(name, value);
-       }
-
-       private void processStartDataMapProperty(Attributes attributes) throws 
SAXException {
-               String name = attributes.getValue("", "name");
-               if (null == name) {
-                       throw new 
SAXException("MapLoader::processStartDataMapProperty(), no property name.");
-               }
-
-               String value = attributes.getValue("", "value");
-               if (null == value) {
-                       throw new 
SAXException("MapLoader::processStartDataMapProperty(), no property value.");
-               }
-
-               if (mapProperties == null) {
-                       mapProperties = new TreeMap<String, Object>();
-               }
-
-               mapProperties.put(name, value);
-       }
-
-       private void processEndQueryPrefetch() {
-               queryBuilder.addPrefetch(charactersBuffer.toString());
-       }
-
-       private void processStartQueryOrdering(Attributes attributes) {
-               descending = attributes.getValue("", "descending");
-               ignoreCase = attributes.getValue("", "ignore-case");
-       }
-
-       private void processEndQuery() {
-               dataMap.addQueryDescriptor(queryBuilder.buildQueryDescriptor());
-               queryBuilder = null;
-       }
-
-       private void processEndEjbqlQuery() throws SAXException {
-               queryBuilder.setEjbql(charactersBuffer.toString());
-       }
-
-       private void processEndQuerySQL() {
-               queryBuilder.addSql(charactersBuffer.toString(), sqlKey);
-               sqlKey = null;
-       }
-
-       private void processEndQualifier() {
-               String qualifier = charactersBuffer.toString();
-               if (qualifier.trim().length() == 0) {
-                       return;
-               }
-
-               // qualifier can belong to ObjEntity, DbEntity or a query
-               if (objEntity != null) {
-                       
objEntity.setDeclaredQualifier(ExpressionFactory.exp(qualifier));
-               } else if (dbEntity != null) {
-                       dbEntity.setQualifier(ExpressionFactory.exp(qualifier));
-               } else {
-                       queryBuilder.setQualifier(qualifier);
-               }
-       }
-
-       private void processEndQueryOrdering() {
-               String path = charactersBuffer.toString();
-               queryBuilder.addOrdering(path, descending, ignoreCase);
-       }
-
-       private void processEndDbAttribute() {
-               attrib = null;
-       }
-
-       private void processEndDbEntity() {
-               dbEntity = null;
-       }
-
-       private void processEndProcedure() {
-               procedure = null;
-       }
-
-       private void processEndProcedureParameter() {
-       }
-
-       private void processEndDbGeneratorType() {
-               if (dbEntity == null)
-                       return;
-               DbKeyGenerator pkGenerator = dbEntity.getPrimaryKeyGenerator();
-               if (pkGenerator == null)
-                       return;
-               pkGenerator.setGeneratorType(charactersBuffer.toString());
-               if (pkGenerator.getGeneratorType() == null) {
-                       dbEntity.setPrimaryKeyGenerator(null);
-               }
-       }
-
-       private void processEndDbGeneratorName() {
-               if (dbEntity == null)
-                       return;
-               DbKeyGenerator pkGenerator = dbEntity.getPrimaryKeyGenerator();
-               if (pkGenerator == null)
-                       return;
-               pkGenerator.setGeneratorName(charactersBuffer.toString());
-       }
-
-       private void processEndDbKeyCacheSize() {
-               if (dbEntity == null)
-                       return;
-               DbKeyGenerator pkGenerator = dbEntity.getPrimaryKeyGenerator();
-               if (pkGenerator == null)
-                       return;
-               try {
-                       pkGenerator.setKeyCacheSize(new 
Integer(charactersBuffer.toString().trim()));
-               } catch (Exception ex) {
-                       pkGenerator.setKeyCacheSize(null);
-               }
-       }
-
-       private void processEndDataMap() {
-               if (mapProperties != null) {
-                       dataMap.initWithProperties(mapProperties);
-               }
-
-               mapProperties = null;
-               mapVersion = null;
-       }
-
-       private void processEndObjEntity() {
-               objEntity = null;
-       }
-
-       private void processEndEmbeddable() {
-               embeddable = null;
-       }
-
-       private void processEndEmbeddedAttribute() {
-               embeddedAttribute = null;
-       }
-
-       private void processEndDbRelationship() {
-               dbRelationship = null;
-       }
-
-       private void processEndObjRelationship() {
-               objRelationship = null;
-       }
-
-       /** Prints the attributes. Used for error reporting purposes. */
-       private StringBuffer printAttributes(Attributes atts) {
-               StringBuffer sb = new StringBuffer();
-               String name, value;
-               for (int i = 0; i < atts.getLength(); i++) {
-                       value = atts.getQName(i);
-                       name = atts.getValue(i);
-                       sb.append("Name: ").append(name).append("\tValue: 
").append(value).append("\n");
-               }
-               return sb;
-       }
-
-       @Override
-       public void characters(char[] text, int start, int length) throws 
org.xml.sax.SAXException {
-               if (charactersBuffer != null) {
-                       charactersBuffer.append(text, start, length);
-               }
-       }
-
-       private void rememberCurrentState(String tag, Attributes attrs) {
-               currentTag = tag;
-               currentAttributes = attrs;
-       }
-
-       private void resetCurrentState() {
-               currentTag = null;
-               currentAttributes = null;
-       }
-
-       /**
-        * @since 2.0
-        */
-       String convertClassNameFromV1_2(String name) {
-               if (name == null) {
-                       return null;
-               }
-
-               // upgrade from v. <= 1.2
-               if (name.startsWith(_1_2_PACKAGE_PREFIX)) {
-                       return _2_0_PACKAGE_PREFIX + 
name.substring(_1_2_PACKAGE_PREFIX.length());
-               }
-
-               return name;
-       }
-
-       abstract class StartClosure {
-
-               abstract void execute(Attributes attributes) throws 
SAXException;
-       }
-
-       abstract class EndClosure {
-
-               abstract void execute() throws SAXException;
-       }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c58b6f40/cayenne-server/src/main/java/org/apache/cayenne/map/ObjAttribute.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/map/ObjAttribute.java 
b/cayenne-server/src/main/java/org/apache/cayenne/map/ObjAttribute.java
index 57bfca3..ae03d13 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/map/ObjAttribute.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/map/ObjAttribute.java
@@ -24,7 +24,6 @@ import java.util.Iterator;
 import org.apache.cayenne.CayenneRuntimeException;
 import org.apache.cayenne.configuration.ConfigurationNode;
 import org.apache.cayenne.configuration.ConfigurationNodeVisitor;
-import org.apache.cayenne.dba.TypesMapping;
 import org.apache.cayenne.util.CayenneMapEntry;
 import org.apache.cayenne.util.Util;
 import org.apache.cayenne.util.XMLEncoder;
@@ -100,28 +99,15 @@ public class ObjAttribute extends Attribute implements 
ConfigurationNode {
      * @since 1.1
      */
     @Override
-    public void encodeAsXML(XMLEncoder encoder) {
-        encoder.print("<obj-attribute name=\"" + getName() + '\"');
-
-        if (getType() != null) {
-            encoder.print(" type=\"");
-            encoder.print(Util.encodeXmlAttribute(getType()));
-            encoder.print('\"');
-        }
-
-        if (isUsedForLocking()) {
-            encoder.print(" lock=\"true\"");
-        }
-
-        // If this obj attribute is mapped to db attribute
-        if (/*getDbAttribute() != null
-                || (((ObjEntity) getEntity()).isAbstract() && 
*/!Util.isEmptyString(getDbAttributePath())) {
-            encoder.print(" db-attribute-path=\"");
-            encoder.print(Util.encodeXmlAttribute(getDbAttributePath()));
-            encoder.print('\"');
-        }
-
-        encoder.println("/>");
+    public void encodeAsXML(XMLEncoder encoder, ConfigurationNodeVisitor 
delegate) {
+        encoder.start("obj-attribute")
+                .attribute("name", getName())
+                .attribute("type", getType())
+                .attribute("lock", isUsedForLocking())
+                .attribute("db-attribute-path", getDbAttributePath());
+
+        delegate.visitObjAttribute(this);
+        encoder.end();
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c58b6f40/cayenne-server/src/main/java/org/apache/cayenne/map/ObjEntity.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/map/ObjEntity.java 
b/cayenne-server/src/main/java/org/apache/cayenne/map/ObjEntity.java
index fee79a3..311f5ed 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/map/ObjEntity.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/map/ObjEntity.java
@@ -118,103 +118,59 @@ public class ObjEntity extends Entity implements 
ObjEntityListener, Configuratio
      * @since 1.1
      */
     @Override
-    public void encodeAsXML(XMLEncoder encoder) {
-        encoder.print("<obj-entity name=\"");
-        encoder.print(getName());
+    public void encodeAsXML(XMLEncoder encoder, ConfigurationNodeVisitor 
delegate) {
+        encoder.start("obj-entity").attribute("name", getName());
 
-        // additionally validate that superentity exists
+        // additionally validate that super entity exists
         if (getSuperEntityName() != null && getSuperEntity() != null) {
-            encoder.print("\" superEntityName=\"");
-            encoder.print(getSuperEntityName());
+            encoder.attribute("superEntityName", getSuperEntityName());
         }
 
-        if (isAbstract()) {
-            encoder.print("\" abstract=\"true");
-        }
-
-        if (isServerOnly()) {
-            encoder.print("\" serverOnly=\"true");
-        }
-
-        if (getClassName() != null) {
-            encoder.print("\" className=\"");
-            encoder.print(getClassName());
-        }
-
-        if (getClientClassName() != null) {
-            encoder.print("\" clientClassName=\"");
-            encoder.print(getClientClassName());
-        }
-
-        if (isReadOnly()) {
-            encoder.print("\" readOnly=\"true");
-        }
+        encoder.attribute("abstract", isAbstract())
+                .attribute("serverOnly", isServerOnly())
+                .attribute("className", getClassName())
+                .attribute("clientClassName", getClientClassName())
+                .attribute("readOnly", isReadOnly());
 
         if (getDeclaredLockType() == LOCK_TYPE_OPTIMISTIC) {
-            encoder.print("\" lock-type=\"optimistic");
+            encoder.attribute("lock-type", "optimistic");
         }
 
         if (getDbEntityName() != null && getDbEntity() != null) {
-
             // not writing DbEntity name if sub entity has same DbEntity
             // as super entity, see CAY-1477
             if (!(getSuperEntity() != null && getSuperEntity().getDbEntity() 
== getDbEntity())) {
-                encoder.print("\" dbEntityName=\"");
-                encoder.print(Util.encodeXmlAttribute(getDbEntityName()));
+                encoder.attribute("dbEntityName", getDbEntityName());
             }
         }
 
         if (getSuperEntityName() == null && getSuperClassName() != null) {
-            encoder.print("\" superClassName=\"");
-            encoder.print(getSuperClassName());
+            encoder.attribute("superClassName", getSuperClassName());
         }
 
         if (getSuperEntityName() == null && getClientSuperClassName() != null) 
{
-            encoder.print("\" clientSuperClassName=\"");
-            encoder.print(getClientSuperClassName());
-        }
-
-        // deprecated
-        if (isExcludingSuperclassListeners()) {
-            encoder.print("\" exclude-superclass-listeners=\"true");
-        }
-
-        // deprecated
-        if (isExcludingDefaultListeners()) {
-            encoder.print("\" exclude-default-listeners=\"true");
+            encoder.attribute("clientSuperClassName", 
getClientSuperClassName());
         }
 
-        encoder.println("\">");
-        encoder.indent(1);
-
         if (qualifier != null) {
-            encoder.print("<qualifier>");
-            qualifier.encodeAsXML(encoder);
-            encoder.println("</qualifier>");
+            encoder.start("qualifier").nested(qualifier, delegate).end();
         }
 
         // store attributes
-        encoder.print(getDeclaredAttributes());
+        encoder.nested(getDeclaredAttributes(), delegate);
 
         for (Map.Entry<String, String> override : 
attributeOverrides.entrySet()) {
-            encoder.print("<attribute-override name=\"" + override.getKey() + 
'\"');
-            encoder.print(" db-attribute-path=\"");
-            encoder.print(Util.encodeXmlAttribute(override.getValue()));
-            encoder.print('\"');
-            encoder.println("/>");
-        }
-
-        // deprecated
-        // write entity listeners
-        for (EntityListener entityListener : entityListeners) {
-            entityListener.encodeAsXML(encoder);
+            encoder.start("attribute-override")
+                    .attribute("name", override.getKey())
+                    .attribute("db-attribute-path", override.getValue())
+                    .end();
         }
 
         // write entity-level callbacks
         getCallbackMap().encodeCallbacksAsXML(encoder);
 
-        encoder.indent(-1);
-        encoder.println("</obj-entity>");
+        delegate.visitObjEntity(this);
+        encoder.end();
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c58b6f40/cayenne-server/src/main/java/org/apache/cayenne/map/ObjRelationship.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/map/ObjRelationship.java 
b/cayenne-server/src/main/java/org/apache/cayenne/map/ObjRelationship.java
index 6163684..0fa0812 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/map/ObjRelationship.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/map/ObjRelationship.java
@@ -104,51 +104,43 @@ public class ObjRelationship extends Relationship 
implements ConfigurationNode {
      * 
      * @since 1.1
      */
-    public void encodeAsXML(XMLEncoder encoder) {
-        ObjEntity source = (ObjEntity) getSourceEntity();
+    @Override
+    public void encodeAsXML(XMLEncoder encoder, ConfigurationNodeVisitor 
delegate) {
+        ObjEntity source = getSourceEntity();
         if (source == null) {
             return;
         }
 
-        encoder.print("<obj-relationship name=\"" + getName());
-        encoder.print("\" source=\"" + source.getName());
+        encoder.start("obj-relationship")
+                .attribute("name", getName())
+                .attribute("source", source.getName());
 
         // looking up a target entity ensures that bogus names are not saved...
-        // whether
-        // this is good or bad is debatable, as users may want to point to
-        // non-existent
-        // entities on purpose.
-        ObjEntity target = (ObjEntity) getTargetEntity();
+        // whether this is good or bad is debatable, as users may want to 
point to
+        // non-existent entities on purpose.
+        ObjEntity target = getTargetEntity();
         if (target != null) {
-            encoder.print("\" target=\"" + target.getName());
+            encoder.attribute("target", target.getName());
         }
 
         if (getCollectionType() != null && 
!DEFAULT_COLLECTION_TYPE.equals(getCollectionType())) {
-            encoder.print("\" collection-type=\"" + getCollectionType());
+            encoder.attribute("collection-type", getCollectionType());
         }
 
-        if (getMapKey() != null) {
-            encoder.print("\" map-key=\"" + getMapKey());
-        }
-
-        if (isUsedForLocking()) {
-            encoder.print("\" lock=\"true");
-        }
+        encoder.attribute("lock", isUsedForLocking())
+                .attribute("map-key", getMapKey());
 
         String deleteRule = DeleteRule.deleteRuleName(getDeleteRule());
-        if (getDeleteRule() != DeleteRule.NO_ACTION && deleteRule != null) {
-            encoder.print("\" deleteRule=\"" + deleteRule);
+        if (deleteRule != null && getDeleteRule() != DeleteRule.NO_ACTION) {
+            encoder.attribute("deleteRule", deleteRule);
         }
 
         // quietly get rid of invalid path... this is not the best way of doing
-        // things,
-        // but it is consistent across map package
-        String path = getValidRelationshipPath();
-        if (path != null) {
-            encoder.print("\" db-relationship-path=\"" + path);
-        }
+        // things, but it is consistent across map package
+        encoder.attribute("db-relationship-path", getValidRelationshipPath());
 
-        encoder.println("\"/>");
+        delegate.visitObjRelationship(this);
+        encoder.end();
     }
 
     /**
@@ -563,8 +555,9 @@ public class ObjRelationship extends Relationship 
implements ConfigurationNode {
     /**
      * Sets relationship path, but does not trigger its conversion to
      * List<DbRelationship> For internal purposes, primarily datamap loading
+     * @since 4.1 this method is public as it is used by new XML loaders
      */
-    void setDeferredDbRelationshipPath(String relationshipPath) {
+    public void setDeferredDbRelationshipPath(String relationshipPath) {
         if (!Util.nullSafeEquals(getDbRelationshipPath(), relationshipPath)) {
             deferredPath = relationshipPath;
         }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c58b6f40/cayenne-server/src/main/java/org/apache/cayenne/map/Procedure.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/map/Procedure.java 
b/cayenne-server/src/main/java/org/apache/cayenne/map/Procedure.java
index 8ec25cb..5358178 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/map/Procedure.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/map/Procedure.java
@@ -22,7 +22,6 @@ package org.apache.cayenne.map;
 import org.apache.cayenne.configuration.ConfigurationNode;
 import org.apache.cayenne.configuration.ConfigurationNodeVisitor;
 import org.apache.cayenne.util.CayenneMapEntry;
-import org.apache.cayenne.util.Util;
 import org.apache.cayenne.util.XMLEncoder;
 import org.apache.cayenne.util.XMLSerializable;
 
@@ -90,34 +89,16 @@ public class Procedure implements ConfigurationNode, 
CayenneMapEntry, XMLSeriali
      * 
      * @since 1.1
      */
-    public void encodeAsXML(XMLEncoder encoder) {
-        encoder.print("<procedure name=\"");
-        encoder.print(Util.encodeXmlAttribute(getName()));
-        encoder.print('\"');
-
-        if (getSchema() != null && getSchema().trim().length() > 0) {
-            encoder.print(" schema=\"");
-            encoder.print(getSchema().trim());
-            encoder.print('\"');
-        }
-
-        if (getCatalog() != null && getCatalog().trim().length() > 0) {
-            encoder.print(" catalog=\"");
-            encoder.print(getCatalog().trim());
-            encoder.print('\"');
-        }
-
-        if (isReturningValue()) {
-            encoder.print(" returningValue=\"true\"");
-        }
-
-        encoder.println('>');
-
-        encoder.indent(1);
-        encoder.print(getCallParameters());
-        encoder.indent(-1);
-
-        encoder.println("</procedure>");
+    @Override
+    public void encodeAsXML(XMLEncoder encoder, ConfigurationNodeVisitor 
delegate) {
+        encoder.start("procedure")
+                .attribute("name", getName())
+                .attribute("schema", getSchema())
+                .attribute("catalog", getCatalog())
+                .attribute("returningValue", isReturningValue())
+                .nested(getCallParameters(), delegate);
+        delegate.visitProcedure(this);
+        encoder.end();
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c58b6f40/cayenne-server/src/main/java/org/apache/cayenne/map/ProcedureParameter.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/map/ProcedureParameter.java 
b/cayenne-server/src/main/java/org/apache/cayenne/map/ProcedureParameter.java
index 1180f30..b9fb55a 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/map/ProcedureParameter.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/map/ProcedureParameter.java
@@ -25,7 +25,6 @@ import org.apache.cayenne.configuration.ConfigurationNode;
 import org.apache.cayenne.configuration.ConfigurationNodeVisitor;
 import org.apache.cayenne.dba.TypesMapping;
 import org.apache.cayenne.util.CayenneMapEntry;
-import org.apache.cayenne.util.Util;
 import org.apache.cayenne.util.XMLEncoder;
 import org.apache.cayenne.util.XMLSerializable;
 
@@ -85,8 +84,7 @@ public class ProcedureParameter implements ConfigurationNode, 
CayenneMapEntry,
 
     public void setParent(Object parent) {
         if (parent != null && !(parent instanceof Procedure)) {
-            throw new IllegalArgumentException("Expected null or Procedure, 
got: "
-                    + parent);
+            throw new IllegalArgumentException("Expected null or Procedure, 
got: " + parent);
         }
 
         setProcedure((Procedure) parent);
@@ -97,40 +95,25 @@ public class ProcedureParameter implements 
ConfigurationNode, CayenneMapEntry,
      * 
      * @since 1.1
      */
-    public void encodeAsXML(XMLEncoder encoder) {
-        encoder.print("<procedure-parameter name=\""
-                + Util.encodeXmlAttribute(getName())
-                + '\"');
-
-        String type = TypesMapping.getSqlNameByType(getType());
-        if (type != null) {
-            encoder.print(" type=\"" + type + '\"');
-        }
-
-        if (getMaxLength() > 0) {
-            encoder.print(" length=\"");
-            encoder.print(getMaxLength());
-            encoder.print('\"');
-        }
-
-        if (getPrecision() > 0) {
-            encoder.print(" precision=\"");
-            encoder.print(getPrecision());
-            encoder.print('\"');
-        }
+    @Override
+    public void encodeAsXML(XMLEncoder encoder, ConfigurationNodeVisitor 
delegate) {
+        encoder.start("procedure-parameter")
+                .attribute("name", getName())
+                .attribute("type", TypesMapping.getSqlNameByType(getType()))
+                .attribute("length", getMaxLength() > 0 ? getMaxLength() : 0)
+                .attribute("precision", getPrecision() > 0 ? getPrecision() : 
0);
 
         int direction = getDirection();
         if (direction == ProcedureParameter.IN_PARAMETER) {
-            encoder.print(" direction=\"in\"");
-        }
-        else if (direction == ProcedureParameter.IN_OUT_PARAMETER) {
-            encoder.print(" direction=\"in_out\"");
-        }
-        else if (direction == ProcedureParameter.OUT_PARAMETER) {
-            encoder.print(" direction=\"out\"");
+            encoder.attribute("direction", "in");
+        } else if (direction == ProcedureParameter.IN_OUT_PARAMETER) {
+            encoder.attribute("direction", "in_out");
+        } else if (direction == ProcedureParameter.OUT_PARAMETER) {
+            encoder.attribute("direction", "out");
         }
 
-        encoder.println("/>");
+        delegate.visitProcedureParameter(this);
+        encoder.end();
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c58b6f40/cayenne-server/src/main/java/org/apache/cayenne/map/ProcedureQueryDescriptor.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/map/ProcedureQueryDescriptor.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/map/ProcedureQueryDescriptor.java
index fa1a087..a3ad9cf 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/map/ProcedureQueryDescriptor.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/map/ProcedureQueryDescriptor.java
@@ -18,6 +18,7 @@
  ****************************************************************/
 package org.apache.cayenne.map;
 
+import org.apache.cayenne.configuration.ConfigurationNodeVisitor;
 import org.apache.cayenne.query.ProcedureQuery;
 import org.apache.cayenne.util.XMLEncoder;
 
@@ -65,41 +66,26 @@ public class ProcedureQueryDescriptor extends 
QueryDescriptor {
     }
 
     @Override
-    public void encodeAsXML(XMLEncoder encoder) {
-        encoder.print("<query name=\"");
-        encoder.print(getName());
-        encoder.print("\" type=\"");
-        encoder.print(type);
-
-        encoder.print("\" root=\"");
-        encoder.print(MapLoader.PROCEDURE_ROOT);
+    public void encodeAsXML(XMLEncoder encoder, ConfigurationNodeVisitor 
delegate) {
+        encoder.start("query")
+                .attribute("name", getName())
+                .attribute("type", getType())
+                .attribute("root", QueryDescriptor.PROCEDURE_ROOT);
 
         String rootString = null;
-
         if (root instanceof String) {
             rootString = root.toString();
-        }
-        else if (root instanceof Procedure) {
+        } else if (root instanceof Procedure) {
             rootString = ((Procedure) root).getName();
         }
 
-        if (rootString != null) {
-            encoder.print("\" root-name=\"");
-            encoder.print(rootString);
-        }
-
-        if (resultEntityName != null) {
-            encoder.print("\" result-entity=\"");
-            encoder.print(resultEntityName);
-        }
-
-        encoder.println("\">");
-        encoder.indent(1);
+        encoder.attribute("root-name", rootString)
+                .attribute("result-entity", resultEntityName);
 
         // print properties
         encodeProperties(encoder);
 
-        encoder.indent(-1);
-        encoder.println("</query>");
+        delegate.visitQuery(this);
+        encoder.end();
     }
 }

Reply via email to