<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>
<type>test-jar</type>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>xmlunit</groupId>
+ <artifactId>xmlunit</artifactId>
+ </dependency>
</dependencies>
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;
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++]);
<type>test-jar</type>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>xmlunit</groupId>
+ <artifactId>xmlunit</artifactId>
+ </dependency>
</dependencies>
<build>
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;
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 {
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();
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
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()));
}
*/
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;
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
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
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;
@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,
@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();
}
}
}
}
- 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;
@Override
public void setDomProviderContext(final ProviderSession domProviderContext) {
- this.context = domProviderContext;
+ this.context = domProviderContext;
}
@Override
// NOOP
}
-
-
-
}
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;
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(
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()) {
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);
}
package org.opendaylight.controller.md.sal.binding.impl;
import java.util.Collections;
+import java.util.HashMap;
import java.util.Map;
import java.util.Set;
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;
@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
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;
@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
--- /dev/null
+/*
+ * 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));
+ }
+
+}
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;
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);
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);
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);
}
}
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());
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());
}
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;
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));
}
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;
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
+ ", 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;
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) {
}
}
+ 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();
+ }
+ }
+
}
+++ /dev/null
-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();
- }
-}
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();
}
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()) {
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);
}
}
private DataAndMetadataSnapshot storeSnapshot;
private Optional<StoreMetadataNode> proposedSubtree;
- private DataChangeEventResolver listenerResolver;
+ private ResolveDataChangeEventsTask listenerResolver;
public ThreePhaseCommitImpl(final SnaphostBackedWriteTransaction writeTransaction) {
this.transaction = writeTransaction;
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) //
--- /dev/null
+/*
+ * 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();
+ }
+}
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> //
<groupId>org.opendaylight.controller</groupId>
<artifactId>commons.logback_settings</artifactId>
</dependency>
+ <dependency>
+ <groupId>xmlunit</groupId>
+ <artifactId>xmlunit</artifactId>
+ </dependency>
</dependencies>
<build>
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;
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;
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;
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;
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;
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;
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
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>"));
}
createModule(INSTANCE_NAME);
edit("netconfMessages/editConfig.xml");
- Element configCandidate = getConfigCandidate();
+ Document configCandidate = getConfigCandidate();
checkBinaryLeafEdited(configCandidate);
// check after edit
commit();
- Element response = getConfigRunning();
+ Document response = getConfigRunning();
checkBinaryLeafEdited(response);
checkTypeConfigAttribute(response);
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();
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>"));
}
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);
edit("netconfMessages/editConfig.xml");
commit();
- Element response = getConfigRunning();
+ Document response = getConfigRunning();
final int allInstances = response.getElementsByTagName("module").getLength();
edit("netconfMessages/editConfig_replace_default.xml");
} 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;
}
} 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;
}
edit("netconfMessages/namespaces/editConfig_typeNameConfigAttributeMatching.xml");
commit();
- Element response = getConfigRunning();
+ Document response = getConfigRunning();
checkTypeConfigAttribute(response);
}
edit("netconfMessages/editConfig.xml");
commit();
- Element response = getConfigRunning();
+ Document response = getConfigRunning();
final int allInstances = response.getElementsByTagName("instance").getLength();
edit("netconfMessages/editConfig_replace_module.xml");
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";
if(name.equals(INSTANCE_NAME)) {
XmlElement enumAttr = moduleElement.getOnlyChildElement(enumName);
assertEquals(enumContent, enumAttr.getTextContent());
-
return;
}
}
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");
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());
}
}
// 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
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);
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 {
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;
NetconfMessage receivedMessage = (NetconfMessage) testChunkChannel.readInbound();
assertNotNull(receivedMessage);
- assertTrue(this.msg.getDocument().isEqualNode(receivedMessage.getDocument()));
+ assertXMLEqual(this.msg.getDocument(), receivedMessage.getDocument());
}
@Test
testChunkChannel.writeInbound(recievedOutbound);
NetconfMessage receivedMessage = (NetconfMessage) testChunkChannel.readInbound();
assertNotNull(receivedMessage);
- assertTrue(this.msg.getDocument().isEqualNode(receivedMessage.getDocument()));
+ assertXMLEqual(this.msg.getDocument(), receivedMessage.getDocument());
}
}
<version>${tinybundles.version}</version>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>xmlunit</groupId>
+ <artifactId>xmlunit</artifactId>
+ </dependency>
</dependencies>
<build>
*/
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;
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 {
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");
}
}
}
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");
}
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
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;
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) {
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;
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)
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) {
<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>
<groupId>org.opendaylight.controller.thirdparty</groupId>
<artifactId>ganymed</artifactId>
</dependency>
+ <dependency>
+ <groupId>xmlunit</groupId>
+ <artifactId>xmlunit</artifactId>
+ </dependency>
</dependencies>
<build>
--- /dev/null
+/*
+ * 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);
+ }
+}
--- /dev/null
+<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