You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by nt...@apache.org on 2017/07/25 12:25:21 UTC

[06/16] cayenne git commit: CAY-2335: New XML loading/saving mechanics with support of plugable handlers - new XML loader for DataMap - new project version - updated test projects

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c58b6f40/cayenne-server/src/main/java/org/apache/cayenne/map/QueryDescriptor.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/map/QueryDescriptor.java b/cayenne-server/src/main/java/org/apache/cayenne/map/QueryDescriptor.java
index fe18894..335f109 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/map/QueryDescriptor.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/map/QueryDescriptor.java
@@ -42,6 +42,31 @@ public class QueryDescriptor implements Serializable, ConfigurationNode, XMLSeri
     public static final String PROCEDURE_QUERY = "ProcedureQuery";
 
     /**
+     * @since 4.1
+     */
+    public static final String OBJ_ENTITY_ROOT = "obj-entity";
+
+    /**
+     * @since 4.1
+     */
+    public static final String DB_ENTITY_ROOT = "db-entity";
+
+    /**
+     * @since 4.1
+     */
+    public static final String PROCEDURE_ROOT = "procedure";
+
+    /**
+     * @since 4.1
+     */
+    public static final String DATA_MAP_ROOT = "data-map";
+
+    /**
+     * @since 4.1
+     */
+    public static final String JAVA_CLASS_ROOT = "java-class";
+
+    /**
      * Creates new SelectQuery query descriptor.
      */
     public static SelectQueryDescriptor selectQueryDescriptor() {
@@ -189,47 +214,36 @@ public class QueryDescriptor implements Serializable, ConfigurationNode, XMLSeri
     }
 
     @Override
-    public void encodeAsXML(XMLEncoder encoder) {
-        encoder.print("<query name=\"");
-        encoder.print(getName());
-        encoder.print("\" type=\"");
-        encoder.print(type);
+    public void encodeAsXML(XMLEncoder encoder, ConfigurationNodeVisitor delegate) {
+        encoder.start("query").attribute("name", getName()).attribute("type", type);
 
         String rootString = null;
         String rootType = null;
 
         if (root instanceof String) {
-            rootType = MapLoader.OBJ_ENTITY_ROOT;
+            rootType = OBJ_ENTITY_ROOT;
             rootString = root.toString();
         } else if (root instanceof ObjEntity) {
-            rootType = MapLoader.OBJ_ENTITY_ROOT;
+            rootType = OBJ_ENTITY_ROOT;
             rootString = ((ObjEntity) root).getName();
         } else if (root instanceof DbEntity) {
-            rootType = MapLoader.DB_ENTITY_ROOT;
+            rootType = DB_ENTITY_ROOT;
             rootString = ((DbEntity) root).getName();
         } else if (root instanceof Procedure) {
-            rootType = MapLoader.PROCEDURE_ROOT;
+            rootType = PROCEDURE_ROOT;
             rootString = ((Procedure) root).getName();
         } else if (root instanceof Class<?>) {
-            rootType = MapLoader.JAVA_CLASS_ROOT;
+            rootType = JAVA_CLASS_ROOT;
             rootString = ((Class<?>) root).getName();
         }
 
         if (rootType != null) {
-            encoder.print("\" root=\"");
-            encoder.print(rootType);
-            encoder.print("\" root-name=\"");
-            encoder.print(rootString);
+            encoder.attribute("root", rootType).attribute("root-name", rootString);
         }
-
-        encoder.println("\">");
-
-        encoder.indent(1);
-
         encodeProperties(encoder);
 
-        encoder.indent(-1);
-        encoder.println("</query>");
+        delegate.visitQuery(this);
+        encoder.end();
     }
 
     void encodeProperties(XMLEncoder encoder) {
@@ -238,7 +252,7 @@ public class QueryDescriptor implements Serializable, ConfigurationNode, XMLSeri
             if(value == null || value.isEmpty()) {
                 continue;
             }
-            encoder.printProperty(property.getKey(), value);
+            encoder.property(property.getKey(), value);
         }
     }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c58b6f40/cayenne-server/src/main/java/org/apache/cayenne/map/QueryDescriptorLoader.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/map/QueryDescriptorLoader.java b/cayenne-server/src/main/java/org/apache/cayenne/map/QueryDescriptorLoader.java
index 10038cc..cd5235f 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/map/QueryDescriptorLoader.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/map/QueryDescriptorLoader.java
@@ -88,7 +88,7 @@ public class QueryDescriptorLoader {
         return descriptor;
     }
 
-    void setName(String name) {
+    public void setName(String name) {
         this.name = name;
     }
 
@@ -98,7 +98,7 @@ public class QueryDescriptorLoader {
      * and can't be changed without complete upgrade system rewrite
      * @param factory old style query factory class
      */
-    void setLegacyFactory(String factory) {
+    public void setLegacyFactory(String factory) {
         switch (factory) {
             case "org.apache.cayenne.map.SelectQueryBuilder":
                 queryType = QueryDescriptor.SELECT_QUERY;
@@ -117,7 +117,7 @@ public class QueryDescriptorLoader {
         }
     }
 
-    void setQueryType(String queryType) {
+    public void setQueryType(String queryType) {
         this.queryType = queryType;
     }
 
@@ -132,20 +132,20 @@ public class QueryDescriptorLoader {
         Object root = null;
 
         if (rootType == null
-                || MapLoader.DATA_MAP_ROOT.equals(rootType)
+                || QueryDescriptor.DATA_MAP_ROOT.equals(rootType)
                 || rootName == null) {
             root = dataMap;
         }
-        else if (MapLoader.OBJ_ENTITY_ROOT.equals(rootType)) {
+        else if (QueryDescriptor.OBJ_ENTITY_ROOT.equals(rootType)) {
             root = dataMap.getObjEntity(rootName);
         }
-        else if (MapLoader.DB_ENTITY_ROOT.equals(rootType)) {
+        else if (QueryDescriptor.DB_ENTITY_ROOT.equals(rootType)) {
             root = dataMap.getDbEntity(rootName);
         }
-        else if (MapLoader.PROCEDURE_ROOT.equals(rootType)) {
+        else if (QueryDescriptor.PROCEDURE_ROOT.equals(rootType)) {
             root = dataMap.getProcedure(rootName);
         }
-        else if (MapLoader.JAVA_CLASS_ROOT.equals(rootType)) {
+        else if (QueryDescriptor.JAVA_CLASS_ROOT.equals(rootType)) {
             // setting root to ObjEntity, since creating a Class requires
             // the knowledge of the ClassLoader
             root = dataMap.getObjEntityForJavaClass(rootName);
@@ -154,20 +154,20 @@ public class QueryDescriptorLoader {
         return (root != null) ? root : dataMap;
     }
 
-    void setResultEntity(String resultEntity) {
+    public void setResultEntity(String resultEntity) {
         this.resultEntity = resultEntity;
     }
 
     /**
      * Sets the information pertaining to the root of the query.
      */
-    void setRoot(DataMap dataMap, String rootType, String rootName) {
+    public void setRoot(DataMap dataMap, String rootType, String rootName) {
         this.dataMap = dataMap;
         this.rootType = rootType;
         this.rootName = rootName;
     }
 
-    void setEjbql(String ejbql) {
+    public void setEjbql(String ejbql) {
         this.ejbql = ejbql;
     }
 
@@ -175,7 +175,7 @@ public class QueryDescriptorLoader {
      * Adds raw sql. If adapterClass parameter is not null, sets the SQL string to be
      * adapter-specific. Otherwise it is used as a default SQL string.
      */
-    void addSql(String sql, String adapterClass) {
+    public void addSql(String sql, String adapterClass) {
         if (adapterClass == null) {
             this.sql = sql;
         }
@@ -188,7 +188,7 @@ public class QueryDescriptorLoader {
         }
     }
 
-    void setQualifier(String qualifier) {
+    public void setQualifier(String qualifier) {
         if (qualifier == null || qualifier.trim().length() == 0) {
             this.qualifier = null;
         }
@@ -197,7 +197,7 @@ public class QueryDescriptorLoader {
         }
     }
 
-    void addProperty(String name, String value) {
+    public void addProperty(String name, String value) {
         if (properties == null) {
             properties = new HashMap<>();
         }
@@ -205,7 +205,7 @@ public class QueryDescriptorLoader {
         properties.put(name, value);
     }
 
-    void addOrdering(String path, String descending, String ignoreCase) {
+    public void addOrdering(String path, String descending, String ignoreCase) {
         if (orderings == null) {
             orderings = new ArrayList<>();
         }
@@ -232,7 +232,7 @@ public class QueryDescriptorLoader {
         orderings.add(new Ordering(path, order));
     }
 
-    void addPrefetch(String path) {
+    public void addPrefetch(String path) {
         if (path == null || (path != null && path.trim().length() == 0)) {
             // throw??
             return;

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c58b6f40/cayenne-server/src/main/java/org/apache/cayenne/map/SQLTemplateDescriptor.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/map/SQLTemplateDescriptor.java b/cayenne-server/src/main/java/org/apache/cayenne/map/SQLTemplateDescriptor.java
index f9b1ff0..e9c14f7 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/map/SQLTemplateDescriptor.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/map/SQLTemplateDescriptor.java
@@ -18,6 +18,7 @@
  ****************************************************************/
 package org.apache.cayenne.map;
 
+import org.apache.cayenne.configuration.ConfigurationNodeVisitor;
 import org.apache.cayenne.query.SQLTemplate;
 import org.apache.cayenne.util.XMLEncoder;
 
@@ -97,78 +98,64 @@ public class SQLTemplateDescriptor extends QueryDescriptor {
     }
 
     @Override
-    public void encodeAsXML(XMLEncoder encoder) {
-        encoder.print("<query name=\"");
-        encoder.print(getName());
-        encoder.print("\" type=\"");
-        encoder.print(type);
+    public void encodeAsXML(XMLEncoder encoder, ConfigurationNodeVisitor delegate) {
+        encoder.start("query")
+                .attribute("name", getName())
+                .attribute("type", type);
 
         String rootString = null;
         String rootType = null;
 
         if (root instanceof String) {
-            rootType = MapLoader.OBJ_ENTITY_ROOT;
+            rootType = QueryDescriptor.OBJ_ENTITY_ROOT;
             rootString = root.toString();
         } else if (root instanceof ObjEntity) {
-            rootType = MapLoader.OBJ_ENTITY_ROOT;
+            rootType = QueryDescriptor.OBJ_ENTITY_ROOT;
             rootString = ((ObjEntity) root).getName();
         } else if (root instanceof DbEntity) {
-            rootType = MapLoader.DB_ENTITY_ROOT;
+            rootType = QueryDescriptor.DB_ENTITY_ROOT;
             rootString = ((DbEntity) root).getName();
         } else if (root instanceof Procedure) {
-            rootType = MapLoader.PROCEDURE_ROOT;
+            rootType = QueryDescriptor.PROCEDURE_ROOT;
             rootString = ((Procedure) root).getName();
         } else if (root instanceof Class<?>) {
-            rootType = MapLoader.JAVA_CLASS_ROOT;
+            rootType = QueryDescriptor.JAVA_CLASS_ROOT;
             rootString = ((Class<?>) root).getName();
         } else if (root instanceof DataMap) {
-            rootType = MapLoader.DATA_MAP_ROOT;
+            rootType = QueryDescriptor.DATA_MAP_ROOT;
             rootString = ((DataMap) root).getName();
         }
 
         if (rootType != null) {
-            encoder.print("\" root=\"");
-            encoder.print(rootType);
-            encoder.print("\" root-name=\"");
-            encoder.print(rootString);
+            encoder.attribute("root", rootType).attribute("root-name", rootString);
         }
 
-        encoder.println("\">");
-
-        encoder.indent(1);
-
         // print properties
         encodeProperties(encoder);
-
         // encode default SQL
         if (sql != null) {
-            encoder.print("<sql><![CDATA[");
-            encoder.print(sql);
-            encoder.println("]]></sql>");
+            encoder.start("sql").cdata(sql, true).end();
         }
 
         // encode adapter SQL
         if (adapterSql != null && !adapterSql.isEmpty()) {
-
             // sorting entries by adapter name
-            TreeSet<String> keys = new TreeSet<String>(adapterSql.keySet());
+            TreeSet<String> keys = new TreeSet<>(adapterSql.keySet());
             for (String key : keys) {
                 String value = adapterSql.get(key);
-
                 if (key != null && value != null) {
                     String sql = value.trim();
                     if (sql.length() > 0) {
-                        encoder.print("<sql adapter-class=\"");
-                        encoder.print(key);
-                        encoder.print("\"><![CDATA[");
-                        encoder.print(sql);
-                        encoder.println("]]></sql>");
+                        encoder.start("sql")
+                                .attribute("adapter-class", key)
+                                .cdata(sql, 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/SelectQueryDescriptor.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/map/SelectQueryDescriptor.java b/cayenne-server/src/main/java/org/apache/cayenne/map/SelectQueryDescriptor.java
index e844b3e..7a677fb 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/map/SelectQueryDescriptor.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/map/SelectQueryDescriptor.java
@@ -18,6 +18,7 @@
  ****************************************************************/
 package org.apache.cayenne.map;
 
+import org.apache.cayenne.configuration.ConfigurationNodeVisitor;
 import org.apache.cayenne.exp.Expression;
 import org.apache.cayenne.query.Ordering;
 import org.apache.cayenne.query.PrefetchTreeNode;
@@ -26,7 +27,6 @@ import org.apache.cayenne.util.XMLEncoder;
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Map;
 
 /**
  * @since 4.0
@@ -153,59 +153,45 @@ public class SelectQueryDescriptor extends QueryDescriptor {
     }
 
     @Override
-    public void encodeAsXML(XMLEncoder encoder) {
-        encoder.print("<query name=\"");
-        encoder.print(getName());
-        encoder.print("\" type=\"");
-        encoder.print(type);
+    public void encodeAsXML(XMLEncoder encoder, ConfigurationNodeVisitor delegate) {
+        encoder.start("query")
+                .attribute("name", getName())
+                .attribute("type", type);
 
         String rootString = null;
         String rootType = null;
 
         if (root instanceof String) {
-            rootType = MapLoader.OBJ_ENTITY_ROOT;
+            rootType = QueryDescriptor.OBJ_ENTITY_ROOT;
             rootString = root.toString();
         } else if (root instanceof ObjEntity) {
-            rootType = MapLoader.OBJ_ENTITY_ROOT;
+            rootType = QueryDescriptor.OBJ_ENTITY_ROOT;
             rootString = ((ObjEntity) root).getName();
         } else if (root instanceof DbEntity) {
-            rootType = MapLoader.DB_ENTITY_ROOT;
+            rootType = QueryDescriptor.DB_ENTITY_ROOT;
             rootString = ((DbEntity) root).getName();
         } else if (root instanceof Procedure) {
-            rootType = MapLoader.PROCEDURE_ROOT;
+            rootType = QueryDescriptor.PROCEDURE_ROOT;
             rootString = ((Procedure) root).getName();
         } else if (root instanceof Class<?>) {
-            rootType = MapLoader.JAVA_CLASS_ROOT;
+            rootType = QueryDescriptor.JAVA_CLASS_ROOT;
             rootString = ((Class<?>) root).getName();
         }
 
         if (rootType != null) {
-            encoder.print("\" root=\"");
-            encoder.print(rootType);
-            encoder.print("\" root-name=\"");
-            encoder.print(rootString);
+            encoder.attribute("root", rootType).attribute("root-name", rootString);
         }
 
-        encoder.println("\">");
-
-        encoder.indent(1);
-
         // print properties
         encodeProperties(encoder);
 
         // encode qualifier
         if (qualifier != null) {
-            encoder.print("<qualifier>");
-            qualifier.encodeAsXML(encoder);
-            encoder.println("</qualifier>");
+            encoder.start("qualifier").nested(qualifier, delegate).end();
         }
 
         // encode orderings
-        if (orderings != null && !orderings.isEmpty()) {
-            for (Ordering ordering : orderings) {
-                ordering.encodeAsXML(encoder);
-            }
-        }
+        encoder.nested(orderings, delegate);
 
         PrefetchTreeNode prefetchTree = new PrefetchTreeNode();
 
@@ -215,9 +201,9 @@ public class SelectQueryDescriptor extends QueryDescriptor {
             node.setPhantom(false);
         }
 
-        prefetchTree.encodeAsXML(encoder);
+        encoder.nested(prefetchTree, delegate);
 
-        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/query/BaseQueryMetadata.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/BaseQueryMetadata.java b/cayenne-server/src/main/java/org/apache/cayenne/query/BaseQueryMetadata.java
index bc13719..a450b3b 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/query/BaseQueryMetadata.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/query/BaseQueryMetadata.java
@@ -27,6 +27,7 @@ import java.util.Map;
 import java.util.StringTokenizer;
 
 import org.apache.cayenne.Persistent;
+import org.apache.cayenne.configuration.ConfigurationNodeVisitor;
 import org.apache.cayenne.map.DataMap;
 import org.apache.cayenne.map.DbEntity;
 import org.apache.cayenne.map.EntityResolver;
@@ -41,7 +42,7 @@ import org.apache.cayenne.util.XMLSerializable;
  * 
  * @since 1.1
  */
-class BaseQueryMetadata implements QueryMetadata, XMLSerializable, Serializable {
+class BaseQueryMetadata implements QueryMetadata, Serializable {
 
 	private static final long serialVersionUID = 5129792493303459115L;
 
@@ -195,41 +196,6 @@ class BaseQueryMetadata implements QueryMetadata, XMLSerializable, Serializable
 		}
 	}
 
-	public void encodeAsXML(XMLEncoder encoder) {
-
-		if (fetchingDataRows != QueryMetadata.FETCHING_DATA_ROWS_DEFAULT) {
-			encoder.printProperty(QueryMetadata.FETCHING_DATA_ROWS_PROPERTY, fetchingDataRows);
-		}
-
-		if (fetchOffset != QueryMetadata.FETCH_OFFSET_DEFAULT) {
-			encoder.printProperty(QueryMetadata.FETCH_OFFSET_PROPERTY, fetchOffset);
-		}
-
-		if (fetchLimit != QueryMetadata.FETCH_LIMIT_DEFAULT) {
-			encoder.printProperty(QueryMetadata.FETCH_LIMIT_PROPERTY, fetchLimit);
-		}
-
-		if (pageSize != QueryMetadata.PAGE_SIZE_DEFAULT) {
-			encoder.printProperty(QueryMetadata.PAGE_SIZE_PROPERTY, pageSize);
-		}
-
-		if (cacheStrategy != null && QueryCacheStrategy.getDefaultStrategy() != cacheStrategy) {
-			encoder.printProperty(QueryMetadata.CACHE_STRATEGY_PROPERTY, cacheStrategy.name());
-		}
-
-		if (statementFetchSize != QueryMetadata.STATEMENT_FETCH_SIZE_DEFAULT) {
-			encoder.printProperty(QueryMetadata.STATEMENT_FETCH_SIZE_PROPERTY, statementFetchSize);
-		}
-
-		if (prefetchTree != null) {
-			prefetchTree.encodeAsXML(encoder);
-		}
-
-		if (cacheGroup != null) {
-			encoder.printProperty(QueryMetadata.CACHE_GROUPS_PROPERTY, cacheGroup);
-		}
-	}
-
 	/**
 	 * @since 1.2
 	 */

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c58b6f40/cayenne-server/src/main/java/org/apache/cayenne/query/EJBQLQuery.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/EJBQLQuery.java b/cayenne-server/src/main/java/org/apache/cayenne/query/EJBQLQuery.java
index 18732c7..7103562 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/query/EJBQLQuery.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/query/EJBQLQuery.java
@@ -19,6 +19,7 @@
 package org.apache.cayenne.query;
 
 import org.apache.cayenne.CayenneRuntimeException;
+import org.apache.cayenne.configuration.ConfigurationNodeVisitor;
 import org.apache.cayenne.ejbql.EJBQLCompiledExpression;
 import org.apache.cayenne.ejbql.EJBQLException;
 import org.apache.cayenne.ejbql.EJBQLParserFactory;
@@ -36,7 +37,7 @@ import java.util.Map;
  * 
  * @since 3.0
  */
-public class EJBQLQuery extends CacheableQuery implements XMLSerializable {
+public class EJBQLQuery extends CacheableQuery {
 
     @Deprecated
     protected String name;
@@ -224,28 +225,6 @@ public class EJBQLQuery extends CacheableQuery implements XMLSerializable {
         metadata.setFetchOffset(fetchOffset);
     }
 
-    public void encodeAsXML(XMLEncoder encoder) {
-        encoder.print("<query name=\"");
-        encoder.print(getName());
-        encoder.print("\" factory=\"");
-        encoder.print("org.apache.cayenne.map.EjbqlBuilder");
-
-        encoder.println("\">");
-
-        encoder.indent(1);
-
-        metadata.encodeAsXML(encoder);
-
-        if (ejbqlStatement != null) {
-            encoder.print("<ejbql><![CDATA[");
-            encoder.print(ejbqlStatement);
-            encoder.println("]]></ejbql>");
-        }
-
-        encoder.indent(-1);
-        encoder.println("</query>");
-    }
-
     public void setEjbqlStatement(String text) {
         this.ejbqlStatement = text;
     }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c58b6f40/cayenne-server/src/main/java/org/apache/cayenne/query/Ordering.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/Ordering.java b/cayenne-server/src/main/java/org/apache/cayenne/query/Ordering.java
index 3625df6..38d6a2e 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/query/Ordering.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/query/Ordering.java
@@ -19,6 +19,8 @@
 
 package org.apache.cayenne.query;
 
+import org.apache.cayenne.configuration.ConfigurationNodeVisitor;
+import org.apache.cayenne.configuration.EmptyConfigurationNodeVisitor;
 import org.apache.cayenne.exp.Expression;
 import org.apache.cayenne.exp.ExpressionException;
 import org.apache.cayenne.exp.parser.ASTDbPath;
@@ -431,22 +433,12 @@ public class Ordering implements Comparator<Object>, Serializable, XMLSerializab
 	 * @since 1.1
 	 */
 	@Override
-	public void encodeAsXML(XMLEncoder encoder) {
-		encoder.print("<ordering");
-
-		if (isDescending()) {
-			encoder.print(" descending=\"true\"");
-		}
-
-		if (isCaseInsensitive()) {
-			encoder.print(" ignore-case=\"true\"");
-		}
-
-		encoder.print(">");
-		if (getSortSpec() != null) {
-			getSortSpec().encodeAsXML(encoder);
-		}
-		encoder.println("</ordering>");
+	public void encodeAsXML(XMLEncoder encoder, ConfigurationNodeVisitor delegate) {
+		encoder.start("ordering")
+				.attribute("descending", isDescending())
+				.attribute("ignore-case", isCaseInsensitive())
+				.nested(getSortSpec(), delegate)
+				.end();
 	}
 
 	@Override
@@ -454,7 +446,7 @@ public class Ordering implements Comparator<Object>, Serializable, XMLSerializab
 		StringWriter buffer = new StringWriter();
 		PrintWriter pw = new PrintWriter(buffer);
 		XMLEncoder encoder = new XMLEncoder(pw);
-		encodeAsXML(encoder);
+		encodeAsXML(encoder, new EmptyConfigurationNodeVisitor());
 		pw.close();
 		buffer.flush();
 		return buffer.toString();

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c58b6f40/cayenne-server/src/main/java/org/apache/cayenne/query/PrefetchTreeNode.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/PrefetchTreeNode.java b/cayenne-server/src/main/java/org/apache/cayenne/query/PrefetchTreeNode.java
index a1d0fb6..2f72e4e 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/query/PrefetchTreeNode.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/query/PrefetchTreeNode.java
@@ -19,6 +19,7 @@
 
 package org.apache.cayenne.query;
 
+import org.apache.cayenne.configuration.ConfigurationNodeVisitor;
 import org.apache.cayenne.map.Entity;
 import org.apache.cayenne.util.Util;
 import org.apache.cayenne.util.XMLEncoder;
@@ -96,7 +97,8 @@ public class PrefetchTreeNode implements Serializable, XMLSerializable {
 		this.semantics = UNDEFINED_SEMANTICS;
 	}
 
-	public void encodeAsXML(XMLEncoder encoder) {
+	@Override
+	public void encodeAsXML(XMLEncoder encoder, ConfigurationNodeVisitor delegate) {
 		traverse(new XMLEncoderOperation(encoder));
 	}
 
@@ -218,7 +220,7 @@ public class PrefetchTreeNode implements Serializable, XMLSerializable {
 	 */
 	public void traverse(PrefetchProcessor processor) {
 
-		boolean result = false;
+		boolean result;
 
 		if (isPhantom()) {
 			result = processor.startPhantomPrefetch(this);
@@ -500,31 +502,33 @@ public class PrefetchTreeNode implements Serializable, XMLSerializable {
 		}
 
 		public boolean startDisjointPrefetch(PrefetchTreeNode node) {
-			encoder.print("<prefetch type=\"disjoint\">");
-			encoder.print(node.getPath());
-			encoder.println("</prefetch>");
+			encoder.start("prefetch")
+					.attribute("type", "disjoint")
+					.cdata(node.getPath(), true)
+					.end();
 			return true;
 		}
 
 		public boolean startDisjointByIdPrefetch(PrefetchTreeNode node) {
-			encoder.print("<prefetch type=\"disjointById\">");
-			encoder.print(node.getPath());
-			encoder.println("</prefetch>");
+			encoder.start("prefetch")
+					.attribute("type", "disjointById")
+					.cdata(node.getPath(), true)
+					.end();
 			return true;
 		}
 
 		public boolean startJointPrefetch(PrefetchTreeNode node) {
-			encoder.print("<prefetch type=\"joint\">");
-			encoder.print(node.getPath());
-			encoder.println("</prefetch>");
+			encoder.start("prefetch")
+					.attribute("type", "joint")
+					.cdata(node.getPath(), true)
+					.end();
 			return true;
 		}
 
 		public boolean startUnknownPrefetch(PrefetchTreeNode node) {
-			encoder.print("<prefetch>");
-			encoder.print(node.getPath());
-			encoder.println("</prefetch>");
-
+			encoder.start("prefetch")
+					.cdata(node.getPath(), true)
+					.end();
 			return true;
 		}
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c58b6f40/cayenne-server/src/main/java/org/apache/cayenne/query/ProcedureQuery.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/ProcedureQuery.java b/cayenne-server/src/main/java/org/apache/cayenne/query/ProcedureQuery.java
index 12ac3c7..462bd8d 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/query/ProcedureQuery.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/query/ProcedureQuery.java
@@ -20,9 +20,10 @@
 package org.apache.cayenne.query;
 
 import org.apache.cayenne.access.jdbc.ColumnDescriptor;
+import org.apache.cayenne.configuration.ConfigurationNodeVisitor;
 import org.apache.cayenne.map.EntityResolver;
-import org.apache.cayenne.map.MapLoader;
 import org.apache.cayenne.map.Procedure;
+import org.apache.cayenne.map.QueryDescriptor;
 import org.apache.cayenne.util.XMLEncoder;
 import org.apache.cayenne.util.XMLSerializable;
 
@@ -54,7 +55,7 @@ import java.util.Map;
  * {@link org.apache.cayenne.access.DataContext#performGenericQuery(Query)}.
  * </p>
  */
-public class ProcedureQuery extends AbstractQuery implements ParameterizedQuery, XMLSerializable {
+public class ProcedureQuery extends AbstractQuery implements ParameterizedQuery {
 
     public static final String COLUMN_NAME_CAPITALIZATION_PROPERTY = "cayenne.ProcedureQuery.columnNameCapitalization";
 
@@ -226,53 +227,6 @@ public class ProcedureQuery extends AbstractQuery implements ParameterizedQuery,
     }
 
     /**
-     * Prints itself as XML to the provided PrintWriter.
-     * 
-     * @since 1.1
-     */
-    public void encodeAsXML(XMLEncoder encoder) {
-        encoder.print("<query name=\"");
-        encoder.print(getName());
-        encoder.print("\" factory=\"");
-        encoder.print("org.apache.cayenne.map.ProcedureQueryBuilder");
-
-        encoder.print("\" root=\"");
-        encoder.print(MapLoader.PROCEDURE_ROOT);
-
-        String rootString = null;
-
-        if (root instanceof String) {
-            rootString = root.toString();
-        }
-        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);
-
-        metaData.encodeAsXML(encoder);
-        if (getColumnNamesCapitalization() != CapsStrategy.DEFAULT) {
-            encoder.printProperty(
-                    COLUMN_NAME_CAPITALIZATION_PROPERTY,
-                    getColumnNamesCapitalization().name());
-        }
-
-        encoder.indent(-1);
-        encoder.println("</query>");
-    }
-
-    /**
      * Creates and returns a new ProcedureQuery built using this query as a prototype and
      * substituting template parameters with the values from the map.
      * 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c58b6f40/cayenne-server/src/main/java/org/apache/cayenne/query/SQLTemplate.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/SQLTemplate.java b/cayenne-server/src/main/java/org/apache/cayenne/query/SQLTemplate.java
index 1fe3be2..554bb8b 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/query/SQLTemplate.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/query/SQLTemplate.java
@@ -20,12 +20,13 @@
 package org.apache.cayenne.query;
 
 import org.apache.cayenne.access.QueryEngine;
+import org.apache.cayenne.configuration.ConfigurationNodeVisitor;
 import org.apache.cayenne.map.DataMap;
 import org.apache.cayenne.map.DbEntity;
 import org.apache.cayenne.map.EntityResolver;
-import org.apache.cayenne.map.MapLoader;
 import org.apache.cayenne.map.ObjEntity;
 import org.apache.cayenne.map.Procedure;
+import org.apache.cayenne.map.QueryDescriptor;
 import org.apache.cayenne.map.SQLResult;
 import org.apache.cayenne.util.XMLEncoder;
 import org.apache.cayenne.util.XMLSerializable;
@@ -69,7 +70,7 @@ import java.util.TreeSet;
  * 
  * @since 1.1
  */
-public class SQLTemplate extends AbstractQuery implements ParameterizedQuery, XMLSerializable {
+public class SQLTemplate extends AbstractQuery implements ParameterizedQuery {
 
 	private static final long serialVersionUID = -3073521388289663641L;
 
@@ -198,92 +199,6 @@ public class SQLTemplate extends AbstractQuery implements ParameterizedQuery, XM
 	}
 
 	/**
-	 * Prints itself as XML to the provided PrintWriter.
-	 * 
-	 * @since 1.1
-	 */
-	@Override
-	public void encodeAsXML(XMLEncoder encoder) {
-		encoder.print("<query name=\"");
-		encoder.print(getName());
-		encoder.print("\" factory=\"");
-		encoder.print("org.apache.cayenne.map.SQLTemplateBuilder");
-
-		String rootString = null;
-		String rootType = null;
-
-		if (root instanceof String) {
-			rootType = MapLoader.OBJ_ENTITY_ROOT;
-			rootString = root.toString();
-		} else if (root instanceof ObjEntity) {
-			rootType = MapLoader.OBJ_ENTITY_ROOT;
-			rootString = ((ObjEntity) root).getName();
-		} else if (root instanceof DbEntity) {
-			rootType = MapLoader.DB_ENTITY_ROOT;
-			rootString = ((DbEntity) root).getName();
-		} else if (root instanceof Procedure) {
-			rootType = MapLoader.PROCEDURE_ROOT;
-			rootString = ((Procedure) root).getName();
-		} else if (root instanceof Class<?>) {
-			rootType = MapLoader.JAVA_CLASS_ROOT;
-			rootString = ((Class<?>) root).getName();
-		} else if (root instanceof DataMap) {
-			rootType = MapLoader.DATA_MAP_ROOT;
-			rootString = ((DataMap) root).getName();
-		}
-
-		if (rootType != null) {
-			encoder.print("\" root=\"");
-			encoder.print(rootType);
-			encoder.print("\" root-name=\"");
-			encoder.print(rootString);
-		}
-
-		encoder.println("\">");
-
-		encoder.indent(1);
-
-		metaData.encodeAsXML(encoder);
-
-		if (getColumnNamesCapitalization() != CapsStrategy.DEFAULT) {
-			encoder.printProperty(COLUMN_NAME_CAPITALIZATION_PROPERTY, getColumnNamesCapitalization().name());
-		}
-
-		// encode default SQL
-		if (defaultTemplate != null) {
-			encoder.print("<sql><![CDATA[");
-			encoder.print(defaultTemplate);
-			encoder.println("]]></sql>");
-		}
-
-		// encode adapter SQL
-		if (templates != null && !templates.isEmpty()) {
-
-			// sorting entries by adapter name
-			TreeSet<String> keys = new TreeSet<String>(templates.keySet());
-			for (String key : keys) {
-				String value = templates.get(key);
-
-				if (key != null && value != null) {
-					String sql = value.trim();
-					if (sql.length() > 0) {
-						encoder.print("<sql adapter-class=\"");
-						encoder.print(key);
-						encoder.print("\"><![CDATA[");
-						encoder.print(sql);
-						encoder.println("]]></sql>");
-					}
-				}
-			}
-		}
-
-		// TODO: support parameter encoding
-
-		encoder.indent(-1);
-		encoder.println("</query>");
-	}
-
-	/**
 	 * Initializes query parameters using a set of properties.
 	 * 
 	 * @since 1.1

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c58b6f40/cayenne-server/src/main/java/org/apache/cayenne/query/SelectQuery.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/query/SelectQuery.java b/cayenne-server/src/main/java/org/apache/cayenne/query/SelectQuery.java
index ff9660e..b2d5bd1 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/query/SelectQuery.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/query/SelectQuery.java
@@ -24,14 +24,15 @@ import org.apache.cayenne.ObjectContext;
 import org.apache.cayenne.ResultBatchIterator;
 import org.apache.cayenne.ResultIterator;
 import org.apache.cayenne.ResultIteratorCallback;
+import org.apache.cayenne.configuration.ConfigurationNodeVisitor;
 import org.apache.cayenne.exp.Expression;
 import org.apache.cayenne.exp.ExpressionFactory;
 import org.apache.cayenne.exp.Property;
 import org.apache.cayenne.map.DbEntity;
 import org.apache.cayenne.map.EntityResolver;
-import org.apache.cayenne.map.MapLoader;
 import org.apache.cayenne.map.ObjEntity;
 import org.apache.cayenne.map.Procedure;
+import org.apache.cayenne.map.QueryDescriptor;
 import org.apache.cayenne.util.XMLEncoder;
 import org.apache.cayenne.util.XMLSerializable;
 
@@ -48,7 +49,7 @@ import java.util.Map;
  * other parameters that serve as runtime hints to Cayenne on how to optimize
  * the fetch and result processing.
  */
-public class SelectQuery<T> extends AbstractQuery implements ParameterizedQuery, XMLSerializable, Select<T> {
+public class SelectQuery<T> extends AbstractQuery implements ParameterizedQuery, Select<T> {
 
 	private static final long serialVersionUID = 5486418811888197559L;
 
@@ -434,73 +435,6 @@ public class SelectQuery<T> extends AbstractQuery implements ParameterizedQuery,
 	}
 
 	/**
-	 * Prints itself as XML to the provided PrintWriter.
-	 * 
-	 * @since 1.1
-	 */
-	public void encodeAsXML(XMLEncoder encoder) {
-		encoder.print("<query name=\"");
-		encoder.print(getName());
-		encoder.print("\" factory=\"");
-		encoder.print("org.apache.cayenne.map.SelectQueryBuilder");
-
-		String rootString = null;
-		String rootType = null;
-
-		if (root instanceof String) {
-			rootType = MapLoader.OBJ_ENTITY_ROOT;
-			rootString = root.toString();
-		} else if (root instanceof ObjEntity) {
-			rootType = MapLoader.OBJ_ENTITY_ROOT;
-			rootString = ((ObjEntity) root).getName();
-		} else if (root instanceof DbEntity) {
-			rootType = MapLoader.DB_ENTITY_ROOT;
-			rootString = ((DbEntity) root).getName();
-		} else if (root instanceof Procedure) {
-			rootType = MapLoader.PROCEDURE_ROOT;
-			rootString = ((Procedure) root).getName();
-		} else if (root instanceof Class<?>) {
-			rootType = MapLoader.JAVA_CLASS_ROOT;
-			rootString = ((Class<?>) root).getName();
-		}
-
-		if (rootType != null) {
-			encoder.print("\" root=\"");
-			encoder.print(rootType);
-			encoder.print("\" root-name=\"");
-			encoder.print(rootString);
-		}
-
-		encoder.println("\">");
-
-		encoder.indent(1);
-
-		// print properties
-		if (distinct != DISTINCT_DEFAULT) {
-			encoder.printProperty(DISTINCT_PROPERTY, distinct);
-		}
-
-		metaData.encodeAsXML(encoder);
-
-		// encode qualifier
-		if (qualifier != null) {
-			encoder.print("<qualifier>");
-			qualifier.encodeAsXML(encoder);
-			encoder.println("</qualifier>");
-		}
-
-		// encode orderings
-		if (orderings != null && !orderings.isEmpty()) {
-			for (Ordering ordering : orderings) {
-				ordering.encodeAsXML(encoder);
-			}
-		}
-
-		encoder.indent(-1);
-		encoder.println("</query>");
-	}
-
-	/**
 	 * A shortcut for {@link #queryWithParameters(Map, boolean)}that prunes
 	 * parts of qualifier that have no parameter value set.
 	 */

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c58b6f40/cayenne-server/src/main/java/org/apache/cayenne/reflect/LifecycleCallbackEventHandler.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/reflect/LifecycleCallbackEventHandler.java b/cayenne-server/src/main/java/org/apache/cayenne/reflect/LifecycleCallbackEventHandler.java
index 429efdb..db596bb 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/reflect/LifecycleCallbackEventHandler.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/reflect/LifecycleCallbackEventHandler.java
@@ -35,26 +35,14 @@ import java.util.Map;
  */
 class LifecycleCallbackEventHandler {
 
-    private EntityResolver resolver;
     private Map<String, Collection<AbstractCallback>> listeners;
     private Collection<AbstractCallback> defaultListeners;
 
     LifecycleCallbackEventHandler(EntityResolver resolver) {
-        this.resolver = resolver;
         this.listeners = new HashMap<>();
         this.defaultListeners = new ArrayList<>();
     }
 
-    private boolean excludingDefaultListeners(String entityName) {
-        ObjEntity entity = resolver.getObjEntity(entityName);
-        return entity != null && entity.isExcludingDefaultListeners();
-    }
-
-    private boolean excludingSuperclassListeners(String entityName) {
-        ObjEntity entity = resolver.getObjEntity(entityName);
-        return entity != null && entity.isExcludingSuperclassListeners();
-    }
-
     boolean isEmpty() {
         return listeners.isEmpty() && defaultListeners.isEmpty();
     }
@@ -140,8 +128,7 @@ class LifecycleCallbackEventHandler {
     void performCallbacks(Persistent object) {
 
         // default listeners are invoked first
-        if (!defaultListeners.isEmpty()
-                && !excludingDefaultListeners(object.getObjectId().getEntityName())) {
+        if (!defaultListeners.isEmpty()) {
             for (AbstractCallback listener : defaultListeners) {
                 listener.performCallback(object);
             }
@@ -171,9 +158,7 @@ class LifecycleCallbackEventHandler {
         }
 
         // recursively perform super callbacks first
-        if (!excludingSuperclassListeners(object.getObjectId().getEntityName())) {
-            performCallbacks(object, callbackEntityClass.getSuperclass());
-        }
+        performCallbacks(object, callbackEntityClass.getSuperclass());
 
         // perform callbacks on provided class
         String key = callbackEntityClass.getName();

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c58b6f40/cayenne-server/src/main/java/org/apache/cayenne/remote/IncrementalSelectQuery.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/remote/IncrementalSelectQuery.java b/cayenne-server/src/main/java/org/apache/cayenne/remote/IncrementalSelectQuery.java
index d869d4c..7314488 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/remote/IncrementalSelectQuery.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/remote/IncrementalSelectQuery.java
@@ -127,11 +127,6 @@ class IncrementalSelectQuery<T> extends SelectQuery<T> {
 	}
 
 	@Override
-	public void encodeAsXML(XMLEncoder encoder) {
-		query.encodeAsXML(encoder);
-	}
-
-	@Override
 	public boolean equals(Object obj) {
 		return query.equals(obj);
 	}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c58b6f40/cayenne-server/src/main/java/org/apache/cayenne/util/XMLEncoder.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/util/XMLEncoder.java b/cayenne-server/src/main/java/org/apache/cayenne/util/XMLEncoder.java
index 7551d99..039ca4d 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/util/XMLEncoder.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/util/XMLEncoder.java
@@ -21,12 +21,26 @@ package org.apache.cayenne.util;
 
 import java.io.PrintWriter;
 import java.util.Collection;
+import java.util.Deque;
+import java.util.LinkedList;
 import java.util.Map;
 
+import org.apache.cayenne.configuration.ConfigurationNodeVisitor;
+
 /**
+ * <p>
  * A helper class to encode objects to XML.
- * 
+ * </p>
+ * Usage: <pre>{@code
+ *      XMLEncoder encoder = new XMLEncoder(writer);
+ *      encoder
+ *          .start("tag").attribute("name", "tag_name_attribute")
+ *          .start("nested_tag").attribute("name", "nested_tag_name).cdata("tag text element").end()
+ *          .end();
+ * }</pre>
+ *
  * @since 1.1
+ * @since 4.1 API is greatly reworked to be more usable
  */
 public class XMLEncoder {
 
@@ -37,6 +51,12 @@ public class XMLEncoder {
     protected boolean indentLine;
     protected int indentTimes;
 
+    protected boolean tagOpened;
+    protected boolean cdata;
+    protected int currentTagLevel;
+    protected int lastTagLevel;
+    protected Deque<String> openTags = new LinkedList<>();
+
     public XMLEncoder(PrintWriter out) {
         this(out, null, null);
     }
@@ -54,163 +74,275 @@ public class XMLEncoder {
         this.projectVersion = projectVersion;
     }
 
-    public PrintWriter getPrintWriter() {
-        return out;
-    }
-
-    public void indent(int i) {
+    public XMLEncoder indent(int i) {
         indentTimes += i;
         if (indentTimes < 0) {
             indentTimes = 0;
         }
+        return this;
+    }
+
+    public XMLEncoder print(String text) {
+        printIndent();
+        out.print(text);
+        return this;
+    }
+
+    public XMLEncoder println(String text) {
+        printIndent();
+        out.println(text);
+        indentLine = true;
+        return this;
     }
 
     /**
-     * Utility method that prints all map values, assuming they are XMLSerializable
-     * objects.
+     * @since 3.1
      */
-    public void print(Map<?, ? extends XMLSerializable> map) {
-        for (XMLSerializable value : map.values()) {
-            value.encodeAsXML(this);
+    public XMLEncoder println() {
+        out.println();
+        indentLine = true;
+        return this;
+    }
+
+    private XMLEncoder printIndent() {
+        if (indentLine) {
+            indentLine = false;
+
+            if (indentTimes > 0 && indent != null) {
+                for (int i = 0; i < indentTimes; i++) {
+                    out.print(indent);
+                }
+            }
         }
+        return this;
     }
 
     /**
-     * Utility method that prints all map values, assuming they are XMLSerializable
-     * objects.
+     * @since 4.1
+     * @param tag to start
+     * @return this
      */
-    public void print(Collection<? extends XMLSerializable> c) {
-        for (XMLSerializable value : c) {
-            value.encodeAsXML(this);
+    public XMLEncoder start(String tag) {
+        if(tagOpened) {
+            println(">").indent(1);
         }
+        printIndent().print("<").print(tag);
+
+        lastTagLevel = ++currentTagLevel;
+        tagOpened = true;
+        openTags.push(tag);
+        return this;
     }
 
     /**
-     * Inserts an optional project version attribute in the output. If the project version
-     * is not initialized for encoder, will do nothing.
-     * 
-     * @since 3.1
+     * This method will track presence of nested tags and print closure accordingly
+     *
+     * @since 4.1
+     * @return this
      */
-    public void printProjectVersion() {
-        printAttribute("project-version", projectVersion);
+    public XMLEncoder end() {
+        tagOpened = false;
+        if(lastTagLevel == currentTagLevel-- && !cdata) {
+            openTags.pop();
+            println("/>");
+        } else {
+            if(!cdata) {
+                indent(-1).printIndent();
+            }
+            cdata = false;
+            print("</").print(openTags.pop()).println(">");
+        }
+        return this;
     }
 
     /**
-     * Prints an XML attribute. The value is trimmed (so leading and following spaces are
-     * lost) and then encoded to be a proper XML attribute value. E.g. "&amp;" becomes
-     * "&amp;amp;", etc.
-     * 
-     * @since 3.1
+     * @since 4.1
+     * @param name of the attribute
+     * @param value of the attribute
+     * @return this
      */
-    public void printAttribute(String name, String value) {
-        printAttribute(name, value, false);
+    public XMLEncoder attribute(String name, String value) {
+        return attribute(name, value, false);
     }
 
     /**
-     * @since 3.1
+     * @since 4.1
+     * @param name of the attribute
+     * @param value of the attribute
+     * @param newLine should this attribute be printed on new line
+     * @return this
      */
-    public void printlnAttribute(String name, String value) {
-        printAttribute(name, value, true);
-    }
-
-    private void printAttribute(String name, String value, boolean lineBreak) {
-        if (value == null) {
-            return;
+    public XMLEncoder attribute(String name, String value, boolean newLine) {
+        if(Util.isEmptyString(value)) {
+            return this;
         }
 
-        value = value.trim();
-        if (value.length() == 0) {
-            return;
+        if(newLine) {
+            indent(1).println().printIndent();
         }
-
-        value = Util.encodeXmlAttribute(value);
-
-        printIndent();
-        out.print(' ');
-        out.print(name);
-        out.print("=\"");
-        out.print(value);
-        out.print("\"");
-        if (lineBreak) {
-            println();
+        print(" ").print(name).print("=\"").print(Util.encodeXmlAttribute(value)).print("\"");
+        if(newLine) {
+            indent(-1);
         }
+        return this;
     }
 
     /**
-     * Prints a common XML element - property with name and value.
+     * @since 4.1
+     * @param name of the attribute
+     * @param value of the attribute
+     * @return this
      */
-    public void printProperty(String name, String value) {
-        printIndent();
-        out.print("<property");
-        printAttribute("name", name);
-        printAttribute("value", value);
-        out.println("/>");
-        indentLine = true;
+    public XMLEncoder attribute(String name, boolean value) {
+        if(!value) {
+            return this;
+        }
+        return attribute(name, "true");
     }
 
     /**
-     * Prints a common XML element - property with name and value.
+     * @since 4.1
+     * @param name of the attribute
+     * @param value of the attribute
+     * @return this
      */
-    public void printProperty(String name, boolean b) {
-        printProperty(name, String.valueOf(b));
+    public XMLEncoder attribute(String name, int value) {
+        if(value == 0) {
+            return this;
+        }
+        return attribute(name, String.valueOf(value));
     }
 
     /**
-     * Prints a common XML element - property with name and value.
+     * @since 4.1
+     * @param data char data
+     * @return this
      */
-    public void printProperty(String name, int i) {
-        printProperty(name, String.valueOf(i));
+    public XMLEncoder cdata(String data) {
+        return cdata(data, false);
     }
 
-    public void print(String text) {
-        printIndent();
-        out.print(text);
+    /**
+     * @since 4.1
+     * @param data char data
+     * @param escape does this data need to be enclosed into &lt;![CDATA[ ... ]]&gt;
+     * @return this
+     */
+    public XMLEncoder cdata(String data, boolean escape) {
+        if(tagOpened) {
+            print(">");
+        }
+        cdata = true;
+        if(escape) {
+            print("<![CDATA[");
+        }
+        print(data);
+        if(escape) {
+            print("]]>");
+        }
+        return this;
     }
 
-    public void print(char c) {
-        printIndent();
-        out.print(c);
+    /**
+     * @since 4.1
+     * @param object nested object to serialize
+     * @param delegate visitor
+     * @return this
+     */
+    public XMLEncoder nested(XMLSerializable object, ConfigurationNodeVisitor delegate) {
+        if(object == null) {
+            return this;
+        }
+        object.encodeAsXML(this, delegate);
+        return this;
     }
 
-    public void print(boolean b) {
-        printIndent();
-        out.print(b);
+    /**
+     * @since 4.1
+     * @param collection of nested objects
+     * @param delegate visitor
+     * @return this
+     */
+    public XMLEncoder nested(Collection<? extends XMLSerializable> collection, ConfigurationNodeVisitor delegate) {
+        if(collection == null) {
+            return this;
+        }
+        for (XMLSerializable value : collection) {
+            value.encodeAsXML(this, delegate);
+        }
+        return this;
     }
 
-    public void print(int i) {
-        printIndent();
-        out.print(i);
+    /**
+     * @since 4.1
+     * @param map of nested objects
+     * @param delegate visitor
+     * @return this
+     */
+    public XMLEncoder nested(Map<?, ? extends XMLSerializable> map, ConfigurationNodeVisitor delegate) {
+        if(map == null) {
+            return this;
+        }
+        for (XMLSerializable value : map.values()) {
+            value.encodeAsXML(this, delegate);
+        }
+        return this;
     }
 
-    public void println(String text) {
-        printIndent();
-        out.println(text);
+    /**
+     * Prints a common XML element - property with name and value.
+     * @since 4.1
+     */
+    public XMLEncoder property(String name, String value) {
+        if(Util.isEmptyString(value)) {
+            return this;
+        }
+        start("property").attribute("name", name).attribute("value", value).end();
         indentLine = true;
+        return this;
     }
 
     /**
-     * @since 3.1
+     * Prints a common XML element - property with name and value.
+     * @since 4.1
      */
-    public void println() {
-        out.println();
-        indentLine = true;
+    public XMLEncoder property(String name, boolean b) {
+        if(!b) {
+            return this;
+        }
+        return property(name, "true");
     }
 
-    public void println(char c) {
-        printIndent();
-        out.println(c);
-        indentLine = true;
+    /**
+     * Prints a common XML element - property with name and value.
+     * @since 4.1
+     */
+    public XMLEncoder property(String name, int i) {
+        if(i == 0) {
+            return this;
+        }
+        return property(name, String.valueOf(i));
     }
 
-    private void printIndent() {
-        if (indentLine) {
-            indentLine = false;
-
-            if (indentTimes > 0 && indent != null) {
-                for (int i = 0; i < indentTimes; i++) {
-                    out.print(indent);
-                }
-            }
+    /**
+     * Prints common XML element - tag with name and text value (&lt;tag>value&lt;/tag>)
+     * If value is empty, nothing will be printed.
+     * @since 4.1
+     */
+    public XMLEncoder simpleTag(String tag, String value) {
+        if (!Util.isEmptyString(value)) {
+            start(tag).cdata(value).end();
         }
+        return this;
+    }
+
+    /**
+     * Inserts an optional project version attribute in the output. If the project version
+     * is not initialized for encoder, will do nothing.
+     *
+     * @since 4.1
+     */
+    public XMLEncoder projectVersion() {
+        return attribute("project-version", projectVersion, true);
     }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c58b6f40/cayenne-server/src/main/java/org/apache/cayenne/util/XMLSerializable.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/util/XMLSerializable.java b/cayenne-server/src/main/java/org/apache/cayenne/util/XMLSerializable.java
index 3c6cba0..2646785 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/util/XMLSerializable.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/util/XMLSerializable.java
@@ -19,6 +19,8 @@
 
 package org.apache.cayenne.util;
 
+import org.apache.cayenne.configuration.ConfigurationNodeVisitor;
+
 /**
  * Interface for Cayenne objects that can be saved to XML.
  * 
@@ -30,5 +32,5 @@ public interface XMLSerializable {
      * 
      * @since 1.1
      */
-    public void encodeAsXML(XMLEncoder encoder);
+    void encodeAsXML(XMLEncoder encoder, ConfigurationNodeVisitor delegate);
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c58b6f40/cayenne-server/src/main/resources/org/apache/cayenne/schema/10/dbimport.xsd
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/resources/org/apache/cayenne/schema/10/dbimport.xsd b/cayenne-server/src/main/resources/org/apache/cayenne/schema/10/dbimport.xsd
new file mode 100644
index 0000000..a100799
--- /dev/null
+++ b/cayenne-server/src/main/resources/org/apache/cayenne/schema/10/dbimport.xsd
@@ -0,0 +1,106 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+  ~   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.
+  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~-->
+
+<xs:schema targetNamespace="http://cayenne.apache.org/schema/10/dbimport"
+           xmlns:dbi="http://cayenne.apache.org/schema/10/dbimport"
+           xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" version="10">
+
+    <xs:element name="config">
+        <xs:complexType>
+            <xs:complexContent>
+                <xs:extension base="dbi:container">
+                    <xs:sequence>
+                        <xs:element name="catalog" minOccurs="0" maxOccurs="unbounded" type="dbi:catalog"/>
+                        <xs:element name="schema" minOccurs="0" maxOccurs="unbounded" type="dbi:schema"/>
+                        <xs:element name="tableType" minOccurs="0" maxOccurs="unbounded" type="dbi:db-type"/>
+
+                        <xs:element name="defaultPackage" minOccurs="0" type="xs:string"/>
+                        <xs:element name="forceDataMapCatalog" minOccurs="0" type="xs:boolean"/>
+                        <xs:element name="forceDataMapSchema" minOccurs="0" type="xs:boolean"/>
+                        <xs:element name="meaningfulPkTables" minOccurs="0" type="xs:string"/>
+                        <xs:element name="namingStrategy" minOccurs="0" type="xs:string"/>
+                        <xs:element name="skipPrimaryKeyLoading" minOccurs="0" type="xs:boolean"/>
+                        <xs:element name="skipRelationshipsLoading" minOccurs="0" type="xs:boolean"/>
+                        <xs:element name="stripFromTableNames" minOccurs="0" type="xs:string"/>
+                        <xs:element name="useJava7Types" minOccurs="0" type="xs:boolean"/>
+                        <xs:element name="usePrimitives" minOccurs="0" type="xs:boolean"/>
+
+                        <xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other" processContents="lax"/>
+                    </xs:sequence>
+                </xs:extension>
+            </xs:complexContent>
+        </xs:complexType>
+    </xs:element>
+
+    <xs:simpleType name="db-type">
+        <xs:restriction base="xs:string">
+            <xs:pattern value="[0-9a-zA-Z$_.]+"/>
+        </xs:restriction>
+    </xs:simpleType>
+
+    <xs:complexType name="includeTable" mixed="true">
+        <xs:sequence>
+            <xs:element name="name" minOccurs="0" type="dbi:db-type"/>
+            <xs:element name="includeColumn" type="dbi:db-type" minOccurs="0" maxOccurs="unbounded"/>
+            <xs:element name="excludeColumn" type="dbi:db-type" minOccurs="0" maxOccurs="unbounded"/>
+
+            <xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other" processContents="lax"/>
+        </xs:sequence>
+    </xs:complexType>
+
+    <xs:complexType name="container" abstract="true">
+        <xs:sequence>
+            <xs:element name="includeTable" type="dbi:includeTable" minOccurs="0" maxOccurs="unbounded"/>
+            <xs:element name="excludeTable" type="dbi:db-type" minOccurs="0" maxOccurs="unbounded"/>
+            <xs:element name="includeColumn" type="dbi:db-type" minOccurs="0" maxOccurs="unbounded"/>
+            <xs:element name="excludeColumn" type="dbi:db-type" minOccurs="0" maxOccurs="unbounded"/>
+            <xs:element name="includeProcedure" type="dbi:db-type" minOccurs="0" maxOccurs="unbounded"/>
+            <xs:element name="excludeProcedure" type="dbi:db-type" minOccurs="0" maxOccurs="unbounded"/>
+
+            <xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other" processContents="lax"/>
+        </xs:sequence>
+    </xs:complexType>
+
+    <xs:complexType name="schema">
+        <xs:complexContent>
+            <xs:extension base="dbi:container">
+                <xs:sequence>
+                    <xs:element name="name" type="dbi:db-type"/>
+
+                    <xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other" processContents="lax"/>
+                </xs:sequence>
+            </xs:extension>
+        </xs:complexContent>
+    </xs:complexType>
+
+    <xs:complexType name="catalog">
+        <xs:complexContent>
+            <xs:extension base="dbi:container">
+                <xs:sequence>
+                    <xs:element name="name" type="dbi:db-type"/>
+                    <xs:element name="schema" type="dbi:schema" minOccurs="0" maxOccurs="unbounded"/>
+
+                    <xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other" processContents="lax"/>
+                </xs:sequence>
+            </xs:extension>
+        </xs:complexContent>
+    </xs:complexType>
+
+</xs:schema>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c58b6f40/cayenne-server/src/main/resources/org/apache/cayenne/schema/10/domain.xsd
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/resources/org/apache/cayenne/schema/10/domain.xsd b/cayenne-server/src/main/resources/org/apache/cayenne/schema/10/domain.xsd
new file mode 100644
index 0000000..40809d5
--- /dev/null
+++ b/cayenne-server/src/main/resources/org/apache/cayenne/schema/10/domain.xsd
@@ -0,0 +1,130 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+  ~   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.
+  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~-->
+
+<xs:schema targetNamespace="http://cayenne.apache.org/schema/10/domain"
+           elementFormDefault="qualified" version="10"
+           xmlns:xs="http://www.w3.org/2001/XMLSchema"
+           xmlns:cay="http://cayenne.apache.org/schema/10/domain">
+
+    <xs:element name="domain">
+        <xs:complexType>
+            <xs:sequence>
+                <xs:element minOccurs="0" maxOccurs="unbounded" ref="cay:property"/>
+                <xs:element minOccurs="0" maxOccurs="unbounded" ref="cay:map"/>
+                <xs:element minOccurs="0" maxOccurs="unbounded" ref="cay:node"/>
+                <xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other" processContents="lax"/>
+            </xs:sequence>
+            <xs:attribute name="project-version" use="required" type="xs:string"/>
+        </xs:complexType>
+    </xs:element>
+
+    <xs:element name="property">
+        <xs:annotation>
+            <xs:documentation>A generic property used by other elements.</xs:documentation>
+        </xs:annotation>
+        <xs:complexType>
+            <xs:attribute name="name" use="required" type="xs:string"/>
+            <xs:attribute name="value" use="required" type="xs:string"/>
+        </xs:complexType>
+    </xs:element>
+
+    <xs:element name="map">
+        <xs:annotation>
+            <xs:documentation>Link to an external file with data map.</xs:documentation>
+        </xs:annotation>
+        <xs:complexType>
+            <xs:attribute name="name" use="required" type="xs:string"/>
+        </xs:complexType>
+    </xs:element>
+
+    <xs:element name="node">
+        <xs:annotation>
+            <xs:documentation>Data node description.</xs:documentation>
+        </xs:annotation>
+        <xs:complexType>
+            <xs:sequence>
+                <xs:element minOccurs="0" maxOccurs="unbounded" ref="cay:map-ref"/>
+                <xs:element minOccurs="0" maxOccurs="unbounded" ref="cay:data-source"/>
+                <xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other" processContents="lax"/>
+            </xs:sequence>
+            <xs:attribute name="name" use="required" type="xs:string"/>
+            <xs:attribute name="factory" use="required" type="xs:string"/>
+            <xs:attribute name="adapter" type="xs:string"/>
+            <xs:attribute name="schema-update-strategy" type="xs:string"/>
+            <xs:attribute name="parameters" type="xs:string"/>
+        </xs:complexType>
+    </xs:element>
+
+    <xs:element name="map-ref">
+        <xs:annotation>
+            <xs:documentation>A reference to a map.</xs:documentation>
+        </xs:annotation>
+        <xs:complexType>
+            <xs:attribute name="name" use="required" type="xs:string"/>
+        </xs:complexType>
+    </xs:element>
+
+    <xs:element name="data-source">
+        <xs:annotation>
+            <xs:documentation>Data source configuration.</xs:documentation>
+        </xs:annotation>
+        <xs:complexType>
+            <xs:sequence>
+                <xs:element minOccurs="0" maxOccurs="1" ref="cay:driver"/>
+                <xs:element minOccurs="0" maxOccurs="1" ref="cay:url"/>
+                <xs:element minOccurs="0" maxOccurs="1" ref="cay:connectionPool"/>
+                <xs:element minOccurs="0" maxOccurs="1" ref="cay:login"/>
+                <xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other" processContents="lax"/>
+            </xs:sequence>
+        </xs:complexType>
+    </xs:element>
+
+    <xs:element name="driver">
+        <xs:complexType>
+            <xs:attribute name="value" type="xs:string"/>
+        </xs:complexType>
+    </xs:element>
+
+    <xs:element name="url">
+        <xs:complexType>
+            <xs:attribute name="value" type="xs:string"/>
+        </xs:complexType>
+    </xs:element>
+
+    <xs:element name="connectionPool">
+        <xs:complexType>
+            <xs:attribute name="min" use="required" type="xs:int"/>
+            <xs:attribute name="max" use="required" type="xs:int"/>
+        </xs:complexType>
+    </xs:element>
+
+    <xs:element name="login">
+        <xs:complexType>
+            <xs:attribute name="userName" type="xs:string"/>
+            <xs:attribute name="password" type="xs:string"/>
+            <xs:attribute name="passwordLocation" type="xs:string"/>
+            <xs:attribute name="passwordSource" type="xs:string"/>
+            <xs:attribute name="encoderClass" type="xs:string"/>
+            <xs:attribute name="encoderKey" type="xs:string"/>
+            <xs:attribute name="encoderSalt" type="xs:string"/>
+        </xs:complexType>
+    </xs:element>
+
+</xs:schema>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c58b6f40/cayenne-server/src/main/resources/org/apache/cayenne/schema/10/info.xsd
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/resources/org/apache/cayenne/schema/10/info.xsd b/cayenne-server/src/main/resources/org/apache/cayenne/schema/10/info.xsd
new file mode 100644
index 0000000..480b24d
--- /dev/null
+++ b/cayenne-server/src/main/resources/org/apache/cayenne/schema/10/info.xsd
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+  ~   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.
+  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~-->
+
+<xs:schema targetNamespace="http://cayenne.apache.org/schema/10/info"
+           xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" version="10">
+    <xs:element name="property">
+        <xs:complexType>
+            <xs:attribute name="name" use="required" type="xs:string"/>
+            <xs:attribute name="value" type="xs:string"/>
+        </xs:complexType>
+    </xs:element>
+</xs:schema>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/c58b6f40/cayenne-server/src/main/resources/org/apache/cayenne/schema/10/modelMap.xsd
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/resources/org/apache/cayenne/schema/10/modelMap.xsd b/cayenne-server/src/main/resources/org/apache/cayenne/schema/10/modelMap.xsd
new file mode 100644
index 0000000..7f36483
--- /dev/null
+++ b/cayenne-server/src/main/resources/org/apache/cayenne/schema/10/modelMap.xsd
@@ -0,0 +1,356 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+  ~   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.
+  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~-->
+
+<!--
+	Cayenne entity map schema 
+	Defines format of Cayenne DataMap XML files (*.map.xml).  DataMap files contain
+	the metadata needed for Cayenne object-relational features. Multiple DataMaps
+	are usually combined in one shared namespace, so the elements of the DataMap
+	may reference objects from other DataMaps.
+--> 
+<xs:schema targetNamespace="http://cayenne.apache.org/schema/10/modelMap"
+	xmlns:cay="http://cayenne.apache.org/schema/10/modelMap"
+	xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" version="10">
+	<xs:element name="data-map">
+		<xs:complexType>
+			<xs:sequence>
+				<xs:element minOccurs="0" maxOccurs="unbounded" ref="cay:property"/>
+				<xs:element minOccurs="0" maxOccurs="unbounded" ref="cay:embeddable"/>
+				<xs:element minOccurs="0" maxOccurs="unbounded" ref="cay:procedure"/>
+				<xs:element minOccurs="0" maxOccurs="unbounded" ref="cay:db-entity"/>
+				<xs:element minOccurs="0" maxOccurs="unbounded" ref="cay:obj-entity"/>
+				<xs:element minOccurs="0" maxOccurs="unbounded" ref="cay:db-relationship"/>
+				<xs:element minOccurs="0" maxOccurs="unbounded" ref="cay:obj-relationship"/>
+				<xs:element minOccurs="0" maxOccurs="unbounded" ref="cay:query"/>
+				<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other" processContents="lax"/>
+			</xs:sequence>
+			<xs:attribute name="project-version" use="required" type="xs:string"/>
+		</xs:complexType>
+	</xs:element>
+	<xs:element name="db-entity">
+		<xs:complexType>
+			<xs:sequence>
+				<xs:element maxOccurs="unbounded" ref="cay:db-attribute"/>
+				<xs:element minOccurs="0" ref="cay:db-key-generator"/>
+				
+				<!-- Qualifier for DB Entity -->
+				<xs:element minOccurs="0" ref="cay:qualifier"/>
+
+				<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other" processContents="lax"/>
+			</xs:sequence>
+			<xs:attribute name="name" use="required" type="xs:string"/>
+			<xs:attribute name="schema" type="xs:string"/>
+			<xs:attribute name="catalog" type="xs:string"/>
+		</xs:complexType>
+	</xs:element>
+	<xs:element name="db-attribute">
+		<xs:annotation>
+			<xs:documentation>A database column.</xs:documentation>
+		</xs:annotation>
+		<xs:complexType>
+			<xs:sequence>
+				<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other" processContents="lax"/>
+			</xs:sequence>
+			<xs:attribute name="isMandatory" type="xs:boolean"/>
+			<xs:attribute name="isPrimaryKey" type="xs:boolean">
+				<xs:annotation>
+					<xs:documentation>If true, the value of attribute is unique and used as a primary key identifier.</xs:documentation>
+				</xs:annotation>
+			</xs:attribute>
+			<xs:attribute name="isGenerated" type="xs:boolean"/>
+			<xs:attribute name="length" type="xs:integer"/>
+			<xs:attribute name="name" use="required" type="xs:string"/>
+			<xs:attribute name="scale" type="xs:integer"/>
+			<xs:attribute name="type" use="required" type="xs:string"/>
+		</xs:complexType>
+	</xs:element>
+
+	<xs:element name="obj-entity">
+		<xs:annotation>
+			<xs:documentation>A persistent Java class managed by Cayenne.</xs:documentation>
+		</xs:annotation>
+		<xs:complexType>
+			<xs:sequence>
+				<xs:element minOccurs="0" ref="cay:qualifier"/>
+				<xs:element minOccurs="0" maxOccurs="unbounded" ref="cay:embedded-attribute"/>
+				<xs:element minOccurs="0" maxOccurs="unbounded" ref="cay:obj-attribute"/>
+				<xs:element minOccurs="0" maxOccurs="unbounded" ref="cay:attribute-override"/>
+				
+				<!--  Callbacks -->
+				<xs:element minOccurs="0" maxOccurs="unbounded" ref="cay:post-add"/>
+				<xs:element minOccurs="0" maxOccurs="unbounded" ref="cay:pre-persist"/>
+				<xs:element minOccurs="0" maxOccurs="unbounded" ref="cay:post-persist"/>
+				<xs:element minOccurs="0" maxOccurs="unbounded" ref="cay:pre-update"/>
+				<xs:element minOccurs="0" maxOccurs="unbounded" ref="cay:post-update"/>
+				<xs:element minOccurs="0" maxOccurs="unbounded" ref="cay:pre-remove"/>
+				<xs:element minOccurs="0" maxOccurs="unbounded" ref="cay:post-remove"/>
+				<xs:element minOccurs="0" maxOccurs="unbounded" ref="cay:post-load"/>
+
+				<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other" processContents="lax"/>
+			</xs:sequence>
+			<xs:attribute name="className" type="xs:string"/>
+			<xs:attribute name="abstract" type="xs:boolean"/>
+			<xs:attribute name="readOnly" type="xs:boolean"/>
+			<xs:attribute name="clientClassName" type="xs:string"/>
+			<xs:attribute name="clientSuperClassName" type="xs:string"/>
+			<xs:attribute name="dbEntityName" type="xs:string"/>
+			<xs:attribute name="lock-type" type="xs:string"/>
+			<xs:attribute name="name" use="required" type="xs:string"/>
+			<xs:attribute name="superClassName" type="xs:string"/>
+			<xs:attribute name="superEntityName" type="xs:string"/>
+			<xs:attribute name="serverOnly" type="xs:boolean"/>
+		</xs:complexType>
+	</xs:element>
+
+	<xs:element name="qualifier" type="xs:string"/>
+
+	<xs:element name="obj-attribute">
+		<xs:complexType>
+			<xs:sequence>
+				<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other" processContents="lax"/>
+			</xs:sequence>
+			<xs:attribute name="db-attribute-path" type="xs:string"/>
+			<xs:attribute name="lock" type="xs:boolean"/>
+			<xs:attribute name="name" use="required" type="xs:string"/>
+			<xs:attribute name="type" use="required" type="xs:string"/>
+		</xs:complexType>
+	</xs:element>
+
+	<xs:element name="attribute-override">
+		<xs:complexType>
+			<xs:attribute name="db-attribute-path" type="xs:string"/>
+			<xs:attribute name="lock" type="xs:boolean"/>
+			<xs:attribute name="name" type="xs:string"/>
+			<xs:attribute name="type" type="xs:string"/>
+		</xs:complexType>
+	</xs:element>
+
+	<xs:element name="db-relationship">
+		<xs:complexType>
+			<xs:sequence>
+				<xs:element minOccurs="1" maxOccurs="unbounded" ref="cay:db-attribute-pair"/>
+				<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other" processContents="lax"/>
+			</xs:sequence>
+			<xs:attribute name="name" use="required" type="xs:string"/>
+			<xs:attribute name="source" use="required" type="xs:string"/>
+			<xs:attribute name="target" use="required" type="xs:string"/>
+			<xs:attribute name="toDependentPK" type="xs:boolean"/>
+			<xs:attribute name="toMany" type="xs:boolean"/>
+		</xs:complexType>
+	</xs:element>
+	<xs:element name="db-attribute-pair">
+		<xs:complexType>
+			<xs:attribute name="source" use="required" type="xs:string"/>
+			<xs:attribute name="target" use="required" type="xs:string"/>
+		</xs:complexType>
+	</xs:element>
+	<xs:element name="obj-relationship">
+		<xs:complexType>
+			<xs:sequence>
+				<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other" processContents="lax"/>
+			</xs:sequence>
+			<xs:attribute name="db-relationship-path" use="required" type="xs:string"/>
+			<xs:attribute name="deleteRule" type="xs:string"/>
+			<xs:attribute name="lock" type="xs:boolean"/>
+			<xs:attribute name="name" use="required" type="xs:string"/>
+			<xs:attribute name="source" use="required" type="xs:string"/>
+			<xs:attribute name="target" use="required" type="xs:string"/>
+			<xs:attribute name="collection-type" type="xs:string"/>
+			<xs:attribute name="map-key" type="xs:string"/>
+		</xs:complexType>
+	</xs:element>
+
+	<xs:element name="query">
+		<xs:complexType>
+			<xs:sequence>
+				<xs:element minOccurs="0" maxOccurs="unbounded" ref="cay:property"/>
+				<xs:element minOccurs="0" maxOccurs="unbounded" ref="cay:sql"/>
+				<xs:element minOccurs="0" maxOccurs="unbounded" ref="cay:ejbql"/>
+				<xs:element name="qualifier" minOccurs="0" maxOccurs="unbounded" type="xs:string"/>
+				<xs:element minOccurs="0" maxOccurs="unbounded" ref="cay:ordering"/>
+				<xs:element name="prefetch" minOccurs="0" maxOccurs="unbounded" type="xs:string"/>
+				<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other" processContents="lax"/>
+			</xs:sequence>
+			<xs:attribute name="type" use="required" type="xs:string"/>
+			<xs:attribute name="name" use="required" type="xs:string"/>
+			<xs:attribute name="root" type="xs:string"/>
+			<xs:attribute name="root-name" type="xs:string"/>
+			<xs:attribute name="result-entity" type="xs:string"/>
+		</xs:complexType>
+	</xs:element>
+
+	<xs:element name="ordering">
+		<xs:complexType>
+			<xs:simpleContent>
+				<xs:extension base="xs:string">
+					<xs:attribute name="descending" type="xs:boolean"/>
+					<xs:attribute name="ignore-case" type="xs:boolean"/>
+				</xs:extension>
+			</xs:simpleContent>
+		</xs:complexType>
+	</xs:element>
+
+	<xs:element name="sql">
+		<xs:annotation>
+			<xs:documentation>Defines arbitrary SQL statement. Note that SQL statement can be customized for different SQL dialects per DbAdapter class. If no adapter-specific statement is found, the one with no adapter label is used by default.</xs:documentation>
+		</xs:annotation>
+		<xs:complexType>
+			<xs:simpleContent>
+				<xs:extension base="xs:string">
+					<xs:attribute name="adapter-class" type="xs:string"/>
+				</xs:extension>
+			</xs:simpleContent>
+		</xs:complexType>
+	</xs:element>
+
+	<xs:element name="ejbql" type="xs:string"/>
+
+	<xs:element name="property">
+		<xs:annotation>
+			<xs:documentation>A generic property used by other elements.</xs:documentation>
+		</xs:annotation>
+		<xs:complexType>
+			<xs:attribute name="name" use="required" type="xs:string"/>
+			<xs:attribute name="value" use="required" type="xs:string"/>
+		</xs:complexType>
+	</xs:element>
+	<xs:element name="embeddable">
+		<xs:complexType>
+			<xs:sequence>
+				<xs:element name="embeddable-attribute" minOccurs="0" maxOccurs="unbounded">
+					<xs:complexType>
+						<xs:attribute name="name" use="required" type="xs:string"/>
+						<xs:attribute name="type" use="required" type="xs:string"/>
+						<xs:attribute name="db-attribute-name" use="required" type="xs:string"/>
+					</xs:complexType>
+				</xs:element>
+				<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other" processContents="lax"/>
+			</xs:sequence>
+			<xs:attribute name="className" use="required" type="xs:string"/>
+		</xs:complexType>
+	</xs:element>
+
+	<xs:element name="embedded-attribute">
+		<xs:complexType>
+			<xs:sequence>
+				<xs:element minOccurs="0" maxOccurs="unbounded"
+					ref="cay:embeddable-attribute-override"/>
+			</xs:sequence>
+			<xs:attribute name="type" use="required" type="xs:string"/>
+			<xs:attribute name="name" use="required" type="xs:string"/>
+		</xs:complexType>
+	</xs:element>
+
+	<xs:element name="embeddable-attribute-override">
+		<xs:complexType>
+			<xs:attribute name="db-attribute-path" use="required" type="xs:string"/>
+			<xs:attribute name="name" use="required" type="xs:string"/>
+		</xs:complexType>
+	</xs:element>
+
+	<xs:element name="procedure">
+		<xs:complexType>
+			<xs:sequence>
+				<xs:element minOccurs="0" maxOccurs="unbounded" name="procedure-parameter">
+					<xs:complexType>
+						<xs:attribute name="name" use="required" type="xs:string"/>
+						<xs:attribute name="type" use="required" type="xs:string"/>
+						<xs:attribute name="length" type="xs:integer"/>
+						<xs:attribute name="direction" use="required" type="xs:string"/>
+					</xs:complexType>
+				</xs:element>
+				<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other" processContents="lax"/>
+			</xs:sequence>
+			<xs:attribute name="name" use="required" type="xs:string"/>
+			<xs:attribute name="schema" type="xs:string"/>
+			<xs:attribute name="catalog" type="xs:string"/>
+			<xs:attribute name="returningValue" type="xs:boolean"/>
+		</xs:complexType>
+	</xs:element>
+	<xs:element name="pre-update">
+		<xs:complexType>
+			<xs:attribute name="method-name" use="required" type="xs:string"/>
+		</xs:complexType>
+	</xs:element>
+	<xs:element name="post-persist">
+		<xs:complexType>
+			<xs:attribute name="method-name" use="required" type="xs:string"/>
+		</xs:complexType>
+	</xs:element>
+	<xs:element name="post-update">
+		<xs:complexType>
+			<xs:attribute name="method-name" use="required" type="xs:string"/>
+		</xs:complexType>
+	</xs:element>
+	<xs:element name="post-add">
+		<xs:complexType>
+			<xs:attribute name="method-name" use="required" type="xs:string"/>
+		</xs:complexType>
+	</xs:element>
+	<xs:element name="pre-persist">
+		<xs:complexType>
+			<xs:attribute name="method-name" use="required" type="xs:string"/>
+		</xs:complexType>
+	</xs:element>
+	<xs:element name="post-remove">
+		<xs:complexType>
+			<xs:attribute name="method-name" use="required" type="xs:string"/>
+		</xs:complexType>
+	</xs:element>
+	<xs:element name="post-load">
+		<xs:complexType>
+			<xs:attribute name="method-name" use="required" type="xs:string"/>
+		</xs:complexType>
+	</xs:element>
+	<xs:element name="pre-remove">
+		<xs:complexType>
+			<xs:attribute name="method-name" use="required" type="xs:string"/>
+		</xs:complexType>
+	</xs:element>
+
+	<xs:element name="db-key-generator">
+		<xs:annotation>
+			<xs:documentation>Used to install the Automatic Sequence/Key Generation facility for db-entity. This feature is intended for use with simple (non-compound) integral primary keys.</xs:documentation>
+		</xs:annotation>
+		<xs:complexType>
+			<xs:sequence>
+				<xs:element name="db-generator-type" type="xs:string">
+					<xs:annotation>
+						<xs:documentation>Specifies the Key Generation Method that will be employed
+      'ORACLE'               - use Oracle's SEQUENCE
+      'NAMED_SEQUENCE_TABLE' - use USER designated SEQUENCE TABLE. User specifies the name of a DBMS Table with the schema (sequence INT) which will be used to hold sequence values (not supported yet)</xs:documentation>
+					</xs:annotation>
+				</xs:element>
+				<xs:element minOccurs="0" name="db-generator-name" type="xs:string">
+					<xs:annotation>
+						<xs:documentation>For db-generator-type ORACLE this is the name of the ORACLE SEQUENCE to use. The SEQUENCE is assumed to already exist in the Database.
+If this is db-generator-type NAMED_SEQUENCE_TABLE Key Generation, this specifies the name of the SEQUENCE TABLE to use. The NAMED_SEQUENCE_TABLE is assumed to already exist in the database.</xs:documentation>
+					</xs:annotation>
+				</xs:element>
+				<xs:element minOccurs="0" name="db-key-cache-size" type="xs:integer">
+					<xs:annotation>
+						<xs:documentation>Size of key cache. For db-generator-type ORACLE , this value MUST match the Oracle SEQUENCE  INCREMENT value.  If there is a mismatch between this value and the Oracle SEQUENCE INCREMENT value, then there will likely be duplicate key problems.
+For db-generator-type NAMED_SEQUENCE_TABLE , this tells how many keys the Container will fetch in a single DBMS call.</xs:documentation>
+					</xs:annotation>
+				</xs:element>
+			</xs:sequence>
+		</xs:complexType>
+	</xs:element>
+</xs:schema>