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:26 UTC

[11/16] cayenne git commit: CAY-2335: New XML loading/saving mechanics with support of plugable handlers - ProjectExtension - new upgrade handlers

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38553b16/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/handlers/UpgradeHandler_V8.java
----------------------------------------------------------------------
diff --git a/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/handlers/UpgradeHandler_V8.java b/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/handlers/UpgradeHandler_V8.java
new file mode 100644
index 0000000..c2898bc
--- /dev/null
+++ b/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/handlers/UpgradeHandler_V8.java
@@ -0,0 +1,99 @@
+/*****************************************************************
+ *   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.project.upgrade.handlers;
+
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathFactory;
+
+import org.apache.cayenne.ConfigurationException;
+import org.apache.cayenne.configuration.DataChannelDescriptor;
+import org.apache.cayenne.map.QueryDescriptor;
+import org.apache.cayenne.project.upgrade.UpgradeUnit;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+
+/**
+ * @since 4.1
+ */
+public class UpgradeHandler_V8 implements UpgradeHandler {
+
+    @Override
+    public String getVersion() {
+        return "8";
+    }
+
+    @Override
+    public void processProjectDom(UpgradeUnit upgradeUnit) {
+        Element domain = upgradeUnit.getDocument().getDocumentElement();
+        domain.setAttribute("project-version", getVersion());
+    }
+
+    @Override
+    public void processDataMapDom(UpgradeUnit upgradeUnit) {
+        Element dataMap = upgradeUnit.getDocument().getDocumentElement();
+        dataMap.setAttribute("xmlns","http://cayenne.apache.org/schema/8/modelMap");
+        dataMap.setAttribute("xsi:schemaLocation", "http://cayenne.apache.org/schema/8/modelMap " +
+                "http://cayenne.apache.org/schema/8/modelMap.xsd");
+        dataMap.setAttribute("project-version", getVersion());
+
+        XPath xpath = XPathFactory.newInstance().newXPath();
+        NodeList queryNodes;
+        try {
+            queryNodes = (NodeList) xpath.evaluate("/data-map/query", upgradeUnit.getDocument(), XPathConstants.NODESET);
+        } catch (Exception ex) {
+            return;
+        }
+
+        for (int j = 0; j < queryNodes.getLength(); j++) {
+            Element queryElement = (Element) queryNodes.item(j);
+            String factory = queryElement.getAttribute("factory");
+            if(factory == null || factory.isEmpty()) {
+                continue;
+            }
+
+            String queryType;
+            switch (factory) {
+                case "org.apache.cayenne.map.SelectQueryBuilder":
+                    queryType = QueryDescriptor.SELECT_QUERY;
+                    break;
+                case "org.apache.cayenne.map.SQLTemplateBuilder":
+                    queryType = QueryDescriptor.SQL_TEMPLATE;
+                    break;
+                case "org.apache.cayenne.map.EjbqlBuilder":
+                    queryType = QueryDescriptor.EJBQL_QUERY;
+                    break;
+                case "org.apache.cayenne.map.ProcedureQueryBuilder":
+                    queryType = QueryDescriptor.PROCEDURE_QUERY;
+                    break;
+                default:
+                    throw new ConfigurationException("Unknown query factory: " + factory);
+            }
+
+            queryElement.setAttribute("type", queryType);
+            queryElement.removeAttribute("factory");
+        }
+    }
+
+    @Override
+    public void processModel(DataChannelDescriptor dataChannelDescriptor) {
+
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38553b16/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/handlers/UpgradeHandler_V9.java
----------------------------------------------------------------------
diff --git a/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/handlers/UpgradeHandler_V9.java b/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/handlers/UpgradeHandler_V9.java
new file mode 100644
index 0000000..bc0d078
--- /dev/null
+++ b/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/handlers/UpgradeHandler_V9.java
@@ -0,0 +1,81 @@
+/*****************************************************************
+ *   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.project.upgrade.handlers;
+
+import java.io.File;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathFactory;
+
+import org.apache.cayenne.configuration.DataChannelDescriptor;
+import org.apache.cayenne.project.upgrade.UpgradeUnit;
+import org.apache.cayenne.util.Util;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+/**
+ * @since 4.1
+ */
+public class UpgradeHandler_V9 implements UpgradeHandler {
+
+    @Override
+    public String getVersion() {
+        return "9";
+    }
+
+    @Override
+    public void processProjectDom(UpgradeUnit upgradeUnit) {
+        Element domain = upgradeUnit.getDocument().getDocumentElement();
+        domain.setAttribute("project-version", getVersion());
+    }
+
+    @Override
+    public void processDataMapDom(UpgradeUnit upgradeUnit) {
+        Document document = upgradeUnit.getDocument();
+        Element dataMap = document.getDocumentElement();
+        dataMap.setAttribute("xmlns","http://cayenne.apache.org/schema/9/modelMap");
+        dataMap.setAttribute("xsi:schemaLocation", "http://cayenne.apache.org/schema/9/modelMap " +
+                "http://cayenne.apache.org/schema/9/modelMap.xsd");
+        dataMap.setAttribute("project-version", getVersion());
+
+        XPath xpath = XPathFactory.newInstance().newXPath();
+        try {
+            Node reNode = (Node) xpath.evaluate("/data-map/reverse-engineering-config", document, XPathConstants.NODE);
+
+            if (reNode != null) {
+                String reFileName = ((Element) reNode).getAttribute("name") + ".xml";
+                String directoryPath = Util.toFile(upgradeUnit.getResource().getURL()).getParent();
+
+                File file = new File(directoryPath + "/" + reFileName);
+                if (file.exists()) {
+                    file.delete();
+                }
+                dataMap.removeChild(reNode);
+            }
+        } catch (Exception ex) {
+            ex.printStackTrace();
+        }
+    }
+
+    @Override
+    public void processModel(DataChannelDescriptor dataChannelDescriptor) {
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38553b16/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/v6/ProjectUpgrader_V6.java
----------------------------------------------------------------------
diff --git a/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/v6/ProjectUpgrader_V6.java b/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/v6/ProjectUpgrader_V6.java
deleted file mode 100644
index 0a4a0a0..0000000
--- a/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/v6/ProjectUpgrader_V6.java
+++ /dev/null
@@ -1,43 +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.project.upgrade.v6;
-
-import org.apache.cayenne.di.Inject;
-import org.apache.cayenne.di.Injector;
-import org.apache.cayenne.project.upgrade.ProjectUpgrader;
-import org.apache.cayenne.project.upgrade.UpgradeHandler;
-import org.apache.cayenne.resource.Resource;
-
-/**
- * A ProjectUpgrader that handles project upgrades to version 6.
- * 
- * @since 3.1
- */
-public class ProjectUpgrader_V6 implements ProjectUpgrader {
-
-    @Inject
-    protected Injector injector;
-
-    public UpgradeHandler getUpgradeHandler(Resource projectSource) {
-        UpgradeHandler_V6 handler = new UpgradeHandler_V6(projectSource);
-        injector.injectMembers(handler);
-        return handler;
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38553b16/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/v6/UpgradeHandler_V6.java
----------------------------------------------------------------------
diff --git a/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/v6/UpgradeHandler_V6.java b/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/v6/UpgradeHandler_V6.java
deleted file mode 100644
index f20553e..0000000
--- a/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/v6/UpgradeHandler_V6.java
+++ /dev/null
@@ -1,118 +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.project.upgrade.v6;
-
-import org.apache.cayenne.ConfigurationException;
-import org.apache.cayenne.configuration.ConfigurationTree;
-import org.apache.cayenne.configuration.DataChannelDescriptor;
-import org.apache.cayenne.configuration.DataNodeDescriptor;
-import org.apache.cayenne.di.Inject;
-import org.apache.cayenne.project.Project;
-import org.apache.cayenne.project.ProjectSaver;
-import org.apache.cayenne.project.upgrade.BaseUpgradeHandler;
-import org.apache.cayenne.project.upgrade.UpgradeMetaData;
-import org.apache.cayenne.resource.Resource;
-import org.apache.cayenne.util.Util;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-
-/**
- * @since 3.1
- */
-class UpgradeHandler_V6 extends BaseUpgradeHandler {
-
-    static final String TO_VERSION = "6";
-
-    /**
-     * Notice that the loader is statically typed, intentionally not using DI to ensure
-     * predictable behavior on legacy upgrades.
-     */
-    private XMLDataChannelDescriptorLoader_V3_0_0_1 projectLoader;
-
-    /**
-     * Unlike loader, saver is injected, so that we can save dynamically with the latest
-     * version. This may change once this upgrade handler becomes an intermediate handler.
-     */
-    @Inject
-    private ProjectSaver projectSaver;
-
-    UpgradeHandler_V6(Resource source) {
-        super(source);
-        this.projectLoader = new XMLDataChannelDescriptorLoader_V3_0_0_1();
-    }
-
-    @Override
-    protected Resource doPerformUpgrade(UpgradeMetaData metaData) throws ConfigurationException {
-
-        List<DataChannelDescriptor> domains = projectLoader.load(projectSource);
-        if (domains.isEmpty()) {
-            // create a single domain dummy project if a noop config is being upgraded
-            DataChannelDescriptor descriptor = new DataChannelDescriptor();
-            descriptor.setName("DEFAULT");
-            domains.add(descriptor);
-        }
-
-        // collect resources to delete before the upgrade...
-        Collection<Resource> resourcesToDelete = new ArrayList<>();
-        for (DataChannelDescriptor descriptor : domains) {
-            for (DataNodeDescriptor node : descriptor.getNodeDescriptors()) {
-                Resource nodeResource = node.getConfigurationSource();
-                if (nodeResource != null) {
-                    resourcesToDelete.add(nodeResource);
-                }
-            }
-        }
-
-        // save in the new format
-        for (DataChannelDescriptor descriptor : domains) {
-            Project project = new Project(new ConfigurationTree<>(descriptor));
-            
-            attachToNamespace((DataChannelDescriptor) project.getRootNode());
-
-            // side effect of that is deletion of the common "cayenne.xml"
-            projectSaver.save(project);
-        }
-
-        // delete all .driver.xml files
-        for (Resource resource : resourcesToDelete) {
-            try {
-                File file = Util.toFile(resource.getURL());
-                if (file.exists() && file.getName().endsWith(".driver.xml")) {
-                    file.delete();
-                }
-            }
-            catch (Exception e) {
-                // ignore...
-            }
-        }
-
-        // returns the first domain configuration out of possibly multiple new
-        // configurations...
-        return domains.get(0).getConfigurationSource();
-    }
-
-    @Override
-    protected String getToVersion() {
-        return TO_VERSION;
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38553b16/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/v6/XMLDataChannelDescriptorLoader_V3_0_0_1.java
----------------------------------------------------------------------
diff --git a/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/v6/XMLDataChannelDescriptorLoader_V3_0_0_1.java b/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/v6/XMLDataChannelDescriptorLoader_V3_0_0_1.java
deleted file mode 100644
index f0819ff..0000000
--- a/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/v6/XMLDataChannelDescriptorLoader_V3_0_0_1.java
+++ /dev/null
@@ -1,299 +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.project.upgrade.v6;
-
-import org.apache.cayenne.ConfigurationException;
-import org.apache.cayenne.configuration.DataChannelDescriptor;
-import org.apache.cayenne.configuration.DataNodeDescriptor;
-import org.apache.cayenne.configuration.SAXNestedTagHandler;
-import org.apache.cayenne.configuration.server.JNDIDataSourceFactory;
-import org.apache.cayenne.configuration.server.XMLPoolingDataSourceFactory;
-import org.apache.cayenne.conn.DataSourceInfo;
-import org.apache.cayenne.map.DataMap;
-import org.apache.cayenne.resource.Resource;
-import org.apache.cayenne.util.Util;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.xml.sax.Attributes;
-import org.xml.sax.ContentHandler;
-import org.xml.sax.InputSource;
-import org.xml.sax.XMLReader;
-
-import java.io.InputStream;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * A loader of Cayenne projects descriptor for version "3.0.0.1".
- */
-class XMLDataChannelDescriptorLoader_V3_0_0_1 {
-
-	static final String DBCP_DATA_SOURCE_FACTORY = "org.apache.cayenne.configuration.server.DBCPDataSourceFactory";
-
-	private static Logger logger = LoggerFactory.getLogger(XMLDataChannelDescriptorLoader_V3_0_0_1.class);
-
-	static final String DOMAINS_TAG = "domains";
-	static final String DOMAIN_TAG = "domain";
-	static final String MAP_TAG = "map";
-	static final String NODE_TAG = "node";
-	static final String PROPERTY_TAG = "property";
-	static final String MAP_REF_TAG = "map-ref";
-
-	private static final Map<String, String> dataSourceFactoryLegacyNameMapping;
-
-	static {
-		dataSourceFactoryLegacyNameMapping = new HashMap<>();
-		dataSourceFactoryLegacyNameMapping.put("org.apache.cayenne.conf.DriverDataSourceFactory",
-				XMLPoolingDataSourceFactory.class.getName());
-		dataSourceFactoryLegacyNameMapping.put("org.apache.cayenne.conf.JNDIDataSourceFactory",
-				JNDIDataSourceFactory.class.getName());
-		dataSourceFactoryLegacyNameMapping.put("org.apache.cayenne.conf.DBCPDataSourceFactory",
-				DBCP_DATA_SOURCE_FACTORY);
-	}
-
-	// implementation is statically typed and is intentionally not DI-provided
-	protected XMLDataMapLoader_V3_0_0_1 mapLoader;
-	protected XMLDataSourceInfoLoader_V3_0_0_1 dataSourceInfoLoader;
-
-	XMLDataChannelDescriptorLoader_V3_0_0_1() {
-		mapLoader = new XMLDataMapLoader_V3_0_0_1();
-		dataSourceInfoLoader = new XMLDataSourceInfoLoader_V3_0_0_1();
-	}
-
-	List<DataChannelDescriptor> load(Resource configurationSource) throws ConfigurationException {
-
-		if (configurationSource == null) {
-			throw new NullPointerException("Null configurationSource");
-		}
-
-		URL configurationURL = configurationSource.getURL();
-
-		List<DataChannelDescriptor> domains = new ArrayList<>();
-
-		try (InputStream in = configurationURL.openStream();) {
-
-			XMLReader parser = Util.createXmlReader();
-
-			DomainsHandler rootHandler = new DomainsHandler(configurationSource, domains, parser);
-			parser.setContentHandler(rootHandler);
-			parser.setErrorHandler(rootHandler);
-			parser.parse(new InputSource(in));
-		} catch (Exception e) {
-			throw new ConfigurationException("Error loading configuration from %s", e, configurationURL);
-		}
-
-		return domains;
-	}
-
-	/**
-	 * Make sure the domain name is only made up of Java-identifier-safe
-	 * characters.
-	 */
-	protected String scrubDomainName(String name) {
-		if (name == null || name.length() == 0) {
-			return name;
-		}
-
-		StringBuilder buffer = new StringBuilder(name.length());
-
-		for (int i = 0; i < name.length(); i++) {
-			char c = name.charAt(i);
-			if (i == 0 && !Character.isJavaIdentifierStart(c)) {
-				buffer.append('_');
-			} else if (i > 0 && !Character.isJavaIdentifierPart(c)) {
-				buffer.append('_');
-			} else {
-				buffer.append(c);
-			}
-		}
-
-		return buffer.toString();
-	}
-
-	/**
-	 * Converts the names of standard Cayenne-supplied DataSourceFactories from
-	 * the legacy names to the current names.
-	 */
-	private String convertDataSourceFactory(String dataSourceFactory) {
-
-		if (dataSourceFactory == null) {
-			return null;
-		}
-
-		String converted = dataSourceFactoryLegacyNameMapping.get(dataSourceFactory);
-		return converted != null ? converted : dataSourceFactory;
-	}
-
-	final class DomainsHandler extends SAXNestedTagHandler {
-
-		private Collection<DataChannelDescriptor> domains;
-		private Resource configurationSource;
-
-		DomainsHandler(Resource configurationSource, Collection<DataChannelDescriptor> domains, XMLReader parser) {
-			super(parser, null);
-			this.domains = domains;
-			this.configurationSource = configurationSource;
-		}
-
-		@Override
-		protected ContentHandler createChildTagHandler(String namespaceURI, String localName, String qName,
-				Attributes attributes) {
-
-			if (localName.equals(DOMAINS_TAG)) {
-				return new DomainsChildrenHandler(parser, this);
-			}
-
-			return super.createChildTagHandler(namespaceURI, localName, qName, attributes);
-		}
-	}
-
-	final class DomainsChildrenHandler extends SAXNestedTagHandler {
-
-		private Collection<DataChannelDescriptor> domains;
-		private Resource configurationSource;
-
-		DomainsChildrenHandler(XMLReader parser, DomainsHandler parent) {
-			super(parser, parent);
-			this.domains = parent.domains;
-			this.configurationSource = parent.configurationSource;
-		}
-
-		@Override
-		protected ContentHandler createChildTagHandler(String namespaceURI, String localName, String name,
-				Attributes attributes) {
-
-			if (localName.equals(DOMAIN_TAG)) {
-
-				String domainName = attributes.getValue("", "name");
-				DataChannelDescriptor descriptor = new DataChannelDescriptor();
-				descriptor.setName(scrubDomainName(domainName));
-				descriptor.setConfigurationSource(configurationSource);
-
-				domains.add(descriptor);
-				return new DataChannelChildrenHandler(descriptor, parser, this);
-			}
-
-			logger.info(unexpectedTagMessage(localName, DOMAIN_TAG));
-			return super.createChildTagHandler(namespaceURI, localName, name, attributes);
-		}
-	}
-
-	final class DataChannelChildrenHandler extends SAXNestedTagHandler {
-
-		private DataChannelDescriptor descriptor;
-
-		DataChannelChildrenHandler(DataChannelDescriptor descriptor, XMLReader parser,
-				DomainsChildrenHandler parentHandler) {
-			super(parser, parentHandler);
-			this.descriptor = descriptor;
-		}
-
-		@Override
-		protected ContentHandler createChildTagHandler(String namespaceURI, String localName, String name,
-				Attributes attributes) {
-
-			if (localName.equals(PROPERTY_TAG)) {
-
-				String key = attributes.getValue("", "name");
-				String value = attributes.getValue("", "value");
-				if (key != null && value != null) {
-					descriptor.getProperties().put(key, value);
-				}
-			} else if (localName.equals(MAP_TAG)) {
-
-				String dataMapName = attributes.getValue("", "name");
-				Resource baseResource = descriptor.getConfigurationSource();
-
-				String dataMapLocation = attributes.getValue("", "location");
-				Resource dataMapResource = baseResource.getRelativeResource(dataMapLocation);
-
-				DataMap dataMap = mapLoader.load(dataMapResource);
-				dataMap.setName(dataMapName);
-				dataMap.setLocation(dataMapLocation);
-				dataMap.setConfigurationSource(dataMapResource);
-
-				descriptor.getDataMaps().add(dataMap);
-			} else if (localName.equals(NODE_TAG)) {
-
-				String nodeName = attributes.getValue("", "name");
-				if (nodeName == null) {
-					// TODO: assign dummy name?
-					throw new ConfigurationException("Error: <node> without 'name'.");
-				}
-
-				DataNodeDescriptor nodeDescriptor = new DataNodeDescriptor();
-				nodeDescriptor.setName(nodeName);
-
-				String dataSourceFactory = attributes.getValue("", "factory");
-				String dataSourceFactory6 = convertDataSourceFactory(dataSourceFactory);
-				nodeDescriptor.setDataSourceFactoryType(dataSourceFactory6);
-
-				// depending on the factory, "datasource" attribute is
-				// interpreted
-				// differently
-				String datasource = attributes.getValue("", "datasource");
-				if (XMLPoolingDataSourceFactory.class.getName().equals(dataSourceFactory6)) {
-					Resource baseResource = descriptor.getConfigurationSource();
-					Resource dataNodeResource = baseResource.getRelativeResource(datasource);
-					nodeDescriptor.setConfigurationSource(dataNodeResource);
-
-					DataSourceInfo dataSourceInfo = dataSourceInfoLoader.load(dataNodeResource);
-					nodeDescriptor.setDataSourceDescriptor(dataSourceInfo);
-				} else {
-					nodeDescriptor.setParameters(datasource);
-				}
-
-				descriptor.getNodeDescriptors().add(nodeDescriptor);
-				nodeDescriptor.setAdapterType(attributes.getValue("", "adapter"));
-				nodeDescriptor.setSchemaUpdateStrategyType(attributes.getValue("", "schema-update-strategy"));
-
-				return new DataNodeChildrenHandler(parser, this, nodeDescriptor);
-			}
-
-			return super.createChildTagHandler(namespaceURI, localName, name, attributes);
-		}
-	}
-
-	final class DataNodeChildrenHandler extends SAXNestedTagHandler {
-
-		private DataNodeDescriptor nodeDescriptor;
-
-		DataNodeChildrenHandler(XMLReader parser, SAXNestedTagHandler parentHandler, DataNodeDescriptor nodeDescriptor) {
-			super(parser, parentHandler);
-			this.nodeDescriptor = nodeDescriptor;
-		}
-
-		@Override
-		protected ContentHandler createChildTagHandler(String namespaceURI, String localName, String name,
-				Attributes attributes) {
-
-			if (localName.equals(MAP_REF_TAG)) {
-
-				String mapName = attributes.getValue("", "name");
-				nodeDescriptor.getDataMapNames().add(mapName);
-			}
-
-			return super.createChildTagHandler(namespaceURI, localName, name, attributes);
-		}
-	}
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38553b16/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/v6/XMLDataSourceInfoLoader_V3_0_0_1.java
----------------------------------------------------------------------
diff --git a/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/v6/XMLDataSourceInfoLoader_V3_0_0_1.java b/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/v6/XMLDataSourceInfoLoader_V3_0_0_1.java
deleted file mode 100644
index 188c6f8..0000000
--- a/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/v6/XMLDataSourceInfoLoader_V3_0_0_1.java
+++ /dev/null
@@ -1,300 +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.project.upgrade.v6;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.net.MalformedURLException;
-import java.net.URL;
-
-import org.apache.cayenne.ConfigurationException;
-import org.apache.cayenne.configuration.PasswordEncoding;
-import org.apache.cayenne.configuration.SAXNestedTagHandler;
-import org.apache.cayenne.conn.DataSourceInfo;
-import org.apache.cayenne.resource.Resource;
-import org.apache.cayenne.util.Util;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.xml.sax.Attributes;
-import org.xml.sax.ContentHandler;
-import org.xml.sax.InputSource;
-import org.xml.sax.XMLReader;
-
-/**
- * @since 3.1
- */
-class XMLDataSourceInfoLoader_V3_0_0_1 {
-
-    private static Logger logger = LoggerFactory.getLogger(XMLDataSourceInfoLoader_V3_0_0_1.class);
-
-    private static String passwordFromURL(URL url) {
-        InputStream inputStream = null;
-        String password = null;
-
-        try {
-            inputStream = url.openStream();
-            password = passwordFromInputStream(inputStream);
-        }
-        catch (IOException exception) {
-            // Log the error while trying to open the stream. A null
-            // password will be returned as a result.
-            logger.warn(exception.getMessage(), exception);
-        }
-
-        return password;
-    }
-
-    private static String passwordFromInputStream(InputStream inputStream) {
-        BufferedReader bufferedReader = null;
-        String password = null;
-
-        try {
-            bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
-            password = bufferedReader.readLine();
-        }
-        catch (IOException exception) {
-            logger.warn(exception.getMessage(), exception);
-        }
-        finally {
-            try {
-                if (bufferedReader != null) {
-                    bufferedReader.close();
-                }
-            }
-            catch (Exception exception) {
-            }
-
-            try {
-                inputStream.close();
-            }
-            catch (IOException exception) {
-            }
-        }
-
-        return password;
-    }
-
-    DataSourceInfo load(Resource configurationSource) {
-        if (configurationSource == null) {
-            throw new NullPointerException("Null configurationSource");
-        }
-
-        URL configurationURL = configurationSource.getURL();
-
-        DataSourceInfo dataSourceInfo = new DataSourceInfo();
-
-        InputStream in = null;
-
-        try {
-            in = configurationURL.openStream();
-            XMLReader parser = Util.createXmlReader();
-
-            DriverHandler rootHandler = new DriverHandler(parser, dataSourceInfo);
-            parser.setContentHandler(rootHandler);
-            parser.setErrorHandler(rootHandler);
-            parser.parse(new InputSource(in));
-        }
-        catch (Exception e) {
-            throw new ConfigurationException(
-                    "Error loading configuration from %s",
-                    e,
-                    configurationURL);
-        }
-        finally {
-            try {
-                if (in != null) {
-                    in.close();
-                }
-            }
-            catch (IOException ioex) {
-                logger.info("failure closing input stream for "
-                        + configurationURL
-                        + ", ignoring", ioex);
-            }
-        }
-
-        return dataSourceInfo;
-    }
-
-    final class DriverHandler extends SAXNestedTagHandler {
-
-        private DataSourceInfo dataSourceDescriptor;
-
-        DriverHandler(XMLReader parser, DataSourceInfo dataSourceDescriptor) {
-            super(parser, null);
-            this.dataSourceDescriptor = dataSourceDescriptor;
-        }
-
-        @Override
-        protected ContentHandler createChildTagHandler(
-                String namespaceURI,
-                String localName,
-                String name,
-                Attributes attributes) {
-
-            if (localName.equals("driver")) {
-                String className = attributes.getValue("", "class");
-                dataSourceDescriptor.setJdbcDriver(className);
-
-                return new DataSourceChildrenHandler(parser, this);
-            }
-
-            return super.createChildTagHandler(namespaceURI, localName, name, attributes);
-        }
-    }
-
-    class DataSourceChildrenHandler extends SAXNestedTagHandler {
-
-        private DataSourceInfo dataSourceDescriptor;
-
-        DataSourceChildrenHandler(XMLReader parser, DriverHandler parentHandler) {
-            super(parser, parentHandler);
-            this.dataSourceDescriptor = parentHandler.dataSourceDescriptor;
-        }
-
-        @Override
-        protected ContentHandler createChildTagHandler(
-                String namespaceURI,
-                String localName,
-                String name,
-                Attributes attributes) {
-
-            if (localName.equals("login")) {
-
-                String encoderClass = attributes.getValue("encoderClass");
-
-                String encoderKey = attributes.getValue("encoderKey");
-                if (encoderKey == null) {
-                    encoderKey = attributes.getValue("encoderSalt");
-                }
-
-                String password = attributes.getValue("password");
-                String passwordLocation = attributes.getValue("passwordLocation");
-                String passwordSource = attributes.getValue("passwordSource");
-                if (passwordSource == null) {
-                    passwordSource = DataSourceInfo.PASSWORD_LOCATION_MODEL;
-                }
-
-                String username = attributes.getValue("userName");
-
-                dataSourceDescriptor.setPasswordEncoderClass(encoderClass);
-                dataSourceDescriptor.setPasswordEncoderKey(encoderKey);
-                dataSourceDescriptor.setPasswordLocation(passwordLocation);
-                dataSourceDescriptor.setPasswordSource(passwordSource);
-                dataSourceDescriptor.setUserName(username);
-
-                // Replace {} in passwordSource with encoderSalt -- useful for EXECUTABLE
-                // & URL options
-                if (encoderKey != null) {
-                    passwordSource = passwordSource.replaceAll("\\{\\}", encoderKey);
-                }
-
-                PasswordEncoding passwordEncoder = dataSourceDescriptor
-                        .getPasswordEncoder();
-
-                if (passwordLocation != null) {
-                    if (passwordLocation
-                            .equals(DataSourceInfo.PASSWORD_LOCATION_CLASSPATH)) {
-
-                        ClassLoader classLoader = Thread
-                                .currentThread()
-                                .getContextClassLoader();
-                        URL url = classLoader.getResource(username);
-                        if (url != null) {
-                            password = passwordFromURL(url);
-                        }
-                        else {
-                            logger.error("Could not find resource in CLASSPATH: "
-                                    + passwordSource);
-                        }
-                    }
-                    else if (passwordLocation
-                            .equals(DataSourceInfo.PASSWORD_LOCATION_URL)) {
-                        try {
-                            password = passwordFromURL(new URL(passwordSource));
-                        }
-                        catch (MalformedURLException exception) {
-                            logger.warn(exception.getMessage(), exception);
-                        }
-                    }
-                    else if (passwordLocation
-                            .equals(DataSourceInfo.PASSWORD_LOCATION_EXECUTABLE)) {
-                        if (passwordSource != null) {
-                            try {
-                                Process process = Runtime.getRuntime().exec(
-                                        passwordSource);
-                                password = passwordFromInputStream(process
-                                        .getInputStream());
-                                process.waitFor();
-                            }
-                            catch (IOException exception) {
-                                logger.warn(exception.getMessage(), exception);
-                            }
-                            catch (InterruptedException exception) {
-                                logger.warn(exception.getMessage(), exception);
-                            }
-                        }
-                    }
-                }
-
-                if (password != null && passwordEncoder != null) {
-                    dataSourceDescriptor.setPassword(passwordEncoder.decodePassword(
-                            password,
-                            encoderKey));
-                }
-            }
-            else if (localName.equals("url")) {
-                dataSourceDescriptor.setDataSourceUrl(attributes.getValue("value"));
-            }
-            else if (localName.equals("connectionPool")) {
-                String min = attributes.getValue("min");
-                if (min != null) {
-                    try {
-                        dataSourceDescriptor.setMinConnections(Integer.parseInt(min));
-                    }
-                    catch (NumberFormatException nfex) {
-                        logger.info("Non-numeric 'min' attribute", nfex);
-                        throw new ConfigurationException(
-                                "Non-numeric 'min' attribute '%s'",
-                                nfex,
-                                min);
-                    }
-                }
-
-                String max = attributes.getValue("max");
-                if (max != null) {
-                    try {
-                        dataSourceDescriptor.setMaxConnections(Integer.parseInt(max));
-                    }
-                    catch (NumberFormatException nfex) {
-                        logger.info("Non-numeric 'max' attribute", nfex);
-                        throw new ConfigurationException(
-                                "Non-numeric 'max' attribute '%s'",
-                                nfex,
-                                max);
-                    }
-                }
-            }
-
-            return super.createChildTagHandler(namespaceURI, localName, name, attributes);
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38553b16/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/v7/UpgradeHandler_V7.java
----------------------------------------------------------------------
diff --git a/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/v7/UpgradeHandler_V7.java b/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/v7/UpgradeHandler_V7.java
deleted file mode 100644
index bd30381..0000000
--- a/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/v7/UpgradeHandler_V7.java
+++ /dev/null
@@ -1,137 +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.project.upgrade.v7;
-
-import org.apache.cayenne.ConfigurationException;
-import org.apache.cayenne.configuration.ConfigurationTree;
-import org.apache.cayenne.configuration.DataChannelDescriptor;
-import org.apache.cayenne.configuration.XMLDataChannelDescriptorLoader;
-import org.apache.cayenne.di.Inject;
-import org.apache.cayenne.di.Injector;
-import org.apache.cayenne.map.DataMap;
-import org.apache.cayenne.map.ObjAttribute;
-import org.apache.cayenne.map.ObjEntity;
-import org.apache.cayenne.project.Project;
-import org.apache.cayenne.project.ProjectSaver;
-import org.apache.cayenne.project.upgrade.BaseUpgradeHandler;
-import org.apache.cayenne.project.upgrade.UpgradeHandler;
-import org.apache.cayenne.project.upgrade.UpgradeMetaData;
-import org.apache.cayenne.project.upgrade.v6.ProjectUpgrader_V6;
-import org.apache.cayenne.resource.Resource;
-
-import java.util.ArrayList;
-import java.util.List;
-
-class UpgradeHandler_V7 extends BaseUpgradeHandler {
-
-    static final String PREVIOUS_VERSION = "6";
-    static final String TO_VERSION = "7";
-
-    // this will be removed from DataDomain soon. So caching this property name
-    // in the upgrade handler
-    static final String USING_EXTERNAL_TRANSACTIONS_PROPERTY = "cayenne.DataDomain.usingExternalTransactions";
-
-    @Inject
-    protected Injector injector;
-
-    @Inject
-    private ProjectSaver projectSaver;
-
-    public UpgradeHandler_V7(Resource source) {
-        super(source);
-    }
-
-    @Override
-    protected Resource doPerformUpgrade(UpgradeMetaData metaData) throws ConfigurationException {
-        if (compareVersions(metaData.getProjectVersion(), PREVIOUS_VERSION) == -1) {
-            ProjectUpgrader_V6 upgraderV6 = new ProjectUpgrader_V6();
-            injector.injectMembers(upgraderV6);
-            UpgradeHandler handlerV6 = upgraderV6.getUpgradeHandler(projectSource);
-            projectSource = handlerV6.performUpgrade();
-        }
-
-        XMLDataChannelDescriptorLoader loader = new XMLDataChannelDescriptorLoader();
-        injector.injectMembers(loader);
-        ConfigurationTree<DataChannelDescriptor> tree = loader.load(projectSource);
-        Project project = new Project(tree);
-
-        attachToNamespace((DataChannelDescriptor) project.getRootNode());
-        
-        removeExternalTxProperty(project);
-
-        // remove "shadow" attributes per CAY-1795
-        removeShadowAttributes(project);
-
-        // load and safe cycle removes objects no longer supported, specifically
-        // listeners
-        projectSaver.save(project);
-        return project.getConfigurationResource();
-    }
-
-    private void removeExternalTxProperty(Project project) {
-        DataChannelDescriptor rootNode = (DataChannelDescriptor) project.getRootNode();
-        rootNode.getProperties().remove(USING_EXTERNAL_TRANSACTIONS_PROPERTY);
-    }
-
-    private void removeShadowAttributes(Project project) {
-        DataChannelDescriptor rootNode = (DataChannelDescriptor) project.getRootNode();
-
-        for (DataMap dataMap : rootNode.getDataMaps()) {
-
-            // if objEntity has super entity, then checks it
-            // for duplicated attributes
-            for (ObjEntity objEntity : dataMap.getObjEntities()) {
-                ObjEntity superEntity = objEntity.getSuperEntity();
-                if (superEntity != null) {
-                    removeShadowAttributes(objEntity, superEntity);
-                }
-            }
-        }
-    }
-
-    /**
-     * Remove attributes from objEntity, if superEntity has attributes with same
-     * names.
-     */
-    private void removeShadowAttributes(ObjEntity objEntity, ObjEntity superEntity) {
-
-        List<String> delList = new ArrayList<>();
-
-        // if subAttr and superAttr have same names, adds subAttr to delList
-        for (ObjAttribute subAttr : objEntity.getDeclaredAttributes()) {
-            for (ObjAttribute superAttr : superEntity.getAttributes()) {
-                if (subAttr.getName().equals(superAttr.getName())) {
-                    delList.add(subAttr.getName());
-                }
-            }
-        }
-
-        if (!delList.isEmpty()) {
-            for (String i : delList) {
-                objEntity.removeAttribute(i);
-            }
-        }
-    }
-
-    @Override
-    protected String getToVersion() {
-        return TO_VERSION;
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38553b16/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/v8/UpgradeHandler_V8.java
----------------------------------------------------------------------
diff --git a/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/v8/UpgradeHandler_V8.java b/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/v8/UpgradeHandler_V8.java
deleted file mode 100644
index 2e39599..0000000
--- a/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/v8/UpgradeHandler_V8.java
+++ /dev/null
@@ -1,174 +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.project.upgrade.v8;
-
-
-import org.apache.cayenne.ConfigurationException;
-import org.apache.cayenne.configuration.ConfigurationTree;
-import org.apache.cayenne.configuration.DataChannelDescriptor;
-import org.apache.cayenne.configuration.XMLDataChannelDescriptorLoader;
-import org.apache.cayenne.di.Inject;
-import org.apache.cayenne.di.Injector;
-import org.apache.cayenne.map.QueryDescriptor;
-import org.apache.cayenne.project.Project;
-import org.apache.cayenne.project.ProjectSaver;
-import org.apache.cayenne.project.upgrade.BaseUpgradeHandler;
-import org.apache.cayenne.project.upgrade.UpgradeHandler;
-import org.apache.cayenne.project.upgrade.UpgradeMetaData;
-import org.apache.cayenne.project.upgrade.v7.ProjectUpgrader_V7;
-import org.apache.cayenne.resource.Resource;
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-import org.xml.sax.SAXException;
-
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.transform.Transformer;
-import javax.xml.transform.TransformerFactory;
-import javax.xml.transform.dom.DOMSource;
-import javax.xml.transform.stream.StreamResult;
-import javax.xml.xpath.XPath;
-import javax.xml.xpath.XPathConstants;
-import javax.xml.xpath.XPathFactory;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-
-
-public class UpgradeHandler_V8 extends BaseUpgradeHandler {
-
-    static final String PREVIOUS_VERSION = "7";
-    static final String TO_VERSION = "8";
-
-    @Inject
-    protected Injector injector;
-
-    @Inject
-    private ProjectSaver projectSaver;
-
-    public UpgradeHandler_V8(Resource source) {
-        super(source);
-    }
-
-    @Override
-    protected Resource doPerformUpgrade(UpgradeMetaData metaData) throws ConfigurationException {
-        if (compareVersions(metaData.getProjectVersion(), PREVIOUS_VERSION) == -1) {
-            ProjectUpgrader_V7 upgraderV7 = new ProjectUpgrader_V7();
-            injector.injectMembers(upgraderV7);
-            UpgradeHandler handlerV7 = upgraderV7.getUpgradeHandler(projectSource);
-            projectSource = handlerV7.performUpgrade();
-        }
-
-        upgradeFactories(projectSource);
-
-        XMLDataChannelDescriptorLoader loader = new XMLDataChannelDescriptorLoader();
-        injector.injectMembers(loader);
-        ConfigurationTree<DataChannelDescriptor> tree = loader.load(projectSource);
-        Project project = new Project(tree);
-
-        // load and safe cycle updates project version
-        projectSaver.save(project);
-        return project.getConfigurationResource();
-    }
-
-    private void upgradeFactories(Resource projectSource) {
-        Document projectDoc = readDOMDocument(projectSource);
-
-        try {
-            XPath xpath = XPathFactory.newInstance().newXPath();
-            NodeList nodes = (NodeList) xpath.evaluate("/domain/map/@name", projectDoc, XPathConstants.NODESET);
-
-            TransformerFactory transformerFactory = TransformerFactory.newInstance();
-            Transformer transformer = transformerFactory.newTransformer();
-
-            for (int i = 0; i < nodes.getLength(); i++) {
-                Node mapNode = nodes.item(i);
-
-                Resource mapResource = projectSource.getRelativeResource(mapNode.getNodeValue() + ".map.xml");
-
-                Document datamapDoc = readDOMDocument(mapResource);
-
-                NodeList queryNodes = (NodeList) xpath.evaluate("/data-map/query", datamapDoc, XPathConstants.NODESET);
-
-                for (int j = 0; j < queryNodes.getLength(); j++) {
-                    Element queryElement = (Element) queryNodes.item(j);
-                    String factory = queryElement.getAttribute("factory");
-                    if(factory == null || factory.isEmpty()) {
-                        continue;
-                    }
-
-                    String queryType;
-
-                    switch (factory) {
-                        case "org.apache.cayenne.map.SelectQueryBuilder":
-                            queryType = QueryDescriptor.SELECT_QUERY;
-                            break;
-                        case "org.apache.cayenne.map.SQLTemplateBuilder":
-                            queryType = QueryDescriptor.SQL_TEMPLATE;
-                            break;
-                        case "org.apache.cayenne.map.EjbqlBuilder":
-                            queryType = QueryDescriptor.EJBQL_QUERY;
-                            break;
-                        case "org.apache.cayenne.map.ProcedureQueryBuilder":
-                            queryType = QueryDescriptor.PROCEDURE_QUERY;
-                            break;
-                        default:
-                            throw new ConfigurationException("Unknown query factory: " + factory);
-                    }
-
-                    queryElement.setAttribute("type", queryType);
-                    queryElement.removeAttribute("factory");
-                }
-
-                DOMSource domSource = new DOMSource(datamapDoc);
-                StreamResult result = new StreamResult(
-                        new FileOutputStream(new File(mapResource.getURL().getPath())));
-                transformer.transform(domSource, result);
-            }
-
-        } catch (Exception e) {
-            throw new ConfigurationException("Unable to parse Cayenne XML configuration files.", e);
-        }
-    }
-
-    private Document readDOMDocument(Resource resource) {
-        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
-        try {
-            DocumentBuilder domBuilder = documentBuilderFactory.newDocumentBuilder();
-
-            try (InputStream inputStream = resource.getURL().openStream()) {
-                return domBuilder.parse(inputStream);
-            } catch (IOException | SAXException e) {
-                throw new ConfigurationException("Error loading configuration from %s", e, resource);
-            }
-        } catch (ParserConfigurationException e) {
-            throw new ConfigurationException(e);
-        }
-    }
-
-    @Override
-    protected String getToVersion() {
-        return TO_VERSION;
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38553b16/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/v9/UpgradeHandler_V9.java
----------------------------------------------------------------------
diff --git a/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/v9/UpgradeHandler_V9.java b/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/v9/UpgradeHandler_V9.java
deleted file mode 100644
index 92c2f0b..0000000
--- a/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/v9/UpgradeHandler_V9.java
+++ /dev/null
@@ -1,145 +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.project.upgrade.v9;
-
-import org.apache.cayenne.ConfigurationException;
-import org.apache.cayenne.configuration.ConfigurationTree;
-import org.apache.cayenne.configuration.DataChannelDescriptor;
-import org.apache.cayenne.configuration.XMLDataChannelDescriptorLoader;
-import org.apache.cayenne.di.Inject;
-import org.apache.cayenne.di.Injector;
-import org.apache.cayenne.project.Project;
-import org.apache.cayenne.project.ProjectSaver;
-import org.apache.cayenne.project.upgrade.BaseUpgradeHandler;
-import org.apache.cayenne.project.upgrade.UpgradeHandler;
-import org.apache.cayenne.project.upgrade.UpgradeMetaData;
-import org.apache.cayenne.project.upgrade.v8.ProjectUpgrader_V8;
-import org.apache.cayenne.resource.Resource;
-import org.apache.cayenne.util.Util;
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-import org.xml.sax.SAXException;
-
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
-import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.xpath.XPath;
-import javax.xml.xpath.XPathConstants;
-import javax.xml.xpath.XPathFactory;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-
-public class UpgradeHandler_V9 extends BaseUpgradeHandler {
-
-    static final String PREVIOUS_VERSION = "8";
-    static final String TO_VERSION = "9";
-
-    @Inject
-    protected Injector injector;
-
-    @Inject
-    private ProjectSaver projectSaver;
-
-    public UpgradeHandler_V9(Resource source) {
-        super(source);
-    }
-
-    @Override
-    protected Resource doPerformUpgrade(UpgradeMetaData metaData) throws ConfigurationException {
-        if (compareVersions(metaData.getProjectVersion(), PREVIOUS_VERSION) == -1) {
-            ProjectUpgrader_V8 upgraderV8 = new ProjectUpgrader_V8();
-            injector.injectMembers(upgraderV8);
-            UpgradeHandler handlerV8 = upgraderV8.getUpgradeHandler(projectSource);
-            projectSource = handlerV8.performUpgrade();
-        }
-
-        deleteReverseEngineeringFiles(projectSource);
-
-        XMLDataChannelDescriptorLoader loader = new XMLDataChannelDescriptorLoader();
-        injector.injectMembers(loader);
-        ConfigurationTree<DataChannelDescriptor> tree = loader.load(projectSource);
-        Project project = new Project(tree);
-
-        // load and safe cycle updates project version
-        projectSaver.save(project);
-        return project.getConfigurationResource();
-    }
-
-    private void deleteReverseEngineeringFiles(Resource projectSource) {
-        Document projectDoc = readDOMDocument(projectSource);
-
-        try {
-            XPath xpath = XPathFactory.newInstance().newXPath();
-            NodeList nodes = (NodeList) xpath.evaluate("/domain/map/@name", projectDoc, XPathConstants.NODESET);
-
-            for (int i = 0; i < nodes.getLength(); i++) {
-                Node mapNode = nodes.item(i);
-
-                Resource mapResource = projectSource.getRelativeResource(mapNode.getNodeValue() + ".map.xml");
-
-                Document datamapDoc = readDOMDocument(mapResource);
-
-                Node reNode = (Node) xpath.evaluate("/data-map/reverse-engineering-config",
-                        datamapDoc, XPathConstants.NODE);
-
-                if (reNode != null) {
-                    String reFileName = ((Element) reNode).getAttribute("name") + ".xml";
-
-                    try {
-                        String directoryPath = Util.toFile(projectSource.getURL()).getParent();
-
-                        File file = new File(directoryPath + "/" + reFileName);
-                        if (file.exists()) {
-                            file.delete();
-                        }
-                    } catch (Exception e) {
-                        // ignore...
-                    }
-                }
-            }
-
-        } catch (Exception e) {
-            throw new ConfigurationException("Unable to parse Cayenne XML configuration files.", e);
-        }
-    }
-
-    private Document readDOMDocument(Resource resource) {
-        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
-        try {
-            DocumentBuilder domBuilder = documentBuilderFactory.newDocumentBuilder();
-
-            try (InputStream inputStream = resource.getURL().openStream()) {
-                return domBuilder.parse(inputStream);
-            } catch (IOException | SAXException e) {
-                throw new ConfigurationException("Error loading configuration from %s", e, resource);
-            }
-        } catch (ParserConfigurationException e) {
-            throw new ConfigurationException(e);
-        }
-    }
-
-    @Override
-    protected String getToVersion() {
-        return TO_VERSION;
-    }
-
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38553b16/cayenne-project/src/test/java/org/apache/cayenne/project/DataChannelProjectLoaderTest.java
----------------------------------------------------------------------
diff --git a/cayenne-project/src/test/java/org/apache/cayenne/project/DataChannelProjectLoaderTest.java b/cayenne-project/src/test/java/org/apache/cayenne/project/DataChannelProjectLoaderTest.java
index 7159570..9e136f8 100644
--- a/cayenne-project/src/test/java/org/apache/cayenne/project/DataChannelProjectLoaderTest.java
+++ b/cayenne-project/src/test/java/org/apache/cayenne/project/DataChannelProjectLoaderTest.java
@@ -23,8 +23,12 @@ import org.apache.cayenne.configuration.DataChannelDescriptor;
 import org.apache.cayenne.configuration.DataChannelDescriptorLoader;
 import org.apache.cayenne.configuration.DataMapLoader;
 import org.apache.cayenne.configuration.DefaultConfigurationNameMapper;
-import org.apache.cayenne.configuration.XMLDataChannelDescriptorLoader;
-import org.apache.cayenne.configuration.XMLDataMapLoader;
+import org.apache.cayenne.configuration.xml.DataChannelMetaData;
+import org.apache.cayenne.configuration.xml.DefaultHandlerFactory;
+import org.apache.cayenne.configuration.xml.HandlerFactory;
+import org.apache.cayenne.configuration.xml.NoopDataChannelMetaData;
+import org.apache.cayenne.configuration.xml.XMLDataChannelDescriptorLoader;
+import org.apache.cayenne.configuration.xml.XMLDataMapLoader;
 import org.apache.cayenne.di.AdhocObjectFactory;
 import org.apache.cayenne.di.Binder;
 import org.apache.cayenne.di.ClassLoaderManager;
@@ -58,10 +62,10 @@ public class DataChannelProjectLoaderTest {
                 binder.bind(AdhocObjectFactory.class).to(DefaultAdhocObjectFactory.class);
 
                 binder.bind(DataMapLoader.class).to(XMLDataMapLoader.class);
-                binder.bind(DataChannelDescriptorLoader.class).to(
-                        XMLDataChannelDescriptorLoader.class);
-                binder.bind(ConfigurationNameMapper.class).to(
-                        DefaultConfigurationNameMapper.class);
+                binder.bind(DataChannelDescriptorLoader.class).to(XMLDataChannelDescriptorLoader.class);
+                binder.bind(ConfigurationNameMapper.class).to(DefaultConfigurationNameMapper.class);
+                binder.bind(HandlerFactory.class).to(DefaultHandlerFactory.class);
+                binder.bind(DataChannelMetaData.class).to(NoopDataChannelMetaData.class);
             }
         };
 
@@ -70,8 +74,7 @@ public class DataChannelProjectLoaderTest {
 
         String testConfigName = "PROJECT1";
         String baseUrl = getClass().getPackage().getName().replace('.', '/');
-        URL url = getClass().getClassLoader().getResource(
-                baseUrl + "/cayenne-" + testConfigName + ".xml");
+        URL url = getClass().getClassLoader().getResource(baseUrl + "/cayenne-" + testConfigName + ".xml");
 
         Resource rootSource = new URLResource(url);
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38553b16/cayenne-project/src/test/java/org/apache/cayenne/project/DataChannelProjectSaverTest.java
----------------------------------------------------------------------
diff --git a/cayenne-project/src/test/java/org/apache/cayenne/project/DataChannelProjectSaverTest.java b/cayenne-project/src/test/java/org/apache/cayenne/project/DataChannelProjectSaverTest.java
index 484c9ca..5e55221 100644
--- a/cayenne-project/src/test/java/org/apache/cayenne/project/DataChannelProjectSaverTest.java
+++ b/cayenne-project/src/test/java/org/apache/cayenne/project/DataChannelProjectSaverTest.java
@@ -23,8 +23,12 @@ import org.apache.cayenne.configuration.ConfigurationNameMapper;
 import org.apache.cayenne.configuration.DataChannelDescriptorLoader;
 import org.apache.cayenne.configuration.DataMapLoader;
 import org.apache.cayenne.configuration.DefaultConfigurationNameMapper;
-import org.apache.cayenne.configuration.XMLDataChannelDescriptorLoader;
-import org.apache.cayenne.configuration.XMLDataMapLoader;
+import org.apache.cayenne.configuration.xml.DataChannelMetaData;
+import org.apache.cayenne.configuration.xml.DefaultHandlerFactory;
+import org.apache.cayenne.configuration.xml.HandlerFactory;
+import org.apache.cayenne.configuration.xml.NoopDataChannelMetaData;
+import org.apache.cayenne.configuration.xml.XMLDataChannelDescriptorLoader;
+import org.apache.cayenne.configuration.xml.XMLDataMapLoader;
 import org.apache.cayenne.di.AdhocObjectFactory;
 import org.apache.cayenne.di.Binder;
 import org.apache.cayenne.di.ClassLoaderManager;
@@ -33,6 +37,7 @@ import org.apache.cayenne.di.Injector;
 import org.apache.cayenne.di.Module;
 import org.apache.cayenne.di.spi.DefaultAdhocObjectFactory;
 import org.apache.cayenne.di.spi.DefaultClassLoaderManager;
+import org.apache.cayenne.project.extension.ProjectExtension;
 import org.apache.cayenne.project.unit.Project2Case;
 import org.apache.cayenne.resource.Resource;
 import org.apache.cayenne.resource.URLResource;
@@ -41,6 +46,7 @@ import org.junit.Test;
 import java.io.File;
 import java.io.PrintWriter;
 import java.net.URL;
+import java.util.Collections;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
@@ -51,7 +57,7 @@ public class DataChannelProjectSaverTest extends Project2Case {
     @Test
     public void testSaveAs() throws Exception {
 
-        FileProjectSaver saver = new FileProjectSaver();
+        FileProjectSaver saver = new FileProjectSaver(Collections.<ProjectExtension>emptyList());
 
         Module testModule = new Module() {
 
@@ -61,11 +67,11 @@ public class DataChannelProjectSaverTest extends Project2Case {
                 binder.bind(AdhocObjectFactory.class).to(DefaultAdhocObjectFactory.class);
 
                 binder.bind(DataMapLoader.class).to(XMLDataMapLoader.class);
-                binder.bind(DataChannelDescriptorLoader.class).to(
-                        XMLDataChannelDescriptorLoader.class);
+                binder.bind(DataChannelDescriptorLoader.class).to(XMLDataChannelDescriptorLoader.class);
                 binder.bind(ProjectLoader.class).to(DataChannelProjectLoader.class);
-                binder.bind(ConfigurationNameMapper.class).to(
-                        DefaultConfigurationNameMapper.class);
+                binder.bind(ConfigurationNameMapper.class).to(DefaultConfigurationNameMapper.class);
+                binder.bind(HandlerFactory.class).to(DefaultHandlerFactory.class);
+                binder.bind(DataChannelMetaData.class).to(NoopDataChannelMetaData.class);
             }
         };
 
@@ -100,7 +106,7 @@ public class DataChannelProjectSaverTest extends Project2Case {
     @Test
     public void testSaveAs_RecoverFromSaveError() throws Exception {
 
-        FileProjectSaver saver = new FileProjectSaver() {
+        FileProjectSaver saver = new FileProjectSaver(Collections.<ProjectExtension>emptyList()) {
 
             @Override
             void saveToTempFile(SaveUnit unit, PrintWriter printWriter) {
@@ -115,11 +121,11 @@ public class DataChannelProjectSaverTest extends Project2Case {
                 binder.bind(ClassLoaderManager.class).to(DefaultClassLoaderManager.class);
                 binder.bind(AdhocObjectFactory.class).to(DefaultAdhocObjectFactory.class);
                 binder.bind(DataMapLoader.class).to(XMLDataMapLoader.class);
-                binder.bind(DataChannelDescriptorLoader.class).to(
-                        XMLDataChannelDescriptorLoader.class);
+                binder.bind(DataChannelDescriptorLoader.class).to(XMLDataChannelDescriptorLoader.class);
                 binder.bind(ProjectLoader.class).to(DataChannelProjectLoader.class);
-                binder.bind(ConfigurationNameMapper.class).to(
-                        DefaultConfigurationNameMapper.class);
+                binder.bind(ConfigurationNameMapper.class).to(DefaultConfigurationNameMapper.class);
+                binder.bind(HandlerFactory.class).to(DefaultHandlerFactory.class);
+                binder.bind(DataChannelMetaData.class).to(NoopDataChannelMetaData.class);
             }
         };
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38553b16/cayenne-project/src/test/java/org/apache/cayenne/project/FileProjectSaverTest.java
----------------------------------------------------------------------
diff --git a/cayenne-project/src/test/java/org/apache/cayenne/project/FileProjectSaverTest.java b/cayenne-project/src/test/java/org/apache/cayenne/project/FileProjectSaverTest.java
index a9a432d..747b101 100644
--- a/cayenne-project/src/test/java/org/apache/cayenne/project/FileProjectSaverTest.java
+++ b/cayenne-project/src/test/java/org/apache/cayenne/project/FileProjectSaverTest.java
@@ -28,6 +28,7 @@ import org.apache.cayenne.di.DIBootstrap;
 import org.apache.cayenne.di.Injector;
 import org.apache.cayenne.di.Module;
 import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.project.extension.ProjectExtension;
 import org.apache.cayenne.project.unit.Project2Case;
 import org.apache.cayenne.resource.URLResource;
 import org.junit.Before;
@@ -41,6 +42,7 @@ import javax.xml.xpath.XPathFactory;
 import java.io.File;
 import java.net.URL;
 import java.util.Arrays;
+import java.util.Collections;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
@@ -59,7 +61,7 @@ public class FileProjectSaverTest extends Project2Case {
             }
         };
 
-        saver = new FileProjectSaver();
+        saver = new FileProjectSaver(Collections.<ProjectExtension>emptyList());
         Injector injector = DIBootstrap.createInjector(testModule);
         injector.injectMembers(saver);
     }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38553b16/cayenne-project/src/test/java/org/apache/cayenne/project/upgrade/DefaultUpgradeServiceTest.java
----------------------------------------------------------------------
diff --git a/cayenne-project/src/test/java/org/apache/cayenne/project/upgrade/DefaultUpgradeServiceTest.java b/cayenne-project/src/test/java/org/apache/cayenne/project/upgrade/DefaultUpgradeServiceTest.java
new file mode 100644
index 0000000..e3a0342
--- /dev/null
+++ b/cayenne-project/src/test/java/org/apache/cayenne/project/upgrade/DefaultUpgradeServiceTest.java
@@ -0,0 +1,163 @@
+/*****************************************************************
+ *   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.project.upgrade;
+
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+
+import org.apache.cayenne.project.upgrade.handlers.UpgradeHandler;
+import org.apache.cayenne.resource.Resource;
+import org.apache.cayenne.resource.URLResource;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentMatchers;
+import org.w3c.dom.Document;
+import org.xml.sax.InputSource;
+
+import static org.junit.Assert.*;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+
+/**
+ * @since 4.1
+ */
+public class DefaultUpgradeServiceTest {
+
+    DefaultUpgradeService upgradeService;
+
+    List<UpgradeHandler> handlers;
+
+    @Before
+    public void createService() {
+        createHandlers();
+        upgradeService = new DefaultUpgradeService(handlers);
+    }
+
+    @Test
+    public void getUpgradeType() throws Exception {
+        UpgradeMetaData metaData = upgradeService.getUpgradeType(getResourceForVersion("5"));
+        assertEquals(UpgradeType.INTERMEDIATE_UPGRADE_NEEDED, metaData.getUpgradeType());
+
+        metaData = upgradeService.getUpgradeType(getResourceForVersion("6"));
+        assertEquals(UpgradeType.UPGRADE_NEEDED, metaData.getUpgradeType());
+
+        metaData = upgradeService.getUpgradeType(getResourceForVersion("10"));
+        assertEquals(UpgradeType.UPGRADE_NOT_NEEDED, metaData.getUpgradeType());
+
+        metaData = upgradeService.getUpgradeType(getResourceForVersion("11"));
+        assertEquals(UpgradeType.DOWNGRADE_NEEDED, metaData.getUpgradeType());
+    }
+
+    @Test
+    public void getHandlersForVersion() throws Exception {
+
+        List<UpgradeHandler> handlers = upgradeService.getHandlersForVersion("6");
+        assertEquals(4, handlers.size());
+
+        handlers = upgradeService.getHandlersForVersion("9");
+        assertEquals(1, handlers.size());
+        assertEquals("10", handlers.get(0).getVersion());
+    }
+
+    @Test
+    public void getAdditionalDatamapResources() throws Exception {
+        URL url = getClass().getResource("../cayenne-PROJECT1.xml");
+        Resource resource = new URLResource(url);
+        Document document = readDocument(url);
+        UpgradeUnit unit = new UpgradeUnit(resource, document);
+        List<Resource> resources = upgradeService.getAdditionalDatamapResources(unit);
+
+        assertEquals(2, resources.size());
+        assertTrue(resources.get(0).getURL().sameFile(getClass().getResource("../testProjectMap1_1.map.xml")));
+    }
+
+    @Test
+    public void loadProjectVersion() throws Exception {
+        assertEquals("3.2.1.0", upgradeService.loadProjectVersion(getResourceForVersion("3.2.1.0")));
+        assertEquals("10", upgradeService.loadProjectVersion(getResourceForVersion("10")));
+    }
+
+    @Test
+    public void decodeVersion() throws Exception {
+        assertEquals(1.2340, DefaultUpgradeService.decodeVersion("1.2.3.4"), 0.000001);
+        assertEquals(1.0004, DefaultUpgradeService.decodeVersion("1.0.0.0.4"), 0.000001);
+        assertEquals(10, DefaultUpgradeService.decodeVersion("10"), 0.000001);
+    }
+
+    @Test
+    public void upgradeDOM() throws Exception {
+        Resource resource = new URLResource(getClass().getResource("../cayenne-PROJECT1.xml"));
+
+        // Mock service so it will use actual reading but skip actual saving part
+        upgradeService = mock(DefaultUpgradeService.class);
+        when(upgradeService.upgradeDOM(any(Resource.class), ArgumentMatchers.<UpgradeHandler>anyList()))
+                .thenCallRealMethod();
+        when(upgradeService.readDocument(any(Resource.class)))
+                .thenCallRealMethod();
+        when(upgradeService.getAdditionalDatamapResources(any(UpgradeUnit.class)))
+                .thenCallRealMethod();
+
+        upgradeService.upgradeDOM(resource, handlers);
+
+        // one for project and two for data maps
+        verify(upgradeService, times(3)).saveDocument(any(UpgradeUnit.class));
+        for(UpgradeHandler handler : handlers) {
+            verify(handler).processProjectDom(any(UpgradeUnit.class));
+            // two data maps
+            verify(handler, times(2)).processDataMapDom(any(UpgradeUnit.class));
+        }
+    }
+
+    @Test
+    public void readDocument() throws Exception {
+        Document document = upgradeService
+                .readDocument(new URLResource(getClass().getResource("../cayenne-PROJECT1.xml")));
+        assertEquals("10", document.getDocumentElement().getAttribute("project-version"));
+    }
+
+    private Document readDocument(URL url) throws Exception {
+        DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder();
+        return db.parse(new InputSource(new InputStreamReader(url.openStream())));
+    }
+
+    private void createHandlers() {
+        handlers = new ArrayList<>();
+        String[] versions = {"7", "8", "9", "10"};
+        for(String version : versions) {
+            handlers.add(createHandler(version));
+        }
+    }
+
+    private UpgradeHandler createHandler(String version) {
+        UpgradeHandler handler = mock(UpgradeHandler.class);
+        when(handler.getVersion()).thenReturn(version);
+        return handler;
+    }
+
+    private Resource getResourceForVersion(String version) {
+        return new URLResource(getClass().getResource("handlers/cayenne-project-v"+version+".xml"));
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38553b16/cayenne-project/src/test/java/org/apache/cayenne/project/upgrade/handlers/BaseUpgradeHandlerTest.java
----------------------------------------------------------------------
diff --git a/cayenne-project/src/test/java/org/apache/cayenne/project/upgrade/handlers/BaseUpgradeHandlerTest.java b/cayenne-project/src/test/java/org/apache/cayenne/project/upgrade/handlers/BaseUpgradeHandlerTest.java
new file mode 100644
index 0000000..7595a88
--- /dev/null
+++ b/cayenne-project/src/test/java/org/apache/cayenne/project/upgrade/handlers/BaseUpgradeHandlerTest.java
@@ -0,0 +1,71 @@
+/*****************************************************************
+ *   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.project.upgrade.handlers;
+
+import java.io.InputStreamReader;
+import java.io.StringReader;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+
+import org.apache.cayenne.project.upgrade.UpgradeUnit;
+import org.apache.cayenne.resource.URLResource;
+import org.junit.Before;
+import org.w3c.dom.Document;
+import org.xml.sax.InputSource;
+
+/**
+ * @since 4.1
+ */
+abstract class BaseUpgradeHandlerTest {
+
+    UpgradeHandler handler;
+
+    @Before
+    public void createHandler() {
+        handler = newHandler();
+    }
+
+    abstract UpgradeHandler newHandler();
+
+    Document processProjectDom(String xmlResourceName) throws Exception {
+        UpgradeUnit unit = new UpgradeUnit(new URLResource(getClass().getResource(xmlResourceName)),
+                documentFromResource(xmlResourceName));
+        handler.processProjectDom(unit);
+        return unit.getDocument();
+    }
+
+    Document processDataMapDom(String xmlResourceName) throws Exception {
+        UpgradeUnit unit = new UpgradeUnit(new URLResource(getClass().getResource(xmlResourceName)),
+                documentFromResource(xmlResourceName));
+        handler.processDataMapDom(unit);
+        return unit.getDocument();
+    }
+
+    Document documentFromString(String xml) throws Exception {
+        DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder();
+        return db.parse(new InputSource(new StringReader(xml)));
+    }
+
+    Document documentFromResource(String resource) throws Exception {
+        DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder();
+        return db.parse(new InputSource(new InputStreamReader(getClass().getResourceAsStream(resource))));
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38553b16/cayenne-project/src/test/java/org/apache/cayenne/project/upgrade/handlers/UpgradeHandler_V10Test.java
----------------------------------------------------------------------
diff --git a/cayenne-project/src/test/java/org/apache/cayenne/project/upgrade/handlers/UpgradeHandler_V10Test.java b/cayenne-project/src/test/java/org/apache/cayenne/project/upgrade/handlers/UpgradeHandler_V10Test.java
new file mode 100644
index 0000000..5413a92
--- /dev/null
+++ b/cayenne-project/src/test/java/org/apache/cayenne/project/upgrade/handlers/UpgradeHandler_V10Test.java
@@ -0,0 +1,66 @@
+/*****************************************************************
+ *   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.project.upgrade.handlers;
+
+import org.apache.cayenne.configuration.DataChannelDescriptor;
+import org.junit.Test;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+/**
+ * @since 4.1
+ */
+public class UpgradeHandler_V10Test extends BaseUpgradeHandlerTest{
+
+    UpgradeHandler newHandler() {
+        return new UpgradeHandler_V10();
+    }
+
+    @Test
+    public void testProjectDomUpgrade() throws Exception {
+        Document document = processProjectDom("cayenne-project-v9.xml");
+
+        Element root = document.getDocumentElement();
+        assertEquals("10", root.getAttribute("project-version"));
+        assertEquals("http://cayenne.apache.org/schema/10/domain", root.getAttribute("xmlns"));
+        assertEquals(2, root.getElementsByTagName("map").getLength());
+    }
+
+    @Test
+    public void testDataMapDomUpgrade() throws Exception {
+        Document document = processDataMapDom("test-map-v9.map.xml");
+
+        Element root = document.getDocumentElement();
+        assertEquals("10", root.getAttribute("project-version"));
+        assertEquals("http://cayenne.apache.org/schema/10/modelMap", root.getAttribute("xmlns"));
+        assertEquals(2, root.getElementsByTagName("db-attribute").getLength());
+    }
+
+    @Test
+    public void testModelUpgrade() throws Exception {
+        DataChannelDescriptor descriptor = mock(DataChannelDescriptor.class);
+        handler.processModel(descriptor);
+        verifyZeroInteractions(descriptor);
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38553b16/cayenne-project/src/test/java/org/apache/cayenne/project/upgrade/handlers/UpgradeHandler_V7Test.java
----------------------------------------------------------------------
diff --git a/cayenne-project/src/test/java/org/apache/cayenne/project/upgrade/handlers/UpgradeHandler_V7Test.java b/cayenne-project/src/test/java/org/apache/cayenne/project/upgrade/handlers/UpgradeHandler_V7Test.java
new file mode 100644
index 0000000..5464650
--- /dev/null
+++ b/cayenne-project/src/test/java/org/apache/cayenne/project/upgrade/handlers/UpgradeHandler_V7Test.java
@@ -0,0 +1,94 @@
+/*****************************************************************
+ *   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.project.upgrade.handlers;
+
+import java.util.Collections;
+
+import org.apache.cayenne.configuration.DataChannelDescriptor;
+import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.map.ObjAttribute;
+import org.apache.cayenne.map.ObjEntity;
+import org.junit.Test;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+/**
+ * @since 4.1
+ */
+public class UpgradeHandler_V7Test extends BaseUpgradeHandlerTest {
+
+    @Override
+    UpgradeHandler newHandler() {
+        return new UpgradeHandler_V7();
+    }
+
+    @Test
+    public void testProjectDomUpgrade() throws Exception {
+        Document document = processProjectDom("cayenne-project-v6.xml");
+
+        Element root = document.getDocumentElement();
+        assertEquals("7", root.getAttribute("project-version"));
+        assertEquals("", root.getAttribute("xmlns"));
+        assertEquals(2, root.getElementsByTagName("map").getLength());
+        assertEquals(0, root.getElementsByTagName("property").getLength());
+    }
+
+    @Test
+    public void testDataMapDomUpgrade() throws Exception {
+        Document document = processDataMapDom("test-map-v6.map.xml");
+
+        Element root = document.getDocumentElement();
+        assertEquals("7", root.getAttribute("project-version"));
+        assertEquals("http://cayenne.apache.org/schema/7/modelMap", root.getAttribute("xmlns"));
+        assertEquals(2, root.getElementsByTagName("db-attribute").getLength());
+    }
+
+    @Test
+    public void testModelUpgrade() throws Exception {
+        DataChannelDescriptor descriptor = mock(DataChannelDescriptor.class);
+        DataMap map = new DataMap();
+        when(descriptor.getDataMaps()).thenReturn(Collections.singletonList(map));
+
+        ObjEntity superEntity = new ObjEntity("super");
+        superEntity.addAttribute(new ObjAttribute("super"));
+        superEntity.addAttribute(new ObjAttribute("simple"));
+        map.addObjEntity(superEntity);
+
+        ObjEntity subEntity = new ObjEntity("sub");
+        subEntity.setSuperEntityName("super");
+        subEntity.addAttribute(new ObjAttribute("super"));
+        subEntity.addAttribute(new ObjAttribute("simple_sub"));
+        map.addObjEntity(subEntity);
+
+        assertNotNull(superEntity.getDeclaredAttribute("super"));
+        assertNotNull(subEntity.getDeclaredAttribute("super"));
+
+        handler.processModel(descriptor);
+
+        assertNotNull(superEntity.getDeclaredAttribute("super"));
+        assertNull(subEntity.getDeclaredAttribute("super"));
+
+        verify(descriptor).getDataMaps();
+        verifyNoMoreInteractions(descriptor);
+    }
+}
\ No newline at end of file