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 <[email protected]>
Authored: Tue Jul 25 12:39:56 2017 +0300
Committer: Nikita Timofeev <[email protected]>
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);
+            }
+        }
+    }
+}

Reply via email to