cayenne-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ntimof...@apache.org
Subject cayenne git commit: CAY-2339 Compatibility module support old versions of projects at runtime
Date Wed, 26 Jul 2017 08:39:29 GMT
Repository: cayenne
Updated Branches:
  refs/heads/master 11b916f85 -> 3f375f384


CAY-2339 Compatibility module
   support old versions of projects at runtime


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

Branch: refs/heads/master
Commit: 3f375f38443ce06d15d7024d3408b59a509f7b77
Parents: 11b916f
Author: Nikita Timofeev <stariy95@gmail.com>
Authored: Tue Jul 25 18:51:46 2017 +0300
Committer: Nikita Timofeev <stariy95@gmail.com>
Committed: Tue Jul 25 18:51:46 2017 +0300

----------------------------------------------------------------------
 cayenne-project-compatibility/pom.xml           |  64 +++++++++
 ...ompatibilityDataChannelDescriptorLoader.java | 134 +++++++++++++++++++
 .../xml/CompatibilityDataMapLoader.java         |  89 ++++++++++++
 .../CompatibilityUpgradeService.java            |  62 +++++++++
 .../compatibility/DefaultDocumentProvider.java  |  44 ++++++
 .../project/compatibility/DocumentProvider.java |  34 +++++
 .../ProjectCompatibilityModule.java             |  54 ++++++++
 .../ProjectCompatibilityModuleProvider.java     |  51 +++++++
 ...iguration.server.CayenneServerModuleProvider |  20 +++
 ...patibilityDataChannelDescriptorLoaderIT.java | 105 +++++++++++++++
 .../CompatibilityUpgradeServiceIT.java          |  90 +++++++++++++
 .../ProjectCompatibilityModuleProviderTest.java |  35 +++++
 .../compatibility/cayenne-project-v6.xml        |  35 +++++
 .../project/compatibility/test-map-v6.map.xml   |  21 +++
 .../project/upgrade/DefaultUpgradeService.java  |  63 ++++-----
 .../upgrade/DefaultUpgradeServiceTest.java      |  10 +-
 .../configuration/xml/XMLDataMapLoader.java     |   2 +-
 .../main/java/org/apache/cayenne/util/Util.java |  25 ++++
 pom.xml                                         |   1 +
 19 files changed, 899 insertions(+), 40 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/3f375f38/cayenne-project-compatibility/pom.xml
----------------------------------------------------------------------
diff --git a/cayenne-project-compatibility/pom.xml b/cayenne-project-compatibility/pom.xml
new file mode 100644
index 0000000..78f2516
--- /dev/null
+++ b/cayenne-project-compatibility/pom.xml
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+  ~   Licensed to the Apache Software Foundation (ASF) under one
+  ~  or more contributor license agreements.  See the NOTICE file
+  ~  distributed with this work for additional information
+  ~  regarding copyright ownership.  The ASF licenses this file
+  ~  to you under the Apache License, Version 2.0 (the
+  ~  "License"); you may not use this file except in compliance
+  ~  with the License.  You may obtain a copy of the License at
+  ~
+  ~    http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~  Unless required by applicable law or agreed to in writing,
+  ~  software distributed under the License is distributed on an
+  ~  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~  KIND, either express or implied.  See the License for the
+  ~  specific language governing permissions and limitations
+  ~  under the License.
+  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>cayenne-parent</artifactId>
+        <groupId>org.apache.cayenne</groupId>
+        <version>4.1.M1-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>cayenne-project-compatibility</artifactId>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.cayenne</groupId>
+            <artifactId>cayenne-project</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.cayenne</groupId>
+            <artifactId>cayenne-server</artifactId>
+            <version>${project.version}</version>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-simple</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-core</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+</project>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/3f375f38/cayenne-project-compatibility/src/main/java/org/apache/cayenne/configuration/xml/CompatibilityDataChannelDescriptorLoader.java
----------------------------------------------------------------------
diff --git a/cayenne-project-compatibility/src/main/java/org/apache/cayenne/configuration/xml/CompatibilityDataChannelDescriptorLoader.java b/cayenne-project-compatibility/src/main/java/org/apache/cayenne/configuration/xml/CompatibilityDataChannelDescriptorLoader.java
new file mode 100644
index 0000000..575f080
--- /dev/null
+++ b/cayenne-project-compatibility/src/main/java/org/apache/cayenne/configuration/xml/CompatibilityDataChannelDescriptorLoader.java
@@ -0,0 +1,134 @@
+/*****************************************************************
+ *   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.configuration.xml;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.net.URL;
+
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
+import org.apache.cayenne.ConfigurationException;
+import org.apache.cayenne.configuration.ConfigurationTree;
+import org.apache.cayenne.configuration.DataChannelDescriptor;
+import org.apache.cayenne.di.Inject;
+import org.apache.cayenne.di.Provider;
+import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.project.compatibility.CompatibilityUpgradeService;
+import org.apache.cayenne.project.compatibility.DocumentProvider;
+import org.apache.cayenne.project.upgrade.UpgradeMetaData;
+import org.apache.cayenne.project.upgrade.UpgradeService;
+import org.apache.cayenne.project.upgrade.UpgradeType;
+import org.apache.cayenne.resource.Resource;
+import org.apache.cayenne.util.Util;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.w3c.dom.Document;
+import org.xml.sax.InputSource;
+import org.xml.sax.XMLReader;
+
+
+/**
+ * @since 4.1
+ */
+public class CompatibilityDataChannelDescriptorLoader extends XMLDataChannelDescriptorLoader {
+
+    private static Logger logger = LoggerFactory.getLogger(XMLDataChannelDescriptorLoader.class);
+
+    @Inject
+    Provider<UpgradeService> upgradeServiceProvider;
+
+    @Inject
+    DocumentProvider documentProvider;
+
+    @Override
+    public ConfigurationTree<DataChannelDescriptor> load(Resource configurationResource) throws ConfigurationException {
+        if (configurationResource == null) {
+            throw new NullPointerException("Null configurationResource");
+        }
+
+        if(!(upgradeServiceProvider.get() instanceof CompatibilityUpgradeService)) {
+            throw new ConfigurationException("CompatibilityUpgradeService expected");
+        }
+
+        CompatibilityUpgradeService upgradeService = (CompatibilityUpgradeService)upgradeServiceProvider.get();
+
+        UpgradeMetaData metaData = upgradeService.getUpgradeType(configurationResource);
+        if(metaData.getUpgradeType() == UpgradeType.UPGRADE_NOT_NEEDED) {
+            return super.load(configurationResource);
+        }
+
+        if(metaData.getUpgradeType() == UpgradeType.DOWNGRADE_NEEDED) {
+            throw new ConfigurationException("Unable to load configuration from %s: " +
+                    "It was created using a newer version of the Modeler", configurationResource.getURL());
+        }
+
+        if(metaData.getUpgradeType() == UpgradeType.INTERMEDIATE_UPGRADE_NEEDED) {
+            throw new ConfigurationException("Unable to load configuration from %s: " +
+                    "Open the project in the older Modeler to do an intermediate upgrade.", configurationResource.getURL());
+        }
+
+        URL configurationURL = configurationResource.getURL();
+
+        upgradeService.upgradeProject(configurationResource);
+        Document projectDocument = documentProvider.getDocument(configurationURL);
+        if(projectDocument == null) {
+            throw new ConfigurationException("Unable to upgrade " + configurationURL);
+        }
+
+        logger.info("Loading XML configuration resource from " + configurationURL);
+
+        final DataChannelDescriptor descriptor = new DataChannelDescriptor();
+        descriptor.setConfigurationSource(configurationResource);
+        descriptor.setName(nameMapper.configurationNodeName(DataChannelDescriptor.class, configurationResource));
+
+        try {
+            DOMSource source = new DOMSource(projectDocument);
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            TransformerFactory transFactory = TransformerFactory.newInstance();
+            transFactory.newTransformer().transform(source, new StreamResult(baos));
+            InputSource isource = new InputSource(source.getSystemId());
+            isource.setByteStream(new ByteArrayInputStream(baos.toByteArray()));
+
+            XMLReader parser = Util.createXmlReader();
+            LoaderContext loaderContext = new LoaderContext(parser, handlerFactory);
+            loaderContext.addDataMapListener(new DataMapLoaderListener() {
+                @Override
+                public void onDataMapLoaded(DataMap dataMap) {
+                    descriptor.getDataMaps().add(dataMap);
+                }
+            });
+
+            DataChannelHandler rootHandler = new DataChannelHandler(this, descriptor, loaderContext);
+            parser.setContentHandler(rootHandler);
+            parser.setErrorHandler(rootHandler);
+            parser.parse(isource);
+        } catch (Exception e) {
+            throw new ConfigurationException("Error loading configuration from %s", e, configurationURL);
+        }
+
+        // Finally upgrade model, if needed
+        upgradeService.upgradeModel(configurationResource, descriptor);
+
+        return new ConfigurationTree<>(descriptor, null);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/3f375f38/cayenne-project-compatibility/src/main/java/org/apache/cayenne/configuration/xml/CompatibilityDataMapLoader.java
----------------------------------------------------------------------
diff --git a/cayenne-project-compatibility/src/main/java/org/apache/cayenne/configuration/xml/CompatibilityDataMapLoader.java b/cayenne-project-compatibility/src/main/java/org/apache/cayenne/configuration/xml/CompatibilityDataMapLoader.java
new file mode 100644
index 0000000..77ce2cd
--- /dev/null
+++ b/cayenne-project-compatibility/src/main/java/org/apache/cayenne/configuration/xml/CompatibilityDataMapLoader.java
@@ -0,0 +1,89 @@
+/*****************************************************************
+ *   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.configuration.xml;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
+import org.apache.cayenne.CayenneRuntimeException;
+import org.apache.cayenne.di.Inject;
+import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.project.compatibility.DocumentProvider;
+import org.apache.cayenne.resource.Resource;
+import org.apache.cayenne.util.Util;
+import org.w3c.dom.Document;
+import org.xml.sax.InputSource;
+import org.xml.sax.XMLReader;
+
+/**
+ * @since 4.1
+ */
+public class CompatibilityDataMapLoader extends XMLDataMapLoader {
+
+    @Inject
+    DocumentProvider documentProvider;
+
+    @Override
+    public DataMap load(Resource configurationResource) throws CayenneRuntimeException {
+        Document document = documentProvider.getDocument(configurationResource.getURL());
+        if(document == null) {
+            return super.load(configurationResource);
+        }
+
+        try {
+            DOMSource source = new DOMSource(document);
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            TransformerFactory transFactory = TransformerFactory.newInstance();
+            transFactory.newTransformer().transform(source, new StreamResult(baos));
+            InputSource isource = new InputSource(source.getSystemId());
+            isource.setByteStream(new ByteArrayInputStream(baos.toByteArray()));
+
+            XMLReader parser = Util.createXmlReader();
+            LoaderContext loaderContext = new LoaderContext(parser, handlerFactory);
+            loaderContext.addDataMapListener(new DataMapLoaderListener() {
+                @Override
+                public void onDataMapLoaded(DataMap dataMap) {
+                    map = dataMap;
+                }
+            });
+            RootDataMapHandler rootHandler = new RootDataMapHandler(loaderContext);
+
+            parser.setContentHandler(rootHandler);
+            parser.setErrorHandler(rootHandler);
+            parser.parse(isource);
+        } catch (Exception e) {
+            throw new CayenneRuntimeException("Error loading configuration from %s", e, configurationResource.getURL());
+        }
+
+        if(map == null) {
+            throw new CayenneRuntimeException("Unable to load data map from %s", configurationResource.getURL());
+        }
+
+        if(map.getName() == null) {
+            // set name based on location if no name provided by map itself
+            map.setName(mapNameFromLocation(configurationResource.getURL().getFile()));
+        }
+        return map;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/3f375f38/cayenne-project-compatibility/src/main/java/org/apache/cayenne/project/compatibility/CompatibilityUpgradeService.java
----------------------------------------------------------------------
diff --git a/cayenne-project-compatibility/src/main/java/org/apache/cayenne/project/compatibility/CompatibilityUpgradeService.java b/cayenne-project-compatibility/src/main/java/org/apache/cayenne/project/compatibility/CompatibilityUpgradeService.java
new file mode 100644
index 0000000..3444d37
--- /dev/null
+++ b/cayenne-project-compatibility/src/main/java/org/apache/cayenne/project/compatibility/CompatibilityUpgradeService.java
@@ -0,0 +1,62 @@
+/*****************************************************************
+ *   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.compatibility;
+
+import java.util.List;
+
+import org.apache.cayenne.configuration.DataChannelDescriptor;
+import org.apache.cayenne.di.Inject;
+import org.apache.cayenne.project.upgrade.DefaultUpgradeService;
+import org.apache.cayenne.project.upgrade.UpgradeUnit;
+import org.apache.cayenne.project.upgrade.handlers.UpgradeHandler;
+import org.apache.cayenne.resource.Resource;
+
+/**
+ * @since 4.1
+ */
+public class CompatibilityUpgradeService extends DefaultUpgradeService {
+
+    @Inject
+    DocumentProvider documentProvider;
+
+    public CompatibilityUpgradeService(@Inject List<UpgradeHandler> handlerList) {
+        super(handlerList);
+    }
+
+    @Override
+    public Resource upgradeProject(Resource resource) {
+        List<UpgradeHandler> handlerList = getHandlersForVersion(loadProjectVersion(resource));
+        List<UpgradeUnit> upgradeUnits = upgradeDOM(resource, handlerList);
+
+        for(UpgradeUnit unit : upgradeUnits) {
+            documentProvider.putDocument(unit.getResource().getURL(), unit.getDocument());
+        }
+
+        return resource;
+    }
+
+    public void upgradeModel(Resource resource, DataChannelDescriptor descriptor) {
+        List<UpgradeHandler> handlerList = getHandlersForVersion(loadProjectVersion(resource));
+        for(UpgradeHandler handler : handlerList) {
+            handler.processModel(descriptor);
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/3f375f38/cayenne-project-compatibility/src/main/java/org/apache/cayenne/project/compatibility/DefaultDocumentProvider.java
----------------------------------------------------------------------
diff --git a/cayenne-project-compatibility/src/main/java/org/apache/cayenne/project/compatibility/DefaultDocumentProvider.java b/cayenne-project-compatibility/src/main/java/org/apache/cayenne/project/compatibility/DefaultDocumentProvider.java
new file mode 100644
index 0000000..5682782
--- /dev/null
+++ b/cayenne-project-compatibility/src/main/java/org/apache/cayenne/project/compatibility/DefaultDocumentProvider.java
@@ -0,0 +1,44 @@
+/*****************************************************************
+ *   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.compatibility;
+
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.w3c.dom.Document;
+
+/**
+ * @since 4.1
+ */
+public class DefaultDocumentProvider implements DocumentProvider {
+
+    Map<String, Document> documentMap = new HashMap<>();
+
+    @Override
+    public Document getDocument(URL url) {
+        return documentMap.get(url.toString());
+    }
+
+    @Override
+    public void putDocument(URL url, Document document) {
+        documentMap.put(url.toString(), document);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/3f375f38/cayenne-project-compatibility/src/main/java/org/apache/cayenne/project/compatibility/DocumentProvider.java
----------------------------------------------------------------------
diff --git a/cayenne-project-compatibility/src/main/java/org/apache/cayenne/project/compatibility/DocumentProvider.java b/cayenne-project-compatibility/src/main/java/org/apache/cayenne/project/compatibility/DocumentProvider.java
new file mode 100644
index 0000000..9210a8c
--- /dev/null
+++ b/cayenne-project-compatibility/src/main/java/org/apache/cayenne/project/compatibility/DocumentProvider.java
@@ -0,0 +1,34 @@
+/*****************************************************************
+ *   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.compatibility;
+
+import java.net.URL;
+
+import org.w3c.dom.Document;
+
+/**
+ * @since 4.1
+ */
+public interface DocumentProvider {
+
+    Document getDocument(URL url);
+
+    void putDocument(URL url, Document document);
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/3f375f38/cayenne-project-compatibility/src/main/java/org/apache/cayenne/project/compatibility/ProjectCompatibilityModule.java
----------------------------------------------------------------------
diff --git a/cayenne-project-compatibility/src/main/java/org/apache/cayenne/project/compatibility/ProjectCompatibilityModule.java b/cayenne-project-compatibility/src/main/java/org/apache/cayenne/project/compatibility/ProjectCompatibilityModule.java
new file mode 100644
index 0000000..5a93695
--- /dev/null
+++ b/cayenne-project-compatibility/src/main/java/org/apache/cayenne/project/compatibility/ProjectCompatibilityModule.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.compatibility;
+
+import org.apache.cayenne.configuration.DataChannelDescriptorLoader;
+import org.apache.cayenne.configuration.DataMapLoader;
+import org.apache.cayenne.configuration.xml.CompatibilityDataChannelDescriptorLoader;
+import org.apache.cayenne.configuration.xml.CompatibilityDataMapLoader;
+import org.apache.cayenne.di.Binder;
+import org.apache.cayenne.di.Module;
+import org.apache.cayenne.project.ProjectModule;
+import org.apache.cayenne.project.upgrade.UpgradeService;
+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;
+
+/**
+ * @since 4.1
+ */
+public class ProjectCompatibilityModule implements Module {
+
+    @Override
+    public void configure(Binder binder) {
+        binder.bind(DataChannelDescriptorLoader.class).to(CompatibilityDataChannelDescriptorLoader.class);
+        binder.bind(DataMapLoader.class).to(CompatibilityDataMapLoader.class);
+
+        binder.bind(UpgradeService.class).to(CompatibilityUpgradeService.class);
+        binder.bind(DocumentProvider.class).to(DefaultDocumentProvider.class);
+
+        ProjectModule.contributeUpgradeHandler(binder)
+                .add(UpgradeHandler_V7.class)
+                .add(UpgradeHandler_V8.class)
+                .add(UpgradeHandler_V9.class)
+                .add(UpgradeHandler_V10.class);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/3f375f38/cayenne-project-compatibility/src/main/java/org/apache/cayenne/project/compatibility/ProjectCompatibilityModuleProvider.java
----------------------------------------------------------------------
diff --git a/cayenne-project-compatibility/src/main/java/org/apache/cayenne/project/compatibility/ProjectCompatibilityModuleProvider.java b/cayenne-project-compatibility/src/main/java/org/apache/cayenne/project/compatibility/ProjectCompatibilityModuleProvider.java
new file mode 100644
index 0000000..ae1adc6
--- /dev/null
+++ b/cayenne-project-compatibility/src/main/java/org/apache/cayenne/project/compatibility/ProjectCompatibilityModuleProvider.java
@@ -0,0 +1,51 @@
+/*****************************************************************
+ *   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.compatibility;
+
+import java.util.Collection;
+import java.util.Collections;
+
+import org.apache.cayenne.configuration.server.CayenneServerModuleProvider;
+import org.apache.cayenne.configuration.server.ServerModule;
+import org.apache.cayenne.di.Module;
+
+/**
+ * @since 4.1
+ */
+public class ProjectCompatibilityModuleProvider implements CayenneServerModuleProvider {
+
+    @Override
+    public Module module() {
+        return new ProjectCompatibilityModule();
+    }
+
+    @Override
+    public Class<? extends Module> moduleType() {
+        return ProjectCompatibilityModule.class;
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public Collection<Class<? extends Module>> overrides() {
+        // compatibility module overrides XML loaders defind in ServerModule
+        Collection modules = Collections.singletonList(ServerModule.class);
+        return modules;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/3f375f38/cayenne-project-compatibility/src/main/resources/META-INF/services/org.apache.cayenne.configuration.server.CayenneServerModuleProvider
----------------------------------------------------------------------
diff --git a/cayenne-project-compatibility/src/main/resources/META-INF/services/org.apache.cayenne.configuration.server.CayenneServerModuleProvider b/cayenne-project-compatibility/src/main/resources/META-INF/services/org.apache.cayenne.configuration.server.CayenneServerModuleProvider
new file mode 100644
index 0000000..2d2e31a
--- /dev/null
+++ b/cayenne-project-compatibility/src/main/resources/META-INF/services/org.apache.cayenne.configuration.server.CayenneServerModuleProvider
@@ -0,0 +1,20 @@
+##################################################################
+#   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.
+##################################################################
+
+org.apache.cayenne.project.compatibility.ProjectCompatibilityModuleProvider
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/3f375f38/cayenne-project-compatibility/src/test/java/org/apache/cayenne/configuration/xml/CompatibilityDataChannelDescriptorLoaderIT.java
----------------------------------------------------------------------
diff --git a/cayenne-project-compatibility/src/test/java/org/apache/cayenne/configuration/xml/CompatibilityDataChannelDescriptorLoaderIT.java b/cayenne-project-compatibility/src/test/java/org/apache/cayenne/configuration/xml/CompatibilityDataChannelDescriptorLoaderIT.java
new file mode 100644
index 0000000..acc31d7c
--- /dev/null
+++ b/cayenne-project-compatibility/src/test/java/org/apache/cayenne/configuration/xml/CompatibilityDataChannelDescriptorLoaderIT.java
@@ -0,0 +1,105 @@
+/*****************************************************************
+ *   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.configuration.xml;
+
+import java.net.URL;
+
+import org.apache.cayenne.configuration.ConfigurationNameMapper;
+import org.apache.cayenne.configuration.ConfigurationTree;
+import org.apache.cayenne.configuration.DataChannelDescriptor;
+import org.apache.cayenne.configuration.DataChannelDescriptorLoader;
+import org.apache.cayenne.configuration.DataMapLoader;
+import org.apache.cayenne.configuration.DefaultConfigurationNameMapper;
+import org.apache.cayenne.di.AdhocObjectFactory;
+import org.apache.cayenne.di.Binder;
+import org.apache.cayenne.di.ClassLoaderManager;
+import org.apache.cayenne.di.DIBootstrap;
+import org.apache.cayenne.di.Injector;
+import org.apache.cayenne.di.Module;
+import org.apache.cayenne.di.spi.DefaultAdhocObjectFactory;
+import org.apache.cayenne.di.spi.DefaultClassLoaderManager;
+import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.project.ProjectSaver;
+import org.apache.cayenne.project.compatibility.CompatibilityUpgradeService;
+import org.apache.cayenne.project.compatibility.DefaultDocumentProvider;
+import org.apache.cayenne.project.compatibility.DocumentProvider;
+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.resource.Resource;
+import org.apache.cayenne.resource.URLResource;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.mock;
+
+/**
+ * @since 4.1
+ */
+public class CompatibilityDataChannelDescriptorLoaderIT {
+
+    @Test
+    public void testLoad() throws Exception {
+        Injector injector = DIBootstrap.createInjector(new Module() {
+            @Override
+            public void configure(Binder binder) {
+                binder.bind(UpgradeService.class).to(CompatibilityUpgradeService.class);
+                binder.bind(DocumentProvider.class).to(DefaultDocumentProvider.class);
+                binder.bind(DataChannelDescriptorLoader.class).to(CompatibilityDataChannelDescriptorLoader.class);
+                binder.bind(DataMapLoader.class).to(CompatibilityDataMapLoader.class);
+
+                binder.bind(HandlerFactory.class).to(DefaultHandlerFactory.class);
+                binder.bind(ConfigurationNameMapper.class).to(DefaultConfigurationNameMapper.class);
+                binder.bind(AdhocObjectFactory.class).to(DefaultAdhocObjectFactory.class);
+                binder.bind(ClassLoaderManager.class).to(DefaultClassLoaderManager.class);
+
+                binder.bindList(UpgradeHandler.class)
+                        .add(UpgradeHandler_V7.class)
+                        .add(UpgradeHandler_V8.class)
+                        .add(UpgradeHandler_V9.class)
+                        .add(UpgradeHandler_V10.class);
+
+                binder.bind(ProjectSaver.class).toInstance(mock(ProjectSaver.class));
+            }
+        });
+
+        DataChannelDescriptorLoader loader = injector.getInstance(DataChannelDescriptorLoader.class);
+        assertTrue(loader instanceof CompatibilityDataChannelDescriptorLoader);
+
+        URL resourceUrl = getClass().getResource("../../project/compatibility/cayenne-project-v6.xml");
+        Resource resource = new URLResource(resourceUrl);
+
+        ConfigurationTree<DataChannelDescriptor> configurationTree = loader.load(resource);
+        assertNotNull(configurationTree.getRootNode());
+        assertTrue(configurationTree.getLoadFailures().isEmpty());
+        assertEquals(1, configurationTree.getRootNode().getDataMaps().size());
+
+        DataMap dataMap = configurationTree.getRootNode().getDataMaps().iterator().next();
+        assertEquals(1, dataMap.getDbEntities().size());
+        assertEquals(1, dataMap.getObjEntities().size());
+        assertNotNull(dataMap.getObjEntity("Artist"));
+        assertNotNull(dataMap.getDbEntity("Artist"));
+        assertEquals(2, dataMap.getDbEntity("Artist").getAttributes().size());
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/3f375f38/cayenne-project-compatibility/src/test/java/org/apache/cayenne/project/compatibility/CompatibilityUpgradeServiceIT.java
----------------------------------------------------------------------
diff --git a/cayenne-project-compatibility/src/test/java/org/apache/cayenne/project/compatibility/CompatibilityUpgradeServiceIT.java b/cayenne-project-compatibility/src/test/java/org/apache/cayenne/project/compatibility/CompatibilityUpgradeServiceIT.java
new file mode 100644
index 0000000..8bfbea4
--- /dev/null
+++ b/cayenne-project-compatibility/src/test/java/org/apache/cayenne/project/compatibility/CompatibilityUpgradeServiceIT.java
@@ -0,0 +1,90 @@
+/*****************************************************************
+ *   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.compatibility;
+
+import java.net.URL;
+
+import org.apache.cayenne.configuration.DataChannelDescriptorLoader;
+import org.apache.cayenne.di.Binder;
+import org.apache.cayenne.di.DIBootstrap;
+import org.apache.cayenne.di.Injector;
+import org.apache.cayenne.di.Module;
+import org.apache.cayenne.project.ProjectSaver;
+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.resource.Resource;
+import org.apache.cayenne.resource.URLResource;
+import org.junit.Test;
+import org.w3c.dom.Document;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.mock;
+
+/**
+ * @since 4.1
+ */
+public class CompatibilityUpgradeServiceIT {
+
+    @Test
+    public void testUpgradeDom() throws Exception {
+        Injector injector = DIBootstrap.createInjector(new Module() {
+            @Override
+            public void configure(Binder binder) {
+                binder.bind(UpgradeService.class).to(CompatibilityUpgradeService.class);
+                binder.bind(DocumentProvider.class).to(DefaultDocumentProvider.class);
+                binder.bindList(UpgradeHandler.class)
+                        .add(UpgradeHandler_V7.class)
+                        .add(UpgradeHandler_V8.class)
+                        .add(UpgradeHandler_V9.class)
+                        .add(UpgradeHandler_V10.class);
+
+                binder.bind(ProjectSaver.class).toInstance(mock(ProjectSaver.class));
+                binder.bind(DataChannelDescriptorLoader.class).toInstance(mock(DataChannelDescriptorLoader.class));
+            }
+        });
+
+        CompatibilityUpgradeService upgradeService = (CompatibilityUpgradeService)injector
+                .getInstance(UpgradeService.class);
+
+        DocumentProvider documentProvider = injector.getInstance(DocumentProvider.class);
+
+        URL resourceUrl = getClass().getResource("cayenne-project-v6.xml");
+        Resource resource = new URLResource(resourceUrl);
+        upgradeService.upgradeProject(resource);
+
+        Document domainDocument = documentProvider.getDocument(resourceUrl);
+
+        assertNotNull(domainDocument);
+        assertEquals("10", domainDocument.getDocumentElement().getAttribute("project-version"));
+
+        URL dataMapUrl = getClass().getResource("test-map-v6.map.xml");
+        Document dataMapDocument = documentProvider.getDocument(dataMapUrl);
+        assertNotNull(dataMapDocument);
+        assertEquals("10", dataMapDocument.getDocumentElement().getAttribute("project-version"));
+        assertEquals(1, dataMapDocument.getElementsByTagName("obj-entity").getLength());
+        assertEquals(1, dataMapDocument.getElementsByTagName("db-entity").getLength());
+        assertEquals(2, dataMapDocument.getElementsByTagName("db-attribute").getLength());
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cayenne/blob/3f375f38/cayenne-project-compatibility/src/test/java/org/apache/cayenne/project/compatibility/ProjectCompatibilityModuleProviderTest.java
----------------------------------------------------------------------
diff --git a/cayenne-project-compatibility/src/test/java/org/apache/cayenne/project/compatibility/ProjectCompatibilityModuleProviderTest.java b/cayenne-project-compatibility/src/test/java/org/apache/cayenne/project/compatibility/ProjectCompatibilityModuleProviderTest.java
new file mode 100644
index 0000000..2cc5de3
--- /dev/null
+++ b/cayenne-project-compatibility/src/test/java/org/apache/cayenne/project/compatibility/ProjectCompatibilityModuleProviderTest.java
@@ -0,0 +1,35 @@
+/*****************************************************************
+ *   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.compatibility;
+
+import org.apache.cayenne.configuration.server.CayenneServerModuleProvider;
+import org.apache.cayenne.unit.util.ModuleProviderChecker;
+import org.junit.Test;
+
+/**
+ * @since 4.1
+ */
+public class ProjectCompatibilityModuleProviderTest {
+
+    @Test
+    public void testServerAutoLoadable() {
+        ModuleProviderChecker.testProviderPresent(ProjectCompatibilityModuleProvider.class, CayenneServerModuleProvider.class);
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/3f375f38/cayenne-project-compatibility/src/test/resources/org/apache/cayenne/project/compatibility/cayenne-project-v6.xml
----------------------------------------------------------------------
diff --git a/cayenne-project-compatibility/src/test/resources/org/apache/cayenne/project/compatibility/cayenne-project-v6.xml b/cayenne-project-compatibility/src/test/resources/org/apache/cayenne/project/compatibility/cayenne-project-v6.xml
new file mode 100644
index 0000000..772c3bc
--- /dev/null
+++ b/cayenne-project-compatibility/src/test/resources/org/apache/cayenne/project/compatibility/cayenne-project-v6.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+  ~   Licensed to the Apache Software Foundation (ASF) under one
+  ~  or more contributor license agreements.  See the NOTICE file
+  ~  distributed with this work for additional information
+  ~  regarding copyright ownership.  The ASF licenses this file
+  ~  to you under the Apache License, Version 2.0 (the
+  ~  "License"); you may not use this file except in compliance
+  ~  with the License.  You may obtain a copy of the License at
+  ~
+  ~    http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~  Unless required by applicable law or agreed to in writing,
+  ~  software distributed under the License is distributed on an
+  ~  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~  KIND, either express or implied.  See the License for the
+  ~  specific language governing permissions and limitations
+  ~  under the License.
+  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~-->
+
+<domain project-version="6">
+
+	<property name="cayenne.DataDomain.usingExternalTransactions" value="true"/>
+
+	<map name="test-map-v6" />
+
+	<node name="testProjectNode1"
+		adapter="org.example.test.Adapter" factory="org.example.test.DataSourceFactory">
+		<data-source>
+			<connectionPool min="1" max="1" />
+			<login />
+		</data-source>
+		<map-ref name="test-map-v6" />
+	</node>
+</domain>

http://git-wip-us.apache.org/repos/asf/cayenne/blob/3f375f38/cayenne-project-compatibility/src/test/resources/org/apache/cayenne/project/compatibility/test-map-v6.map.xml
----------------------------------------------------------------------
diff --git a/cayenne-project-compatibility/src/test/resources/org/apache/cayenne/project/compatibility/test-map-v6.map.xml b/cayenne-project-compatibility/src/test/resources/org/apache/cayenne/project/compatibility/test-map-v6.map.xml
new file mode 100644
index 0000000..25167f4
--- /dev/null
+++ b/cayenne-project-compatibility/src/test/resources/org/apache/cayenne/project/compatibility/test-map-v6.map.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<data-map xmlns="http://cayenne.apache.org/schema/3.0/modelMap"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://cayenne.apache.org/schema/3.0/modelMap http://cayenne.apache.org/schema/3.0/modelMap.xsd"
+	project-version="6">
+<db-entity name="Artist">
+        <db-attribute name="attributeOne" type="CHAR" isPrimaryKey="true" isMandatory="true" length="10"/>
+        <db-attribute name="attributeTwo" type="DATE"/>
+    </db-entity>
+    <obj-entity name="Artist" dbEntityName="Artist">
+        <entity-listener class="EntityListener">
+            <post-add method-name="prePersistEntityListener"/>
+			<post-persist method-name="postPersistEntityListener"/>
+			<pre-update method-name="preUpdateEntityListener"/>
+			<post-update method-name="postUpdateEntityListener"/>
+			<pre-remove method-name="preRemoveEntityListener"/>
+			<post-remove method-name="postRemoveEntityListener"/>
+			<post-load method-name="postLoadEntityListener"/>
+        </entity-listener>
+    </obj-entity>
+</data-map>

http://git-wip-us.apache.org/repos/asf/cayenne/blob/3f375f38/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
index aafb3d2..4e492e0 100644
--- 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
@@ -19,18 +19,15 @@
 
 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.Collection;
 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;
@@ -127,7 +124,7 @@ public class DefaultUpgradeService implements UpgradeService {
         return metaData;
     }
 
-    List<UpgradeHandler> getHandlersForVersion(String version) {
+    protected List<UpgradeHandler> getHandlersForVersion(String version) {
         boolean found = MIN_SUPPORTED_VERSION.equals(version);
         List<UpgradeHandler> handlerList = new ArrayList<>();
 
@@ -150,41 +147,50 @@ public class DefaultUpgradeService implements UpgradeService {
     public Resource upgradeProject(Resource resource) {
         List<UpgradeHandler> handlerList = getHandlersForVersion(loadProjectVersion(resource));
 
-        resource = upgradeDOM(resource, handlerList);
-        upgradeModel(resource, handlerList);
+        List<UpgradeUnit> upgradeUnits = upgradeDOM(resource, handlerList);
+        saveDOM(upgradeUnits);
+
+        resource = upgradeUnits.get(0).getResource();
+
+        ConfigurationTree<DataChannelDescriptor> configurationTree = upgradeModel(resource, handlerList);
+        saveModel(configurationTree);
 
         return resource;
     }
 
-    Resource upgradeDOM(Resource resource, List<UpgradeHandler> handlerList) {
+    protected List<UpgradeUnit> upgradeDOM(Resource resource, List<UpgradeHandler> handlerList) {
+        List<UpgradeUnit> allUnits = new ArrayList<>();
+
         // Load DOM for all resources
-        Document projectDocument = readDocument(resource);
+        Document projectDocument = Util.readDocument(resource.getURL());
         UpgradeUnit projectUnit = new UpgradeUnit(resource, projectDocument);
+        allUnits.add(projectUnit);
 
         List<Resource> dataMapResources = getAdditionalDatamapResources(projectUnit);
-        List<UpgradeUnit> upgradeUnits = new ArrayList<>(dataMapResources.size());
+        List<UpgradeUnit> dataMapUnits = new ArrayList<>(dataMapResources.size());
         for (Resource dataMapResource : dataMapResources) {
-            upgradeUnits.add(new UpgradeUnit(dataMapResource, readDocument(dataMapResource)));
+            dataMapUnits.add(new UpgradeUnit(dataMapResource, Util.readDocument(dataMapResource.getURL())));
         }
+        allUnits.addAll(dataMapUnits);
 
         // Update DOM
         for(UpgradeHandler handler : handlerList) {
             handler.processProjectDom(projectUnit);
-            for(UpgradeUnit dataMapUnit : upgradeUnits) {
+            for(UpgradeUnit dataMapUnit : dataMapUnits) {
                 handler.processDataMapDom(dataMapUnit);
             }
         }
 
-        // Save modified DOM back to original files
-        saveDocument(projectUnit);
-        for(UpgradeUnit dataMapUnit : upgradeUnits) {
-            saveDocument(dataMapUnit);
-        }
+        return allUnits;
+    }
 
-        return projectUnit.getResource();
+    protected void saveDOM(Collection<UpgradeUnit> upgradeUnits) {
+        for(UpgradeUnit unit : upgradeUnits) {
+            saveDocument(unit);
+        }
     }
 
-    void upgradeModel(Resource resource, List<UpgradeHandler> handlerList) {
+    protected ConfigurationTree<DataChannelDescriptor> upgradeModel(Resource resource, List<UpgradeHandler> handlerList) {
         // Load Model back from the update XML
         ConfigurationTree<DataChannelDescriptor> configurationTree = loader.load(resource);
 
@@ -193,6 +199,10 @@ public class DefaultUpgradeService implements UpgradeService {
             handler.processModel(configurationTree.getRootNode());
         }
 
+        return configurationTree;
+    }
+
+    protected void saveModel(ConfigurationTree<DataChannelDescriptor> configurationTree) {
         // Save project once again via project saver, this will normalize XML to minimize final diff
         Project project = new Project(configurationTree);
         projectSaver.save(project);
@@ -228,21 +238,6 @@ public class DefaultUpgradeService implements UpgradeService {
         }
     }
 
-    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.

http://git-wip-us.apache.org/repos/asf/cayenne/blob/3f375f38/cayenne-project/src/test/java/org/apache/cayenne/project/upgrade/DefaultUpgradeServiceTest.java
----------------------------------------------------------------------
diff --git a/cayenne-project/src/test/java/org/apache/cayenne/project/upgrade/DefaultUpgradeServiceTest.java b/cayenne-project/src/test/java/org/apache/cayenne/project/upgrade/DefaultUpgradeServiceTest.java
index e3a0342..7391d2a 100644
--- a/cayenne-project/src/test/java/org/apache/cayenne/project/upgrade/DefaultUpgradeServiceTest.java
+++ b/cayenne-project/src/test/java/org/apache/cayenne/project/upgrade/DefaultUpgradeServiceTest.java
@@ -30,6 +30,7 @@ import javax.xml.parsers.DocumentBuilderFactory;
 import org.apache.cayenne.project.upgrade.handlers.UpgradeHandler;
 import org.apache.cayenne.resource.Resource;
 import org.apache.cayenne.resource.URLResource;
+import org.apache.cayenne.util.Util;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentMatchers;
@@ -114,26 +115,25 @@ public class DefaultUpgradeServiceTest {
         upgradeService = mock(DefaultUpgradeService.class);
         when(upgradeService.upgradeDOM(any(Resource.class), ArgumentMatchers.<UpgradeHandler>anyList()))
                 .thenCallRealMethod();
-        when(upgradeService.readDocument(any(Resource.class)))
-                .thenCallRealMethod();
         when(upgradeService.getAdditionalDatamapResources(any(UpgradeUnit.class)))
                 .thenCallRealMethod();
 
         upgradeService.upgradeDOM(resource, handlers);
 
         // one for project and two for data maps
-        verify(upgradeService, times(3)).saveDocument(any(UpgradeUnit.class));
+//        verify(upgradeService, times(3)).saveDocument(any(UpgradeUnit.class));
         for(UpgradeHandler handler : handlers) {
+            verify(handler).getVersion();
             verify(handler).processProjectDom(any(UpgradeUnit.class));
             // two data maps
             verify(handler, times(2)).processDataMapDom(any(UpgradeUnit.class));
+            verifyNoMoreInteractions(handler);
         }
     }
 
     @Test
     public void readDocument() throws Exception {
-        Document document = upgradeService
-                .readDocument(new URLResource(getClass().getResource("../cayenne-PROJECT1.xml")));
+        Document document = Util.readDocument(getClass().getResource("../cayenne-PROJECT1.xml"));
         assertEquals("10", document.getDocumentElement().getAttribute("project-version"));
     }
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/3f375f38/cayenne-server/src/main/java/org/apache/cayenne/configuration/xml/XMLDataMapLoader.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/configuration/xml/XMLDataMapLoader.java b/cayenne-server/src/main/java/org/apache/cayenne/configuration/xml/XMLDataMapLoader.java
index eb82d32..68b236d 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/configuration/xml/XMLDataMapLoader.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/configuration/xml/XMLDataMapLoader.java
@@ -40,7 +40,7 @@ public class XMLDataMapLoader implements DataMapLoader {
     @Inject
     protected HandlerFactory handlerFactory;
 
-    private DataMap map;
+    DataMap map;
 
     public synchronized DataMap load(Resource configurationResource) throws CayenneRuntimeException {
         try(InputStream in = configurationResource.getURL().openStream()) {

http://git-wip-us.apache.org/repos/asf/cayenne/blob/3f375f38/cayenne-server/src/main/java/org/apache/cayenne/util/Util.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/util/Util.java b/cayenne-server/src/main/java/org/apache/cayenne/util/Util.java
index 7dfd57e..ff477ff 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/util/Util.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/util/Util.java
@@ -20,6 +20,7 @@
 package org.apache.cayenne.util;
 
 import org.apache.cayenne.Cayenne;
+import org.apache.cayenne.ConfigurationException;
 import org.apache.cayenne.PersistenceState;
 import org.apache.cayenne.Persistent;
 import org.apache.cayenne.di.AdhocObjectFactory;
@@ -30,9 +31,12 @@ import org.apache.cayenne.reflect.AttributeProperty;
 import org.apache.cayenne.reflect.PropertyVisitor;
 import org.apache.cayenne.reflect.ToManyProperty;
 import org.apache.cayenne.reflect.ToOneProperty;
+import org.w3c.dom.Document;
 import org.xml.sax.SAXException;
 import org.xml.sax.XMLReader;
 
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
 import javax.xml.parsers.ParserConfigurationException;
 import javax.xml.parsers.SAXParser;
 import javax.xml.parsers.SAXParserFactory;
@@ -42,6 +46,7 @@ import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileReader;
 import java.io.IOException;
+import java.io.InputStream;
 import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
 import java.io.Serializable;
@@ -275,6 +280,26 @@ public class Util {
 	}
 
 	/**
+	 * @since 4.1
+	 * @param url to read
+	 * @return org.w3c.dom.Document from the given URL
+	 */
+	public static Document readDocument(URL url) {
+		DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
+		documentBuilderFactory.setNamespaceAware(false);
+		try {
+			DocumentBuilder domBuilder = documentBuilderFactory.newDocumentBuilder();
+			try (InputStream inputStream = url.openStream()) {
+				return domBuilder.parse(inputStream);
+			} catch (IOException | SAXException e) {
+				throw new ConfigurationException("Error loading configuration from %s", e, url);
+			}
+		} catch (ParserConfigurationException e) {
+			throw new ConfigurationException(e);
+		}
+	}
+
+	/**
 	 * Returns package name for the Java class as a path separated with forward
 	 * slash ("/"). Method is used to lookup resources that are located in
 	 * package subdirectories. For example, a String "a/b/c" will be returned

http://git-wip-us.apache.org/repos/asf/cayenne/blob/3f375f38/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index 99e972b..92f39f0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -66,6 +66,7 @@
 		<module>cayenne-joda</module>
 		<module>cayenne-lifecycle</module>
 		<module>cayenne-project</module>
+		<module>cayenne-project-compatibility</module>
 		<module>cayenne-server</module>
 		<module>eventbridges</module>
 		<module>itests</module>


Mime
View raw message