cayenne-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ntimof...@apache.org
Subject [12/16] cayenne git commit: CAY-2335: New XML loading/saving mechanics with support of plugable handlers - ProjectExtension - new upgrade handlers
Date Tue, 25 Jul 2017 12:25:27 GMT
CAY-2335: New XML loading/saving mechanics with support of plugable handlers
  - ProjectExtension
  - new upgrade handlers


Project: http://git-wip-us.apache.org/repos/asf/cayenne/repo
Commit: http://git-wip-us.apache.org/repos/asf/cayenne/commit/38553b16
Tree: http://git-wip-us.apache.org/repos/asf/cayenne/tree/38553b16
Diff: http://git-wip-us.apache.org/repos/asf/cayenne/diff/38553b16

Branch: refs/heads/master
Commit: 38553b1603b9622a6eae691131a4106591649556
Parents: c58b6f4
Author: Nikita Timofeev <stariy95@gmail.com>
Authored: Tue Jul 25 12:39:56 2017 +0300
Committer: Nikita Timofeev <stariy95@gmail.com>
Committed: Tue Jul 25 12:39:56 2017 +0300

----------------------------------------------------------------------
 .../cayenne/project/CompoundSaverDelegate.java  | 183 +++++++
 .../cayenne/project/ConfigurationSaver.java     |  12 +-
 .../cayenne/project/FileProjectSaver.java       |  25 +-
 .../org/apache/cayenne/project/Project.java     |  10 +-
 .../apache/cayenne/project/ProjectModule.java   |  43 +-
 .../cayenne/project/SaveableNodesGetter.java    |  11 +-
 .../project/extension/BaseSaverDelegate.java    | 133 +++++
 .../extension/ExtensionAwareHandlerFactory.java |  69 +++
 .../project/extension/LoaderDelegate.java       |  45 ++
 .../project/extension/ProjectExtension.java     |  49 ++
 .../project/extension/SaverDelegate.java        |  45 ++
 .../project/upgrade/BaseUpgradeHandler.java     | 218 --------
 .../upgrade/DataSourceInfoLoader_3_0_0_1.java   | 228 ---------
 .../project/upgrade/DefaultUpgradeService.java  | 318 ++++++++++++
 .../project/upgrade/ProjectUpgrader.java        |  34 --
 .../cayenne/project/upgrade/UpgradeHandler.java |  57 ---
 .../cayenne/project/upgrade/UpgradeService.java |  33 ++
 .../cayenne/project/upgrade/UpgradeUnit.java    |  54 ++
 .../upgrade/handlers/UpgradeHandler.java        |  58 +++
 .../upgrade/handlers/UpgradeHandler_V10.java    |  68 +++
 .../upgrade/handlers/UpgradeHandler_V7.java     | 109 ++++
 .../upgrade/handlers/UpgradeHandler_V8.java     |  99 ++++
 .../upgrade/handlers/UpgradeHandler_V9.java     |  81 +++
 .../project/upgrade/v6/ProjectUpgrader_V6.java  |  43 --
 .../project/upgrade/v6/UpgradeHandler_V6.java   | 118 -----
 ...XMLDataChannelDescriptorLoader_V3_0_0_1.java | 299 -----------
 .../v6/XMLDataSourceInfoLoader_V3_0_0_1.java    | 300 -----------
 .../project/upgrade/v7/UpgradeHandler_V7.java   | 137 -----
 .../project/upgrade/v8/UpgradeHandler_V8.java   | 174 -------
 .../project/upgrade/v9/UpgradeHandler_V9.java   | 145 ------
 .../project/DataChannelProjectLoaderTest.java   |  19 +-
 .../project/DataChannelProjectSaverTest.java    |  30 +-
 .../cayenne/project/FileProjectSaverTest.java   |   4 +-
 .../upgrade/DefaultUpgradeServiceTest.java      | 163 ++++++
 .../handlers/BaseUpgradeHandlerTest.java        |  71 +++
 .../handlers/UpgradeHandler_V10Test.java        |  66 +++
 .../upgrade/handlers/UpgradeHandler_V7Test.java |  94 ++++
 .../upgrade/handlers/UpgradeHandler_V8Test.java |  97 ++++
 .../upgrade/handlers/UpgradeHandler_V9Test.java |  69 +++
 .../upgrade/v7/ProjectUpgrader_V7Test.java      | 499 -------------------
 .../upgrade/v8/ProjectUpgrader_V8Test.java      | 197 --------
 .../upgrade/v9/ProjectUpgrader_V9Test.java      | 249 ---------
 .../apache/cayenne/project/cayenne-PROJECT1.xml |  14 +-
 .../apache/cayenne/project/cayenne-PROJECT2.xml |  16 +-
 .../cayenne/project/testProjectMap1_1.map.xml   |  10 +-
 .../cayenne/project/testProjectMap1_2.map.xml   |  10 +-
 .../cayenne/project/testProjectMap2_1.map.xml   |  10 +-
 .../cayenne/project/testProjectMap2_2.map.xml   |  10 +-
 .../upgrade/handlers/cayenne-project-v10.xml    |   3 +
 .../upgrade/handlers/cayenne-project-v11.xml    |   3 +
 .../handlers/cayenne-project-v3.2.1.0.xml       |   3 +
 .../upgrade/handlers/cayenne-project-v5.xml     |   3 +
 .../upgrade/handlers/cayenne-project-v6.xml     |  17 +
 .../upgrade/handlers/cayenne-project-v7.xml     |  15 +
 .../upgrade/handlers/cayenne-project-v8.xml     |  15 +
 .../upgrade/handlers/cayenne-project-v9.xml     |  15 +
 .../upgrade/handlers/test-map-v6.map.xml        |  21 +
 .../upgrade/handlers/test-map-v7.map.xml        |  31 ++
 .../upgrade/handlers/test-map-v8.map.xml        |  13 +
 .../upgrade/handlers/test-map-v9.map.xml        |  12 +
 60 files changed, 2194 insertions(+), 2783 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/38553b16/cayenne-project/src/main/java/org/apache/cayenne/project/CompoundSaverDelegate.java
----------------------------------------------------------------------
diff --git a/cayenne-project/src/main/java/org/apache/cayenne/project/CompoundSaverDelegate.java b/cayenne-project/src/main/java/org/apache/cayenne/project/CompoundSaverDelegate.java
new file mode 100644
index 0000000..5ea7a29
--- /dev/null
+++ b/cayenne-project/src/main/java/org/apache/cayenne/project/CompoundSaverDelegate.java
@@ -0,0 +1,183 @@
+/*****************************************************************
+ *   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;
+
+import java.util.Collection;
+
+import org.apache.cayenne.configuration.DataChannelDescriptor;
+import org.apache.cayenne.configuration.DataNodeDescriptor;
+import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.DbRelationship;
+import org.apache.cayenne.map.Embeddable;
+import org.apache.cayenne.map.EmbeddableAttribute;
+import org.apache.cayenne.map.ObjAttribute;
+import org.apache.cayenne.map.ObjEntity;
+import org.apache.cayenne.map.ObjRelationship;
+import org.apache.cayenne.map.Procedure;
+import org.apache.cayenne.map.ProcedureParameter;
+import org.apache.cayenne.map.QueryDescriptor;
+import org.apache.cayenne.project.extension.SaverDelegate;
+import org.apache.cayenne.util.XMLEncoder;
+
+/**
+ * @since 4.1
+ */
+class CompoundSaverDelegate implements SaverDelegate {
+
+    Collection<SaverDelegate> delegates;
+
+    CompoundSaverDelegate(Collection<SaverDelegate> delegates) {
+        this.delegates = delegates;
+        setParentDelegate(this);
+    }
+
+    @Override
+    public Void visitDataChannelDescriptor(DataChannelDescriptor channelDescriptor) {
+        for(SaverDelegate delegate : delegates) {
+            delegate.visitDataChannelDescriptor(channelDescriptor);
+        }
+        return null;
+    }
+
+    @Override
+    public Void visitDataNodeDescriptor(DataNodeDescriptor nodeDescriptor) {
+        for(SaverDelegate delegate : delegates) {
+            delegate.visitDataNodeDescriptor(nodeDescriptor);
+        }
+        return null;
+    }
+
+    @Override
+    public Void visitDataMap(DataMap dataMap) {
+        for(SaverDelegate delegate : delegates) {
+            delegate.visitDataMap(dataMap);
+        }
+        return null;
+    }
+
+    @Override
+    public Void visitObjEntity(ObjEntity entity) {
+        for(SaverDelegate delegate : delegates) {
+            delegate.visitObjEntity(entity);
+        }
+        return null;
+    }
+
+    @Override
+    public Void visitDbEntity(DbEntity entity) {
+        for(SaverDelegate delegate : delegates) {
+            delegate.visitDbEntity(entity);
+        }
+        return null;
+    }
+
+    @Override
+    public Void visitEmbeddable(Embeddable embeddable) {
+        for(SaverDelegate delegate : delegates) {
+            delegate.visitEmbeddable(embeddable);
+        }
+        return null;
+    }
+
+    @Override
+    public Void visitEmbeddableAttribute(EmbeddableAttribute attribute) {
+        for(SaverDelegate delegate : delegates) {
+            delegate.visitEmbeddableAttribute(attribute);
+        }
+        return null;
+    }
+
+    @Override
+    public Void visitObjAttribute(ObjAttribute attribute) {
+        for(SaverDelegate delegate : delegates) {
+            delegate.visitObjAttribute(attribute);
+        }
+        return null;
+    }
+
+    @Override
+    public Void visitDbAttribute(DbAttribute attribute) {
+        for(SaverDelegate delegate : delegates) {
+            delegate.visitDbAttribute(attribute);
+        }
+        return null;
+    }
+
+    @Override
+    public Void visitObjRelationship(ObjRelationship relationship) {
+        for(SaverDelegate delegate : delegates) {
+            delegate.visitObjRelationship(relationship);
+        }
+        return null;
+    }
+
+    @Override
+    public Void visitDbRelationship(DbRelationship relationship) {
+        for(SaverDelegate delegate : delegates) {
+            delegate.visitDbRelationship(relationship);
+        }
+        return null;
+    }
+
+    @Override
+    public Void visitProcedure(Procedure procedure) {
+        for(SaverDelegate delegate : delegates) {
+            delegate.visitProcedure(procedure);
+        }
+        return null;
+    }
+
+    @Override
+    public Void visitProcedureParameter(ProcedureParameter parameter) {
+        for(SaverDelegate delegate : delegates) {
+            delegate.visitProcedureParameter(parameter);
+        }
+        return null;
+    }
+
+    @Override
+    public Void visitQuery(QueryDescriptor query) {
+        for(SaverDelegate delegate : delegates) {
+            delegate.visitQuery(query);
+        }
+        return null;
+    }
+
+    @Override
+    public void setXMLEncoder(XMLEncoder encoder) {
+        for(SaverDelegate delegate : delegates) {
+            delegate.setXMLEncoder(encoder);
+        }
+    }
+
+    @Override
+    public void setParentDelegate(SaverDelegate parentDelegate) {
+        for(SaverDelegate delegate : delegates) {
+            delegate.setParentDelegate(parentDelegate);
+        }
+    }
+
+    @Override
+    public SaverDelegate getParentDelegate() {
+        return null;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38553b16/cayenne-project/src/main/java/org/apache/cayenne/project/ConfigurationSaver.java
----------------------------------------------------------------------
diff --git a/cayenne-project/src/main/java/org/apache/cayenne/project/ConfigurationSaver.java b/cayenne-project/src/main/java/org/apache/cayenne/project/ConfigurationSaver.java
index 9ed8a88..1aab516 100644
--- a/cayenne-project/src/main/java/org/apache/cayenne/project/ConfigurationSaver.java
+++ b/cayenne-project/src/main/java/org/apache/cayenne/project/ConfigurationSaver.java
@@ -19,10 +19,12 @@
 package org.apache.cayenne.project;
 
 import java.io.PrintWriter;
+import java.util.Collection;
 
 import org.apache.cayenne.configuration.BaseConfigurationNodeVisitor;
 import org.apache.cayenne.configuration.DataChannelDescriptor;
 import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.project.extension.SaverDelegate;
 import org.apache.cayenne.util.XMLEncoder;
 
 /**
@@ -32,17 +34,20 @@ class ConfigurationSaver extends BaseConfigurationNodeVisitor<Void> {
 
     private PrintWriter printWriter;
     private String version;
+    private SaverDelegate delegate;
 
-    ConfigurationSaver(PrintWriter printWriter, String version) {
+    ConfigurationSaver(PrintWriter printWriter, String version, SaverDelegate delegate) {
         this.printWriter = printWriter;
         this.version = version;
+        this.delegate = delegate;
     }
 
     @Override
     public Void visitDataChannelDescriptor(DataChannelDescriptor node) {
         XMLEncoder encoder = new XMLEncoder(printWriter, "\t", version);
         printXMLHeader(encoder);
-        node.encodeAsXML(encoder);
+        delegate.setXMLEncoder(encoder);
+        node.encodeAsXML(encoder, delegate);
         return null;
     }
 
@@ -50,7 +55,8 @@ class ConfigurationSaver extends BaseConfigurationNodeVisitor<Void> {
     public Void visitDataMap(DataMap node) {
         XMLEncoder encoder = new XMLEncoder(printWriter, "\t", version);
         printXMLHeader(encoder);
-        node.encodeAsXML(encoder);
+        delegate.setXMLEncoder(encoder);
+        node.encodeAsXML(encoder, delegate);
         return null;
     }
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38553b16/cayenne-project/src/main/java/org/apache/cayenne/project/FileProjectSaver.java
----------------------------------------------------------------------
diff --git a/cayenne-project/src/main/java/org/apache/cayenne/project/FileProjectSaver.java b/cayenne-project/src/main/java/org/apache/cayenne/project/FileProjectSaver.java
index c37d763..fe01162 100644
--- a/cayenne-project/src/main/java/org/apache/cayenne/project/FileProjectSaver.java
+++ b/cayenne-project/src/main/java/org/apache/cayenne/project/FileProjectSaver.java
@@ -23,6 +23,8 @@ import org.apache.cayenne.configuration.ConfigurationNameMapper;
 import org.apache.cayenne.configuration.ConfigurationNode;
 import org.apache.cayenne.configuration.ConfigurationNodeVisitor;
 import org.apache.cayenne.di.Inject;
+import org.apache.cayenne.project.extension.ProjectExtension;
+import org.apache.cayenne.project.extension.SaverDelegate;
 import org.apache.cayenne.resource.Resource;
 import org.apache.cayenne.resource.URLResource;
 import org.apache.cayenne.util.Util;
@@ -38,6 +40,7 @@ import java.net.MalformedURLException;
 import java.net.URL;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.List;
 
 /**
  * A ProjectSaver saving project configuration to the file system.
@@ -53,16 +56,24 @@ public class FileProjectSaver implements ProjectSaver {
 	protected ConfigurationNodeVisitor<Collection<ConfigurationNode>> saveableNodesGetter;
 	protected String fileEncoding;
 
-	public FileProjectSaver() {
+	protected Collection<SaverDelegate> saverDelegates;
+
+	public FileProjectSaver(@Inject List<ProjectExtension> extensions) {
 		resourceGetter = new ConfigurationSourceGetter();
 		saveableNodesGetter = new SaveableNodesGetter();
 
 		// this is not configurable yet... probably doesn't have to be
 		fileEncoding = "UTF-8";
+
+		saverDelegates = new ArrayList<>(extensions.size());
+		for(ProjectExtension extension : extensions) {
+			SaverDelegate delegate = extension.createSaverDelegate();
+			saverDelegates.add(delegate);
+		}
 	}
 
 	public String getSupportedVersion() {
-		return "9";
+		return String.valueOf(Project.VERSION);
 	}
 
 	public void save(Project project) {
@@ -169,7 +180,7 @@ public class FileProjectSaver implements ProjectSaver {
 		for (SaveUnit unit : units) {
 
 			String name = unit.targetFile.getName();
-			if (name == null || name.length() < 3) {
+			if (name.length() < 3) {
 				name = "cayenne-project";
 			}
 
@@ -185,8 +196,8 @@ public class FileProjectSaver implements ProjectSaver {
 				unit.targetTempFile.delete();
 			}
 
-			try (PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(new FileOutputStream(
-					unit.targetTempFile), fileEncoding));) {
+			try (PrintWriter printWriter = new PrintWriter(new OutputStreamWriter(
+					new FileOutputStream(unit.targetTempFile), fileEncoding))) {
 				saveToTempFile(unit, printWriter);
 			} catch (UnsupportedEncodingException e) {
 				throw new CayenneRuntimeException("Unsupported encoding '%s' (%s)", e, fileEncoding, e.getMessage());
@@ -198,7 +209,9 @@ public class FileProjectSaver implements ProjectSaver {
 	}
 
 	void saveToTempFile(SaveUnit unit, PrintWriter printWriter) {
-		unit.node.acceptVisitor(new ConfigurationSaver(printWriter, getSupportedVersion()));
+		unit.node.acceptVisitor(
+				new ConfigurationSaver(printWriter, getSupportedVersion(), new CompoundSaverDelegate(saverDelegates))
+		);
 	}
 
 	void saveCommit(Collection<SaveUnit> units) {

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38553b16/cayenne-project/src/main/java/org/apache/cayenne/project/Project.java
----------------------------------------------------------------------
diff --git a/cayenne-project/src/main/java/org/apache/cayenne/project/Project.java b/cayenne-project/src/main/java/org/apache/cayenne/project/Project.java
index ad0458b..a1ebd70 100644
--- a/cayenne-project/src/main/java/org/apache/cayenne/project/Project.java
+++ b/cayenne-project/src/main/java/org/apache/cayenne/project/Project.java
@@ -36,6 +36,14 @@ import org.apache.cayenne.resource.Resource;
  */
 public class Project {
 
+	/**
+	 * Current version of Cayenne project.
+	 * Used by different parsers and savers of project's XML files.
+	 *
+	 * @since 4.1
+	 */
+	static public final int VERSION = 10;
+
 	protected boolean modified;
 
 	protected ConfigurationTree<?> configurationTree;
@@ -45,7 +53,7 @@ public class Project {
 	public Project(ConfigurationTree<?> configurationTree) {
 		this.configurationTree = configurationTree;
 		this.configurationSourceGetter = new ConfigurationSourceGetter();
-		this.unusedResources = new HashSet<URL>();
+		this.unusedResources = new HashSet<>();
 	}
 
 	public ConfigurationTree<?> getConfigurationTree() {

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38553b16/cayenne-project/src/main/java/org/apache/cayenne/project/ProjectModule.java
----------------------------------------------------------------------
diff --git a/cayenne-project/src/main/java/org/apache/cayenne/project/ProjectModule.java b/cayenne-project/src/main/java/org/apache/cayenne/project/ProjectModule.java
index a1eeb98..8f5d011 100644
--- a/cayenne-project/src/main/java/org/apache/cayenne/project/ProjectModule.java
+++ b/cayenne-project/src/main/java/org/apache/cayenne/project/ProjectModule.java
@@ -19,26 +19,55 @@
 package org.apache.cayenne.project;
 
 import org.apache.cayenne.di.Binder;
+import org.apache.cayenne.di.ListBuilder;
 import org.apache.cayenne.di.Module;
-import org.apache.cayenne.project.upgrade.ProjectUpgrader;
-import org.apache.cayenne.project.upgrade.v9.ProjectUpgrader_V9;
+import org.apache.cayenne.project.extension.ProjectExtension;
+import org.apache.cayenne.project.upgrade.DefaultUpgradeService;
+import org.apache.cayenne.project.upgrade.UpgradeService;
+import org.apache.cayenne.project.upgrade.handlers.UpgradeHandler;
+import org.apache.cayenne.project.upgrade.handlers.UpgradeHandler_V10;
+import org.apache.cayenne.project.upgrade.handlers.UpgradeHandler_V7;
+import org.apache.cayenne.project.upgrade.handlers.UpgradeHandler_V8;
+import org.apache.cayenne.project.upgrade.handlers.UpgradeHandler_V9;
 import org.apache.cayenne.project.validation.DefaultProjectValidator;
 import org.apache.cayenne.project.validation.ProjectValidator;
 
 /**
- * A dependency injection (DI) module contributing configuration related to Cayenne mapping project manipulation to a
- * DI container.
+ * A dependency injection (DI) module contributing configuration related
+ * to Cayenne mapping project manipulation to a DI container.
  *
  * @since 4.0
  */
 public class ProjectModule implements Module {
 
+    /**
+     * @since 4.1
+     */
+    public static ListBuilder<ProjectExtension> contributeExtension(Binder binder) {
+        return binder.bindList(ProjectExtension.class);
+    }
+
+    /**
+     * @since 4.1
+     */
+    public static ListBuilder<UpgradeHandler> contributeUpgradeHandler(Binder binder) {
+        return binder.bindList(UpgradeHandler.class);
+    }
+
     public void configure(Binder binder) {
         binder.bind(ProjectLoader.class).to(DataChannelProjectLoader.class);
         binder.bind(ProjectSaver.class).to(FileProjectSaver.class);
-        binder.bind(ProjectUpgrader.class).to(ProjectUpgrader_V9.class);
         binder.bind(ProjectValidator.class).to(DefaultProjectValidator.class);
-        binder.bind(ConfigurationNodeParentGetter.class).to(
-                DefaultConfigurationNodeParentGetter.class);
+        binder.bind(ConfigurationNodeParentGetter.class).to(DefaultConfigurationNodeParentGetter.class);
+
+        binder.bind(UpgradeService.class).to(DefaultUpgradeService.class);
+        // Note: order is important
+        contributeUpgradeHandler(binder)
+                .add(UpgradeHandler_V7.class)
+                .add(UpgradeHandler_V8.class)
+                .add(UpgradeHandler_V9.class)
+                .add(UpgradeHandler_V10.class);
+
+        contributeExtension(binder);
     }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38553b16/cayenne-project/src/main/java/org/apache/cayenne/project/SaveableNodesGetter.java
----------------------------------------------------------------------
diff --git a/cayenne-project/src/main/java/org/apache/cayenne/project/SaveableNodesGetter.java b/cayenne-project/src/main/java/org/apache/cayenne/project/SaveableNodesGetter.java
index ca74cc2..8da76e6 100644
--- a/cayenne-project/src/main/java/org/apache/cayenne/project/SaveableNodesGetter.java
+++ b/cayenne-project/src/main/java/org/apache/cayenne/project/SaveableNodesGetter.java
@@ -30,19 +30,14 @@ import java.util.Collections;
 /**
  * @since 3.1
  */
-class SaveableNodesGetter extends
-        BaseConfigurationNodeVisitor<Collection<ConfigurationNode>> {
+class SaveableNodesGetter extends BaseConfigurationNodeVisitor<Collection<ConfigurationNode>> {
 
     @Override
-    public Collection<ConfigurationNode> visitDataChannelDescriptor(
-            DataChannelDescriptor descriptor) {
+    public Collection<ConfigurationNode> visitDataChannelDescriptor(DataChannelDescriptor descriptor) {
 
         Collection<ConfigurationNode> nodes = new ArrayList<>();
         nodes.add(descriptor);
-
-        for (DataMap map : descriptor.getDataMaps()) {
-            nodes.add(map);
-        }
+        nodes.addAll(descriptor.getDataMaps());
 
         return nodes;
     }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38553b16/cayenne-project/src/main/java/org/apache/cayenne/project/extension/BaseSaverDelegate.java
----------------------------------------------------------------------
diff --git a/cayenne-project/src/main/java/org/apache/cayenne/project/extension/BaseSaverDelegate.java b/cayenne-project/src/main/java/org/apache/cayenne/project/extension/BaseSaverDelegate.java
new file mode 100644
index 0000000..08565b2
--- /dev/null
+++ b/cayenne-project/src/main/java/org/apache/cayenne/project/extension/BaseSaverDelegate.java
@@ -0,0 +1,133 @@
+/*****************************************************************
+ *   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.extension;
+
+import org.apache.cayenne.configuration.DataChannelDescriptor;
+import org.apache.cayenne.configuration.DataNodeDescriptor;
+import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.map.DbAttribute;
+import org.apache.cayenne.map.DbEntity;
+import org.apache.cayenne.map.DbRelationship;
+import org.apache.cayenne.map.Embeddable;
+import org.apache.cayenne.map.EmbeddableAttribute;
+import org.apache.cayenne.map.ObjAttribute;
+import org.apache.cayenne.map.ObjEntity;
+import org.apache.cayenne.map.ObjRelationship;
+import org.apache.cayenne.map.Procedure;
+import org.apache.cayenne.map.ProcedureParameter;
+import org.apache.cayenne.map.QueryDescriptor;
+import org.apache.cayenne.util.XMLEncoder;
+
+/**
+ * Base saver delegate that handles common setters/getters, as well as provides empty stub for all methods.
+ *
+ * @since 4.1
+ */
+public class BaseSaverDelegate implements SaverDelegate {
+
+    protected XMLEncoder encoder;
+
+    protected SaverDelegate parentDelegate;
+
+    @Override
+    public Void visitDataChannelDescriptor(DataChannelDescriptor channelDescriptor) {
+        return null;
+    }
+
+    @Override
+    public Void visitDataNodeDescriptor(DataNodeDescriptor nodeDescriptor) {
+        return null;
+    }
+
+    @Override
+    public Void visitDataMap(DataMap dataMap) {
+        return null;
+    }
+
+    @Override
+    public Void visitObjEntity(ObjEntity entity) {
+        return null;
+    }
+
+    @Override
+    public Void visitDbEntity(DbEntity entity) {
+        return null;
+    }
+
+    @Override
+    public Void visitEmbeddable(Embeddable embeddable) {
+        return null;
+    }
+
+    @Override
+    public Void visitEmbeddableAttribute(EmbeddableAttribute attribute) {
+        return null;
+    }
+
+    @Override
+    public Void visitObjAttribute(ObjAttribute attribute) {
+        return null;
+    }
+
+    @Override
+    public Void visitDbAttribute(DbAttribute attribute) {
+        return null;
+    }
+
+    @Override
+    public Void visitObjRelationship(ObjRelationship relationship) {
+        return null;
+    }
+
+    @Override
+    public Void visitDbRelationship(DbRelationship relationship) {
+        return null;
+    }
+
+    @Override
+    public Void visitProcedure(Procedure procedure) {
+        return null;
+    }
+
+    @Override
+    public Void visitProcedureParameter(ProcedureParameter parameter) {
+        return null;
+    }
+
+    @Override
+    public Void visitQuery(QueryDescriptor query) {
+        return null;
+    }
+
+    @Override
+    public void setXMLEncoder(XMLEncoder encoder) {
+        this.encoder = encoder;
+    }
+
+    @Override
+    public void setParentDelegate(SaverDelegate parentDelegate) {
+        this.parentDelegate = parentDelegate;
+    }
+
+    @Override
+    public SaverDelegate getParentDelegate() {
+        return parentDelegate;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38553b16/cayenne-project/src/main/java/org/apache/cayenne/project/extension/ExtensionAwareHandlerFactory.java
----------------------------------------------------------------------
diff --git a/cayenne-project/src/main/java/org/apache/cayenne/project/extension/ExtensionAwareHandlerFactory.java b/cayenne-project/src/main/java/org/apache/cayenne/project/extension/ExtensionAwareHandlerFactory.java
new file mode 100644
index 0000000..fc7e35d
--- /dev/null
+++ b/cayenne-project/src/main/java/org/apache/cayenne/project/extension/ExtensionAwareHandlerFactory.java
@@ -0,0 +1,69 @@
+/*****************************************************************
+ *   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.extension;
+
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.cayenne.CayenneRuntimeException;
+import org.apache.cayenne.configuration.xml.DefaultHandlerFactory;
+import org.apache.cayenne.configuration.xml.NamespaceAwareNestedTagHandler;
+import org.apache.cayenne.di.Inject;
+
+/**
+ * Project parser handlers factory that will use third-party extensions
+ * to provide handlers for unknown tags.
+ *
+ * @see ProjectExtension
+ * @see DefaultHandlerFactory
+ *
+ * @since 4.1
+ */
+public class ExtensionAwareHandlerFactory extends DefaultHandlerFactory {
+
+    Map<String, LoaderDelegate> loaderDelegates = new ConcurrentHashMap<>();
+
+    public ExtensionAwareHandlerFactory(@Inject List<ProjectExtension> extensions) {
+        for(ProjectExtension extension : extensions) {
+            LoaderDelegate delegate = extension.createLoaderDelegate();
+            LoaderDelegate old = loaderDelegates.put(delegate.getTargetNamespace(), delegate);
+            if(old != null) {
+                throw new CayenneRuntimeException("Found two loader delegates for namespace %s",
+                        delegate.getTargetNamespace());
+            }
+        }
+    }
+
+    @Override
+    public NamespaceAwareNestedTagHandler createHandler(String namespace, String localName,
+                                                        NamespaceAwareNestedTagHandler parent) {
+
+        LoaderDelegate delegate = loaderDelegates.get(namespace);
+        if(delegate != null) {
+            NamespaceAwareNestedTagHandler handler = delegate.createHandler(parent, localName);
+            if(handler != null) {
+                return handler;
+            }
+        }
+
+        return super.createHandler(namespace, localName, parent);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38553b16/cayenne-project/src/main/java/org/apache/cayenne/project/extension/LoaderDelegate.java
----------------------------------------------------------------------
diff --git a/cayenne-project/src/main/java/org/apache/cayenne/project/extension/LoaderDelegate.java b/cayenne-project/src/main/java/org/apache/cayenne/project/extension/LoaderDelegate.java
new file mode 100644
index 0000000..938e7c0
--- /dev/null
+++ b/cayenne-project/src/main/java/org/apache/cayenne/project/extension/LoaderDelegate.java
@@ -0,0 +1,45 @@
+/*****************************************************************
+ *   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.extension;
+
+import org.apache.cayenne.configuration.xml.NamespaceAwareNestedTagHandler;
+
+/**
+ * Delegate that handles loading process for extension specific parts of XML document.
+ *
+ * @since 4.1
+ */
+public interface LoaderDelegate {
+
+    /**
+     * @return target namespace that this extension is using
+     */
+    String getTargetNamespace();
+
+    /**
+     * Create handler that will handle parsing process further.
+     *
+     * @param parent parent handler
+     * @param tag current tag that in question
+     * @return new handler that will process tag or null if there is no interest in tag
+     */
+    NamespaceAwareNestedTagHandler createHandler(NamespaceAwareNestedTagHandler parent, String tag);
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38553b16/cayenne-project/src/main/java/org/apache/cayenne/project/extension/ProjectExtension.java
----------------------------------------------------------------------
diff --git a/cayenne-project/src/main/java/org/apache/cayenne/project/extension/ProjectExtension.java b/cayenne-project/src/main/java/org/apache/cayenne/project/extension/ProjectExtension.java
new file mode 100644
index 0000000..1bfa33e
--- /dev/null
+++ b/cayenne-project/src/main/java/org/apache/cayenne/project/extension/ProjectExtension.java
@@ -0,0 +1,49 @@
+/*****************************************************************
+ *   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.extension;
+
+/**
+ * <p>DataMap XML file extension mechanics.</p>
+ * <p>
+ *     Can be used to enhance datamap.map.xml files with additional (really random) information.
+ *     By default extensions not used by {@link org.apache.cayenne.configuration.server.ServerRuntime} or
+ *     ClientRuntime so they can safely store big chunks of data.
+ * </p>
+ * <p>
+ *     Extensions can be contributed by {@link org.apache.cayenne.project.ProjectModule#contributeExtension(org.apache.cayenne.di.Binder)}.
+ *     {@link org.apache.cayenne.project.ProjectModule} currently used by Modeler and cli tools, e.g. cdbimport and cgen.
+ * </p>
+ *
+ * @see org.apache.cayenne.project.extension.info.InfoExtension as reference implementation
+ * @since 4.1
+ */
+public interface ProjectExtension {
+
+    /**
+     * @return delegate that handle loading phase of XML processing
+     */
+    LoaderDelegate createLoaderDelegate();
+
+    /**
+     * @return delegate that handle saving phase of XML processing
+     */
+    SaverDelegate createSaverDelegate();
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38553b16/cayenne-project/src/main/java/org/apache/cayenne/project/extension/SaverDelegate.java
----------------------------------------------------------------------
diff --git a/cayenne-project/src/main/java/org/apache/cayenne/project/extension/SaverDelegate.java b/cayenne-project/src/main/java/org/apache/cayenne/project/extension/SaverDelegate.java
new file mode 100644
index 0000000..4ed5c92
--- /dev/null
+++ b/cayenne-project/src/main/java/org/apache/cayenne/project/extension/SaverDelegate.java
@@ -0,0 +1,45 @@
+/*****************************************************************
+ *   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.extension;
+
+import org.apache.cayenne.configuration.ConfigurationNodeVisitor;
+import org.apache.cayenne.util.XMLEncoder;
+
+/**
+ * Delegate that handles saving XML of extension.
+ * {@link BaseSaverDelegate} should be used as a base class for custom delegates.
+ *
+ * @since 4.1
+ */
+public interface SaverDelegate extends ConfigurationNodeVisitor<Void> {
+
+    /**
+     * @param encoder provided by caller
+     */
+    void setXMLEncoder(XMLEncoder encoder);
+
+    /**
+     * @param parentDelegate parent delegate, provided by caller
+     */
+    void setParentDelegate(SaverDelegate parentDelegate);
+
+    SaverDelegate getParentDelegate();
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38553b16/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/BaseUpgradeHandler.java
----------------------------------------------------------------------
diff --git a/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/BaseUpgradeHandler.java b/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/BaseUpgradeHandler.java
deleted file mode 100644
index 89a7b82..0000000
--- a/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/BaseUpgradeHandler.java
+++ /dev/null
@@ -1,218 +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;
-
-import org.apache.cayenne.ConfigurationException;
-import org.apache.cayenne.configuration.DataChannelDescriptor;
-import org.apache.cayenne.map.DataMap;
-import org.apache.cayenne.map.EntityResolver;
-import org.apache.cayenne.resource.Resource;
-import org.apache.cayenne.util.Util;
-import org.xml.sax.Attributes;
-import org.xml.sax.InputSource;
-import org.xml.sax.SAXException;
-import org.xml.sax.XMLReader;
-import org.xml.sax.helpers.DefaultHandler;
-
-import java.io.InputStream;
-import java.net.URL;
-
-/**
- * A common superclass of UpgradeHandlers.
- * 
- * @since 3.1
- */
-// there's no guarantee this will survive the further version upgrades, but for
-// now all
-// the code here seems like version-agnostic
-public abstract class BaseUpgradeHandler implements UpgradeHandler {
-
-	static final String UNKNOWN_VERSION = "0";
-	static final String MIN_SUPPORTED_VERSION = "3.0.0.1";
-
-	protected Resource projectSource;
-	protected UpgradeMetaData metaData;
-
-	public BaseUpgradeHandler(Resource projectSource) {
-
-		if (projectSource == null) {
-			throw new NullPointerException("Null project source");
-		}
-
-		this.projectSource = projectSource;
-	}
-
-	/**
-	 * Creates a single common EntityResolver for all project DataMaps, setting
-	 * it as a namespace for all of them. This is needed for resolving cross-map
-	 * relationships.
-	 */
-	protected void attachToNamespace(DataChannelDescriptor channelDescriptor) {
-		EntityResolver entityResolver = new EntityResolver(channelDescriptor.getDataMaps());
-
-		for (DataMap map : entityResolver.getDataMaps()) {
-			map.setNamespace(entityResolver);
-		}
-	}
-
-	@Override
-	public Resource getProjectSource() {
-		return projectSource;
-	}
-
-	@Override
-	public UpgradeMetaData getUpgradeMetaData() {
-		// no attempts at thread-safety... shouldn't be needed for upgrades
-		if (metaData == null) {
-			metaData = loadMetaData();
-		}
-
-		return metaData;
-	}
-
-	@Override
-	public Resource performUpgrade() throws ConfigurationException {
-		UpgradeMetaData metaData = getUpgradeMetaData();
-		switch (metaData.getUpgradeType()) {
-			case DOWNGRADE_NEEDED:
-				throw new ConfigurationException("Downgrade can not be performed");
-			case INTERMEDIATE_UPGRADE_NEEDED:
-				throw new ConfigurationException("Upgrade can not be performed - intermediate version upgrade needed");
-			case UPGRADE_NEEDED:
-				return doPerformUpgrade(metaData);
-			default:
-				return getProjectSource();
-		}
-	}
-
-	/**
-	 * Does the actual project upgrade, assuming the caller already verified
-	 * that the upgrade is possible.
-	 * 
-	 * @param metaData
-	 *            object describing the type of upgrade
-	 */
-	protected abstract Resource doPerformUpgrade(UpgradeMetaData metaData) throws ConfigurationException;
-
-	protected abstract String getToVersion();
-
-	/**
-	 * Creates a metadata object describing the type of upgrade needed.
-	 */
-	protected UpgradeMetaData loadMetaData() {
-		String version = loadProjectVersion();
-
-		UpgradeMetaData metadata = new UpgradeMetaData();
-		metadata.setSupportedVersion(getToVersion());
-		metadata.setProjectVersion(version);
-
-		int c1 = compareVersions(version, MIN_SUPPORTED_VERSION);
-		int c2 = compareVersions(getToVersion(), version);
-
-		if (c1 < 0) {
-			metadata.setIntermediateUpgradeVersion(MIN_SUPPORTED_VERSION);
-			metadata.setUpgradeType(UpgradeType.INTERMEDIATE_UPGRADE_NEEDED);
-		} else if (c2 < 0) {
-			metadata.setUpgradeType(UpgradeType.DOWNGRADE_NEEDED);
-		} else if (c2 == 0) {
-			metadata.setUpgradeType(UpgradeType.UPGRADE_NOT_NEEDED);
-		} else {
-			metadata.setUpgradeType(UpgradeType.UPGRADE_NEEDED);
-		}
-
-		return metadata;
-	}
-
-	/**
-	 * A default method for quick extraction of the project version from an XML
-	 * file.
-	 */
-	protected String loadProjectVersion() {
-
-		RootTagHandler rootHandler = new RootTagHandler();
-		URL url = projectSource.getURL();
-
-		try (InputStream in = url.openStream();) {
-
-			XMLReader parser = Util.createXmlReader();
-
-			parser.setContentHandler(rootHandler);
-			parser.setErrorHandler(rootHandler);
-			parser.parse(new InputSource(in));
-		} catch (SAXException e) {
-			// expected ... handler will terminate as soon as it finds a root
-			// tag.
-		} catch (Exception e) {
-			throw new ConfigurationException("Error reading configuration from %s", e, url);
-		}
-
-		return rootHandler.projectVersion != null ? rootHandler.projectVersion : UNKNOWN_VERSION;
-	}
-
-	/**
-	 * Compares two String versions.
-	 */
-	protected int compareVersions(String v1, String v2) {
-
-		if (v1.equals(v2)) {
-			return 0;
-		}
-
-		double v1Double = decodeVersion(v1);
-		double v2Double = decodeVersion(v2);
-		return v1Double < v2Double ? -1 : 1;
-	}
-
-	protected double decodeVersion(String version) {
-		if (version == null || version.trim().length() == 0) {
-			return 0;
-		}
-
-		// leave the first dot, and treat remaining as a fraction
-		// remove all non digit chars
-		StringBuilder buffer = new StringBuilder(version.length());
-		boolean dotProcessed = false;
-		for (int i = 0; i < version.length(); i++) {
-			char nextChar = version.charAt(i);
-			if (nextChar == '.' && !dotProcessed) {
-				dotProcessed = true;
-				buffer.append('.');
-			} else if (Character.isDigit(nextChar)) {
-				buffer.append(nextChar);
-			}
-		}
-
-		return Double.parseDouble(buffer.toString());
-	}
-
-	class RootTagHandler extends DefaultHandler {
-
-		private String projectVersion;
-
-		@Override
-		public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
-
-			this.projectVersion = attributes.getValue("", "project-version");
-
-			// bail right away - we are not interested in reading this to the
-			// end
-			throw new SAXException("finished");
-		}
-	}
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38553b16/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/DataSourceInfoLoader_3_0_0_1.java
----------------------------------------------------------------------
diff --git a/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/DataSourceInfoLoader_3_0_0_1.java b/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/DataSourceInfoLoader_3_0_0_1.java
deleted file mode 100644
index dc52a50..0000000
--- a/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/DataSourceInfoLoader_3_0_0_1.java
+++ /dev/null
@@ -1,228 +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;
-
-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.xml.sax.Attributes;
-import org.xml.sax.ContentHandler;
-import org.xml.sax.InputSource;
-import org.xml.sax.XMLReader;
-
-/**
- * A loader of XML for the {@link DataSourceInfo} object. The loader is
- * compatible with project version 3.0.0.1 and earlier.
- * 
- * @since 3.1
- */
-// TODO: andrus 12.13.2009 - unused yet.. will be used in upgrade manager
-class DataSourceInfoLoader_3_0_0_1 {
-
-	public DataSourceInfo load(Resource configurationResource) throws Exception {
-
-		if (configurationResource == null) {
-			throw new NullPointerException("Null configurationResource");
-		}
-
-		DataSourceInfo dataSourceDescriptor = new DataSourceInfo();
-
-		XMLReader parser = Util.createXmlReader();
-
-		DriverHandler handler = new DriverHandler(dataSourceDescriptor, parser);
-		parser.setContentHandler(handler);
-		parser.setErrorHandler(handler);
-		parser.parse(new InputSource(configurationResource.getURL().openStream()));
-
-		return dataSourceDescriptor;
-	}
-
-	private static String passwordFromURL(URL url) {
-		InputStream inputStream = null;
-		String password = null;
-
-		try {
-			inputStream = url.openStream();
-			password = passwordFromInputStream(inputStream);
-		} catch (IOException exception) {
-			// ignore
-		}
-
-		return password;
-	}
-
-	private static String passwordFromInputStream(InputStream inputStream) {
-		String password = null;
-
-		try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));) {
-
-			password = bufferedReader.readLine();
-		} catch (IOException exception) {
-			// ignoring...
-		} finally {
-
-			try {
-				inputStream.close();
-			} catch (IOException exception) {
-			}
-		}
-
-		return password;
-	}
-
-	private class DriverHandler extends SAXNestedTagHandler {
-
-		private DataSourceInfo dataSourceDescriptor;
-
-		DriverHandler(DataSourceInfo dataSourceDescriptor, XMLReader parser) {
-			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 DriverChildrenHandler(parser, this);
-			}
-
-			return super.createChildTagHandler(namespaceURI, localName, name, attributes);
-		}
-	}
-
-	private class DriverChildrenHandler extends SAXNestedTagHandler {
-
-		private DataSourceInfo dataSourceDescriptor;
-
-		DriverChildrenHandler(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 {
-							// ignoring..
-						}
-					} else if (passwordLocation.equals(DataSourceInfo.PASSWORD_LOCATION_URL)) {
-						try {
-							password = passwordFromURL(new URL(passwordSource));
-						} catch (MalformedURLException exception) {
-							// ignoring...
-						}
-					} 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) {
-								// ignoring...
-							} catch (InterruptedException exception) {
-								// ignoring...
-							}
-						}
-					}
-				}
-
-				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) {
-						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) {
-						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/DefaultUpgradeService.java
----------------------------------------------------------------------
diff --git a/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/DefaultUpgradeService.java b/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/DefaultUpgradeService.java
new file mode 100644
index 0000000..aafb3d2
--- /dev/null
+++ b/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/DefaultUpgradeService.java
@@ -0,0 +1,318 @@
+/*****************************************************************
+ *   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.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.Result;
+import javax.xml.transform.Source;
+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 org.apache.cayenne.ConfigurationException;
+import org.apache.cayenne.configuration.ConfigurationTree;
+import org.apache.cayenne.configuration.DataChannelDescriptor;
+import org.apache.cayenne.configuration.DataChannelDescriptorLoader;
+import org.apache.cayenne.di.Inject;
+import org.apache.cayenne.project.Project;
+import org.apache.cayenne.project.ProjectSaver;
+import org.apache.cayenne.project.upgrade.handlers.UpgradeHandler;
+import org.apache.cayenne.resource.Resource;
+import org.apache.cayenne.util.Util;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ *
+ * Upgrade service sequence is following:
+ * 1. This cycle should be done by Modeler and will result in a full project upgrade
+ *
+ *  - find all project and datamap resources
+ *  - define set of upgrade handlers to process those resources
+ *  - process DOM (project + N data maps)
+ *  - save & load cycle to flush all DOM changes
+ *  - process project model
+ *  - save once again to cleanup and sort final XML
+ *
+ * 2. This cycle can be used by ServerRuntime to optionally support old project versions
+ *
+ *  - find all project and datamap resources
+ *  - define set of upgrade handlers to process those resources
+ *  - process DOM (project + N data maps)
+ *  - directly load model from DOM w/o saving
+ *  - process project model
+ *
+ * @since 4.1
+ */
+public class DefaultUpgradeService implements UpgradeService {
+
+    public static final String UNKNOWN_VERSION = "0";
+    public static final String MIN_SUPPORTED_VERSION = "6";
+
+    TreeMap<String, UpgradeHandler> handlers = new TreeMap<>(VersionComparator.INSTANCE);
+
+    @Inject
+    private ProjectSaver projectSaver;
+
+    @Inject
+    private DataChannelDescriptorLoader loader;
+
+    public DefaultUpgradeService(@Inject List<UpgradeHandler> handlerList) {
+        for(UpgradeHandler handler : handlerList) {
+            handlers.put(handler.getVersion(), handler);
+        }
+    }
+
+    @Override
+    public UpgradeMetaData getUpgradeType(Resource resource) {
+        UpgradeMetaData metaData = new UpgradeMetaData();
+
+        String version = loadProjectVersion(resource);
+        metaData.setProjectVersion(version);
+        metaData.setSupportedVersion(String.valueOf(Project.VERSION));
+
+        int c1 = VersionComparator.INSTANCE.compare(version, MIN_SUPPORTED_VERSION);
+        if (c1 < 0) {
+            metaData.setIntermediateUpgradeVersion(MIN_SUPPORTED_VERSION);
+            metaData.setUpgradeType(UpgradeType.INTERMEDIATE_UPGRADE_NEEDED);
+            return metaData;
+        }
+
+        int c2 = VersionComparator.INSTANCE.compare(String.valueOf(Project.VERSION), version);
+        if (c2 < 0) {
+            metaData.setUpgradeType(UpgradeType.DOWNGRADE_NEEDED);
+        } else if (c2 == 0) {
+            metaData.setUpgradeType(UpgradeType.UPGRADE_NOT_NEEDED);
+        } else {
+            metaData.setUpgradeType(UpgradeType.UPGRADE_NEEDED);
+        }
+        return metaData;
+    }
+
+    List<UpgradeHandler> getHandlersForVersion(String version) {
+        boolean found = MIN_SUPPORTED_VERSION.equals(version);
+        List<UpgradeHandler> handlerList = new ArrayList<>();
+
+        for(Map.Entry<String, UpgradeHandler> entry : handlers.entrySet()) {
+            if(entry.getKey().equals(version)) {
+                found = true;
+                continue;
+            }
+            if(!found) {
+                continue;
+            }
+
+            handlerList.add(entry.getValue());
+        }
+
+        return handlerList;
+    }
+
+    @Override
+    public Resource upgradeProject(Resource resource) {
+        List<UpgradeHandler> handlerList = getHandlersForVersion(loadProjectVersion(resource));
+
+        resource = upgradeDOM(resource, handlerList);
+        upgradeModel(resource, handlerList);
+
+        return resource;
+    }
+
+    Resource upgradeDOM(Resource resource, List<UpgradeHandler> handlerList) {
+        // Load DOM for all resources
+        Document projectDocument = readDocument(resource);
+        UpgradeUnit projectUnit = new UpgradeUnit(resource, projectDocument);
+
+        List<Resource> dataMapResources = getAdditionalDatamapResources(projectUnit);
+        List<UpgradeUnit> upgradeUnits = new ArrayList<>(dataMapResources.size());
+        for (Resource dataMapResource : dataMapResources) {
+            upgradeUnits.add(new UpgradeUnit(dataMapResource, readDocument(dataMapResource)));
+        }
+
+        // Update DOM
+        for(UpgradeHandler handler : handlerList) {
+            handler.processProjectDom(projectUnit);
+            for(UpgradeUnit dataMapUnit : upgradeUnits) {
+                handler.processDataMapDom(dataMapUnit);
+            }
+        }
+
+        // Save modified DOM back to original files
+        saveDocument(projectUnit);
+        for(UpgradeUnit dataMapUnit : upgradeUnits) {
+            saveDocument(dataMapUnit);
+        }
+
+        return projectUnit.getResource();
+    }
+
+    void upgradeModel(Resource resource, List<UpgradeHandler> handlerList) {
+        // Load Model back from the update XML
+        ConfigurationTree<DataChannelDescriptor> configurationTree = loader.load(resource);
+
+        // Update model level if needed
+        for(UpgradeHandler handler : handlerList) {
+            handler.processModel(configurationTree.getRootNode());
+        }
+
+        // Save project once again via project saver, this will normalize XML to minimize final diff
+        Project project = new Project(configurationTree);
+        projectSaver.save(project);
+    }
+
+    List<Resource> getAdditionalDatamapResources(UpgradeUnit upgradeUnit) {
+        List<Resource> resources = new ArrayList<>();
+        try {
+            XPath xpath = XPathFactory.newInstance().newXPath();
+            NodeList nodes = (NodeList) xpath.evaluate("/domain/map/@name", upgradeUnit.getDocument(), XPathConstants.NODESET);
+            for (int i = 0; i < nodes.getLength(); i++) {
+                Node mapNode = nodes.item(i);
+                // in version 3.0.0.1 and earlier map tag had attribute location,
+                // but it was always equal to data map name + ".map.xml"
+                Resource mapResource = upgradeUnit.getResource().getRelativeResource(mapNode.getNodeValue() + ".map.xml");
+                resources.add(mapResource);
+            }
+        } catch (Exception ex) {
+            ex.printStackTrace();
+        }
+        return resources;
+    }
+
+    protected void saveDocument(UpgradeUnit upgradeUnit) {
+        try {
+            Source input = new DOMSource(upgradeUnit.getDocument());
+            Result output = new StreamResult(Util.toFile(upgradeUnit.getResource().getURL()));
+
+            Transformer transformer = TransformerFactory.newInstance().newTransformer();
+            transformer.transform(input, output);
+        } catch (Exception ex) {
+            ex.printStackTrace();
+        }
+    }
+
+    protected Document readDocument(Resource resource) {
+        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
+        documentBuilderFactory.setNamespaceAware(false);
+        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);
+        }
+    }
+
+    /**
+     * A default method for quick extraction of the project version from an XML
+     * file.
+     */
+    protected String loadProjectVersion(Resource resource) {
+
+        RootTagHandler rootHandler = new RootTagHandler();
+        URL url = resource.getURL();
+        try (InputStream in = url.openStream()) {
+            XMLReader parser = Util.createXmlReader();
+            parser.setContentHandler(rootHandler);
+            parser.setErrorHandler(rootHandler);
+            parser.parse(new InputSource(in));
+        } catch (SAXException e) {
+            // expected... handler will terminate as soon as it finds a root tag.
+        } catch (Exception e) {
+            throw new ConfigurationException("Error reading configuration from %s", e, url);
+        }
+
+        return rootHandler.projectVersion != null ? rootHandler.projectVersion : UNKNOWN_VERSION;
+    }
+
+    protected static double decodeVersion(String version) {
+        if (version == null || version.trim().length() == 0) {
+            return 0;
+        }
+
+        // leave the first dot, and treat remaining as a fraction
+        // remove all non digit chars
+        StringBuilder buffer = new StringBuilder(version.length());
+        boolean dotProcessed = false;
+        for (int i = 0; i < version.length(); i++) {
+            char nextChar = version.charAt(i);
+            if (nextChar == '.' && !dotProcessed) {
+                dotProcessed = true;
+                buffer.append('.');
+            } else if (Character.isDigit(nextChar)) {
+                buffer.append(nextChar);
+            }
+        }
+
+        return Double.parseDouble(buffer.toString());
+    }
+
+    private static class VersionComparator implements Comparator<String> {
+
+        private static final VersionComparator INSTANCE = new VersionComparator();
+
+        @Override
+        public int compare(String o1, String o2) {
+            if (o1.equals(o2)) {
+                return 0;
+            }
+            double v1Double = decodeVersion(o1);
+            double v2Double = decodeVersion(o2);
+            return v1Double < v2Double ? -1 : 1;
+        }
+    }
+
+    class RootTagHandler extends DefaultHandler {
+
+        private String projectVersion;
+
+        @Override
+        public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
+
+            this.projectVersion = attributes.getValue("", "project-version");
+
+            // bail right away - we are not interested in reading this to the end
+            throw new SAXException("finished");
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38553b16/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/ProjectUpgrader.java
----------------------------------------------------------------------
diff --git a/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/ProjectUpgrader.java b/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/ProjectUpgrader.java
deleted file mode 100644
index 8eac85f..0000000
--- a/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/ProjectUpgrader.java
+++ /dev/null
@@ -1,34 +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;
-
-import org.apache.cayenne.resource.Resource;
-
-/**
- * Defines API of an upgrade handler for Cayenne projects.
- * 
- * @since 3.1
- */
-public interface ProjectUpgrader {
-
-    /**
-     * Returns an upgrade handler to process upgrades of a given project.
-     */
-    UpgradeHandler getUpgradeHandler(Resource projectSource);
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38553b16/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/UpgradeHandler.java
----------------------------------------------------------------------
diff --git a/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/UpgradeHandler.java b/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/UpgradeHandler.java
deleted file mode 100644
index e307b90..0000000
--- a/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/UpgradeHandler.java
+++ /dev/null
@@ -1,57 +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;
-
-import org.apache.cayenne.ConfigurationException;
-import org.apache.cayenne.resource.Resource;
-
-/**
- * A stateful helper object for analyzing the projects and performing upgrades.
- * 
- * @since 3.1
- */
-public interface UpgradeHandler {
-
-    /**
-     * Returns the original configuration source for the project before the upgrade.
-     */
-    Resource getProjectSource();
-
-    /**
-     * Returns a metadata object containing information about the upgrade to be performed.
-     * Users should call this method before invoking {@link #performUpgrade()}, to make
-     * sure upgrade is needed and possible. Tools (like CayenneModeler) may use this
-     * object to build user-friendly messages asking for user input on the upgrade.
-     */
-    UpgradeMetaData getUpgradeMetaData();
-
-    /**
-     * Performs an in-place project configuration upgrade, throwing a
-     * {@link ConfigurationException} if the upgrade fails. Before doing the upgrade,
-     * check the handler {@link UpgradeMetaData}. Upgrades will succeed only for projects
-     * that have {@link UpgradeType#UPGRADE_NEEDED} or
-     * {@link UpgradeType#UPGRADE_NOT_NEEDED} statuses. In the later case of course,
-     * upgrade will simply be skipped.
-     * 
-     * @return a configuration Resource for the upgraded project. Depending on the upgrade
-     *         type, it may be the same resource as the original configuration, or a
-     *         totally different resource.
-     */
-    Resource performUpgrade() throws ConfigurationException;
-}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38553b16/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/UpgradeService.java
----------------------------------------------------------------------
diff --git a/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/UpgradeService.java b/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/UpgradeService.java
new file mode 100644
index 0000000..a9cb480
--- /dev/null
+++ b/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/UpgradeService.java
@@ -0,0 +1,33 @@
+/*****************************************************************
+ *   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 org.apache.cayenne.resource.Resource;
+
+/**
+ * @since 4.1
+ */
+public interface UpgradeService {
+
+    UpgradeMetaData getUpgradeType(Resource resource);
+
+    Resource upgradeProject(Resource resource);
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38553b16/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/UpgradeUnit.java
----------------------------------------------------------------------
diff --git a/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/UpgradeUnit.java b/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/UpgradeUnit.java
new file mode 100644
index 0000000..3fb0b95
--- /dev/null
+++ b/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/UpgradeUnit.java
@@ -0,0 +1,54 @@
+/*****************************************************************
+ *   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 org.apache.cayenne.resource.Resource;
+import org.w3c.dom.Document;
+
+/**
+ * @since 4.1
+ */
+public class UpgradeUnit {
+
+    private Resource resource;
+
+    private Document document;
+
+    public UpgradeUnit(Resource resource, Document document) {
+        this.resource = resource;
+        this.document = document;
+    }
+
+    public Document getDocument() {
+        return document;
+    }
+
+    public Resource getResource() {
+        return resource;
+    }
+
+    public void setDocument(Document document) {
+        this.document = document;
+    }
+
+    public void setResource(Resource resource) {
+        this.resource = resource;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38553b16/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/handlers/UpgradeHandler.java
----------------------------------------------------------------------
diff --git a/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/handlers/UpgradeHandler.java b/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/handlers/UpgradeHandler.java
new file mode 100644
index 0000000..535ec8c
--- /dev/null
+++ b/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/handlers/UpgradeHandler.java
@@ -0,0 +1,58 @@
+/*****************************************************************
+ *   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.apache.cayenne.project.upgrade.UpgradeUnit;
+
+/**
+ * Interface that upgrade handlers should implement.
+ * Implementation also should be injected into DI stack in right order.
+ *
+ * @since 4.1
+ */
+public interface UpgradeHandler {
+
+    /**
+     * @return target version for this handler
+     */
+    String getVersion();
+
+    /**
+     * Process DOM for the project root file (e.g. cayenne-project.xml)
+     */
+    void processProjectDom(UpgradeUnit upgradeUnit);
+
+    /**
+     * Process DOM for the data map file (e.g. datamap.map.xml)
+     */
+    void processDataMapDom(UpgradeUnit upgradeUnit);
+
+    /**
+     * This method should be avoided as much as possible, as
+     * using this method will make upgrade process not future proof and
+     * will require refactoring if model should change.
+     */
+    void processModel(DataChannelDescriptor dataChannelDescriptor);
+    // should be this really, but no Java 8 yet:
+    //default 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_V10.java
----------------------------------------------------------------------
diff --git a/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/handlers/UpgradeHandler_V10.java b/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/handlers/UpgradeHandler_V10.java
new file mode 100644
index 0000000..44b55b6
--- /dev/null
+++ b/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/handlers/UpgradeHandler_V10.java
@@ -0,0 +1,68 @@
+/*****************************************************************
+ *   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.apache.cayenne.project.upgrade.UpgradeUnit;
+import org.w3c.dom.Element;
+
+/**
+ * Upgrade handler for the project version "10" introduced by 4.1.M1 release.
+ * Changes highlight:
+ *      - strict schema for domain (e.g. main project document)
+ *      - new schema for data map allowing usage of additional elements (e.g. XML extensions)
+ *
+ * @since 4.1
+ */
+public class UpgradeHandler_V10 implements UpgradeHandler {
+
+    @Override
+    public String getVersion() {
+        return "10";
+    }
+
+    @Override
+    public void processProjectDom(UpgradeUnit upgradeUnit) {
+        Element domain = upgradeUnit.getDocument().getDocumentElement();
+        // introduce xml namespace and schema for domain
+        domain.setAttribute("xmlns","http://cayenne.apache.org/schema/10/domain");
+        domain.setAttribute("xmlns:xsi","http://www.w3.org/2001/XMLSchema-instance");
+        domain.setAttribute("xsi:schemaLocation", "http://cayenne.apache.org/schema/10/domain " +
+                "http://cayenne.apache.org/schema/10/domain.xsd");
+        // update version
+        domain.setAttribute("project-version", getVersion());
+    }
+
+    @Override
+    public void processDataMapDom(UpgradeUnit upgradeUnit) {
+        Element dataMap = upgradeUnit.getDocument().getDocumentElement();
+        // update schema
+        dataMap.setAttribute("xmlns","http://cayenne.apache.org/schema/10/modelMap");
+        dataMap.setAttribute("xsi:schemaLocation", "http://cayenne.apache.org/schema/10/modelMap " +
+                "http://cayenne.apache.org/schema/10/modelMap.xsd");
+        // update version
+        dataMap.setAttribute("project-version", getVersion());
+    }
+
+    @Override
+    public void processModel(DataChannelDescriptor dataChannelDescriptor) {
+        // noop
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/38553b16/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/handlers/UpgradeHandler_V7.java
----------------------------------------------------------------------
diff --git a/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/handlers/UpgradeHandler_V7.java b/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/handlers/UpgradeHandler_V7.java
new file mode 100644
index 0000000..174e660
--- /dev/null
+++ b/cayenne-project/src/main/java/org/apache/cayenne/project/upgrade/handlers/UpgradeHandler_V7.java
@@ -0,0 +1,109 @@
+/*****************************************************************
+ *   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.ArrayList;
+import java.util.List;
+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.map.DataMap;
+import org.apache.cayenne.map.ObjAttribute;
+import org.apache.cayenne.map.ObjEntity;
+import org.apache.cayenne.project.upgrade.UpgradeUnit;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+/**
+ * @since 4.1
+ */
+public class UpgradeHandler_V7 implements UpgradeHandler {
+
+    @Override
+    public String getVersion() {
+        return "7";
+    }
+
+    @Override
+    public void processProjectDom(UpgradeUnit upgradeUnit) {
+        Element domain = upgradeUnit.getDocument().getDocumentElement();
+        domain.setAttribute("project-version", getVersion());
+
+        XPath xpath = XPathFactory.newInstance().newXPath();
+        Node node;
+        try {
+            node = (Node) xpath.evaluate("/domain/property[@name='cayenne.DataDomain.usingExternalTransactions']",
+                    upgradeUnit.getDocument(), XPathConstants.NODE);
+        }catch (Exception ex) {
+            return;
+        }
+
+        if(node != null) {
+            domain.removeChild(node);
+        }
+    }
+
+    @Override
+    public void processDataMapDom(UpgradeUnit upgradeUnit) {
+        Element dataMap = upgradeUnit.getDocument().getDocumentElement();
+        dataMap.setAttribute("xmlns","http://cayenne.apache.org/schema/7/modelMap");
+        dataMap.setAttribute("xsi:schemaLocation", "http://cayenne.apache.org/schema/7/modelMap " +
+                "http://cayenne.apache.org/schema/7/modelMap.xsd");
+        dataMap.setAttribute("project-version", getVersion());
+    }
+
+    @Override
+    public void processModel(DataChannelDescriptor dataChannelDescriptor) {
+        for (DataMap dataMap : dataChannelDescriptor.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);
+            }
+        }
+    }
+}


Mime
View raw message