Author: aadamchik
Date: Sun May 15 09:59:57 2011
New Revision: 1103306

URL: http://svn.apache.org/viewvc?rev=1103306&view=rev
Log:
CAY-943 Support multiple cayenne.xml files in the project

(finished)

* merging names
* merging DataMaps
* merging DataNodes
* merging DataNode to DataMap links
* merging properties

Added:
    
cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/configuration/DefaultDataChannelDescriptorMergerTest.java
Modified:
    cayenne/main/trunk/docs/doc/src/main/resources/RELEASE-NOTES.txt
    
cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/configuration/DataChannelDescriptor.java
    
cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/configuration/DefaultDataChannelDescriptorMerger.java

Modified: cayenne/main/trunk/docs/doc/src/main/resources/RELEASE-NOTES.txt
URL: 
http://svn.apache.org/viewvc/cayenne/main/trunk/docs/doc/src/main/resources/RELEASE-NOTES.txt?rev=1103306&r1=1103305&r2=1103306&view=diff
==============================================================================
--- cayenne/main/trunk/docs/doc/src/main/resources/RELEASE-NOTES.txt (original)
+++ cayenne/main/trunk/docs/doc/src/main/resources/RELEASE-NOTES.txt Sun May 15 
09:59:57 2011
@@ -13,6 +13,7 @@ Date: 
 ----------------------------------
 Changes/New Features Since 3.1M2:
 
+CAY-943 Support multiple cayenne.xml files in the project
 CAY-1556 Add path construction feature to make constructing paths from 
constants easier for queries and orderings
 CAY-1525 CharType: don't trim spaces on the left
 CAY-1544 Remove jdk1.6 module from Cayenne sources

Modified: 
cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/configuration/DataChannelDescriptor.java
URL: 
http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/configuration/DataChannelDescriptor.java?rev=1103306&r1=1103305&r2=1103306&view=diff
==============================================================================
--- 
cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/configuration/DataChannelDescriptor.java
 (original)
+++ 
cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/configuration/DataChannelDescriptor.java
 Sun May 15 09:59:57 2011
@@ -36,7 +36,8 @@ import org.apache.cayenne.util.XMLSerial
  * 
  * @since 3.1
  */
-public class DataChannelDescriptor implements ConfigurationNode, Serializable, 
XMLSerializable {
+public class DataChannelDescriptor implements ConfigurationNode, Serializable,
+        XMLSerializable {
 
     protected String name;
     protected Map<String, String> properties;
@@ -140,6 +141,16 @@ public class DataChannelDescriptor imple
         return nodeDescriptors;
     }
 
+    public DataNodeDescriptor getNodeDescriptor(String name) {
+        for (DataNodeDescriptor node : nodeDescriptors) {
+            if (name.equals(node.getName())) {
+                return node;
+            }
+        }
+
+        return null;
+    }
+
     public Resource getConfigurationSource() {
         return configurationSource;
     }

Modified: 
cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/configuration/DefaultDataChannelDescriptorMerger.java
URL: 
http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/configuration/DefaultDataChannelDescriptorMerger.java?rev=1103306&r1=1103305&r2=1103306&view=diff
==============================================================================
--- 
cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/configuration/DefaultDataChannelDescriptorMerger.java
 (original)
+++ 
cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/configuration/DefaultDataChannelDescriptorMerger.java
 Sun May 15 09:59:57 2011
@@ -18,11 +18,39 @@
  ****************************************************************/
 package org.apache.cayenne.configuration;
 
+import org.apache.cayenne.map.DataMap;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
 /**
+ * A default implementation of {@link DataChannelDescriptorMerger}. The 
general rule of
+ * merge is that the order of descriptors on the merge list matters. If there 
are two
+ * conflicting metadata objects belonging to two descriptors, an object from 
the last
+ * descriptor takes precedence over the object from the first one. This way it 
is easy to
+ * override pieces of metadata. This is also similar to how DI modules are 
merged in
+ * Cayenne. So this is how the merge works:
+ * <ul>
+ * <li></li>
+ * <li>Merged descriptor name is the same as the name of the last descriptor 
on the merge
+ * list.</li> *
+ * <li>Merged descriptor properties are the same as the properties of the last 
descriptor
+ * on the merge list. I.e. properties are not merged to avoid invalid 
combinations and
+ * unexpected runtime behavior.</li>
+ * <li>If there are two or more DataMaps with the same name, only one DataMap 
is placed in
+ * the merged descriptor, the rest are discarded. DataMap with highest index 
in the
+ * descriptor array is chosen per precedence rule above.</li>
+ * <li>If there are two or more DataNodes with the same name, only one 
DataNodes is placed
+ * in the merged descriptor, the rest are discarded. DataNodes with highest 
index in the
+ * descriptor array is chosen per precedence rule above.</li>
+ * </ul>
+ * 
  * @since 3.1
  */
 public class DefaultDataChannelDescriptorMerger implements 
DataChannelDescriptorMerger {
 
+    private static Log logger = LogFactory
+            .getLog(DefaultDataChannelDescriptorMerger.class);
+
     public DataChannelDescriptor merge(DataChannelDescriptor... descriptors) {
         if (descriptors == null || descriptors.length == 0) {
             throw new IllegalArgumentException("Null or empty descriptors");
@@ -32,8 +60,91 @@ public class DefaultDataChannelDescripto
             return descriptors[0];
         }
 
-        throw new UnsupportedOperationException(
-                "Merging multiple descriptors is not yet implemented");
+        int len = descriptors.length;
+
+        // merge into a new descriptor; do not alter source descriptors
+        DataChannelDescriptor merged = new DataChannelDescriptor();
+        merged.setName(descriptors[len - 1].getName());
+        merged.getProperties().putAll(descriptors[len - 1].getProperties());
+
+        // iterate in reverse order to reduce add/remove operations
+        for (int i = len - 1; i >= 0; i--) {
+            DataChannelDescriptor descriptor = descriptors[i];
+
+            // DataMaps are merged by reference, as we don't change them
+            // TODO: they still have a link to the unmerged descriptor, is it 
bad?
+            for (DataMap map : descriptor.getDataMaps()) {
+
+                // report conflicting DataMap and leave the existing copy
+                DataMap existing = merged.getDataMap(map.getName());
+                if (existing != null) {
+
+                    logger.info("Discarding overridden DataMap '"
+                            + map.getName()
+                            + "' from descriptor '"
+                            + descriptor.getName()
+                            + "'");
+                }
+                else {
+
+                    logger.info("Using DataMap '"
+                            + map.getName()
+                            + "' from descriptor '"
+                            + descriptor.getName()
+                            + "' in merged descriptor");
+                    merged.getDataMaps().add(map);
+                }
+            }
+
+            // DataNodes are merged by copy as we may modify them (changing 
map linking)
+            for (DataNodeDescriptor node : descriptor.getNodeDescriptors()) {
+
+                DataNodeDescriptor existing = 
merged.getNodeDescriptor(node.getName());
+                if (existing != null) {
+                    logger.info("Discarding overridden DataNode '"
+                            + node.getName()
+                            + "' from descriptor '"
+                            + descriptor.getName()
+                            + "'");
+
+                    for (String mapName : node.getDataMapNames()) {
+                        if (!existing.getDataMapNames().contains(mapName)) {
+                            existing.getDataMapNames().add(mapName);
+                        }
+                    }
+                }
+                else {
+                    logger.info("Using DataNode '"
+                            + node.getName()
+                            + "' from descriptor '"
+                            + descriptor.getName()
+                            + "' in merged descriptor");
+                    merged
+                            .getNodeDescriptors()
+                            .add(cloneDataNodeDescriptor(node, merged));
+                }
+            }
+        }
+
+        return merged;
     }
 
+    protected DataNodeDescriptor cloneDataNodeDescriptor(
+            DataNodeDescriptor original,
+            DataChannelDescriptor targetOwner) {
+        DataNodeDescriptor clone = new DataNodeDescriptor(original.getName());
+
+        // do not clone 'configurationSource' as we may change the structure 
of the node
+
+        clone.setAdapterType(original.getAdapterType());
+        clone.setDataChannelDescriptor(targetOwner);
+        clone.setDataSourceDescriptor(original.getDataSourceDescriptor());
+        clone.setDataSourceFactoryType(original.getDataSourceFactoryType());
+        clone.setParameters(original.getParameters());
+        
clone.setSchemaUpdateStrategyType(original.getSchemaUpdateStrategyType());
+
+        clone.getDataMapNames().addAll(original.getDataMapNames());
+
+        return clone;
+    }
 }

Added: 
cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/configuration/DefaultDataChannelDescriptorMergerTest.java
URL: 
http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/configuration/DefaultDataChannelDescriptorMergerTest.java?rev=1103306&view=auto
==============================================================================
--- 
cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/configuration/DefaultDataChannelDescriptorMergerTest.java
 (added)
+++ 
cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/configuration/DefaultDataChannelDescriptorMergerTest.java
 Sun May 15 09:59:57 2011
@@ -0,0 +1,165 @@
+/*****************************************************************
+ *   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;
+
+import org.apache.cayenne.map.DataMap;
+
+import junit.framework.TestCase;
+
+public class DefaultDataChannelDescriptorMergerTest extends TestCase {
+
+    public void testSingleDescriptor() {
+        DataChannelDescriptor descriptor = new DataChannelDescriptor();
+        descriptor.setName("Zx");
+
+        DefaultDataChannelDescriptorMerger merger = new 
DefaultDataChannelDescriptorMerger();
+
+        DataChannelDescriptor merged = merger.merge(descriptor);
+        assertSame(descriptor, merged);
+        assertEquals("Zx", merged.getName());
+    }
+
+    public void testMerged_Name() {
+        DataChannelDescriptor d1 = new DataChannelDescriptor();
+        d1.setName("Zx");
+
+        DataChannelDescriptor d2 = new DataChannelDescriptor();
+        d2.setName("Ym");
+
+        DefaultDataChannelDescriptorMerger merger = new 
DefaultDataChannelDescriptorMerger();
+
+        DataChannelDescriptor merged = merger.merge(d1, d2);
+        assertNotSame(d1, merged);
+        assertNotSame(d2, merged);
+        assertEquals("Ym", merged.getName());
+    }
+    
+    public void testMerged_Properties() {
+        DataChannelDescriptor d1 = new DataChannelDescriptor();
+        d1.getProperties().put("X", "1");
+        d1.getProperties().put("Y", "2");
+
+        DataChannelDescriptor d2 = new DataChannelDescriptor();
+        d2.getProperties().put("X", "3");
+        d2.getProperties().put("Z", "4");
+
+        DefaultDataChannelDescriptorMerger merger = new 
DefaultDataChannelDescriptorMerger();
+
+        DataChannelDescriptor merged = merger.merge(d1, d2);
+        assertEquals(2, merged.getProperties().size());
+        assertEquals("3", merged.getProperties().get("X"));
+        assertEquals("4", merged.getProperties().get("Z"));
+    }
+
+    public void testMerged_DataMaps() {
+        DataChannelDescriptor d1 = new DataChannelDescriptor();
+        d1.setName("Zx");
+        DataMap m11 = new DataMap("A");
+        DataMap m12 = new DataMap("B");
+        d1.getDataMaps().add(m11);
+        d1.getDataMaps().add(m12);
+
+        DataChannelDescriptor d2 = new DataChannelDescriptor();
+        d2.setName("Ym");
+        DataMap m21 = new DataMap("C");
+        DataMap m22 = new DataMap("A");
+        d2.getDataMaps().add(m21);
+        d2.getDataMaps().add(m22);
+
+        DefaultDataChannelDescriptorMerger merger = new 
DefaultDataChannelDescriptorMerger();
+
+        DataChannelDescriptor merged = merger.merge(d1, d2);
+
+        assertEquals(3, merged.getDataMaps().size());
+        assertSame(m22, merged.getDataMap("A"));
+        assertSame(m12, merged.getDataMap("B"));
+        assertSame(m21, merged.getDataMap("C"));
+    }
+
+    public void testMerge_DataNodes() {
+        DataChannelDescriptor d1 = new DataChannelDescriptor();
+        d1.setName("Zx");
+        DataNodeDescriptor dn11 = new DataNodeDescriptor("A");
+        DataNodeDescriptor dn12 = new DataNodeDescriptor("B");
+        dn12.setAdapterType("Xa");
+        d1.getNodeDescriptors().add(dn11);
+        d1.getNodeDescriptors().add(dn12);
+
+        DataChannelDescriptor d2 = new DataChannelDescriptor();
+        d2.setName("Ym");
+        DataNodeDescriptor dn21 = new DataNodeDescriptor("B");
+        dn21.setAdapterType("Uy");
+        DataNodeDescriptor dn22 = new DataNodeDescriptor("C");
+        d2.getNodeDescriptors().add(dn21);
+        d2.getNodeDescriptors().add(dn22);
+
+        DefaultDataChannelDescriptorMerger merger = new 
DefaultDataChannelDescriptorMerger();
+
+        DataChannelDescriptor merged = merger.merge(d1, d2);
+
+        assertEquals(3, merged.getNodeDescriptors().size());
+
+        // DataNodes are merged by copy .. so check they are not same as 
originals
+        DataNodeDescriptor mergedA = merged.getNodeDescriptor("A");
+        assertNotNull(mergedA);
+        assertNotSame(dn11, mergedA);
+
+        DataNodeDescriptor mergedB = merged.getNodeDescriptor("B");
+        assertNotNull(mergedB);
+        assertNotSame(dn12, mergedB);
+        assertNotSame(dn21, mergedB);
+        assertEquals("Uy", mergedB.getAdapterType());
+
+        DataNodeDescriptor mergedC = merged.getNodeDescriptor("C");
+        assertNotNull(mergedC);
+        assertNotSame(dn22, mergedC);
+    }
+
+    public void testMerge_DataNodesMapLinks() {
+        DataChannelDescriptor d1 = new DataChannelDescriptor();
+        d1.setName("Zx");
+        DataNodeDescriptor dn11 = new DataNodeDescriptor("A");
+        dn11.getDataMapNames().add("MA");
+        dn11.getDataMapNames().add("MB");
+        d1.getNodeDescriptors().add(dn11);
+
+        DataChannelDescriptor d2 = new DataChannelDescriptor();
+        d2.setName("Ym");
+        DataNodeDescriptor dn21 = new DataNodeDescriptor("A");
+        dn21.getDataMapNames().add("MA");
+        dn21.getDataMapNames().add("MC");
+        d2.getNodeDescriptors().add(dn21);
+
+        DefaultDataChannelDescriptorMerger merger = new 
DefaultDataChannelDescriptorMerger();
+
+        DataChannelDescriptor merged = merger.merge(d1, d2);
+
+        assertEquals(1, merged.getNodeDescriptors().size());
+
+        // DataNodes are merged by copy .. so check they are not same as 
originals
+        DataNodeDescriptor mergedA = merged.getNodeDescriptor("A");
+        assertNotNull(mergedA);
+        assertNotSame(dn11, mergedA);
+        assertNotSame(dn21, mergedA);
+        assertEquals(3, mergedA.getDataMapNames().size());
+        assertTrue(mergedA.getDataMapNames().contains("MA"));
+        assertTrue(mergedA.getDataMapNames().contains("MB"));
+        assertTrue(mergedA.getDataMapNames().contains("MC"));
+    }
+}


Reply via email to