/* * Copyright (c) 2018 Inocybe Technologies 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.test; import static org.junit.Assert.fail; import com.google.common.util.concurrent.SettableFuture; import com.google.common.util.concurrent.Uninterruptibles; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Objects; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.function.Function; import org.opendaylight.controller.md.sal.binding.api.DataObjectModification.ModificationType; import org.opendaylight.controller.md.sal.binding.api.DataTreeChangeListener; import org.opendaylight.controller.md.sal.binding.api.DataTreeIdentifier; import org.opendaylight.controller.md.sal.binding.api.DataTreeModification; import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType; import org.opendaylight.yangtools.yang.binding.DataObject; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; /** * Abstract base that provides a DTCL for verification. * * @author Thomas Pantelis */ public class AbstractDataTreeChangeListenerTest extends AbstractConcurrentDataBrokerTest { protected static final class TestListener implements DataTreeChangeListener { private final List> accumulatedChanges = new ArrayList<>(); private final SettableFuture>> future = SettableFuture.create(); private final Function, Boolean>[] matchers; private final int expChangeCount; private TestListener(Function, Boolean>[] matchers) { this.expChangeCount = matchers.length; this.matchers = matchers; } @Override public void onDataTreeChanged(Collection> changes) { synchronized (accumulatedChanges) { accumulatedChanges.addAll(changes); if (expChangeCount == accumulatedChanges.size()) { future.set(new ArrayList<>(accumulatedChanges)); } } } public Collection> changes() { try { final Collection> changes = future.get(5, TimeUnit.SECONDS); Uninterruptibles.sleepUninterruptibly(500, TimeUnit.MILLISECONDS); return changes; } catch (InterruptedException | TimeoutException | ExecutionException e) { throw new AssertionError(String.format( "Data tree change notifications not received. Expected: %s. Actual: %s - %s", expChangeCount, accumulatedChanges.size(), accumulatedChanges), e); } } public void verify() { Collection> changes = new ArrayList<>(changes()); Iterator> iter = changes.iterator(); while (iter.hasNext()) { DataTreeModification dataTreeModification = iter.next(); for (Function, Boolean> matcher: matchers) { if (matcher.apply(dataTreeModification)) { iter.remove(); break; } } } if (!changes.isEmpty()) { DataTreeModification mod = changes.iterator().next(); fail(String.format("Received unexpected notification: type: %s, path: %s, before: %s, after: %s", mod.getRootNode().getModificationType(), mod.getRootPath().getRootIdentifier(), mod.getRootNode().getDataBefore(), mod.getRootNode().getDataAfter())); } } public boolean hasChanges() { synchronized (accumulatedChanges) { return !accumulatedChanges.isEmpty(); } } } protected AbstractDataTreeChangeListenerTest() { super(true); } @SafeVarargs protected final TestListener createListener(final LogicalDatastoreType store, final InstanceIdentifier path, Function, Boolean>... matchers) { TestListener listener = new TestListener<>(matchers); getDataBroker().registerDataTreeChangeListener(new DataTreeIdentifier<>(store, path), listener); return listener; } public static Function, Boolean> match( ModificationType type, InstanceIdentifier path, Function checkDataBefore, Function checkDataAfter) { return modification -> type == modification.getRootNode().getModificationType() && path.equals(modification.getRootPath().getRootIdentifier()) && checkDataBefore.apply(modification.getRootNode().getDataBefore()) && checkDataAfter.apply(modification.getRootNode().getDataAfter()); } public static Function, Boolean> match( ModificationType type, InstanceIdentifier path, T expDataBefore, T expDataAfter) { return match(type, path, dataBefore -> Objects.equals(expDataBefore, dataBefore), (Function) dataAfter -> Objects.equals(expDataAfter, dataAfter)); } public static Function, Boolean> added( InstanceIdentifier path, T data) { return match(ModificationType.WRITE, path, null, data); } public static Function, Boolean> replaced( InstanceIdentifier path, T dataBefore, T dataAfter) { return match(ModificationType.WRITE, path, dataBefore, dataAfter); } public static Function, Boolean> deleted( InstanceIdentifier path, T dataBefore) { return match(ModificationType.DELETE, path, dataBefore, null); } public static Function, Boolean> subtreeModified( InstanceIdentifier path, T dataBefore, T dataAfter) { return match(ModificationType.SUBTREE_MODIFIED, path, dataBefore, dataAfter); } }