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")); + } +}