/* * 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.netvirt.neutronvpn; import com.google.common.base.Predicates; import com.google.common.collect.Maps; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Queue; import java.util.Set; import java.util.function.Predicate; import org.opendaylight.controller.md.sal.binding.api.DataObjectModification; import org.opendaylight.controller.md.sal.binding.api.DataTreeModification; import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeEvent; import org.opendaylight.yangtools.yang.binding.ChildOf; import org.opendaylight.yangtools.yang.binding.DataObject; import org.opendaylight.yangtools.yang.binding.Identifiable; import org.opendaylight.yangtools.yang.binding.Identifier; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; import org.opendaylight.yangtools.yang.binding.KeyedInstanceIdentifier; public class ChangeUtils { private ChangeUtils() { } private static Predicate> hasDataBefore() { return input -> input != null && input.getDataBefore() != null; } private static Predicate> hasDataBeforeAndDataAfter() { return input -> input != null && input.getDataBefore() != null && input.getDataAfter() != null; } private static Predicate> hasNoDataBefore() { return input -> input != null && input.getDataBefore() == null; } private static Predicate> hasDataAfterAndMatchesFilter( final Predicate> filter) { return input -> input != null && input.getDataAfter() != null && filter.test(input); } private static Predicate> matchesEverything() { return input -> true; } private static Predicate> modificationIsDeletion() { return input -> input != null && input.getModificationType() == DataObjectModification .ModificationType.DELETE; } private static Predicate> modificationIsDeletionAndHasDataBefore() { return input -> input != null && input.getModificationType() == DataObjectModification .ModificationType.DELETE && input.getDataBefore() != null; } public static Map,T> extractCreated( AsyncDataChangeEvent, DataObject> changes,Class klazz) { return extract(changes.getCreatedData(),klazz); } /** * Extract all the instances of {@code clazz} which were created in the given set of modifications. * * @param changes The changes to process. * @param clazz The class we're interested in. * @param The type of changes we're interested in. * @param The type of changes to process. * @return The created instances, mapped by instance identifier. */ public static Map, T> extractCreated( Collection> changes, Class clazz) { return extractCreatedOrUpdated(changes, clazz, hasNoDataBefore()); } public static Map,T> extractUpdated( AsyncDataChangeEvent,DataObject> changes,Class klazz) { return extract(changes.getUpdatedData(),klazz); } /** * Extract all the instances of {@code clazz} which were updated in the given set of modifications. * * @param changes The changes to process. * @param clazz The class we're interested in. * @param The type of changes we're interested in. * @param The type of changes to process. * @return The updated instances, mapped by instance identifier. */ public static Map, T> extractUpdated( Collection> changes, Class clazz) { return extractCreatedOrUpdated(changes, clazz, hasDataBeforeAndDataAfter()); } /** * Extract all the instance of {@code clazz} which were created or updated in the given set of modifications, and * which satisfy the given filter. * * @param changes The changes to process. * @param clazz The class we're interested in. * @param filter The filter the changes must satisfy. * @param The type of changes we're interested in. * @param The type of changes to process. * @return The created or updated instances which satisfy the filter, mapped by instance identifier. */ public static Map, T> extractCreatedOrUpdated( Collection> changes, Class clazz, Predicate> filter) { Map, T> result = new HashMap<>(); for (Entry, DataObjectModification> entry : extractDataObjectModifications(changes, clazz, hasDataAfterAndMatchesFilter(filter)).entrySet()) { result.put(entry.getKey(), entry.getValue().getDataAfter()); } return result; } public static Map,T> extractCreatedOrUpdated( AsyncDataChangeEvent,DataObject> changes,Class klazz) { Map,T> result = extractUpdated(changes,klazz); result.putAll(extractCreated(changes,klazz)); return result; } /** * Extract all the instances of {@code clazz} which were created or updated in the given set of modifications. * * @param changes The changes to process. * @param clazz The class we're interested in. * @param The type of changes we're interested in. * @param The type of changes to process. * @return The created or updated instances, mapped by instance identifier. */ public static Map, T> extractCreatedOrUpdated( Collection> changes, Class clazz) { return extractCreatedOrUpdated(changes, clazz, matchesEverything()); } public static Map, T> extractCreatedOrUpdatedOrRemoved( AsyncDataChangeEvent, DataObject> changes, Class klazz) { Map,T> result = extractCreatedOrUpdated(changes,klazz); result.putAll(extractRemovedObjects(changes, klazz)); return result; } /** * Extract all the instances of {@code clazz} which were created, updated, or removed in the given set of * modifications. For instances which were created or updated, the new instances are returned; for instances * which were removed, the old instances are returned. * * @param changes The changes to process. * @param clazz The class we're interested in. * @param The type of changes we're interested in. * @param The type of changes to process. * @return The created, updated or removed instances, mapped by instance identifier. */ public static Map, T> extractCreatedOrUpdatedOrRemoved( Collection> changes, Class clazz) { Map, T> result = extractCreatedOrUpdated(changes, clazz); result.putAll(extractRemovedObjects(changes, clazz)); return result; } public static Map,T> extractOriginal( AsyncDataChangeEvent,DataObject> changes,Class klazz) { return extract(changes.getOriginalData(),klazz); } /** * Extract the original instances of class {@code clazz} in the given set of modifications. * * @param changes The changes to process. * @param clazz The class we're interested in. * @param The type of changes we're interested in. * @param The type of changes to process. * @return The original instances, mapped by instance identifier. */ public static Map, T> extractOriginal( Collection> changes, Class clazz) { Map, T> result = new HashMap<>(); for (Entry, DataObjectModification> entry : extractDataObjectModifications(changes, clazz, hasDataBefore()).entrySet()) { result.put(entry.getKey(), entry.getValue().getDataBefore()); } return result; } public static Set> extractRemoved( AsyncDataChangeEvent,DataObject> changes,Class klazz) { Set> result = new HashSet<>(); if (changes != null && changes.getRemovedPaths() != null) { for (InstanceIdentifier iid : changes.getRemovedPaths()) { if (iid.getTargetType().equals(klazz)) { // Actually checked above @SuppressWarnings("unchecked") InstanceIdentifier iidn = (InstanceIdentifier)iid; result.add(iidn); } } } return result; } /** * Extract the instance identifier of removed instances of {@code clazz} from the given set of modifications. * * @param changes The changes to process. * @param clazz The class we're interested in. * @param The type of changes we're interested in. * @param The type of changes to process. * @return The instance identifiers of removed instances. */ public static Set> extractRemoved( Collection> changes, Class clazz) { return extractDataObjectModifications(changes, clazz, modificationIsDeletion()).keySet(); } /** * Extract all the modifications affecting instances of {@code clazz} which are present in the given set of * modifications and satisfy the given filter. * * @param changes The changes to process. * @param clazz The class we're interested in. * @param filter The filter the changes must satisfy. * @param The type of changes we're interested in. * @param The type of changes to process. * @return The modifications, mapped by instance identifier. */ private static Map, DataObjectModification> extractDataObjectModifications(Collection> changes, Class clazz, Predicate> filter) { List> dataObjectModifications = new ArrayList<>(); List> paths = new ArrayList<>(); if (changes != null) { for (DataTreeModification change : changes) { dataObjectModifications.add(change.getRootNode()); paths.add(change.getRootPath().getRootIdentifier()); } } return extractDataObjectModifications(dataObjectModifications, paths, clazz, filter); } /** * Extract all the modifications affecting instances of {@code clazz} which are present in the given set of * modifications and satisfy the given filter. * * @param changes The changes to process. * @param paths The paths of the changes. * @param clazz The class we're interested in. * @param filter The filter the changes must satisfy. * @param The type of changes we're interested in. * @return The modifications, mapped by instance identifier. */ private static Map, DataObjectModification> extractDataObjectModifications( Collection> changes, Collection> paths, Class clazz, Predicate> filter) { Map, DataObjectModification> result = new HashMap<>(); Queue> remainingChanges = new LinkedList<>(changes); Queue> remainingPaths = new LinkedList<>(paths); while (!remainingChanges.isEmpty()) { DataObjectModification change = remainingChanges.remove(); InstanceIdentifier path = remainingPaths.remove(); // Is the change relevant? if (clazz.isAssignableFrom(change.getDataType()) && filter.test((DataObjectModification) change)) { result.put((InstanceIdentifier) path, (DataObjectModification) change); } // Add any children to the queue for (DataObjectModification child : change.getModifiedChildren()) { remainingChanges.add(child); remainingPaths.add(extendPath(path, child)); } } return result; } /** * Extends the given instance identifier path to include the given child. Augmentations are treated in the same way * as children; keyed children are handled correctly. * * @param path The current path. * @param child The child modification to include. * @return The extended path. */ private static & ChildOf, K extends Identifier, T extends DataObject> InstanceIdentifier extendPath( InstanceIdentifier path, DataObjectModification child) { Class item = (Class) child.getDataType(); if (child.getIdentifier() instanceof InstanceIdentifier.IdentifiableItem) { K key = (K) ((InstanceIdentifier.IdentifiableItem) child.getIdentifier()).getKey(); KeyedInstanceIdentifier extendedPath = path.child(item, key); return extendedPath; } else { InstanceIdentifier extendedPath = path.child(item); return extendedPath; } } public static Map, T> extractRemovedObjects( AsyncDataChangeEvent, DataObject> changes, Class klazz) { Set> iids = extractRemoved(changes, klazz); return Maps.filterKeys(extractOriginal(changes, klazz),Predicates.in(iids)); } /** * Extract the removed instances of {@code clazz} from the given set of modifications. * * @param changes The changes to process. * @param clazz The class we're interested in. * @param The type of changes we're interested in. * @param The type of changes to process. * @return The removed instances, keyed by instance identifier. */ public static Map, T> extractRemovedObjects( Collection> changes, Class clazz) { Map, T> result = new HashMap<>(); for (Entry, DataObjectModification> entry : extractDataObjectModifications(changes, clazz, modificationIsDeletionAndHasDataBefore()).entrySet()) { result.put(entry.getKey(), entry.getValue().getDataBefore()); } return result; } public static Map,T> extract( Map, DataObject> changes, Class klazz) { Map,T> result = new HashMap<>(); if (changes != null) { for (Entry, DataObject> created : changes.entrySet()) { if (klazz.isInstance(created.getValue())) { @SuppressWarnings("unchecked") T value = (T) created.getValue(); Class type = created.getKey().getTargetType(); if (type.equals(klazz)) { // Actually checked above @SuppressWarnings("unchecked") InstanceIdentifier iid = (InstanceIdentifier) created.getKey(); result.put(iid, value); } } } } return result; } }