Merge "Resolve Bug:745 - remove windows line endings from config and netconf."
authorTony Tkacik <ttkacik@cisco.com>
Wed, 16 Apr 2014 08:28:40 +0000 (08:28 +0000)
committerGerrit Code Review <gerrit@opendaylight.org>
Wed, 16 Apr 2014 08:28:40 +0000 (08:28 +0000)
26 files changed:
opendaylight/commons/opendaylight/pom.xml
opendaylight/config/config-persister-directory-xml-adapter/pom.xml
opendaylight/config/config-persister-directory-xml-adapter/src/test/java/org/opendaylight/controller/config/persist/storage/directory/xml/DirectoryStorageAdapterTest.java
opendaylight/config/config-persister-file-xml-adapter/pom.xml
opendaylight/config/config-persister-file-xml-adapter/src/test/java/org/opendaylight/controller/config/persist/storage/file/xml/FileStorageAdapterTest.java
opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/AbstractForwardedDataBroker.java
opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/BindingToNormalizedNodeCodec.java
opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/md/sal/binding/impl/LegacyDataChangeEvent.java
opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/md/sal/binding/data/WildcardedDataChangeListenerTest.java [new file with mode: 0644]
opendaylight/md-sal/sal-common-impl/src/main/java/org/opendaylight/controller/md/sal/common/impl/util/compat/DataNormalizer.java
opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/DOMImmutableDataChangeEvent.java
opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/DataChangeEventResolver.java [deleted file]
opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/InMemoryDOMDataStore.java
opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/ResolveDataChangeEventsTask.java [new file with mode: 0644]
opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/tree/ListenerTree.java
opendaylight/netconf/config-netconf-connector/pom.xml
opendaylight/netconf/config-netconf-connector/src/test/java/org/opendaylight/controller/netconf/confignetconfconnector/NetconfMappingTest.java
opendaylight/netconf/netconf-impl/src/test/java/org/opendaylight/controller/netconf/impl/MessageParserTest.java
opendaylight/netconf/netconf-it/pom.xml
opendaylight/netconf/netconf-it/src/test/java/org/opendaylight/controller/netconf/it/NetconfConfigPersisterITTest.java
opendaylight/netconf/netconf-it/src/test/java/org/opendaylight/controller/netconf/it/NetconfMonitoringITTest.java
opendaylight/netconf/netconf-it/src/test/java/org/opendaylight/controller/netconf/it/pax/IdentityRefNetconfTest.java
opendaylight/netconf/netconf-it/src/test/resources/controller.xml
opendaylight/netconf/netconf-util/pom.xml
opendaylight/netconf/netconf-util/src/test/java/org/opendaylight/controller/netconf/util/test/XmlUnitUtil.java [new file with mode: 0644]
opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/rpc-reply_ok.xml [new file with mode: 0644]

index d5d7e8e5c468cac180549eb76248cc1a010ae3c5..140a6ddfe128785edde35853eadac67ae698bcd0 100644 (file)
         <artifactId>org.eclipse.equinox.launcher</artifactId>
         <version>1.3.0.v20120522-1813</version>
       </dependency>
+      <dependency>
+        <groupId>xmlunit</groupId>
+        <artifactId>xmlunit</artifactId>
+        <version>1.5</version>
+        <scope>test</scope>
+      </dependency>
       <!-- Gemini Web -->
       <dependency>
         <groupId>geminiweb</groupId>
index 30c5247004f3a4e704b6d7f068082d177bd6b66e..2b98f4137689163acd0c147eca75ce15c9aa3a44 100644 (file)
             <type>test-jar</type>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>xmlunit</groupId>
+            <artifactId>xmlunit</artifactId>
+        </dependency>
 
     </dependencies>
 
index da442ef4b2fb669b5b1bd0e9a389616cc6755b97..7af06bd3e39cdd341e917779961150b21af4a6f9 100644 (file)
@@ -8,21 +8,26 @@
 
 package org.opendaylight.controller.config.persist.storage.directory.xml;
 
+import static org.custommonkey.xmlunit.XMLAssert.assertXMLEqual;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
 import java.io.File;
+import java.io.IOException;
 import java.util.Collections;
 import java.util.List;
 import java.util.SortedSet;
 
-import com.google.common.base.Optional;
 import org.junit.Test;
 import org.opendaylight.controller.config.persist.api.ConfigSnapshotHolder;
 import org.opendaylight.controller.config.persist.api.Persister;
 import org.opendaylight.controller.config.persist.test.PropertiesProviderTest;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
+import org.xml.sax.SAXException;
+
+import com.google.common.base.Optional;
 
 public class DirectoryStorageAdapterTest {
     Persister tested;
@@ -95,8 +100,8 @@ public class DirectoryStorageAdapterTest {
         logger.info("Testing : " + tested.toString());
     }
 
-    private void assertResult(ConfigSnapshotHolder result, String s, String... caps) {
-        assertEquals(s, result.getConfigSnapshot().replaceAll("\\s", ""));
+    private void assertResult(ConfigSnapshotHolder result, String s, String... caps) throws SAXException, IOException {
+        assertXMLEqual(s, result.getConfigSnapshot());
         int i = 0;
         for (String capFromSnapshot : result.getCapabilities()) {
             assertEquals(capFromSnapshot, caps[i++]);
index 8796f0e49daa715ca7b86418d90ff8929503cad9..a5010f0464f7d5bc1f3df38d8a2c2e41cd714d15 100644 (file)
             <type>test-jar</type>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>xmlunit</groupId>
+            <artifactId>xmlunit</artifactId>
+        </dependency>
     </dependencies>
 
     <build>
index 5322f6357a01b0a3e01e1d58a8d943113b942562..2bfe70adb811d2c5575663540e4a194179f78dae 100644 (file)
@@ -8,7 +8,12 @@
 
 package org.opendaylight.controller.config.persist.storage.file.xml;
 
-import com.google.common.base.Charsets;
+import static junit.framework.Assert.assertFalse;
+import static org.custommonkey.xmlunit.XMLAssert.assertXMLEqual;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
@@ -16,16 +21,16 @@ import java.nio.file.Files;
 import java.util.List;
 import java.util.SortedSet;
 import java.util.TreeSet;
+
 import junit.framework.Assert;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.Mockito;
 import org.opendaylight.controller.config.persist.api.ConfigSnapshotHolder;
 import org.opendaylight.controller.config.persist.test.PropertiesProviderTest;
-import static junit.framework.Assert.assertFalse;
-import static org.hamcrest.CoreMatchers.is;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertThat;
+
+import com.google.common.base.Charsets;
 
 public class FileStorageAdapterTest {
 
@@ -96,8 +101,7 @@ public class FileStorageAdapterTest {
         List<ConfigSnapshotHolder> lastConf = storage.loadLastConfigs();
         assertEquals(1, lastConf.size());
         ConfigSnapshotHolder configSnapshotHolder = lastConf.get(0);
-        assertEquals("<config>2</config>",
-                configSnapshotHolder.getConfigSnapshot().replaceAll("\\s", ""));
+        assertXMLEqual("<config>2</config>", configSnapshotHolder.getConfigSnapshot());
         assertEquals(createCaps(), configSnapshotHolder.getCapabilities());
 
         storage = new XmlFileStorageAdapter();
@@ -147,8 +151,7 @@ public class FileStorageAdapterTest {
         List<ConfigSnapshotHolder> lastConf = storage.loadLastConfigs();
         assertEquals(1, lastConf.size());
         ConfigSnapshotHolder configSnapshotHolder = lastConf.get(0);
-        assertEquals("<config>2</config>",
-                configSnapshotHolder.getConfigSnapshot().replaceAll("\\s", ""));
+        assertXMLEqual("<config>2</config>", configSnapshotHolder.getConfigSnapshot());
     }
 
     @Test
@@ -178,8 +181,7 @@ public class FileStorageAdapterTest {
         List<ConfigSnapshotHolder> lastConf = storage.loadLastConfigs();
         assertEquals(1, lastConf.size());
         ConfigSnapshotHolder configSnapshotHolder = lastConf.get(0);
-        assertEquals("<config>3</config>",
-                configSnapshotHolder.getConfigSnapshot().replaceAll("\\s", ""));
+        assertXMLEqual("<config>3</config>", configSnapshotHolder.getConfigSnapshot());
         assertFalse(readLines.contains(holder.getConfigSnapshot()));
     }
 
index 8a32b0b3026f1d028dbab77a00e6c54be22d07ad..685a91979c7c9f8ea7c1493b047867622dfa8d81 100644 (file)
@@ -7,13 +7,13 @@
  */
 package org.opendaylight.controller.md.sal.binding.impl;
 
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
 
-import org.eclipse.xtext.xbase.lib.Exceptions;
 import org.opendaylight.controller.md.sal.binding.api.BindingDataChangeListener;
 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeEvent;
@@ -36,7 +36,11 @@ import org.opendaylight.yangtools.yang.model.api.SchemaContextListener;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-public abstract class AbstractForwardedDataBroker implements Delegator<DOMDataBroker>, DomForwardedBroker, SchemaContextListener {
+import com.google.common.base.Objects;
+import com.google.common.base.Optional;
+
+public abstract class AbstractForwardedDataBroker implements Delegator<DOMDataBroker>, DomForwardedBroker,
+        SchemaContextListener {
 
     private static final Logger LOG = LoggerFactory.getLogger(AbstractForwardedDataBroker.class);
     // The Broker to whom we do all forwarding
@@ -81,11 +85,12 @@ public abstract class AbstractForwardedDataBroker implements Delegator<DOMDataBr
         DOMDataChangeListener domDataChangeListener = new TranslatingDataChangeInvoker(store, path, listener,
                 triggeringScope);
         org.opendaylight.yangtools.yang.data.api.InstanceIdentifier domPath = codec.toNormalized(path);
-        ListenerRegistration<DOMDataChangeListener> domRegistration = domDataBroker.registerDataChangeListener(store, domPath, domDataChangeListener, triggeringScope);
+        ListenerRegistration<DOMDataChangeListener> domRegistration = domDataBroker.registerDataChangeListener(store,
+                domPath, domDataChangeListener, triggeringScope);
         return new ListenerRegistrationImpl(listener, domRegistration);
     }
 
-    protected Map<InstanceIdentifier<?>, DataObject> fromDOMToData(
+    protected Map<InstanceIdentifier<?>, DataObject> toBinding(
             final Map<org.opendaylight.yangtools.yang.data.api.InstanceIdentifier, ? extends NormalizedNode<?, ?>> normalized) {
         Map<InstanceIdentifier<?>, DataObject> newMap = new HashMap<>();
         for (Map.Entry<org.opendaylight.yangtools.yang.data.api.InstanceIdentifier, ? extends NormalizedNode<?, ?>> entry : normalized
@@ -94,12 +99,38 @@ public abstract class AbstractForwardedDataBroker implements Delegator<DOMDataBr
                 Entry<InstanceIdentifier<? extends DataObject>, DataObject> binding = getCodec().toBinding(entry);
                 newMap.put(binding.getKey(), binding.getValue());
             } catch (DeserializationException e) {
-                LOG.debug("Ommiting {}",entry,e);
+                LOG.debug("Omitting {}", entry, e);
             }
         }
         return newMap;
     }
 
+    protected Set<InstanceIdentifier<?>> toBinding(
+            final Set<org.opendaylight.yangtools.yang.data.api.InstanceIdentifier> normalized) {
+        Set<InstanceIdentifier<?>> hashSet = new HashSet<>();
+        for (org.opendaylight.yangtools.yang.data.api.InstanceIdentifier normalizedPath : normalized) {
+            try {
+                InstanceIdentifier<? extends DataObject> binding = getCodec().toBinding(normalizedPath);
+                hashSet.add(binding);
+            } catch (DeserializationException e) {
+                LOG.debug("Omitting {}", normalizedPath, e);
+            }
+        }
+        return hashSet;
+    }
+
+    protected Optional<DataObject> toBindingData(final InstanceIdentifier<?> path, final NormalizedNode<?, ?> data) {
+        if(path.isWildcarded()) {
+            return Optional.absent();
+        }
+
+        try {
+            return Optional.fromNullable(getCodec().toBinding(path, data));
+        } catch (DeserializationException e) {
+            return Optional.absent();
+        }
+    }
+
     private class TranslatingDataChangeInvoker implements DOMDataChangeListener {
         private final BindingDataChangeListener bindingDataChangeListener;
         private final LogicalDatastoreType store;
@@ -117,18 +148,20 @@ public abstract class AbstractForwardedDataBroker implements Delegator<DOMDataBr
         @Override
         public void onDataChanged(
                 final AsyncDataChangeEvent<org.opendaylight.yangtools.yang.data.api.InstanceIdentifier, NormalizedNode<?, ?>> change) {
-            bindingDataChangeListener.onDataChanged(new TranslatedDataChangeEvent(change,path));
+            bindingDataChangeListener.onDataChanged(new TranslatedDataChangeEvent(change, path));
         }
     }
 
     private class TranslatedDataChangeEvent implements AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> {
         private final AsyncDataChangeEvent<org.opendaylight.yangtools.yang.data.api.InstanceIdentifier, NormalizedNode<?, ?>> domEvent;
-        private InstanceIdentifier<?> path;
+        private final InstanceIdentifier<?> path;
 
-        public TranslatedDataChangeEvent(
-                final AsyncDataChangeEvent<org.opendaylight.yangtools.yang.data.api.InstanceIdentifier, NormalizedNode<?, ?>> change) {
-            this.domEvent = change;
-        }
+        private Map<InstanceIdentifier<?>, DataObject> createdCache;
+        private Map<InstanceIdentifier<?>, DataObject> updatedCache;
+        private Map<InstanceIdentifier<?>, ? extends DataObject> originalCache;
+        private Set<InstanceIdentifier<?>> removedCache;
+        private Optional<DataObject> originalDataCache;
+        private Optional<DataObject> updatedDataCache;
 
         public TranslatedDataChangeEvent(
                 final AsyncDataChangeEvent<org.opendaylight.yangtools.yang.data.api.InstanceIdentifier, NormalizedNode<?, ?>> change,
@@ -139,52 +172,63 @@ public abstract class AbstractForwardedDataBroker implements Delegator<DOMDataBr
 
         @Override
         public Map<InstanceIdentifier<?>, DataObject> getCreatedData() {
-            return fromDOMToData(domEvent.getCreatedData());
+            if (createdCache == null) {
+                createdCache = Collections.unmodifiableMap(toBinding(domEvent.getCreatedData()));
+            }
+            return createdCache;
         }
 
         @Override
         public Map<InstanceIdentifier<?>, DataObject> getUpdatedData() {
-            return fromDOMToData(domEvent.getUpdatedData());
+            if (updatedCache == null) {
+                updatedCache = Collections.unmodifiableMap(toBinding(domEvent.getUpdatedData()));
+            }
+            return updatedCache;
 
         }
 
         @Override
         public Set<InstanceIdentifier<?>> getRemovedPaths() {
-            final Set<org.opendaylight.yangtools.yang.data.api.InstanceIdentifier> removedPaths = domEvent
-                    .getRemovedPaths();
-            final Set<InstanceIdentifier<?>> output = new HashSet<>();
-            for (org.opendaylight.yangtools.yang.data.api.InstanceIdentifier instanceIdentifier : removedPaths) {
-                try {
-                    output.add(mappingService.fromDataDom(instanceIdentifier));
-                } catch (DeserializationException e) {
-                    Exceptions.sneakyThrow(e);
-                }
+            if (removedCache == null) {
+                removedCache = Collections.unmodifiableSet(toBinding(domEvent.getRemovedPaths()));
             }
-
-            return output;
+            return removedCache;
         }
 
         @Override
         public Map<InstanceIdentifier<?>, ? extends DataObject> getOriginalData() {
-            return fromDOMToData(domEvent.getOriginalData());
+            if (originalCache == null) {
+                originalCache = Collections.unmodifiableMap(toBinding(domEvent.getOriginalData()));
+            }
+            return originalCache;
 
         }
 
         @Override
         public DataObject getOriginalSubtree() {
-
-            return toBindingData(path,domEvent.getOriginalSubtree());
+            if (originalDataCache == null) {
+                originalDataCache = toBindingData(path, domEvent.getOriginalSubtree());
+            }
+            return originalDataCache.orNull();
         }
 
         @Override
         public DataObject getUpdatedSubtree() {
+            if (updatedDataCache == null) {
+                updatedDataCache = toBindingData(path, domEvent.getUpdatedSubtree());
+            }
 
-            return toBindingData(path,domEvent.getUpdatedSubtree());
+            return updatedDataCache.orNull();
         }
 
         @Override
         public String toString() {
-            return "TranslatedDataChangeEvent [domEvent=" + domEvent + "]";
+            return Objects.toStringHelper(TranslatedDataChangeEvent.class) //
+                .add("created", getCreatedData()) //
+                .add("updated", getUpdatedData()) //
+                .add("removed", getRemovedPaths()) //
+                .add("dom", domEvent) //
+                .toString();
         }
     }
 
@@ -203,15 +247,6 @@ public abstract class AbstractForwardedDataBroker implements Delegator<DOMDataBr
         }
     }
 
-    protected DataObject toBindingData(final InstanceIdentifier<?> path, final NormalizedNode<?, ?> data) {
-        try {
-            return getCodec().toBinding(path, data);
-        } catch (DeserializationException e) {
-            return null;
-        }
-    }
-
-
     @Override
     public BindingIndependentConnector getConnector() {
         return this.connector;
@@ -229,7 +264,7 @@ public abstract class AbstractForwardedDataBroker implements Delegator<DOMDataBr
 
     @Override
     public void setDomProviderContext(final ProviderSession domProviderContext) {
-       this.context = domProviderContext;
+        this.context = domProviderContext;
     }
 
     @Override
@@ -237,7 +272,4 @@ public abstract class AbstractForwardedDataBroker implements Delegator<DOMDataBr
         // NOOP
     }
 
-
-
-
 }
index f885b323e6c04c34d23356c50722c24dfc755ac7..35ebe76499f51e6bb5b629c4cf42a944fe727ea8 100644 (file)
@@ -10,6 +10,7 @@ package org.opendaylight.controller.md.sal.binding.impl;
 import java.util.AbstractMap.SimpleEntry;
 import java.util.Map.Entry;
 
+import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizationException;
 import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizer;
 import org.opendaylight.yangtools.yang.binding.Augmentation;
 import org.opendaylight.yangtools.yang.binding.DataObject;
@@ -43,7 +44,10 @@ public class BindingToNormalizedNodeCodec implements SchemaContextListener {
 
     public org.opendaylight.yangtools.yang.data.api.InstanceIdentifier toNormalized(
             final InstanceIdentifier<? extends DataObject> binding) {
-        return legacyToNormalized.toNormalized(bindingToLegacy.toDataDom(binding));
+        final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier legacyPath = bindingToLegacy.toDataDom(binding);
+        final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier normalized = legacyToNormalized.toNormalized(legacyPath);
+        LOG.trace("InstanceIdentifier Path {} Serialization: Legacy representation {}, Normalized representation: {}",binding,legacyPath,normalized);
+        return normalized;
     }
 
     public Entry<org.opendaylight.yangtools.yang.data.api.InstanceIdentifier, NormalizedNode<?, ?>> toNormalizedNode(
@@ -54,7 +58,9 @@ public class BindingToNormalizedNodeCodec implements SchemaContextListener {
 
     public Entry<org.opendaylight.yangtools.yang.data.api.InstanceIdentifier, NormalizedNode<?, ?>> toNormalizedNode(
             final Entry<org.opendaylight.yangtools.yang.binding.InstanceIdentifier<? extends DataObject>, DataObject> binding) {
-        Entry<org.opendaylight.yangtools.yang.data.api.InstanceIdentifier, NormalizedNode<?, ?>> normalizedEntry = legacyToNormalized.toNormalized(bindingToLegacy.toDataDom(binding));
+        Entry<org.opendaylight.yangtools.yang.data.api.InstanceIdentifier, CompositeNode> legacyEntry = bindingToLegacy.toDataDom(binding);
+        Entry<org.opendaylight.yangtools.yang.data.api.InstanceIdentifier, NormalizedNode<?, ?>> normalizedEntry = legacyToNormalized.toNormalized(legacyEntry);
+        LOG.trace("Serialization of {}, Legacy Representation: {}, Normalized Representation: {}",binding,legacyEntry,normalizedEntry);
         if(Augmentation.class.isAssignableFrom(binding.getKey().getTargetType())) {
 
             for(DataContainerChild<? extends PathArgument, ?> child : ((DataContainerNode<?>) normalizedEntry.getValue()).getValue()) {
@@ -78,8 +84,13 @@ public class BindingToNormalizedNodeCodec implements SchemaContextListener {
             final org.opendaylight.yangtools.yang.data.api.InstanceIdentifier normalized)
             throws DeserializationException {
 
-        org.opendaylight.yangtools.yang.data.api.InstanceIdentifier legacyPath = legacyToNormalized
-                .toLegacy(normalized);
+        org.opendaylight.yangtools.yang.data.api.InstanceIdentifier legacyPath;
+        try {
+            legacyPath = legacyToNormalized.toLegacy(normalized);
+        } catch (DataNormalizationException e) {
+            throw new IllegalStateException("Could not denormalize path.",e);
+        }
+        LOG.trace("InstanceIdentifier Path Deserialization: Legacy representation {}, Normalized representation: {}",legacyPath,normalized);
         return bindingToLegacy.fromDataDom(legacyPath);
     }
 
index 8cb4a70f9c2054495b965244436955e1eb67d3f3..ce1ff450c979775e2546d8c2463580abc7b57d5e 100644 (file)
@@ -8,6 +8,7 @@
 package org.opendaylight.controller.md.sal.binding.impl;
 
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
 
@@ -96,6 +97,7 @@ public abstract class LegacyDataChangeEvent implements
     private final static class OperationalChangeEvent extends LegacyDataChangeEvent {
 
         private final AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> delegate;
+        private Map<InstanceIdentifier<?>, DataObject> updatedCache;
 
         public OperationalChangeEvent(final AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> change) {
             this.delegate = change;
@@ -128,7 +130,15 @@ public abstract class LegacyDataChangeEvent implements
 
         @Override
         public Map<InstanceIdentifier<?>, DataObject> getUpdatedOperationalData() {
-            return delegate.getUpdatedData();
+            if(updatedCache == null) {
+                Map<InstanceIdentifier<?>, DataObject> created = delegate.getCreatedData();
+                Map<InstanceIdentifier<?>, DataObject> updated = delegate.getUpdatedData();
+                HashMap<InstanceIdentifier<?>, DataObject> updatedComposite = new HashMap<>(created.size() + updated.size());
+                updatedComposite.putAll(created);
+                updatedComposite.putAll(updated);
+                updatedCache = Collections.unmodifiableMap(updatedComposite);
+            }
+            return updatedCache;
         }
 
         @Override
@@ -142,6 +152,7 @@ public abstract class LegacyDataChangeEvent implements
     private final static class ConfigurationChangeEvent extends LegacyDataChangeEvent {
 
         private final AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> delegate;
+        private Map<InstanceIdentifier<?>, DataObject> updatedCache;
 
         public ConfigurationChangeEvent(final AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> change) {
             this.delegate = change;
@@ -174,7 +185,15 @@ public abstract class LegacyDataChangeEvent implements
 
         @Override
         public Map<InstanceIdentifier<?>, DataObject> getUpdatedConfigurationData() {
-            return delegate.getUpdatedData();
+            if(updatedCache == null) {
+                Map<InstanceIdentifier<?>, DataObject> created = delegate.getCreatedData();
+                Map<InstanceIdentifier<?>, DataObject> updated = delegate.getUpdatedData();
+                HashMap<InstanceIdentifier<?>, DataObject> updatedComposite = new HashMap<>(created.size() + updated.size());
+                updatedComposite.putAll(created);
+                updatedComposite.putAll(updated);
+                updatedCache = Collections.unmodifiableMap(updatedComposite);
+            }
+            return updatedCache;
         }
 
         @Override
diff --git a/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/md/sal/binding/data/WildcardedDataChangeListenerTest.java b/opendaylight/md-sal/sal-binding-dom-it/src/test/java/org/opendaylight/controller/md/sal/binding/data/WildcardedDataChangeListenerTest.java
new file mode 100644 (file)
index 0000000..450ca1c
--- /dev/null
@@ -0,0 +1,202 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.md.sal.binding.data;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Collections;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.junit.Test;
+import org.opendaylight.controller.md.sal.common.api.data.DataChangeEvent;
+import org.opendaylight.controller.sal.binding.api.data.DataChangeListener;
+import org.opendaylight.controller.sal.binding.api.data.DataModificationTransaction;
+import org.opendaylight.controller.sal.binding.api.data.DataProviderService;
+import org.opendaylight.controller.sal.binding.test.AbstractDataServiceTest;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNode;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowId;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.Table;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.TableBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.TableKey;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.Flow;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.FlowBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.FlowKey;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.Nodes;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.NodeKey;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.table.types.rev131026.table.features.TableFeatures;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.table.types.rev131026.table.features.TableFeaturesBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.table.types.rev131026.table.features.TableFeaturesKey;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+
+import com.google.common.util.concurrent.SettableFuture;
+
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+public class WildcardedDataChangeListenerTest extends AbstractDataServiceTest {
+
+    private static final NodeKey NODE_0_KEY = new NodeKey(new NodeId("test:0"));
+    private static final NodeKey NODE_1_KEY = new NodeKey(new NodeId("test:1"));
+
+    public static final InstanceIdentifier<Flow> DEEP_WILDCARDED_PATH = InstanceIdentifier.builder(Nodes.class)
+            .child(Node.class) //
+            .augmentation(FlowCapableNode.class) //
+            .child(Table.class) //
+            .child(Flow.class) //
+            .build();
+
+    private static final TableKey TABLE_0_KEY = new TableKey((short) 0);
+    private static final TableFeaturesKey TABLE_FEATURES_KEY = new TableFeaturesKey((short) 0);
+
+    private static final InstanceIdentifier<Table> NODE_0_TABLE_PATH = InstanceIdentifier.builder(Nodes.class)
+            .child(Node.class, NODE_0_KEY) //
+            .augmentation(FlowCapableNode.class) //
+            .child(Table.class, TABLE_0_KEY) //
+            .build();
+
+    private static final InstanceIdentifier<Table> NODE_1_TABLE_PATH = InstanceIdentifier.builder(Nodes.class)
+            .child(Node.class, NODE_1_KEY) //
+            .augmentation(FlowCapableNode.class) //
+            .child(Table.class, TABLE_0_KEY) //
+            .build();
+
+    private static final FlowKey FLOW_KEY = new FlowKey(new FlowId("test"));
+
+    private static final InstanceIdentifier<Flow> NODE_0_FLOW_PATH = InstanceIdentifier.builder(NODE_0_TABLE_PATH)
+            .child(Flow.class, FLOW_KEY).build();
+
+    private static final InstanceIdentifier<Flow> NODE_1_FLOW_PATH = InstanceIdentifier.builder(NODE_1_TABLE_PATH)
+            .child(Flow.class, FLOW_KEY).build();
+
+    private static final InstanceIdentifier<TableFeatures> NODE_0_TABLE_FEATURES_PATH = InstanceIdentifier
+            .builder(NODE_0_TABLE_PATH).child(TableFeatures.class, TABLE_FEATURES_KEY).build();
+
+    private static final TableFeatures TABLE_FEATURES = new TableFeaturesBuilder()//
+            .setKey(TABLE_FEATURES_KEY) //
+            .setName("Foo") //
+            .setMaxEntries(1000L) //
+            .build();
+
+    private static final Flow FLOW = new FlowBuilder() //
+            .setKey(FLOW_KEY) //
+            .setBarrier(true) //
+            .setStrict(true) //
+            .build();
+
+    @Test
+    public void testSepareteWrites() throws InterruptedException, TimeoutException, ExecutionException {
+
+        DataProviderService dataBroker = testContext.getBindingDataBroker();
+
+        final SettableFuture<DataChangeEvent<InstanceIdentifier<?>, DataObject>> eventFuture = SettableFuture.create();
+        dataBroker.registerDataChangeListener(DEEP_WILDCARDED_PATH, new DataChangeListener() {
+
+            @Override
+            public void onDataChanged(final DataChangeEvent<InstanceIdentifier<?>, DataObject> dataChangeEvent) {
+                eventFuture.set(dataChangeEvent);
+            }
+        });
+
+        DataModificationTransaction transaction = dataBroker.beginTransaction();
+        transaction.putOperationalData(NODE_0_TABLE_FEATURES_PATH, TABLE_FEATURES);
+        transaction.putOperationalData(NODE_0_FLOW_PATH, FLOW);
+        transaction.putOperationalData(NODE_1_FLOW_PATH, FLOW);
+        transaction.commit().get();
+
+        DataChangeEvent<InstanceIdentifier<?>, DataObject> event = eventFuture.get(1000, TimeUnit.MILLISECONDS);
+
+        validateEvent(event);
+    }
+
+    @Test
+    public void testWriteByReplace() throws InterruptedException, TimeoutException, ExecutionException {
+
+        DataProviderService dataBroker = testContext.getBindingDataBroker();
+
+        final SettableFuture<DataChangeEvent<InstanceIdentifier<?>, DataObject>> eventFuture = SettableFuture.create();
+        dataBroker.registerDataChangeListener(DEEP_WILDCARDED_PATH, new DataChangeListener() {
+
+            @Override
+            public void onDataChanged(final DataChangeEvent<InstanceIdentifier<?>, DataObject> dataChangeEvent) {
+                eventFuture.set(dataChangeEvent);
+            }
+        });
+
+        DataModificationTransaction tableTx = dataBroker.beginTransaction();
+        tableTx.putOperationalData(NODE_0_TABLE_FEATURES_PATH, TABLE_FEATURES);
+        tableTx.commit().get();
+
+        assertFalse(eventFuture.isDone());
+
+        DataModificationTransaction flowTx = dataBroker.beginTransaction();
+
+        Table table = new TableBuilder() //
+                .setKey(TABLE_0_KEY) //
+                .setFlow(Collections.singletonList(FLOW)) //
+                .build();
+
+        flowTx.putOperationalData(NODE_0_TABLE_PATH, table);
+        flowTx.putOperationalData(NODE_1_FLOW_PATH, FLOW);
+        flowTx.commit().get();
+
+        validateEvent(eventFuture.get(1000, TimeUnit.MILLISECONDS));
+    }
+
+    @Test
+    public void testNoChangeOnReplaceWithSameValue() throws InterruptedException, TimeoutException, ExecutionException {
+
+        DataProviderService dataBroker = testContext.getBindingDataBroker();
+
+        // We wrote initial state NODE_0_FLOW
+        DataModificationTransaction transaction = dataBroker.beginTransaction();
+        transaction.putOperationalData(NODE_0_FLOW_PATH, FLOW);
+        transaction.commit().get();
+
+        // We registered DataChangeListener
+        final SettableFuture<DataChangeEvent<InstanceIdentifier<?>, DataObject>> eventFuture = SettableFuture.create();
+        dataBroker.registerDataChangeListener(DEEP_WILDCARDED_PATH, new DataChangeListener() {
+
+            @Override
+            public void onDataChanged(final DataChangeEvent<InstanceIdentifier<?>, DataObject> dataChangeEvent) {
+                eventFuture.set(dataChangeEvent);
+            }
+        });
+        assertFalse(eventFuture.isDone());
+
+        DataModificationTransaction secondTx = dataBroker.beginTransaction();
+        secondTx.putOperationalData(NODE_0_FLOW_PATH, FLOW);
+        secondTx.putOperationalData(NODE_1_FLOW_PATH, FLOW);
+        secondTx.commit().get();
+
+        DataChangeEvent<InstanceIdentifier<?>, DataObject> event = (eventFuture.get(1000, TimeUnit.MILLISECONDS));
+        assertNotNull(event);
+        // Data change should contains NODE_1 Flow - which was added
+        assertTrue(event.getCreatedOperationalData().containsKey(NODE_1_FLOW_PATH));
+        // Data change must not containe NODE_0 Flow which was replaced with same value.
+        assertFalse(event.getUpdatedOperationalData().containsKey(NODE_0_FLOW_PATH));
+    }
+
+    private static void validateEvent(final DataChangeEvent<InstanceIdentifier<?>, DataObject> event) {
+        assertNotNull(event);
+        assertTrue(event.getCreatedOperationalData().containsKey(NODE_1_FLOW_PATH));
+        assertTrue(event.getCreatedOperationalData().containsKey(NODE_0_FLOW_PATH));
+        assertFalse(event.getCreatedOperationalData().containsKey(NODE_0_TABLE_FEATURES_PATH));
+    }
+
+}
index e1fc3c3cdb200b3c79a2e72b4d96abd03ca0572c..8fb6ff38a2477243545fe6fef86ca48532c4d493 100644 (file)
@@ -18,9 +18,6 @@ import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.data.api.CompositeNode;
 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.AugmentationIdentifier;
-import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifier;
-import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifierWithPredicates;
-import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeWithValue;
 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
 import org.opendaylight.yangtools.yang.data.api.Node;
 import org.opendaylight.yangtools.yang.data.api.SimpleNode;
@@ -54,17 +51,17 @@ public class DataNormalizer {
         Iterator<PathArgument> arguments = legacy.getPath().iterator();
 
         try {
-            while ( arguments.hasNext() ) {
+            while (arguments.hasNext()) {
                 PathArgument legacyArg = arguments.next();
                 currentOp = currentOp.getChild(legacyArg);
-                checkArgument(currentOp != null, "Legacy Instance Identifier %s is not correct. Normalized Instance Identifier so far %s",legacy,normalizedArgs.build());
+                checkArgument(currentOp != null,
+                        "Legacy Instance Identifier %s is not correct. Normalized Instance Identifier so far %s",
+                        legacy, normalizedArgs.build());
                 while (currentOp.isMixin()) {
                     normalizedArgs.add(currentOp.getIdentifier());
                     currentOp = currentOp.getChild(legacyArg.getNodeType());
                 }
-                if(arguments.hasNext() || (!currentOp.isKeyedEntry() || legacyArg instanceof NodeIdentifierWithPredicates || legacyArg instanceof NodeWithValue)) {
-                    normalizedArgs.add(legacyArg);
-                }
+                normalizedArgs.add(legacyArg);
             }
         } catch (DataNormalizationException e) {
             throw new IllegalArgumentException(String.format("Failed to normalize path %s", legacy), e);
@@ -73,11 +70,13 @@ public class DataNormalizer {
         return new InstanceIdentifier(normalizedArgs.build());
     }
 
-    public Map.Entry<InstanceIdentifier,NormalizedNode<?, ?>> toNormalized(final Map.Entry<InstanceIdentifier,CompositeNode> legacy) {
+    public Map.Entry<InstanceIdentifier, NormalizedNode<?, ?>> toNormalized(
+            final Map.Entry<InstanceIdentifier, CompositeNode> legacy) {
         return toNormalized(legacy.getKey(), legacy.getValue());
     }
 
-    public Map.Entry<InstanceIdentifier,NormalizedNode<?, ?>> toNormalized(final InstanceIdentifier legacyPath, final CompositeNode legacyData) {
+    public Map.Entry<InstanceIdentifier, NormalizedNode<?, ?>> toNormalized(final InstanceIdentifier legacyPath,
+            final CompositeNode legacyData) {
 
         InstanceIdentifier normalizedPath = toNormalized(legacyPath);
 
@@ -86,7 +85,8 @@ public class DataNormalizer {
             try {
                 currentOp = currentOp.getChild(arg);
             } catch (DataNormalizationException e) {
-                throw new IllegalArgumentException(String.format("Failed to validate normalized path %s", normalizedPath), e);
+                throw new IllegalArgumentException(String.format("Failed to validate normalized path %s",
+                        normalizedPath), e);
             }
         }
 
@@ -101,7 +101,7 @@ public class DataNormalizer {
                 throw new IllegalArgumentException(String.format("Failed to get child operation for %s", legacyData), e);
             }
 
-            if(potentialOp.getIdentifier() instanceof AugmentationIdentifier) {
+            if (potentialOp.getIdentifier() instanceof AugmentationIdentifier) {
                 currentOp = potentialOp;
                 ArrayList<PathArgument> reworkedArgs = new ArrayList<>(normalizedPath.getPath());
                 reworkedArgs.add(potentialOp.getIdentifier());
@@ -111,28 +111,19 @@ public class DataNormalizer {
 
         Preconditions.checkArgument(currentOp != null,
                 "Instance Identifier %s does not reference correct schema Node.", normalizedPath);
-        return new AbstractMap.SimpleEntry<InstanceIdentifier,NormalizedNode<?, ?>>(normalizedPath,currentOp.normalize(legacyData));
+        return new AbstractMap.SimpleEntry<InstanceIdentifier, NormalizedNode<?, ?>>(normalizedPath,
+                currentOp.normalize(legacyData));
     }
 
-    public InstanceIdentifier toLegacy(final InstanceIdentifier normalized) {
+    public InstanceIdentifier toLegacy(final InstanceIdentifier normalized) throws DataNormalizationException {
         ImmutableList.Builder<PathArgument> legacyArgs = ImmutableList.builder();
         PathArgument previous = null;
+        DataNormalizationOperation<?> currentOp = operation;
         for (PathArgument normalizedArg : normalized.getPath()) {
-            if (normalizedArg instanceof NodeIdentifier) {
-                if (previous != null) {
-                    legacyArgs.add(previous);
-                }
-                previous = normalizedArg;
-            } else if (normalizedArg instanceof NodeIdentifierWithPredicates) {
-                // We skip previous node, which was mixin.
-                previous = normalizedArg;
-            } else if (normalizedArg instanceof AugmentationIdentifier) {
-                // We ignore argument
+            currentOp = currentOp.getChild(normalizedArg);
+            if(!currentOp.isMixin()) {
+                legacyArgs.add(normalizedArg);
             }
-            // FIXME : Add option for reading choice
-        }
-        if (previous != null) {
-            legacyArgs.add(previous);
         }
         return new InstanceIdentifier(legacyArgs.build());
     }
@@ -149,9 +140,8 @@ public class DataNormalizer {
     public static Node<?> toLegacy(final NormalizedNode<?, ?> node) {
         if (node instanceof MixinNode) {
             /**
-             * Direct reading of MixinNodes is not supported,
-             * since it is not possible in legacy APIs create pointer
-             * to Mixin Nodes.
+             * Direct reading of MixinNodes is not supported, since it is not
+             * possible in legacy APIs create pointer to Mixin Nodes.
              *
              */
             return null;
@@ -193,8 +183,8 @@ public class DataNormalizer {
             final NormalizedNodeContainer<?, ?, NormalizedNode<?, ?>> mixin) {
         ArrayList<Node<?>> ret = new ArrayList<>();
         for (NormalizedNode<?, ?> child : mixin.getValue()) {
-            if(child instanceof MixinNode && child instanceof NormalizedNodeContainer<?, ?, ?>) {
-                Iterables.addAll(ret,toLegacyNodesFromMixin((NormalizedNodeContainer) child));
+            if (child instanceof MixinNode && child instanceof NormalizedNodeContainer<?, ?, ?>) {
+                Iterables.addAll(ret, toLegacyNodesFromMixin((NormalizedNodeContainer) child));
             } else {
                 ret.add(toLegacy(child));
             }
index 3c6a3d60d859e4c503c7bc9ed6d3b0045d58a68e..86f08de61566d6da785fe49166cdde694e8ed92a 100644 (file)
@@ -3,22 +3,32 @@ package org.opendaylight.controller.md.sal.dom.store.impl;
 import java.util.Map;
 import java.util.Set;
 
+import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeEvent;
 import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
 
+import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 
 public final class DOMImmutableDataChangeEvent implements
         AsyncDataChangeEvent<InstanceIdentifier, NormalizedNode<?, ?>> {
 
+
+    private static final RemoveEventFactory REMOVE_EVENT_FACTORY = new RemoveEventFactory();
+    private static final CreateEventFactory CREATE_EVENT_FACTORY = new CreateEventFactory();
+
     private final NormalizedNode<?, ?> original;
     private final NormalizedNode<?, ?> updated;
     private final Map<InstanceIdentifier, ? extends NormalizedNode<?, ?>> originalData;
     private final Map<InstanceIdentifier, NormalizedNode<?, ?>> createdData;
     private final Map<InstanceIdentifier, NormalizedNode<?, ?>> updatedData;
     private final Set<InstanceIdentifier> removedPaths;
+    private final DataChangeScope scope;
+
+
 
     private DOMImmutableDataChangeEvent(final Builder change) {
         original = change.before;
@@ -27,10 +37,15 @@ public final class DOMImmutableDataChangeEvent implements
         createdData = change.created.build();
         updatedData = change.updated.build();
         removedPaths = change.removed.build();
+        scope = change.scope;
+    }
+
+    public static final Builder builder(final DataChangeScope scope) {
+        return new Builder(scope);
     }
 
-    public static final Builder builder() {
-        return new Builder();
+    protected DataChangeScope getScope() {
+        return scope;
     }
 
     @Override
@@ -69,8 +84,44 @@ public final class DOMImmutableDataChangeEvent implements
                 + ", removed=" + removedPaths + "]";
     }
 
+    /**
+     * Simple event factory which creates event based on path and data
+     *
+     *
+     */
+    public interface SimpleEventFactory {
+        DOMImmutableDataChangeEvent create(InstanceIdentifier path, NormalizedNode<PathArgument,?> data);
+    }
+
+    /**
+     * Event factory which takes after state and creates event for it.
+     *
+     * Factory for events based on path and after state.
+     * After state is set as {@link #getUpdatedSubtree()} and is path,
+     * state mapping is also present in {@link #getUpdatedData()}.
+     *
+     * @return
+     */
+    public static final SimpleEventFactory getCreateEventFactory() {
+        return CREATE_EVENT_FACTORY;
+    }
+
+    /**
+     * Event factory which takes before state and creates event for it.
+     *
+     * Factory for events based on path and after state.
+     * After state is set as {@link #getOriginalSubtree()} and is path,
+     * state mapping is also present in {@link #getOriginalSubtree()}.
+     *
+     * Path is present in {@link #getRemovedPaths()}.
+     * @return
+     */
+    public static final SimpleEventFactory getRemoveEventFactory() {
+        return REMOVE_EVENT_FACTORY;
+    }
     public static class Builder {
 
+        public DataChangeScope scope;
         private NormalizedNode<?, ?> after;
         private NormalizedNode<?, ?> before;
 
@@ -79,8 +130,9 @@ public final class DOMImmutableDataChangeEvent implements
         private final ImmutableMap.Builder<InstanceIdentifier, NormalizedNode<?, ?>> updated = ImmutableMap.builder();
         private final ImmutableSet.Builder<InstanceIdentifier> removed = ImmutableSet.builder();
 
-        private Builder() {
-
+        private Builder(final DataChangeScope scope) {
+            Preconditions.checkNotNull(scope, "Data change scope should not be null.");
+            this.scope = scope;
         }
 
         public Builder setAfter(final NormalizedNode<?, ?> node) {
@@ -126,4 +178,27 @@ public final class DOMImmutableDataChangeEvent implements
         }
     }
 
+    private static final class RemoveEventFactory implements SimpleEventFactory {
+
+        @Override
+        public DOMImmutableDataChangeEvent create(final InstanceIdentifier path, final NormalizedNode<PathArgument, ?> data) {
+            return builder(DataChangeScope.BASE) //
+                    .setBefore(data) //
+                    .addRemoved(path, data) //
+                    .build();
+        }
+
+    }
+
+    private static final class CreateEventFactory implements SimpleEventFactory {
+
+        @Override
+        public DOMImmutableDataChangeEvent create(final InstanceIdentifier path, final NormalizedNode<PathArgument, ?> data) {
+            return builder(DataChangeScope.BASE) //
+                    .setAfter(data) //
+                    .addCreated(path, data) //
+                    .build();
+        }
+    }
+
 }
diff --git a/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/DataChangeEventResolver.java b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/DataChangeEventResolver.java
deleted file mode 100644 (file)
index df2725d..0000000
+++ /dev/null
@@ -1,233 +0,0 @@
-package org.opendaylight.controller.md.sal.dom.store.impl;
-
-import static org.opendaylight.controller.md.sal.dom.store.impl.DOMImmutableDataChangeEvent.builder;
-import static org.opendaylight.controller.md.sal.dom.store.impl.StoreUtils.append;
-import static org.opendaylight.controller.md.sal.dom.store.impl.tree.TreeNodeUtils.getChild;
-
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.Set;
-
-import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
-import org.opendaylight.controller.md.sal.dom.store.impl.DOMImmutableDataChangeEvent.Builder;
-import org.opendaylight.controller.md.sal.dom.store.impl.tree.ListenerTree;
-import org.opendaylight.controller.md.sal.dom.store.impl.tree.ListenerTree.Walker;
-import org.opendaylight.controller.md.sal.dom.store.impl.tree.NodeModification;
-import org.opendaylight.controller.md.sal.dom.store.impl.tree.StoreMetadataNode;
-import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
-import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
-import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.google.common.base.Optional;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
-
-public class DataChangeEventResolver {
-    private static final Logger LOG = LoggerFactory.getLogger(DataChangeEventResolver.class);
-    private static final DOMImmutableDataChangeEvent NO_CHANGE = builder().build();
-    private final ImmutableList.Builder<ChangeListenerNotifyTask> tasks = ImmutableList.builder();
-    private InstanceIdentifier rootPath;
-    private ListenerTree listenerRoot;
-    private NodeModification modificationRoot;
-    private Optional<StoreMetadataNode> beforeRoot;
-    private Optional<StoreMetadataNode> afterRoot;
-
-    protected InstanceIdentifier getRootPath() {
-        return rootPath;
-    }
-
-    protected DataChangeEventResolver setRootPath(final InstanceIdentifier rootPath) {
-        this.rootPath = rootPath;
-        return this;
-    }
-
-    protected ListenerTree getListenerRoot() {
-        return listenerRoot;
-    }
-
-    protected DataChangeEventResolver setListenerRoot(final ListenerTree listenerRoot) {
-        this.listenerRoot = listenerRoot;
-        return this;
-    }
-
-    protected NodeModification getModificationRoot() {
-        return modificationRoot;
-    }
-
-    protected DataChangeEventResolver setModificationRoot(final NodeModification modificationRoot) {
-        this.modificationRoot = modificationRoot;
-        return this;
-    }
-
-    protected Optional<StoreMetadataNode> getBeforeRoot() {
-        return beforeRoot;
-    }
-
-    protected DataChangeEventResolver setBeforeRoot(final Optional<StoreMetadataNode> beforeRoot) {
-        this.beforeRoot = beforeRoot;
-        return this;
-    }
-
-    protected Optional<StoreMetadataNode> getAfterRoot() {
-        return afterRoot;
-    }
-
-    protected DataChangeEventResolver setAfterRoot(final Optional<StoreMetadataNode> afterRoot) {
-        this.afterRoot = afterRoot;
-        return this;
-    }
-
-    public Iterable<ChangeListenerNotifyTask> resolve() {
-        LOG.trace("Resolving events for {}", modificationRoot);
-
-        try (final Walker w = listenerRoot.getWalker()) {
-            resolveAnyChangeEvent(rootPath, Optional.of(w.getRootNode()), modificationRoot, beforeRoot, afterRoot);
-            return tasks.build();
-        }
-    }
-
-    private DOMImmutableDataChangeEvent resolveAnyChangeEvent(final InstanceIdentifier path,
-            final Optional<ListenerTree.Node> listeners, final NodeModification modification,
-            final Optional<StoreMetadataNode> before, final Optional<StoreMetadataNode> after) {
-        // No listeners are present in listener registration subtree
-        // no before and after state is present
-        if (!before.isPresent() && !after.isPresent()) {
-            return NO_CHANGE;
-        }
-        switch (modification.getModificationType()) {
-        case SUBTREE_MODIFIED:
-            return resolveSubtreeChangeEvent(path, listeners, modification, before.get(), after.get());
-        case WRITE:
-            if (before.isPresent()) {
-                return resolveReplacedEvent(path, listeners, modification, before.get(), after.get());
-            } else {
-                return resolveCreateEvent(path, listeners, after.get());
-            }
-        case DELETE:
-            return resolveDeleteEvent(path, listeners, before.get());
-        default:
-            return NO_CHANGE;
-        }
-
-    }
-
-    /**
-     * Resolves create events deep down the interest listener tree.
-     *
-     *
-     * @param path
-     * @param listeners
-     * @param afterState
-     * @return
-     */
-    private DOMImmutableDataChangeEvent resolveCreateEvent(final InstanceIdentifier path,
-            final Optional<ListenerTree.Node> listeners, final StoreMetadataNode afterState) {
-        final NormalizedNode<?, ?> node = afterState.getData();
-        Builder builder = builder().setAfter(node).addCreated(path, node);
-
-        for (StoreMetadataNode child : afterState.getChildren()) {
-            PathArgument childId = child.getIdentifier();
-            Optional<ListenerTree.Node> childListeners = getChild(listeners, childId);
-
-            InstanceIdentifier childPath = StoreUtils.append(path, childId);
-            builder.merge(resolveCreateEvent(childPath, childListeners, child));
-        }
-
-        return addNotifyTask(listeners, builder.build());
-    }
-
-    private DOMImmutableDataChangeEvent resolveDeleteEvent(final InstanceIdentifier path,
-            final Optional<ListenerTree.Node> listeners, final StoreMetadataNode beforeState) {
-        final NormalizedNode<?, ?> node = beforeState.getData();
-        Builder builder = builder().setBefore(node).addRemoved(path, node);
-
-        for (StoreMetadataNode child : beforeState.getChildren()) {
-            PathArgument childId = child.getIdentifier();
-            Optional<ListenerTree.Node> childListeners = getChild(listeners, childId);
-            InstanceIdentifier childPath = StoreUtils.append(path, childId);
-            builder.merge(resolveDeleteEvent(childPath, childListeners, child));
-        }
-        return addNotifyTask(listeners, builder.build());
-    }
-
-    private DOMImmutableDataChangeEvent resolveSubtreeChangeEvent(final InstanceIdentifier path,
-            final Optional<ListenerTree.Node> listeners, final NodeModification modification,
-            final StoreMetadataNode before, final StoreMetadataNode after) {
-
-        Builder one = builder().setBefore(before.getData()).setAfter(after.getData());
-
-        Builder subtree = builder();
-
-        for (NodeModification childMod : modification.getModifications()) {
-            PathArgument childId = childMod.getIdentifier();
-            InstanceIdentifier childPath = append(path, childId);
-            Optional<ListenerTree.Node> childListen = getChild(listeners, childId);
-
-            Optional<StoreMetadataNode> childBefore = before.getChild(childId);
-            Optional<StoreMetadataNode> childAfter = after.getChild(childId);
-
-            switch (childMod.getModificationType()) {
-            case WRITE:
-            case DELETE:
-                one.merge(resolveAnyChangeEvent(childPath, childListen, childMod, childBefore, childAfter));
-                break;
-            case SUBTREE_MODIFIED:
-                subtree.merge(resolveSubtreeChangeEvent(childPath, childListen, childMod, childBefore.get(),
-                        childAfter.get()));
-                break;
-            case UNMODIFIED:
-                // no-op
-                break;
-            }
-        }
-        DOMImmutableDataChangeEvent oneChangeEvent = one.build();
-        subtree.merge(oneChangeEvent);
-        DOMImmutableDataChangeEvent subtreeEvent = subtree.build();
-        if (listeners.isPresent()) {
-            addNotifyTask(listeners.get(), DataChangeScope.ONE, oneChangeEvent);
-            addNotifyTask(listeners.get(), DataChangeScope.SUBTREE, subtreeEvent);
-        }
-        return subtreeEvent;
-    }
-
-    private DOMImmutableDataChangeEvent resolveReplacedEvent(final InstanceIdentifier path,
-            final Optional<ListenerTree.Node> listeners, final NodeModification modification,
-            final StoreMetadataNode before, final StoreMetadataNode after) {
-        // FIXME Add task
-        return builder().build();
-    }
-
-    private DOMImmutableDataChangeEvent addNotifyTask(final Optional<ListenerTree.Node> listeners, final DOMImmutableDataChangeEvent event) {
-        if (listeners.isPresent()) {
-            final Collection<DataChangeListenerRegistration<?>> l = listeners.get().getListeners();
-            if (!l.isEmpty()) {
-                tasks.add(new ChangeListenerNotifyTask(ImmutableSet.copyOf(l), event));
-            }
-        }
-
-        return event;
-    }
-
-    private void addNotifyTask(final ListenerTree.Node listenerRegistrationNode, final DataChangeScope scope,
-            final DOMImmutableDataChangeEvent event) {
-        Collection<DataChangeListenerRegistration<?>> potential = listenerRegistrationNode.getListeners();
-        if(!potential.isEmpty()) {
-            final Set<DataChangeListenerRegistration<?>> toNotify = new HashSet<>(potential.size());
-            for(DataChangeListenerRegistration<?> listener : potential) {
-                if(scope.equals(listener.getScope())) {
-                    toNotify.add(listener);
-                }
-            }
-
-            if (!toNotify.isEmpty()) {
-                tasks.add(new ChangeListenerNotifyTask(toNotify, event));
-            }
-        }
-    }
-
-    public static DataChangeEventResolver create() {
-        return new DataChangeEventResolver();
-    }
-}
index a854c4806b4a61f8f3d52716e23f978503465a16..a66fa7f1ff47075643965ad055acc7101c2b560b 100644 (file)
@@ -120,7 +120,7 @@ public class InMemoryDOMDataStore implements DOMStore, Identifiable<String>, Sch
             if (currentState.isPresent()) {
                 final NormalizedNode<?, ?> data = currentState.get().getData();
 
-                final DOMImmutableDataChangeEvent event = DOMImmutableDataChangeEvent.builder() //
+                final DOMImmutableDataChangeEvent event = DOMImmutableDataChangeEvent.builder(DataChangeScope.BASE) //
                         .setAfter(data) //
                         .addCreated(path, data) //
                         .build();
@@ -149,7 +149,7 @@ public class InMemoryDOMDataStore implements DOMStore, Identifiable<String>, Sch
     }
 
     private void commit(final DataAndMetadataSnapshot currentSnapshot,
-            final StoreMetadataNode newDataTree, final DataChangeEventResolver listenerResolver) {
+            final StoreMetadataNode newDataTree, final ResolveDataChangeEventsTask listenerResolver) {
         LOG.debug("Updating Store snaphot version: {} with version:{}",currentSnapshot.getMetadataTree().getSubtreeVersion(),newDataTree.getSubtreeVersion());
 
         if(LOG.isTraceEnabled()) {
@@ -168,7 +168,7 @@ public class InMemoryDOMDataStore implements DOMStore, Identifiable<String>, Sch
             final boolean success = snapshot.compareAndSet(currentSnapshot, newSnapshot);
             checkState(success, "Store snapshot and transaction snapshot differ. This should never happen.");
 
-            for (ChangeListenerNotifyTask task : listenerResolver.resolve()) {
+            for (ChangeListenerNotifyTask task : listenerResolver.call()) {
                 executor.submit(task);
             }
         }
@@ -306,7 +306,7 @@ public class InMemoryDOMDataStore implements DOMStore, Identifiable<String>, Sch
 
         private DataAndMetadataSnapshot storeSnapshot;
         private Optional<StoreMetadataNode> proposedSubtree;
-        private DataChangeEventResolver listenerResolver;
+        private ResolveDataChangeEventsTask listenerResolver;
 
         public ThreePhaseCommitImpl(final SnaphostBackedWriteTransaction writeTransaction) {
             this.transaction = writeTransaction;
@@ -347,7 +347,7 @@ public class InMemoryDOMDataStore implements DOMStore, Identifiable<String>, Sch
                     proposedSubtree = operationTree.apply(modification, Optional.of(metadataTree),
                             increase(metadataTree.getSubtreeVersion()));
 
-                    listenerResolver = DataChangeEventResolver.create() //
+                    listenerResolver = ResolveDataChangeEventsTask.create() //
                             .setRootPath(PUBLIC_ROOT_PATH) //
                             .setBeforeRoot(Optional.of(metadataTree)) //
                             .setAfterRoot(proposedSubtree) //
diff --git a/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/ResolveDataChangeEventsTask.java b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/store/impl/ResolveDataChangeEventsTask.java
new file mode 100644 (file)
index 0000000..520bd1b
--- /dev/null
@@ -0,0 +1,550 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.md.sal.dom.store.impl;
+
+import static org.opendaylight.controller.md.sal.dom.store.impl.DOMImmutableDataChangeEvent.builder;
+import static org.opendaylight.controller.md.sal.dom.store.impl.StoreUtils.append;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.concurrent.Callable;
+
+import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
+import org.opendaylight.controller.md.sal.dom.store.impl.DOMImmutableDataChangeEvent.Builder;
+import org.opendaylight.controller.md.sal.dom.store.impl.DOMImmutableDataChangeEvent.SimpleEventFactory;
+import org.opendaylight.controller.md.sal.dom.store.impl.tree.ListenerTree;
+import org.opendaylight.controller.md.sal.dom.store.impl.tree.ListenerTree.Node;
+import org.opendaylight.controller.md.sal.dom.store.impl.tree.ListenerTree.Walker;
+import org.opendaylight.controller.md.sal.dom.store.impl.tree.NodeModification;
+import org.opendaylight.controller.md.sal.dom.store.impl.tree.StoreMetadataNode;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifierWithPredicates;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeWithValue;
+import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Multimap;
+
+/**
+ *
+ * Resolve Data Change Events based on modifications and listeners
+ *
+ * Computes data change events for all affected registered listeners in data
+ * tree.
+ *
+ * Prerequisites for computation is to set all parameters properly:
+ * <ul>
+ * <li>{@link #setRootPath(InstanceIdentifier)} - Root path of datastore
+ * <li>{@link #setListenerRoot(ListenerTree)} - Root of listener registration
+ * tree, which contains listeners to be notified
+ * <li>{@link #setModificationRoot(NodeModification)} - Modification root, for
+ * which events should be computed
+ * <li>{@link #setBeforeRoot(Optional)} - State of before modification occurred
+ * <li>{@link #setAfterRoot(Optional)} - State of after modification occurred
+ * </ul>
+ *
+ */
+public class ResolveDataChangeEventsTask implements Callable<Iterable<ChangeListenerNotifyTask>> {
+    private static final Logger LOG = LoggerFactory.getLogger(ResolveDataChangeEventsTask.class);
+    private static final DOMImmutableDataChangeEvent NO_CHANGE = builder(DataChangeScope.BASE).build();
+
+    private InstanceIdentifier rootPath;
+    private ListenerTree listenerRoot;
+    private NodeModification modificationRoot;
+    private Optional<StoreMetadataNode> beforeRoot;
+    private Optional<StoreMetadataNode> afterRoot;
+    private final Multimap<ListenerTree.Node, DOMImmutableDataChangeEvent> events = HashMultimap.create();
+
+    protected InstanceIdentifier getRootPath() {
+        return rootPath;
+    }
+
+    protected ResolveDataChangeEventsTask setRootPath(final InstanceIdentifier rootPath) {
+        this.rootPath = rootPath;
+        return this;
+    }
+
+    protected ListenerTree getListenerRoot() {
+        return listenerRoot;
+    }
+
+    protected ResolveDataChangeEventsTask setListenerRoot(final ListenerTree listenerRoot) {
+        this.listenerRoot = listenerRoot;
+        return this;
+    }
+
+    protected NodeModification getModificationRoot() {
+        return modificationRoot;
+    }
+
+    protected ResolveDataChangeEventsTask setModificationRoot(final NodeModification modificationRoot) {
+        this.modificationRoot = modificationRoot;
+        return this;
+    }
+
+    protected Optional<StoreMetadataNode> getBeforeRoot() {
+        return beforeRoot;
+    }
+
+    protected ResolveDataChangeEventsTask setBeforeRoot(final Optional<StoreMetadataNode> beforeRoot) {
+        this.beforeRoot = beforeRoot;
+        return this;
+    }
+
+    protected Optional<StoreMetadataNode> getAfterRoot() {
+        return afterRoot;
+    }
+
+    protected ResolveDataChangeEventsTask setAfterRoot(final Optional<StoreMetadataNode> afterRoot) {
+        this.afterRoot = afterRoot;
+        return this;
+    }
+
+    /**
+     * Resolves and creates Notification Tasks
+     *
+     * Implementation of done as Map-Reduce with two steps: 1. resolving events
+     * and their mapping to listeners 2. merging events affecting same listener
+     *
+     * @return Iterable of Notification Tasks which needs to be executed in
+     *         order to delivery data change events.
+     */
+    @Override
+    public Iterable<ChangeListenerNotifyTask> call() {
+        LOG.trace("Resolving events for {}", modificationRoot);
+
+        try (final Walker w = listenerRoot.getWalker()) {
+            resolveAnyChangeEvent(rootPath, Collections.singleton(w.getRootNode()), modificationRoot, beforeRoot,
+                    afterRoot);
+            return createNotificationTasks();
+        }
+    }
+
+    /**
+     *
+     * Walks map of listeners to data change events, creates notification
+     * delivery tasks.
+     *
+     * Walks map of registered and affected listeners and creates notification
+     * tasks from set of listeners and events to be delivered.
+     *
+     * If set of listeners has more then one event (applicable to wildcarded
+     * listeners), merges all data change events into one, final which contains
+     * all separate updates.
+     *
+     * Dispatch between merge variant and reuse variant of notification task is
+     * done in
+     * {@link #addNotificationTask(com.google.common.collect.ImmutableList.Builder, Node, Collection)}
+     *
+     * @return Collection of notification tasks.
+     */
+    private Collection<ChangeListenerNotifyTask> createNotificationTasks() {
+        ImmutableList.Builder<ChangeListenerNotifyTask> taskListBuilder = ImmutableList.builder();
+        for (Entry<ListenerTree.Node, Collection<DOMImmutableDataChangeEvent>> entry : events.asMap().entrySet()) {
+            addNotificationTask(taskListBuilder, entry.getKey(), entry.getValue());
+        }
+        return taskListBuilder.build();
+    }
+
+    /**
+     * Adds notification task to task list.
+     *
+     * If entry collection contains one event, this event is reused and added to
+     * notification tasks for listeners (see
+     * {@link #addNotificationTaskByScope(com.google.common.collect.ImmutableList.Builder, Node, DOMImmutableDataChangeEvent)}
+     * . Otherwise events are merged by scope and distributed between listeners
+     * to particular scope. See
+     * {@link #addNotificationTasksAndMergeEvents(com.google.common.collect.ImmutableList.Builder, Node, Collection)}
+     * .
+     *
+     * @param taskListBuilder
+     * @param listeners
+     * @param entries
+     */
+    private static void addNotificationTask(final ImmutableList.Builder<ChangeListenerNotifyTask> taskListBuilder,
+            final ListenerTree.Node listeners, final Collection<DOMImmutableDataChangeEvent> entries) {
+
+        if (!entries.isEmpty()) {
+            if (entries.size() == 1) {
+                addNotificationTaskByScope(taskListBuilder, listeners, Iterables.getOnlyElement(entries));
+            } else {
+                addNotificationTasksAndMergeEvents(taskListBuilder, listeners, entries);
+            }
+        }
+    }
+
+    /**
+     *
+     * Add notification deliveries task to the listener.
+     *
+     *
+     * @param taskListBuilder
+     * @param listeners
+     * @param event
+     */
+    private static void addNotificationTaskByScope(
+            final ImmutableList.Builder<ChangeListenerNotifyTask> taskListBuilder, final ListenerTree.Node listeners,
+            final DOMImmutableDataChangeEvent event) {
+        DataChangeScope eventScope = event.getScope();
+        for (DataChangeListenerRegistration<?> listenerReg : listeners.getListeners()) {
+            DataChangeScope listenerScope = listenerReg.getScope();
+            List<DataChangeListenerRegistration<?>> listenerSet = Collections
+                    .<DataChangeListenerRegistration<?>> singletonList(listenerReg);
+            if (eventScope == DataChangeScope.BASE) {
+                taskListBuilder.add(new ChangeListenerNotifyTask(listenerSet, event));
+            } else if (eventScope == DataChangeScope.ONE && listenerScope != DataChangeScope.BASE) {
+                taskListBuilder.add(new ChangeListenerNotifyTask(listenerSet, event));
+            } else if (eventScope == DataChangeScope.SUBTREE && listenerScope == DataChangeScope.SUBTREE) {
+                taskListBuilder.add(new ChangeListenerNotifyTask(listenerSet, event));
+            }
+        }
+    }
+
+    /**
+     *
+     * Add notification tasks with merged event
+     *
+     * Separate Events by scope and creates merged notification tasks for each
+     * and every scope which is present.
+     *
+     * Adds merged events to task list based on scope requested by client.
+     *
+     * @param taskListBuilder
+     * @param listeners
+     * @param entries
+     */
+    private static void addNotificationTasksAndMergeEvents(
+            final ImmutableList.Builder<ChangeListenerNotifyTask> taskListBuilder, final ListenerTree.Node listeners,
+            final Collection<DOMImmutableDataChangeEvent> entries) {
+
+        final Builder baseBuilder = builder(DataChangeScope.BASE);
+        final Builder oneBuilder = builder(DataChangeScope.ONE);
+        final Builder subtreeBuilder = builder(DataChangeScope.SUBTREE);
+
+        boolean baseModified = false;
+        boolean oneModified = false;
+        boolean subtreeModified = false;
+        for (final DOMImmutableDataChangeEvent entry : entries) {
+            switch (entry.getScope()) {
+            // Absence of breaks is intentional here. Subtree contains base and
+            // one, one also contains base
+            case BASE:
+                baseBuilder.merge(entry);
+                baseModified = true;
+            case ONE:
+                oneBuilder.merge(entry);
+                oneModified = true;
+            case SUBTREE:
+                subtreeBuilder.merge(entry);
+                subtreeModified = true;
+            }
+        }
+
+        if (baseModified) {
+            addNotificationTaskExclusively(taskListBuilder, listeners, baseBuilder.build());
+        }
+        if (oneModified) {
+            addNotificationTaskExclusively(taskListBuilder, listeners, oneBuilder.build());
+        }
+        if (subtreeModified) {
+            addNotificationTaskExclusively(taskListBuilder, listeners, subtreeBuilder.build());
+        }
+    }
+
+    private static void addNotificationTaskExclusively(
+            final ImmutableList.Builder<ChangeListenerNotifyTask> taskListBuilder, final Node listeners,
+            final DOMImmutableDataChangeEvent event) {
+        for (DataChangeListenerRegistration<?> listener : listeners.getListeners()) {
+            if (listener.getScope() == event.getScope()) {
+                Set<DataChangeListenerRegistration<?>> listenerSet = Collections
+                        .<DataChangeListenerRegistration<?>> singleton(listener);
+                taskListBuilder.add(new ChangeListenerNotifyTask(listenerSet, event));
+            }
+        }
+    }
+
+    /**
+     * Resolves data change event for supplied node
+     *
+     * @param path
+     *            Path to current node in tree
+     * @param listeners
+     *            Collection of Listener registration nodes interested in
+     *            subtree
+     * @param modification
+     *            Modification of current node
+     * @param before
+     *            - Original (before) state of current node
+     * @param after
+     *            - After state of current node
+     * @return Data Change Event of this node and all it's children
+     */
+    private DOMImmutableDataChangeEvent resolveAnyChangeEvent(final InstanceIdentifier path,
+            final Collection<ListenerTree.Node> listeners, final NodeModification modification,
+            final Optional<StoreMetadataNode> before, final Optional<StoreMetadataNode> after) {
+        // No listeners are present in listener registration subtree
+        // no before and after state is present
+        if (!before.isPresent() && !after.isPresent()) {
+            return NO_CHANGE;
+        }
+        switch (modification.getModificationType()) {
+        case SUBTREE_MODIFIED:
+            return resolveSubtreeChangeEvent(path, listeners, modification, before.get(), after.get());
+        case WRITE:
+            if (before.isPresent()) {
+                return resolveReplacedEvent(path, listeners, before.get().getData(), after.get().getData());
+            } else {
+                return resolveCreateEvent(path, listeners, after.get());
+            }
+        case DELETE:
+            return resolveDeleteEvent(path, listeners, before.get());
+        default:
+            return NO_CHANGE;
+        }
+
+    }
+
+    private DOMImmutableDataChangeEvent resolveReplacedEvent(final InstanceIdentifier path,
+            final Collection<Node> listeners, final NormalizedNode<?, ?> beforeData,
+            final NormalizedNode<?, ?> afterData) {
+
+        if (beforeData instanceof NormalizedNodeContainer<?, ?, ?> && !listeners.isEmpty()) {
+            // Node is container (contains child) and we have interested
+            // listeners registered for it, that means we need to do
+            // resolution of changes on children level and can not
+            // shortcut resolution.
+
+            @SuppressWarnings("unchecked")
+            NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>> beforeCont = (NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>>) beforeData;
+            @SuppressWarnings("unchecked")
+            NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>> afterCont = (NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>>) afterData;
+            return resolveNodeContainerReplaced(path, listeners, beforeCont, afterCont);
+        } else if (!beforeData.equals(afterData)) {
+            // Node is either of Leaf type (does not contain child nodes)
+            // or we do not have listeners, so normal equals method is
+            // sufficient for determining change.
+
+            DOMImmutableDataChangeEvent event = builder(DataChangeScope.BASE).setBefore(beforeData).setAfter(afterData)
+                    .addUpdated(path, beforeData, afterData).build();
+            addPartialTask(listeners, event);
+            return event;
+        } else {
+            return NO_CHANGE;
+        }
+    }
+
+    private DOMImmutableDataChangeEvent resolveNodeContainerReplaced(final InstanceIdentifier path,
+            final Collection<Node> listeners,
+            final NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>> beforeCont,
+            final NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>> afterCont) {
+        final Set<PathArgument> alreadyProcessed = new HashSet<>();
+        final List<DOMImmutableDataChangeEvent> childChanges = new LinkedList<>();
+
+        DataChangeScope potentialScope = DataChangeScope.BASE;
+        // We look at all children from before and compare it with after state.
+        for (NormalizedNode<PathArgument, ?> beforeChild : beforeCont.getValue()) {
+            PathArgument childId = beforeChild.getIdentifier();
+            alreadyProcessed.add(childId);
+            InstanceIdentifier childPath = append(path, childId);
+            Collection<ListenerTree.Node> childListeners = getListenerChildrenWildcarded(listeners, childId);
+            Optional<NormalizedNode<PathArgument, ?>> afterChild = afterCont.getChild(childId);
+            DOMImmutableDataChangeEvent childChange = resolveNodeContainerChildUpdated(childPath, childListeners,
+                    beforeChild, afterChild);
+            // If change is empty (equals to NO_CHANGE)
+            if (childChange != NO_CHANGE) {
+                childChanges.add(childChange);
+            }
+
+        }
+
+        for (NormalizedNode<PathArgument, ?> afterChild : afterCont.getValue()) {
+            PathArgument childId = afterChild.getIdentifier();
+            if (!alreadyProcessed.contains(childId)) {
+                // We did not processed that child already
+                // and it was not present in previous loop, that means it is
+                // created.
+                Collection<ListenerTree.Node> childListeners = getListenerChildrenWildcarded(listeners, childId);
+                InstanceIdentifier childPath = append(path,childId);
+                childChanges.add(resolveSameEventRecursivelly(childPath , childListeners, afterChild,
+                        DOMImmutableDataChangeEvent.getCreateEventFactory()));
+            }
+        }
+        if (childChanges.isEmpty()) {
+            return NO_CHANGE;
+        }
+
+        Builder eventBuilder = builder(potentialScope) //
+                .setBefore(beforeCont) //
+                .setAfter(afterCont);
+        for (DOMImmutableDataChangeEvent childChange : childChanges) {
+            eventBuilder.merge(childChange);
+        }
+
+        DOMImmutableDataChangeEvent replaceEvent = eventBuilder.build();
+        addPartialTask(listeners, replaceEvent);
+        return replaceEvent;
+    }
+
+    private DOMImmutableDataChangeEvent resolveNodeContainerChildUpdated(final InstanceIdentifier path,
+            final Collection<Node> listeners, final NormalizedNode<PathArgument, ?> before,
+            final Optional<NormalizedNode<PathArgument, ?>> after) {
+
+        if (after.isPresent()) {
+            // REPLACE or SUBTREE Modified
+            return resolveReplacedEvent(path, listeners, before, after.get());
+
+        } else {
+            // AFTER state is not present - child was deleted.
+            return resolveSameEventRecursivelly(path, listeners, before,
+                    DOMImmutableDataChangeEvent.getRemoveEventFactory());
+        }
+    }
+
+    /**
+     * Resolves create events deep down the interest listener tree.
+     *
+     *
+     * @param path
+     * @param listeners
+     * @param afterState
+     * @return
+     */
+    private DOMImmutableDataChangeEvent resolveCreateEvent(final InstanceIdentifier path,
+            final Collection<ListenerTree.Node> listeners, final StoreMetadataNode afterState) {
+        @SuppressWarnings({ "unchecked", "rawtypes" })
+        final NormalizedNode<PathArgument, ?> node = (NormalizedNode) afterState.getData();
+        return resolveSameEventRecursivelly(path, listeners, node, DOMImmutableDataChangeEvent.getCreateEventFactory());
+    }
+
+    private DOMImmutableDataChangeEvent resolveDeleteEvent(final InstanceIdentifier path,
+            final Collection<ListenerTree.Node> listeners, final StoreMetadataNode beforeState) {
+
+        @SuppressWarnings({ "unchecked", "rawtypes" })
+        final NormalizedNode<PathArgument, ?> node = (NormalizedNode) beforeState.getData();
+        return resolveSameEventRecursivelly(path, listeners, node, DOMImmutableDataChangeEvent.getRemoveEventFactory());
+    }
+
+    private DOMImmutableDataChangeEvent resolveSameEventRecursivelly(final InstanceIdentifier path,
+            final Collection<Node> listeners, final NormalizedNode<PathArgument, ?> node,
+            final SimpleEventFactory eventFactory) {
+
+        DOMImmutableDataChangeEvent event = eventFactory.create(path, node);
+
+        if (!listeners.isEmpty()) {
+            // We have listeners for this node or it's children, so we will try
+            // to do additional processing
+            if (node instanceof NormalizedNodeContainer<?, ?, ?>) {
+                // Node has children, so we will try to resolve it's children
+                // changes.
+                @SuppressWarnings("unchecked")
+                NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>> container = (NormalizedNodeContainer<?, PathArgument, NormalizedNode<PathArgument, ?>>) node;
+                for (NormalizedNode<PathArgument, ?> child : container.getValue()) {
+                    PathArgument childId = child.getIdentifier();
+                    Collection<Node> childListeners = getListenerChildrenWildcarded(listeners, childId);
+                    if (!childListeners.isEmpty()) {
+                        resolveSameEventRecursivelly(append(path, childId), childListeners, child, eventFactory);
+                    }
+                }
+            }
+            addPartialTask(listeners, event);
+        }
+        return event;
+    }
+
+    private DOMImmutableDataChangeEvent resolveSubtreeChangeEvent(final InstanceIdentifier path,
+            final Collection<ListenerTree.Node> listeners, final NodeModification modification,
+            final StoreMetadataNode before, final StoreMetadataNode after) {
+
+        Builder one = builder(DataChangeScope.ONE).setBefore(before.getData()).setAfter(after.getData());
+
+        Builder subtree = builder(DataChangeScope.SUBTREE);
+
+        for (NodeModification childMod : modification.getModifications()) {
+            PathArgument childId = childMod.getIdentifier();
+            InstanceIdentifier childPath = append(path, childId);
+            Collection<ListenerTree.Node> childListeners = getListenerChildrenWildcarded(listeners, childId);
+
+            Optional<StoreMetadataNode> childBefore = before.getChild(childId);
+            Optional<StoreMetadataNode> childAfter = after.getChild(childId);
+
+            switch (childMod.getModificationType()) {
+            case WRITE:
+            case DELETE:
+                one.merge(resolveAnyChangeEvent(childPath, childListeners, childMod, childBefore, childAfter));
+                break;
+            case SUBTREE_MODIFIED:
+                subtree.merge(resolveSubtreeChangeEvent(childPath, childListeners, childMod, childBefore.get(),
+                        childAfter.get()));
+                break;
+            case UNMODIFIED:
+                // no-op
+                break;
+            }
+        }
+        DOMImmutableDataChangeEvent oneChangeEvent = one.build();
+        subtree.merge(oneChangeEvent);
+        DOMImmutableDataChangeEvent subtreeEvent = subtree.build();
+        if (!listeners.isEmpty()) {
+            addPartialTask(listeners, oneChangeEvent);
+            addPartialTask(listeners, subtreeEvent);
+        }
+        return subtreeEvent;
+    }
+
+    private DOMImmutableDataChangeEvent addPartialTask(final Collection<ListenerTree.Node> listeners,
+            final DOMImmutableDataChangeEvent event) {
+
+        for (ListenerTree.Node listenerNode : listeners) {
+            if (!listenerNode.getListeners().isEmpty()) {
+                events.put(listenerNode, event);
+            }
+        }
+        return event;
+    }
+
+    private static Collection<ListenerTree.Node> getListenerChildrenWildcarded(final Collection<ListenerTree.Node> parentNodes,
+            final PathArgument child) {
+        if (parentNodes.isEmpty()) {
+            return Collections.emptyList();
+        }
+        com.google.common.collect.ImmutableList.Builder<ListenerTree.Node> result = ImmutableList.builder();
+        if (child instanceof NodeWithValue || child instanceof NodeIdentifierWithPredicates) {
+            NodeIdentifier wildcardedIdentifier = new NodeIdentifier(child.getNodeType());
+            addChildrenNodesToBuilder(result, parentNodes, wildcardedIdentifier);
+        }
+        addChildrenNodesToBuilder(result, parentNodes, child);
+        return result.build();
+    }
+
+    private static void addChildrenNodesToBuilder(final ImmutableList.Builder<ListenerTree.Node> result,
+            final Collection<ListenerTree.Node> parentNodes, final PathArgument childIdentifier) {
+        for (ListenerTree.Node node : parentNodes) {
+            Optional<ListenerTree.Node> child = node.getChild(childIdentifier);
+            if (child.isPresent()) {
+                result.add(child.get());
+            }
+        }
+    }
+
+    public static ResolveDataChangeEventsTask create() {
+        return new ResolveDataChangeEventsTask();
+    }
+}
index 83cfcaca18e9d3e3ad403f5665a91843867edc3f..f93f40a9cc1f5b9bd85e00543be0497fde16d4de 100644 (file)
@@ -220,6 +220,13 @@ public final class ListenerTree {
             children.remove(arg);
             removeThisIfUnused();
         }
+
+        @Override
+        public String toString() {
+            return "Node [identifier=" + identifier + ", listeners=" + listeners.size() + ", children=" + children.size() + "]";
+        }
+
+
     }
 
     private abstract static class DataChangeListenerRegistrationImpl<T extends AsyncDataChangeListener<InstanceIdentifier, NormalizedNode<?, ?>>> extends AbstractListenerRegistration<T> //
index b37b5babddf9161d87a3d7186aa06e713e965b94..d2a907ba4a809759583b0bd41b4715243dc17f9b 100644 (file)
           <groupId>org.opendaylight.controller</groupId>
           <artifactId>commons.logback_settings</artifactId>
         </dependency>
+        <dependency>
+            <groupId>xmlunit</groupId>
+            <artifactId>xmlunit</artifactId>
+        </dependency>
     </dependencies>
 
     <build>
index 96522712daa90c8e52b67e37fd178296835f9611..9770cc5bee877df6e51cfc72d224f6a07065835a 100644 (file)
@@ -8,6 +8,7 @@
 
 package org.opendaylight.controller.netconf.confignetconfconnector;
 
+import static org.custommonkey.xmlunit.XMLAssert.assertXMLEqual;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
@@ -17,6 +18,9 @@ import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.opendaylight.controller.netconf.util.test.XmlUnitUtil.assertContainsElement;
+import static org.opendaylight.controller.netconf.util.test.XmlUnitUtil.assertContainsElementWithText;
+import static org.opendaylight.controller.netconf.util.xml.XmlUtil.readXmlToElement;
 
 import java.io.FileNotFoundException;
 import java.io.IOException;
@@ -26,6 +30,7 @@ import java.net.URISyntaxException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -36,7 +41,12 @@ import javax.management.InstanceNotFoundException;
 import javax.management.ObjectName;
 import javax.xml.parsers.ParserConfigurationException;
 
-import org.apache.commons.lang3.StringUtils;
+import org.custommonkey.xmlunit.AbstractNodeTester;
+import org.custommonkey.xmlunit.NodeTest;
+import org.custommonkey.xmlunit.NodeTestException;
+import org.custommonkey.xmlunit.NodeTester;
+import org.custommonkey.xmlunit.XMLAssert;
+import org.custommonkey.xmlunit.XMLUnit;
 import org.junit.Before;
 import org.junit.Ignore;
 import org.junit.Test;
@@ -95,7 +105,10 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
+import org.w3c.dom.Node;
 import org.w3c.dom.NodeList;
+import org.w3c.dom.Text;
+import org.w3c.dom.traversal.DocumentTraversal;
 import org.xml.sax.SAXException;
 
 import com.google.common.base.Optional;
@@ -110,6 +123,7 @@ public class NetconfMappingTest extends AbstractConfigTest {
 
     private static final String INSTANCE_NAME = "instance-from-code";
     private static final String NETCONF_SESSION_ID = "foo";
+    private static final String TEST_NAMESPACE= "urn:opendaylight:params:xml:ns:yang:controller:test:impl";
     private NetconfTestImplModuleFactory factory;
     private DepTestImplModuleFactory factory2;
     private IdentityTestModuleFactory factory3;
@@ -182,72 +196,101 @@ public class NetconfMappingTest extends AbstractConfigTest {
         createModule(INSTANCE_NAME);
 
         edit("netconfMessages/editConfig.xml");
-        Element config = getConfigCandidate();
-        assertCorrectServiceNames(config, 6, "ref_test2", "user_to_instance_from_code", "ref_dep_user",
+        Document config = getConfigCandidate();
+        assertCorrectServiceNames(config, Sets.newHashSet("ref_test2", "user_to_instance_from_code", "ref_dep_user",
                 "ref_dep_user_two", "ref_from_code_to_instance-from-code_dep_1",
-                "ref_from_code_to_instance-from-code_1");
+                "ref_from_code_to_instance-from-code_1"));
 
 
         edit("netconfMessages/editConfig_addServiceName.xml");
         config = getConfigCandidate();
-        assertCorrectServiceNames(config, 7, "ref_test2", "user_to_instance_from_code", "ref_dep_user",
+        assertCorrectServiceNames(config, Sets.newHashSet("ref_test2", "user_to_instance_from_code", "ref_dep_user",
                 "ref_dep_user_two", "ref_from_code_to_instance-from-code_dep_1",
-                "ref_from_code_to_instance-from-code_1", "ref_dep_user_another");
+                "ref_from_code_to_instance-from-code_1", "ref_dep_user_another"));
 
         edit("netconfMessages/editConfig_addServiceNameOnTest.xml");
         config = getConfigCandidate();
-        assertCorrectServiceNames(config, 7, "ref_test2", "user_to_instance_from_code", "ref_dep_user",
+        assertCorrectServiceNames(config, Sets.newHashSet("ref_test2", "user_to_instance_from_code", "ref_dep_user",
                 "ref_dep_user_two", "ref_from_code_to_instance-from-code_dep_1",
-                "ref_from_code_to_instance-from-code_1", "ref_dep_user_another");
+                "ref_from_code_to_instance-from-code_1", "ref_dep_user_another"));
 
         commit();
         config = getConfigRunning();
         assertCorrectRefNamesForDependencies(config);
-        assertCorrectServiceNames(config, 7, "ref_test2", "user_to_instance_from_code", "ref_dep_user",
+        assertCorrectServiceNames(config, Sets.newHashSet("ref_test2", "user_to_instance_from_code", "ref_dep_user",
                 "ref_dep_user_two", "ref_from_code_to_instance-from-code_dep_1",
-                "ref_from_code_to_instance-from-code_1", "ref_dep_user_another");
+                "ref_from_code_to_instance-from-code_1", "ref_dep_user_another"));
 
         edit("netconfMessages/editConfig_replace_default.xml");
         config = getConfigCandidate();
-        assertCorrectServiceNames(config, 2, "ref_dep", "ref_dep2");
+        assertCorrectServiceNames(config, Sets.newHashSet("ref_dep", "ref_dep2"));
 
         edit("netconfMessages/editConfig_remove.xml");
         config = getConfigCandidate();
-        assertCorrectServiceNames(config, 0);
+        assertCorrectServiceNames(config, Collections.<String>emptySet());
 
         commit();
         config = getConfigCandidate();
-        assertCorrectServiceNames(config, 0);
+        assertCorrectServiceNames(config, Collections.<String>emptySet());
 
     }
 
-    private void assertCorrectRefNamesForDependencies(Element config) {
+    private void assertCorrectRefNamesForDependencies(Document config) throws NodeTestException {
         NodeList modulesList = config.getElementsByTagName("modules");
         assertEquals(1, modulesList.getLength());
 
-        Element modules = (Element) modulesList.item(0);
+        NodeTest nt = new NodeTest((DocumentTraversal) config, modulesList.item(0));
+        NodeTester tester = new AbstractNodeTester() {
+            private int defaultRefNameCount = 0;
+            private int userRefNameCount = 0;
 
-        String trimmedModules = XmlUtil.toString(modules).replaceAll("\\s", "");
-        int defaultRefNameCount = StringUtils.countMatches(trimmedModules, "ref_dep2");
-        int userRefNameCount = StringUtils.countMatches(trimmedModules, "ref_dep_user_two");
+            @Override
+            public void testText(Text text) throws NodeTestException {
+                if(text.getData().equals("ref_dep2")) {
+                    defaultRefNameCount++;
+                } else if(text.getData().equals("ref_dep_user_two")) {
+                    userRefNameCount++;
+                }
+            }
 
-        assertEquals(0, defaultRefNameCount);
-        assertEquals(2, userRefNameCount);
+            @Override
+            public void noMoreNodes(NodeTest forTest) throws NodeTestException {
+                assertEquals(0, defaultRefNameCount);
+                assertEquals(2, userRefNameCount);
+            }
+        };
+        nt.performTest(tester, Node.TEXT_NODE);
     }
 
-    private void assertCorrectServiceNames(Element configCandidate, int servicesSize, String... refNames) {
-        NodeList elements = configCandidate.getElementsByTagName("provider");
-        assertEquals(servicesSize, elements.getLength());
+    private void assertCorrectServiceNames(Document configCandidate, final Set<String> refNames) throws NodeTestException {
 
-        NodeList servicesList = configCandidate.getElementsByTagName("services");
-        assertEquals(1, servicesList.getLength());
+        NodeList servicesNodes = configCandidate.getElementsByTagName("services");
+        assertEquals(1, servicesNodes.getLength());
 
-        Element services = (Element) servicesList.item(0);
-        String trimmedServices = XmlUtil.toString(services).replaceAll("\\s", "");
+        NodeTest nt = new NodeTest((DocumentTraversal) configCandidate, servicesNodes.item(0));
+        NodeTester tester = new AbstractNodeTester() {
 
-        for (String s : refNames) {
-            assertThat(trimmedServices, JUnitMatchers.containsString(s));
-        }
+            @Override
+            public void testElement(Element element) throws NodeTestException {
+                if(element.getNodeName() != null) {
+                    if(element.getNodeName().equals("name")) {
+                        String elmText = element.getTextContent();
+                        if(refNames.contains(elmText)) {
+                            refNames.remove(elmText);
+                            return;
+                        } else {
+                            throw new NodeTestException("Unexpected services defined: " + elmText);
+                        }
+                    }
+                }
+            }
+
+            @Override
+            public void noMoreNodes(NodeTest forTest) throws NodeTestException {
+                assertTrue(refNames.isEmpty());
+            }
+        };
+        nt.performTest(tester, Node.ELEMENT_NODE);
     }
 
     @Test
@@ -257,19 +300,18 @@ public class NetconfMappingTest extends AbstractConfigTest {
 
         edit("netconfMessages/editConfig.xml");
         commit();
-        Element response = getConfigRunning();
-        String trimmedResponse = XmlUtil.toString(response).replaceAll("\\s", "");
-        assertContainsString(trimmedResponse, "<ipxmlns=\"urn:opendaylight:params:xml:ns:yang:controller:test:impl\">0:0:0:0:0:0:0:1</ip>");
-        assertContainsString(trimmedResponse, "<union-test-attrxmlns=\"urn:opendaylight:params:xml:ns:yang:controller:test:impl\">456</union-test-attr>");
+        Document response = getConfigRunning();
+        Element ipElement = readXmlToElement("<ip xmlns=\"urn:opendaylight:params:xml:ns:yang:controller:test:impl\">0:0:0:0:0:0:0:1</ip>");
+        assertContainsElement(response, readXmlToElement("<ip xmlns=\"urn:opendaylight:params:xml:ns:yang:controller:test:impl\">0:0:0:0:0:0:0:1</ip>"));
+
+        assertContainsElement(response, readXmlToElement("<union-test-attr xmlns=\"urn:opendaylight:params:xml:ns:yang:controller:test:impl\">456</union-test-attr>"));
 
 
         edit("netconfMessages/editConfig_setUnions.xml");
         commit();
         response = getConfigRunning();
-
-        trimmedResponse = XmlUtil.toString(response).replaceAll("\\s", "");
-        assertContainsString(trimmedResponse, "<ipxmlns=\"urn:opendaylight:params:xml:ns:yang:controller:test:impl\">127.1.2.3</ip>");
-        assertContainsString(trimmedResponse, "<union-test-attrxmlns=\"urn:opendaylight:params:xml:ns:yang:controller:test:impl\">randomStringForUnion</union-test-attr>");
+        assertContainsElement(response, readXmlToElement("<ip xmlns=\"urn:opendaylight:params:xml:ns:yang:controller:test:impl\">127.1.2.3</ip>"));
+        assertContainsElement(response, readXmlToElement("<union-test-attr xmlns=\"urn:opendaylight:params:xml:ns:yang:controller:test:impl\">randomStringForUnion</union-test-attr>"));
 
     }
 
@@ -279,7 +321,7 @@ public class NetconfMappingTest extends AbstractConfigTest {
         createModule(INSTANCE_NAME);
 
         edit("netconfMessages/editConfig.xml");
-        Element configCandidate = getConfigCandidate();
+        Document configCandidate = getConfigCandidate();
         checkBinaryLeafEdited(configCandidate);
 
 
@@ -289,7 +331,7 @@ public class NetconfMappingTest extends AbstractConfigTest {
 
         // check after edit
         commit();
-        Element response = getConfigRunning();
+        Document response = getConfigRunning();
 
         checkBinaryLeafEdited(response);
         checkTypeConfigAttribute(response);
@@ -301,17 +343,12 @@ public class NetconfMappingTest extends AbstractConfigTest {
         edit("netconfMessages/editConfig_remove.xml");
 
         commit();
-        response = getConfigCandidate();
-        final String responseFromCandidate = XmlUtil.toString(response).replaceAll("\\s+", "");
-        response = getConfigRunning();
-        final String responseFromRunning = XmlUtil.toString(response).replaceAll("\\s+", "");
-        assertEquals(responseFromCandidate, responseFromRunning);
-
-        final String expectedResult = XmlFileLoader.fileToString("netconfMessages/editConfig_expectedResult.xml")
-                .replaceAll("\\s+", "");
+        assertXMLEqual(getConfigCandidate(), getConfigRunning());
 
-        assertEquals(expectedResult, responseFromRunning);
-        assertEquals(expectedResult, responseFromCandidate);
+        final Document expectedResult = XmlFileLoader.xmlFileToDocument("netconfMessages/editConfig_expectedResult.xml");
+        XMLUnit.setIgnoreWhitespace(true);
+        assertXMLEqual(expectedResult, getConfigRunning());
+        assertXMLEqual(expectedResult, getConfigCandidate());
 
         edit("netconfMessages/editConfig_none.xml");
         closeSession();
@@ -320,12 +357,10 @@ public class NetconfMappingTest extends AbstractConfigTest {
         verifyNoMoreInteractions(netconfOperationServiceSnapshot);
     }
 
-    private void checkBigDecimal(Element response) {
-        String responseTrimmed = XmlUtil.toString(response).replaceAll("\\s", "");
-
-        assertContainsString(responseTrimmed, "<sleep-factorxmlns=\"urn:opendaylight:params:xml:ns:yang:controller:test:impl\">2.58</sleep-factor>");
+    private void checkBigDecimal(Document response) throws NodeTestException, SAXException, IOException {
+        assertContainsElement(response, readXmlToElement("<sleep-factor xmlns=\"urn:opendaylight:params:xml:ns:yang:controller:test:impl\">2.58</sleep-factor>"));
         // Default
-        assertContainsString(responseTrimmed, "<sleep-factorxmlns=\"urn:opendaylight:params:xml:ns:yang:controller:test:impl\">2.00</sleep-factor>");
+        assertContainsElement(response, readXmlToElement("<sleep-factor xmlns=\"urn:opendaylight:params:xml:ns:yang:controller:test:impl\">2.00</sleep-factor>"));
 
     }
 
@@ -347,14 +382,14 @@ public class NetconfMappingTest extends AbstractConfigTest {
         executeOp(commitOp, "netconfMessages/commit.xml");
     }
 
-    private Element getConfigCandidate() throws ParserConfigurationException, SAXException, IOException,
+    private Document getConfigCandidate() throws ParserConfigurationException, SAXException, IOException,
             NetconfDocumentedException {
         GetConfig getConfigOp = new GetConfig(yangStoreSnapshot, Optional.<String> absent(), transactionProvider,
                 configRegistryClient, NETCONF_SESSION_ID);
         return executeOp(getConfigOp, "netconfMessages/getConfig_candidate.xml");
     }
 
-    private Element getConfigRunning() throws ParserConfigurationException, SAXException, IOException,
+    private Document getConfigRunning() throws ParserConfigurationException, SAXException, IOException,
             NetconfDocumentedException {
         GetConfig getConfigOp = new GetConfig(yangStoreSnapshot, Optional.<String> absent(), transactionProvider,
                 configRegistryClient, NETCONF_SESSION_ID);
@@ -378,7 +413,7 @@ public class NetconfMappingTest extends AbstractConfigTest {
 
         edit("netconfMessages/editConfig.xml");
         commit();
-        Element response = getConfigRunning();
+        Document response = getConfigRunning();
         final int allInstances = response.getElementsByTagName("module").getLength();
 
         edit("netconfMessages/editConfig_replace_default.xml");
@@ -398,7 +433,7 @@ public class NetconfMappingTest extends AbstractConfigTest {
         } catch (NetconfDocumentedException e) {
             String message = e.getMessage();
             assertContainsString(message, "Element simple-long-2 present multiple times with different namespaces");
-            assertContainsString(message, "urn:opendaylight:params:xml:ns:yang:controller:test:impl");
+            assertContainsString(message, TEST_NAMESPACE);
             assertContainsString(message, XmlNetconfConstants.URN_OPENDAYLIGHT_PARAMS_XML_NS_YANG_CONTROLLER_CONFIG);
             throw e;
         }
@@ -424,7 +459,7 @@ public class NetconfMappingTest extends AbstractConfigTest {
         } catch (NetconfDocumentedException e) {
             String message = e.getMessage();
             assertContainsString(message, "Element binaryLeaf present multiple times with different namespaces");
-            assertContainsString(message, "urn:opendaylight:params:xml:ns:yang:controller:test:impl");
+            assertContainsString(message, TEST_NAMESPACE);
             assertContainsString(message, XmlNetconfConstants.URN_OPENDAYLIGHT_PARAMS_XML_NS_YANG_CONTROLLER_CONFIG);
             throw e;
         }
@@ -437,7 +472,7 @@ public class NetconfMappingTest extends AbstractConfigTest {
         edit("netconfMessages/namespaces/editConfig_typeNameConfigAttributeMatching.xml");
         commit();
 
-        Element response = getConfigRunning();
+        Document response = getConfigRunning();
         checkTypeConfigAttribute(response);
     }
 
@@ -479,7 +514,7 @@ public class NetconfMappingTest extends AbstractConfigTest {
 
         edit("netconfMessages/editConfig.xml");
         commit();
-        Element response = getConfigRunning();
+        Document response = getConfigRunning();
         final int allInstances = response.getElementsByTagName("instance").getLength();
 
         edit("netconfMessages/editConfig_replace_module.xml");
@@ -508,42 +543,32 @@ public class NetconfMappingTest extends AbstractConfigTest {
         executeOp(discardOp, "netconfMessages/discardChanges.xml");
     }
 
-    private void checkBinaryLeafEdited(final Element response) {
-        String responseTrimmed = XmlUtil.toString(response).replaceAll("\\s", "");
-        String substring = "<binaryLeafxmlns=\"urn:opendaylight:params:xml:ns:yang:controller:test:impl\">YmluYXJ5</binaryLeaf>";
-        assertContainsString(responseTrimmed, substring);
-        substring = "<binaryLeafxmlns=\"urn:opendaylight:params:xml:ns:yang:controller:test:impl\">ZGVmYXVsdEJpbg==</binaryLeaf>";
-        assertContainsString(responseTrimmed, substring);
+    private void checkBinaryLeafEdited(final Document response) throws NodeTestException, SAXException, IOException {
+        assertContainsElement(response, readXmlToElement("<binaryLeaf xmlns=\"urn:opendaylight:params:xml:ns:yang:controller:test:impl\">YmluYXJ5</binaryLeaf>"));
+        assertContainsElement(response, readXmlToElement("<binaryLeaf xmlns=\"urn:opendaylight:params:xml:ns:yang:controller:test:impl\">ZGVmYXVsdEJpbg==</binaryLeaf>"));
     }
 
-    private void checkTypedefs(final Element response) {
-        String responseTrimmed = XmlUtil.toString(response).replaceAll("\\s", "");
+    private void checkTypedefs(final Document response) throws NodeTestException, SAXException, IOException {
 
-        String substring = "<extendedxmlns=\"urn:opendaylight:params:xml:ns:yang:controller:test:impl\">10</extended>";
-        assertContainsString(responseTrimmed, substring);
+        assertContainsElement(response, readXmlToElement("<extended xmlns=\"urn:opendaylight:params:xml:ns:yang:controller:test:impl\">10</extended>"));
         // Default
-        assertContainsString(responseTrimmed,
-                "<extendedxmlns=\"urn:opendaylight:params:xml:ns:yang:controller:test:impl\">1</extended>");
+        assertContainsElement(response, readXmlToElement("<extended xmlns=\"urn:opendaylight:params:xml:ns:yang:controller:test:impl\">1</extended>"));
 
-        assertContainsString(responseTrimmed,
-                "<extended-twicexmlns=\"urn:opendaylight:params:xml:ns:yang:controller:test:impl\">20</extended-twice>");
+        assertContainsElement(response, readXmlToElement("<extended-twice xmlns=\"urn:opendaylight:params:xml:ns:yang:controller:test:impl\">20</extended-twice>"));
         // Default
-        assertContainsString(responseTrimmed,
-                "<extended-twicexmlns=\"urn:opendaylight:params:xml:ns:yang:controller:test:impl\">2</extended-twice>");
+        assertContainsElement(response, readXmlToElement("<extended-twice xmlns=\"urn:opendaylight:params:xml:ns:yang:controller:test:impl\">2</extended-twice>"));
 
-        assertContainsString(responseTrimmed,
-                "<extended-enumxmlns=\"urn:opendaylight:params:xml:ns:yang:controller:test:impl\">TWO</extended-enum>");
+        assertContainsElement(response, readXmlToElement("<extended-enum xmlns=\"urn:opendaylight:params:xml:ns:yang:controller:test:impl\">TWO</extended-enum>"));
         // Default
-        assertContainsString(responseTrimmed,
-                "<extended-enumxmlns=\"urn:opendaylight:params:xml:ns:yang:controller:test:impl\">ONE</extended-enum>");
+        assertContainsElement(response, readXmlToElement("<extended-enum xmlns=\"urn:opendaylight:params:xml:ns:yang:controller:test:impl\">ONE</extended-enum>"));
     }
 
     private void assertContainsString(String string, String substring) {
         assertThat(string, JUnitMatchers.containsString(substring));
     }
 
-    private void checkEnum(final Element response) {
-        XmlElement modulesElement = XmlElement.fromDomElement(response).getOnlyChildElement("data")
+    private void checkEnum(final Document response) {
+        XmlElement modulesElement = XmlElement.fromDomElement(response.getDocumentElement()).getOnlyChildElement("data")
                 .getOnlyChildElement("modules");
 
         String enumName = "extended-enum";
@@ -554,7 +579,6 @@ public class NetconfMappingTest extends AbstractConfigTest {
             if(name.equals(INSTANCE_NAME)) {
                 XmlElement enumAttr = moduleElement.getOnlyChildElement(enumName);
                 assertEquals(enumContent, enumAttr.getTextContent());
-
                 return;
             }
         }
@@ -562,14 +586,14 @@ public class NetconfMappingTest extends AbstractConfigTest {
         fail("Enum attribute " + enumName + ":" + enumContent + " not present in " + XmlUtil.toString(response));
     }
 
-    private void checkTestingDeps(Element response) {
+    private void checkTestingDeps(Document response) {
         int testingDepsSize = response.getElementsByTagName("testing-deps").getLength();
         assertEquals(2, testingDepsSize);
     }
 
-    private void checkTypeConfigAttribute(Element response) {
+    private void checkTypeConfigAttribute(Document response) {
 
-        XmlElement modulesElement = XmlElement.fromDomElement(response).getOnlyChildElement("data")
+        XmlElement modulesElement = XmlElement.fromDomElement(response.getDocumentElement()).getOnlyChildElement("data")
                 .getOnlyChildElement("modules");
 
         List<String> expectedValues = Lists.newArrayList("default-string", "configAttributeType");
@@ -577,7 +601,7 @@ public class NetconfMappingTest extends AbstractConfigTest {
 
         for (XmlElement moduleElement : modulesElement.getChildElements("module")) {
             for (XmlElement type : moduleElement.getChildElements("type")) {
-                if (type.getAttribute(XmlUtil.XMLNS_ATTRIBUTE_KEY).equals("") == false) {
+                if (type.getNamespace() != null) {
                     configAttributeType.add(type.getTextContent());
                 }
             }
@@ -631,7 +655,7 @@ public class NetconfMappingTest extends AbstractConfigTest {
 
         // check after edit
         commit();
-        Element response = get();
+        Document response = get();
 
         assertEquals(2/*With runtime beans*/ + 2 /*Without runtime beans*/, getElementsSize(response, "module"));
         // data from state
@@ -651,29 +675,31 @@ public class NetconfMappingTest extends AbstractConfigTest {
         RuntimeRpc netconf = new RuntimeRpc(yangStoreSnapshot, configRegistryClient, NETCONF_SESSION_ID);
 
         response = executeOp(netconf, "netconfMessages/rpc.xml");
-        assertContainsString(XmlUtil.toString(response), "testarg1".toUpperCase());
+        assertContainsElementWithText(response, "testarg1");
 
         response = executeOp(netconf, "netconfMessages/rpcInner.xml");
-        assertContainsString(XmlUtil.toString(response), "ok");
+        Document expectedReplyOk = XmlFileLoader.xmlFileToDocument("netconfMessages/rpc-reply_ok.xml");
+        XMLUnit.setIgnoreWhitespace(true);
+        XMLAssert.assertXMLEqual(expectedReplyOk, response);
 
         response = executeOp(netconf, "netconfMessages/rpcInnerInner.xml");
-        assertContainsString(XmlUtil.toString(response), "true");
+        assertContainsElementWithText(response, "true");
 
         response = executeOp(netconf, "netconfMessages/rpcInnerInner_complex_output.xml");
-        assertContainsString(XmlUtil.toString(response), "1");
-        assertContainsString(XmlUtil.toString(response), "2");
+        assertContainsElementWithText(response, "1");
+        assertContainsElementWithText(response, "2");
     }
 
-    private Element get() throws NetconfDocumentedException, ParserConfigurationException, SAXException, IOException {
+    private Document get() throws NetconfDocumentedException, ParserConfigurationException, SAXException, IOException {
         Get getOp = new Get(yangStoreSnapshot, configRegistryClient, NETCONF_SESSION_ID, transactionProvider);
         return executeOp(getOp, "netconfMessages/get.xml");
     }
 
-    private int getElementsSize(Element response, String elementName) {
+    private int getElementsSize(Document response, String elementName) {
         return response.getElementsByTagName(elementName).getLength();
     }
 
-    private Element executeOp(final NetconfOperation op, final String filename) throws ParserConfigurationException,
+    private Document executeOp(final NetconfOperation op, final String filename) throws ParserConfigurationException,
             SAXException, IOException, NetconfDocumentedException {
 
         final Document request = XmlFileLoader.xmlFileToDocument(filename);
@@ -685,7 +711,7 @@ public class NetconfMappingTest extends AbstractConfigTest {
 
         final Document response = op.handle(request, NetconfOperationChainedExecution.EXECUTION_TERMINATION_POINT);
         logger.debug("Got response\n{}", XmlUtil.toString(response));
-        return response.getDocumentElement();
+        return response;
     }
 
     private List<InputStream> getYangs() throws FileNotFoundException {
index d1c0b066d7a0a5ef15399a0544deb380177b0b01..e13415b64b71ad31aa6e2e846adbb360f3740183 100644 (file)
@@ -8,6 +8,7 @@
 
 package org.opendaylight.controller.netconf.impl;
 
+import static org.custommonkey.xmlunit.XMLAssert.assertXMLEqual;
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -87,7 +88,7 @@ public class MessageParserTest {
 
         NetconfMessage receivedMessage = (NetconfMessage) testChunkChannel.readInbound();
         assertNotNull(receivedMessage);
-        assertTrue(this.msg.getDocument().isEqualNode(receivedMessage.getDocument()));
+        assertXMLEqual(this.msg.getDocument(), receivedMessage.getDocument());
     }
 
     @Test
@@ -106,6 +107,6 @@ public class MessageParserTest {
         testChunkChannel.writeInbound(recievedOutbound);
         NetconfMessage receivedMessage = (NetconfMessage) testChunkChannel.readInbound();
         assertNotNull(receivedMessage);
-        assertTrue(this.msg.getDocument().isEqualNode(receivedMessage.getDocument()));
+        assertXMLEqual(this.msg.getDocument(), receivedMessage.getDocument());
     }
 }
index 3ca79be9d9d1cafca126370f768ef8f68f2a90f0..340e295d525fcde4ab7b543b01b77f09d0f516bf 100644 (file)
             <version>${tinybundles.version}</version>
             <scope>test</scope>
         </dependency>
+        <dependency>
+          <groupId>xmlunit</groupId>
+          <artifactId>xmlunit</artifactId>
+        </dependency>
     </dependencies>
 
     <build>
index fc1c73f7b08a663e6b876f827710a0928d7e5deb..8b2af393439e932ac069481ad3670b3b154fe0cb 100644 (file)
@@ -7,15 +7,32 @@
  */
 package org.opendaylight.controller.netconf.it;
 
-import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
+import static junit.framework.Assert.assertEquals;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.opendaylight.controller.netconf.util.test.XmlUnitUtil.assertContainsElementWithName;
+import static org.opendaylight.controller.netconf.util.test.XmlUnitUtil.assertElementsCount;
+import static org.opendaylight.controller.netconf.util.xml.XmlUtil.readXmlToDocument;
 import io.netty.channel.ChannelFuture;
-import org.apache.commons.lang3.StringUtils;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.InetSocketAddress;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+import javax.management.InstanceNotFoundException;
+import javax.management.Notification;
+import javax.management.NotificationListener;
+
 import org.junit.After;
-import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
-import org.junit.matchers.JUnitMatchers;
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
 import org.opendaylight.controller.config.manager.impl.factoriesresolver.HardcodedModuleFactoriesResolver;
@@ -32,36 +49,22 @@ import org.opendaylight.controller.netconf.confignetconfconnector.osgi.YangStore
 import org.opendaylight.controller.netconf.impl.DefaultCommitNotificationProducer;
 import org.opendaylight.controller.netconf.impl.NetconfServerDispatcher;
 import org.opendaylight.controller.netconf.impl.osgi.NetconfMonitoringServiceImpl;
-import org.opendaylight.controller.netconf.mapping.api.NetconfOperationProvider;
 import org.opendaylight.controller.netconf.impl.osgi.NetconfOperationServiceFactoryListenerImpl;
 import org.opendaylight.controller.netconf.impl.osgi.NetconfOperationServiceSnapshotImpl;
 import org.opendaylight.controller.netconf.impl.osgi.SessionMonitoringService;
 import org.opendaylight.controller.netconf.mapping.api.Capability;
+import org.opendaylight.controller.netconf.mapping.api.NetconfOperationProvider;
 import org.opendaylight.controller.netconf.mapping.api.NetconfOperationService;
 import org.opendaylight.controller.netconf.monitoring.osgi.NetconfMonitoringActivator;
 import org.opendaylight.controller.netconf.monitoring.osgi.NetconfMonitoringOperationService;
 import org.opendaylight.controller.netconf.persist.impl.ConfigPersisterNotificationHandler;
 import org.opendaylight.controller.netconf.util.test.XmlFileLoader;
-import org.opendaylight.controller.netconf.util.xml.XmlUtil;
+import org.w3c.dom.Document;
 import org.w3c.dom.Element;
+import org.xml.sax.SAXException;
 
-import javax.management.InstanceNotFoundException;
-import javax.management.Notification;
-import javax.management.NotificationListener;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.InetSocketAddress;
-import java.util.Collection;
-import java.util.List;
-import java.util.Set;
-
-import static junit.framework.Assert.assertEquals;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
 
 public class NetconfConfigPersisterITTest extends AbstractNetconfConfigTest {
 
@@ -128,15 +131,15 @@ public class NetconfConfigPersisterITTest extends AbstractNetconfConfigTest {
 
                 try (TestingNetconfClient netconfClient = new TestingNetconfClient("client", tcpAddress, 4000, clientDispatcher)) {
                     NetconfMessage response = netconfClient.sendMessage(loadGetConfigMessage());
-                    assertResponse(response, "<modules");
-                    assertResponse(response, "<services");
+                    assertContainsElementWithName(response.getDocument(), "modules");
+                    assertContainsElementWithName(response.getDocument(), "services");
                     response = netconfClient.sendMessage(loadCommitMessage());
-                    assertResponse(response, "ok");
+                    assertContainsElementWithName(response.getDocument(), "ok");
 
                     response = netconfClient.sendMessage(loadEditConfigMessage());
-                    assertResponse(response, "ok");
+                    assertContainsElementWithName(response.getDocument(), "ok");
                     response = netconfClient.sendMessage(loadCommitMessage());
-                    assertResponse(response, "ok");
+                    assertContainsElementWithName(response.getDocument(), "ok");
                 }
             }
         }
@@ -161,10 +164,6 @@ public class NetconfConfigPersisterITTest extends AbstractNetconfConfigTest {
         return listener;
     }
 
-    private void assertResponse(NetconfMessage response, String content) {
-        Assert.assertThat(XmlUtil.toString(response.getDocument()), JUnitMatchers.containsString(content));
-    }
-
     private NetconfMessage loadGetConfigMessage() throws Exception {
         return XmlFileLoader.xmlFileToNetconfMessage("netconfMessages/getConfig.xml");
     }
@@ -240,15 +239,14 @@ public class NetconfConfigPersisterITTest extends AbstractNetconfConfigTest {
             assertEquals(size, snapshots.size());
         }
 
-        void assertSnapshotContent(int notificationIndex, int expectedModulesSize, int expectedServicesSize, int expectedCapsSize) {
+        void assertSnapshotContent(int notificationIndex, int expectedModulesSize, int expectedServicesSize, int expectedCapsSize)
+                throws SAXException, IOException {
             ConfigSnapshotHolder snapshot = snapshots.get(notificationIndex);
             int capsSize = snapshot.getCapabilities().size();
             assertEquals("Expected capabilities count", expectedCapsSize, capsSize);
-            String configSnapshot = snapshot.getConfigSnapshot();
-            int modulesSize = StringUtils.countMatches(configSnapshot, "<module>");
-            assertEquals("Expected modules count", expectedModulesSize, modulesSize);
-            int servicesSize = StringUtils.countMatches(configSnapshot, "<instance>");
-            assertEquals("Expected services count", expectedServicesSize, servicesSize);
+            Document configSnapshot = readXmlToDocument(snapshot.getConfigSnapshot());
+            assertElementsCount(configSnapshot, "module", expectedModulesSize);
+            assertElementsCount(configSnapshot, "instance", expectedServicesSize);
         }
 
         @Override
index e36261c6eb4803b68498c592860d15f65381f357..f581342a9c946946a498afa9584eace953982711 100644 (file)
@@ -10,11 +10,11 @@ package org.opendaylight.controller.netconf.it;
 import com.google.common.base.Charsets;
 import com.google.common.base.Optional;
 import com.google.common.collect.Sets;
+import static org.opendaylight.controller.netconf.util.test.XmlUnitUtil.assertContainsElementWithText;
 import io.netty.channel.ChannelFuture;
 import junit.framework.Assert;
 import org.junit.Before;
 import org.junit.Test;
-import org.junit.matchers.JUnitMatchers;
 import org.mockito.Mock;
 import org.opendaylight.controller.config.manager.impl.factoriesresolver.HardcodedModuleFactoriesResolver;
 import org.opendaylight.controller.config.spi.ModuleFactory;
@@ -165,8 +165,13 @@ public class NetconfMonitoringITTest extends AbstractNetconfConfigTest {
 
         sock.close();
 
-        org.junit.Assert.assertThat(responseBuilder.toString(), JUnitMatchers.containsString("<capability>urn:ietf:params:netconf:capability:candidate:1.0</capability>"));
-        org.junit.Assert.assertThat(responseBuilder.toString(), JUnitMatchers.containsString("<username>tomas</username>"));
+        String helloMsg = responseBuilder.substring(0, responseBuilder.indexOf(separator));
+        Document doc = XmlUtil.readXmlToDocument(helloMsg);
+        assertContainsElementWithText(doc, "urn:ietf:params:netconf:capability:candidate:1.0");
+
+        String replyMsg = responseBuilder.substring(responseBuilder.indexOf(separator) + separator.length());
+        doc = XmlUtil.readXmlToDocument(replyMsg);
+        assertContainsElementWithText(doc, "tomas");
     }
 
     private void assertSessionElementsInResponse(Document document, int i) {
index 18275ad78b0c53081fee38fdbfbffb7f34aa6867..266b245b7f585f7934bd0ead12786b9c00d7f719 100644 (file)
@@ -19,13 +19,16 @@ import static org.ops4j.pax.exam.CoreOptions.options;
 import static org.ops4j.pax.exam.CoreOptions.streamBundle;
 import static org.ops4j.pax.exam.CoreOptions.systemPackages;
 import static org.ops4j.pax.exam.CoreOptions.systemProperty;
+import io.netty.channel.nio.NioEventLoopGroup;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
 
 import javax.inject.Inject;
 import javax.xml.parsers.ParserConfigurationException;
 
-import com.google.common.base.Preconditions;
-import com.google.common.base.Throwables;
-import io.netty.channel.nio.NioEventLoopGroup;
 import org.junit.Assert;
 import org.junit.Ignore;
 import org.junit.Test;
@@ -46,10 +49,8 @@ import org.ops4j.pax.tinybundles.core.TinyBundles;
 import org.osgi.framework.Constants;
 import org.xml.sax.SAXException;
 
-import java.io.IOException;
-import java.net.InetSocketAddress;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeoutException;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Throwables;
 
 @Ignore
 @RunWith(PaxExam.class)
@@ -125,10 +126,10 @@ public class IdentityRefNetconfTest {
             sendMessage(edit, netconfClient);
             sendMessage(commit, netconfClient);
             sendMessage(getConfig, netconfClient, "id-test",
-                    "<afi xmlns:prefix=\"urn:opendaylight:params:xml:ns:yang:controller:config:test:types\">prefix:test-identity1</afi>",
-                    "<afi xmlns:prefix=\"urn:opendaylight:params:xml:ns:yang:controller:config:test:types\">prefix:test-identity2</afi>",
-                    "<safi xmlns:prefix=\"urn:opendaylight:params:xml:ns:yang:controller:config:test:types\">prefix:test-identity2</safi>",
-                    "<safi xmlns:prefix=\"urn:opendaylight:params:xml:ns:yang:controller:config:test:types\">prefix:test-identity1</safi>");
+                        "<prefix:afi xmlns:prefix=\"urn:opendaylight:params:xml:ns:yang:controller:config:test:types\">prefix:test-identity1</prefix:afi>",
+                        "<prefix:afi xmlns:prefix=\"urn:opendaylight:params:xml:ns:yang:controller:config:test:types\">prefix:test-identity2</prefix:afi>",
+                        "<prefix:safi xmlns:prefix=\"urn:opendaylight:params:xml:ns:yang:controller:config:test:types\">prefix:test-identity2</prefix:safi>",
+                        "<prefix:safi xmlns:prefix=\"urn:opendaylight:params:xml:ns:yang:controller:config:test:types\">prefix:test-identity1</prefix:safi>");
 
             clientDispatcher.close();
         } catch (Exception e) {
index 664a30daa57d74810ea9213847f17e0c91ce689a..96efc10403da50e82cbeb854e89033f5e4e366bf 100644 (file)
             <data xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
                <modules xmlns="urn:opendaylight:params:xml:ns:yang:controller:config">
                   <module>
-                     <type xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:test:impl">prefix:impl-identity-test</type>
-                     <name>id-test</name>
+                     <prefix:type xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:test:impl">prefix:impl-identity-test</prefix:type>
+                     <prefix:name xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:test:impl">id-test</prefix:name>
                      <identities-container xmlns="urn:opendaylight:params:xml:ns:yang:controller:test:impl">
-                        <afi xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:config:test:types">prefix:test-identity2</afi>
+                        <prefix:afi xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:config:test:types">prefix:test-identity2</prefix:afi>
                      </identities-container>
                      <identities xmlns="urn:opendaylight:params:xml:ns:yang:controller:test:impl">
-                        <safi xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:config:test:types">prefix:test-identity2</safi>
-                        <afi xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:config:test:types">prefix:test-identity1</afi>
+                        <prefix:safi xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:config:test:types">prefix:test-identity2</prefix:safi>
+                        <prefix:afi xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:config:test:types">prefix:test-identity1</prefix:afi>
                      </identities>
                      <identities xmlns="urn:opendaylight:params:xml:ns:yang:controller:test:impl">
-                        <safi xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:config:test:types">prefix:test-identity1</safi>
-                        <afi xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:config:test:types">prefix:test-identity2</afi>
+                        <prefix:safi xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:config:test:types">prefix:test-identity1</prefix:safi>
+                        <prefix:afi xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:config:test:types">prefix:test-identity2</prefix:afi>
                      </identities>
-                     <afi xmlns="urn:opendaylight:params:xml:ns:yang:controller:test:impl" xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:config:test:types">prefix:test-identity1</afi>
+                     <prefix:afi xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:config:test:types">prefix:test-identity1</prefix:afi>
                   </module>
                   <module>
-                     <type xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:md:sal:binding:impl">prefix:binding-broker-impl</type>
-                     <name>binding-broker-impl</name>
+                     <prefix:type xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:md:sal:binding:impl">prefix:binding-broker-impl</prefix:type>
+                     <prefix:name xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:md:sal:binding:impl">binding-broker-impl</prefix:name>
                      <notification-service xmlns="urn:opendaylight:params:xml:ns:yang:controller:md:sal:binding:impl">
-                        <type xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:md:sal:binding">prefix:binding-notification-service</type>
+                        <prefix:type xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:md:sal:binding">prefix:binding-notification-service</prefix:type>
                         <name>ref_binding-notification-broker</name>
                      </notification-service>
                      <data-broker xmlns="urn:opendaylight:params:xml:ns:yang:controller:md:sal:binding:impl">
-                        <type xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:md:sal:binding">prefix:binding-data-broker</type>
+                        <prefix:type xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:md:sal:binding">prefix:binding-data-broker</prefix:type>
                         <name>ref_binding-data-broker</name>
                      </data-broker>
                   </module>
                   <module>
-                     <type xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:md:sal:binding:impl">prefix:runtime-generated-mapping</type>
-                     <name>runtime-mapping-singleton</name>
+                     <prefix:type xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:md:sal:binding:impl">prefix:runtime-generated-mapping</prefix:type>
+                     <prefix:name xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:md:sal:binding:impl">runtime-mapping-singleton</prefix:name>
                   </module>
                   <module>
-                     <type xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:md:sal:binding:impl">prefix:binding-notification-broker</type>
-                     <name>binding-notification-broker</name>
+                     <prefix:type xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:md:sal:binding:impl">prefix:binding-notification-broker</prefix:type>
+                     <prefix:name xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:md:sal:binding:impl">binding-notification-broker</prefix:name>
                   </module>
                   <module>
-                     <type xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:md:sal:binding:impl">prefix:binding-data-broker</type>
-                     <name>binding-data-broker</name>
+                     <prefix:type xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:md:sal:binding:impl">prefix:binding-data-broker</prefix:type>
+                     <prefix:name xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:md:sal:binding:impl">binding-data-broker</prefix:name>
                      <dom-broker xmlns="urn:opendaylight:params:xml:ns:yang:controller:md:sal:binding:impl">
-                        <type xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:md:sal:dom">prefix:dom-broker-osgi-registry</type>
+                        <prefix:type xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:md:sal:dom">prefix:dom-broker-osgi-registry</prefix:type>
                         <name>ref_dom-broker</name>
                      </dom-broker>
                      <mapping-service xmlns="urn:opendaylight:params:xml:ns:yang:controller:md:sal:binding:impl">
-                        <type xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:md:sal:binding:impl">prefix:binding-dom-mapping-service</type>
+                        <prefix:type xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:md:sal:binding:impl">prefix:binding-dom-mapping-service</prefix:type>
                         <name>ref_runtime-mapping-singleton</name>
                      </mapping-service>
                   </module>
                   <module>
-                     <type xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:logback:config">prefix:logback</type>
-                     <name>singleton</name>
+                     <prefix:type xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:logback:config">prefix:logback</prefix:type>
+                     <prefix:name xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:logback:config">singleton</prefix:name>
                      <console-appenders xmlns="urn:opendaylight:params:xml:ns:yang:controller:logback:config">
                         <threshold-filter>DEBUG</threshold-filter>
                         <name>console</name>
                      </loggers>
                   </module>
                   <module>
-                     <type xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:md:sal:dom:impl">prefix:schema-service-singleton</type>
-                     <name>yang-schema-service</name>
+                     <prefix:type xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:md:sal:dom:impl">prefix:schema-service-singleton</prefix:type>
+                     <prefix:name xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:md:sal:dom:impl">yang-schema-service</prefix:name>
                   </module>
                   <module>
-                     <type xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:md:sal:dom:impl">prefix:hash-map-data-store</type>
-                     <name>hash-map-data-store</name>
+                     <prefix:type xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:md:sal:dom:impl">prefix:hash-map-data-store</prefix:type>
+                     <prefix:name xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:md:sal:dom:impl">hash-map-data-store</prefix:name>
                   </module>
                   <module>
-                     <type xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:md:sal:dom:impl">prefix:dom-broker-impl</type>
-                     <name>dom-broker</name>
+                     <prefix:type xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:md:sal:dom:impl">prefix:dom-broker-impl</prefix:type>
+                     <prefix:name xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:md:sal:dom:impl">dom-broker</prefix:name>
                      <data-store xmlns="urn:opendaylight:params:xml:ns:yang:controller:md:sal:dom:impl">
-                        <type xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:md:sal:dom">prefix:dom-data-store</type>
+                        <prefix:type xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:md:sal:dom">prefix:dom-data-store</prefix:type>
                         <name>ref_hash-map-data-store</name>
                      </data-store>
                   </module>
                </modules>
                <services xmlns="urn:opendaylight:params:xml:ns:yang:controller:config">
                   <service>
-                     <type xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:md:sal:dom">prefix:schema-service</type>
+                     <prefix:type xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:md:sal:dom">prefix:schema-service</prefix:type>
                      <instance>
                         <name>ref_yang-schema-service</name>
                         <provider>/modules/module[type='schema-service-singleton'][name='yang-schema-service']</provider>
                      </instance>
                   </service>
                   <service>
-                     <type xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:md:sal:dom">prefix:dom-data-store</type>
+                     <prefix:type xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:md:sal:dom">prefix:dom-data-store</prefix:type>
                      <instance>
                         <name>ref_hash-map-data-store</name>
                         <provider>/modules/module[type='hash-map-data-store'][name='hash-map-data-store']</provider>
                      </instance>
                   </service>
                   <service>
-                     <type xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:md:sal:dom">prefix:dom-broker-osgi-registry</type>
+                     <prefix:type xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:md:sal:dom">prefix:dom-broker-osgi-registry</prefix:type>
                      <instance>
                         <name>ref_dom-broker</name>
                         <provider>/modules/module[type='dom-broker-impl'][name='dom-broker']</provider>
                      </instance>
                   </service>
                   <service>
-                     <type xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:test">prefix:testing</type>
+                     <prefix:type xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:test">prefix:testing</prefix:type>
                      <instance>
                         <name>ref_id-test</name>
                         <provider>/modules/module[type='impl-identity-test'][name='id-test']</provider>
                      </instance>
                   </service>
                   <service>
-                     <type xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:md:sal:binding:impl">prefix:binding-dom-mapping-service</type>
+                     <prefix:type xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:md:sal:binding:impl">prefix:binding-dom-mapping-service</prefix:type>
                      <instance>
                         <name>ref_runtime-mapping-singleton</name>
                         <provider>/modules/module[type='runtime-generated-mapping'][name='runtime-mapping-singleton']</provider>
                      </instance>
                   </service>
                   <service>
-                     <type xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:md:sal:binding">prefix:binding-data-consumer-broker</type>
+                     <prefix:type xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:md:sal:binding">prefix:binding-data-consumer-broker</prefix:type>
                      <instance>
                         <name>ref_binding-data-broker</name>
                         <provider>/modules/module[type='binding-data-broker'][name='binding-data-broker']</provider>
                      </instance>
                   </service>
                   <service>
-                     <type xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:md:sal:binding">prefix:binding-rpc-registry</type>
+                     <prefix:type xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:md:sal:binding">prefix:binding-rpc-registry</prefix:type>
                      <instance>
                         <name>ref_binding-broker-impl</name>
                         <provider>/modules/module[type='binding-broker-impl'][name='binding-broker-impl']</provider>
                      </instance>
                   </service>
                   <service>
-                     <type xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:md:sal:binding">prefix:binding-notification-service</type>
+                     <prefix:type xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:md:sal:binding">prefix:binding-notification-service</prefix:type>
                      <instance>
                         <name>ref_binding-notification-broker</name>
                         <provider>/modules/module[type='binding-notification-broker'][name='binding-notification-broker']</provider>
                      </instance>
                   </service>
                   <service>
-                     <type xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:md:sal:binding">prefix:binding-broker-osgi-registry</type>
+                     <prefix:type xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:md:sal:binding">prefix:binding-broker-osgi-registry</prefix:type>
                      <instance>
                         <name>ref_binding-broker-impl</name>
                         <provider>/modules/module[type='binding-broker-impl'][name='binding-broker-impl']</provider>
                      </instance>
                   </service>
                   <service>
-                     <type xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:md:sal:binding">prefix:binding-notification-subscription-service</type>
+                     <prefix:type xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:md:sal:binding">prefix:binding-notification-subscription-service</prefix:type>
                      <instance>
                         <name>ref_binding-notification-broker</name>
                         <provider>/modules/module[type='binding-notification-broker'][name='binding-notification-broker']</provider>
                      </instance>
                   </service>
                   <service>
-                     <type xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:md:sal:binding">prefix:binding-data-broker</type>
+                     <prefix:type xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:md:sal:binding">prefix:binding-data-broker</prefix:type>
                      <instance>
                         <name>ref_binding-data-broker</name>
                         <provider>/modules/module[type='binding-data-broker'][name='binding-data-broker']</provider>
index d623dd979646e8f724f7abdefc9ce287a528a9fa..b75d3382b169b382c0b63039a4f7c7488ed2582d 100644 (file)
             <groupId>org.opendaylight.controller.thirdparty</groupId>
             <artifactId>ganymed</artifactId>
         </dependency>
+        <dependency>
+            <groupId>xmlunit</groupId>
+            <artifactId>xmlunit</artifactId>
+        </dependency>
     </dependencies>
 
     <build>
diff --git a/opendaylight/netconf/netconf-util/src/test/java/org/opendaylight/controller/netconf/util/test/XmlUnitUtil.java b/opendaylight/netconf/netconf-util/src/test/java/org/opendaylight/controller/netconf/util/test/XmlUnitUtil.java
new file mode 100644 (file)
index 0000000..e8a453e
--- /dev/null
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.controller.netconf.util.test;
+
+import static org.custommonkey.xmlunit.XMLAssert.assertNodeTestPasses;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.custommonkey.xmlunit.AbstractNodeTester;
+import org.custommonkey.xmlunit.NodeTest;
+import org.custommonkey.xmlunit.NodeTestException;
+import org.custommonkey.xmlunit.NodeTester;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.Text;
+
+public class XmlUnitUtil {
+
+    private XmlUnitUtil() {}
+
+    public static void assertContainsElementWithText(final Document doc, final String textToFind) throws NodeTestException {
+        NodeTest nt = new NodeTest(doc);
+        NodeTester tester = new AbstractNodeTester() {
+
+            boolean textFound = false;
+
+            @Override
+            public void testText(Text text) throws NodeTestException {
+                if(!textFound) {
+                    if (text.getData().equalsIgnoreCase(textToFind)) {
+                        textFound = true;
+                    }
+                }
+            }
+
+            @Override
+            public void noMoreNodes(NodeTest forTest) throws NodeTestException {
+                assertTrue(textFound);
+            }
+        };
+        assertNodeTestPasses(nt, tester, new short[]{Node.TEXT_NODE}, true);
+    }
+
+    public static void assertContainsElement(final Document doc, final Element testElement) throws NodeTestException {
+        NodeTest nt = new NodeTest(doc);
+        NodeTester tester = new AbstractNodeTester() {
+
+            private boolean elementFound = false;
+
+            @Override
+            public void testElement(Element element) throws NodeTestException {
+                if (!elementFound) {
+                    if(element.isEqualNode(testElement)) {
+                        elementFound = true;
+                    }
+                }
+            }
+
+            @Override
+            public void noMoreNodes(NodeTest forTest) throws NodeTestException {
+                assertTrue(elementFound);
+            }
+        };
+        assertNodeTestPasses(nt, tester, new short[]{Node.ELEMENT_NODE}, true);
+    }
+
+    public static void assertContainsElementWithName(final Document doc, final String elementName) throws NodeTestException {
+        NodeTest nt = new NodeTest(doc);
+        NodeTester tester = new AbstractNodeTester() {
+
+            private boolean elementFound = false;
+
+            @Override
+            public void testElement(Element element) throws NodeTestException {
+                if (!elementFound) {
+                    if (element.getNodeName() != null && element.getNodeName().equals(elementName)) {
+                        elementFound = true;
+                    }
+                }
+            }
+
+            @Override
+            public void noMoreNodes(NodeTest forTest) throws NodeTestException {
+                assertTrue(elementFound);
+            }
+        };
+        assertNodeTestPasses(nt, tester, new short[]{Node.ELEMENT_NODE}, true);
+    }
+
+    public static void assertElementsCount(final Document doc, final String elementName, final int expectedCount) {
+        NodeTest nt = new NodeTest(doc);
+        NodeTester tester = new AbstractNodeTester() {
+
+            private int elementFound = 0;
+
+            @Override
+            public void testElement(Element element) throws NodeTestException {
+                if (element.getNodeName() != null && element.getNodeName().equals(elementName)) {
+                    elementFound++;
+                }
+            }
+
+            @Override
+            public void noMoreNodes(NodeTest forTest) throws NodeTestException {
+                assertEquals(expectedCount, elementFound);
+            }
+        };
+        assertNodeTestPasses(nt, tester, new short[]{Node.ELEMENT_NODE}, true);
+    }
+}
diff --git a/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/rpc-reply_ok.xml b/opendaylight/netconf/netconf-util/src/test/resources/netconfMessages/rpc-reply_ok.xml
new file mode 100644 (file)
index 0000000..5949633
--- /dev/null
@@ -0,0 +1,3 @@
+<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" a="64" message-id="a">
+    <ok/>
+</rpc-reply>
\ No newline at end of file