/* * Copyright (c) 2015 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.protocol.bgp.rib.spi; import static com.google.common.base.Verify.verify; import static com.google.common.base.Verify.verifyNotNull; import static java.util.Objects.requireNonNull; import static org.opendaylight.protocol.bgp.rib.spi.RIBNodeIdentifiers.BGPRIB_NID; import static org.opendaylight.protocol.bgp.rib.spi.RIBNodeIdentifiers.LOCRIB_NID; import static org.opendaylight.protocol.bgp.rib.spi.RIBNodeIdentifiers.RIB_NID; import static org.opendaylight.protocol.bgp.rib.spi.RIBNodeIdentifiers.ROUTES_NID; import static org.opendaylight.protocol.bgp.rib.spi.RIBNodeIdentifiers.TABLES_NID; import static org.opendaylight.protocol.bgp.rib.spi.RIBQNames.AFI_QNAME; import static org.opendaylight.protocol.bgp.rib.spi.RIBQNames.SAFI_QNAME; import com.google.common.annotations.Beta; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.collect.ImmutableList; import java.util.Collection; import java.util.List; import java.util.Set; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; import org.opendaylight.bgp.concepts.RouteDistinguisherUtil; import org.opendaylight.mdsal.binding.dom.codec.api.BindingNormalizedNodeSerializer; import org.opendaylight.mdsal.common.api.LogicalDatastoreType; import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteTransaction; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev200120.Update; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev200120.UpdateBuilder; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev200120.path.attributes.Attributes; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev200120.path.attributes.AttributesBuilder; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.AttributesReachBuilder; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.AttributesUnreachBuilder; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.attributes.reach.MpReachNlri; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.attributes.reach.MpReachNlriBuilder; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.attributes.reach.mp.reach.nlri.AdvertizedRoutes; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.attributes.reach.mp.reach.nlri.AdvertizedRoutesBuilder; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.attributes.unreach.MpUnreachNlri; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.attributes.unreach.MpUnreachNlriBuilder; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.attributes.unreach.mp.unreach.nlri.WithdrawnRoutes; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.attributes.unreach.mp.unreach.nlri.WithdrawnRoutesBuilder; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.destination.DestinationType; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.BgpRib; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.Route; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.bgp.rib.Rib; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.bgp.rib.rib.LocRib; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.rib.Tables; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.rib.TablesBuilder; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.rib.TablesKey; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.rib.tables.Routes; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev200120.AddressFamily; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev200120.RouteDistinguisher; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev200120.SubsequentAddressFamily; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev200120.next.hop.CNextHop; import org.opendaylight.yangtools.util.ImmutableOffsetMapTemplate; import org.opendaylight.yangtools.yang.binding.ChildOf; import org.opendaylight.yangtools.yang.binding.ChoiceIn; import org.opendaylight.yangtools.yang.binding.DataObject; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; import org.opendaylight.yangtools.yang.binding.KeyAware; import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.common.QNameModule; import org.opendaylight.yangtools.yang.common.Uint32; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument; import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode; import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild; import org.opendaylight.yangtools.yang.data.api.schema.DataContainerNode; import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode.BuilderFactory; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodes; import org.opendaylight.yangtools.yang.data.api.schema.builder.DataContainerNodeBuilder; import org.opendaylight.yangtools.yang.data.spi.node.ImmutableNodes; import org.opendaylight.yangtools.yang.data.tree.api.DataTreeCandidateNode; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @Beta public abstract class AbstractRIBSupport< C extends Routes & DataObject & ChoiceIn, S extends ChildOf, R extends Route & ChildOf & KeyAware> implements RIBSupport { public static final String ROUTE_KEY = "route-key"; private static final Logger LOG = LoggerFactory.getLogger(AbstractRIBSupport.class); private static final NodeIdentifier ADVERTISED_ROUTES = NodeIdentifier.create(AdvertizedRoutes.QNAME); private static final NodeIdentifier WITHDRAWN_ROUTES = NodeIdentifier.create(WithdrawnRoutes.QNAME); private static final NodeIdentifier DESTINATION_TYPE = NodeIdentifier.create(DestinationType.QNAME); private static final InstanceIdentifier TABLES_II = InstanceIdentifier.builder(BgpRib.class) .child(Rib.class).child(LocRib.class).child(Tables.class).build(); private static final ApplyRoute DELETE_ROUTE = new DeleteRoute(); private static final ImmutableOffsetMapTemplate TABLES_KEY_TEMPLATE = ImmutableOffsetMapTemplate.ordered( ImmutableList.of(AFI_QNAME, SAFI_QNAME)); private static final BuilderFactory BUILDER_FACTORY = ImmutableNodes.builderFactory(); // Instance identifier to table/(choice routes)/(map of route) private final LoadingCache routesPath = CacheBuilder.newBuilder() .weakValues().build(new CacheLoader() { @Override public YangInstanceIdentifier load(final YangInstanceIdentifier routesTablePaths) { return routesTablePaths.node(routesContainerIdentifier()).node(routeQName()); } }); private final NodeIdentifier routesContainerIdentifier; private final NodeIdentifier routesListIdentifier; private final NodeIdentifier routeAttributesIdentifier; private final Class cazeClass; private final Class containerClass; private final Class listClass; private final ApplyRoute putRoute = new PutRoute(); private final MapEntryNode emptyTable; private final QName routeQname; private final QName routeKeyQname; private final NodeIdentifier destinationNid; private final NodeIdentifier pathIdNid; private final NodeIdentifier prefixTypeNid; private final NodeIdentifier rdNid; protected final BindingNormalizedNodeSerializer mappingService; protected final YangInstanceIdentifier routeDefaultYii; private final @NonNull TablesKey tk; private final NodeIdentifierWithPredicates tablesKey; private final ImmutableList relativeRoutesPath; private final ImmutableOffsetMapTemplate routeKeyTemplate; /** * Default constructor. Requires the QName of the container augmented under the routes choice * node in instantiations of the rib grouping. It is assumed that this container is defined by * the same model which populates it with route grouping instantiation, and by extension with * the route attributes container. * * @param mappingService Serialization service * @param cazeClass Binding class of the AFI/SAFI-specific case statement, must not be null * @param containerClass Binding class of the container in routes choice, must not be null. * @param listClass Binding class of the route list, nust not be null; * @param afi Address Family * @param safi Subsequent Address Family * @param destContainerQname destination Container Qname */ protected AbstractRIBSupport( final BindingNormalizedNodeSerializer mappingService, final Class cazeClass, final QName cazeQName, final Class containerClass, final QName containerQName, final Class listClass, final QName listQName, final AddressFamily afi, final QName afiQName, final SubsequentAddressFamily safi, final QName safiQName, final QName destContainerQname) { this.mappingService = requireNonNull(mappingService); this.cazeClass = requireNonNull(cazeClass); this.containerClass = requireNonNull(containerClass); this.listClass = requireNonNull(listClass); tk = new TablesKey(afi, safi); tablesKey = NodeIdentifierWithPredicates.of(Tables.QNAME, TABLES_KEY_TEMPLATE.instantiateWithValues(afiQName, safiQName)); destinationNid = NodeIdentifier.create(destContainerQname); final QNameModule module = cazeQName.getModule(); routesContainerIdentifier = NodeIdentifier.create(containerQName.bindTo(module)); routeAttributesIdentifier = NodeIdentifier.create(Attributes.QNAME.bindTo(module)); routeQname = listQName.bindTo(module); routeKeyQname = QName.create(module, ROUTE_KEY).intern(); routesListIdentifier = NodeIdentifier.create(routeQname); emptyTable = (MapEntryNode) mappingService.toNormalizedDataObject(TABLES_II, new TablesBuilder().withKey(tk) .setAttributes(new org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329 .rib.tables.AttributesBuilder().build()).build()).node(); pathIdNid = NodeIdentifier.create(QName.create(routeQName(), "path-id").intern()); prefixTypeNid = NodeIdentifier.create(QName.create(destContainerQname, "prefix").intern()); rdNid = NodeIdentifier.create(QName.create(destContainerQname, "route-distinguisher").intern()); routeDefaultYii = YangInstanceIdentifier.of(BGPRIB_NID, RIB_NID, RIB_NID, LOCRIB_NID, TABLES_NID, TABLES_NID, ROUTES_NID, routesContainerIdentifier, routesListIdentifier, routesListIdentifier); relativeRoutesPath = ImmutableList.of(routesContainerIdentifier, routesListIdentifier); routeKeyTemplate = ImmutableOffsetMapTemplate.ordered( ImmutableList.of(pathIdNid.getNodeType(), routeKeyQname)); } @Override public final TablesKey getTablesKey() { return tk; } @Override public final NodeIdentifierWithPredicates tablesKey() { return tablesKey; } @Override public final Class routesCaseClass() { return cazeClass; } @Override public final Class routesContainerClass() { return containerClass; } @Override public final Class routesListClass() { return listClass; } @Override public final MapEntryNode emptyTable() { return emptyTable; } public final QName routeQName() { return routeQname; } protected final NodeIdentifier prefixNid() { return prefixTypeNid; } protected final NodeIdentifier routeNid() { return routesListIdentifier; } /** * Build MpReachNlri object from DOM representation. * * @param routes Collection of MapEntryNode DOM representation of routes * @param hop CNextHop as it was parsed from Attributes, to be included in MpReach object * @return MpReachNlri */ private MpReachNlri buildReach(final Collection routes, final CNextHop hop) { return new MpReachNlriBuilder() .setAfi(tk.getAfi()) .setSafi(tk.getSafi()) .setCNextHop(hop) .setAdvertizedRoutes(new AdvertizedRoutesBuilder().setDestinationType(buildDestination(routes)).build()) .build(); } /** * Build MpUnReachNlri object from DOM representation. * * @param routes Collection of MapEntryNode DOM representation of routes * @return MpUnreachNlri */ private MpUnreachNlri buildUnreach(final Collection routes) { return new MpUnreachNlriBuilder() .setAfi(tk.getAfi()) .setSafi(tk.getSafi()) .setWithdrawnRoutes(new WithdrawnRoutesBuilder() .setDestinationType(buildWithdrawnDestination(routes)) .build()) .build(); } protected abstract DestinationType buildDestination(Collection routes); protected abstract DestinationType buildWithdrawnDestination(Collection routes); /** * Return the {@link NodeIdentifier} of the AFI/SAFI-specific container under * the RIB routes. * * @return Container identifier, may not be null. */ public final NodeIdentifier routesContainerIdentifier() { return routesContainerIdentifier; } /** * Return the {@link NodeIdentifier} of the AFI/SAFI-specific container under * the NLRI destination. * * @return Container identifier, may not be null. */ private NodeIdentifier destinationContainerIdentifier() { return destinationNid; } /** * Given the destination as ContainerNode, implementation needs to parse the DOM model * from this point onward: * * {@code /bgp-mp:mp-unreach-nlri/bgp-mp:withdrawn-routes/bgp-mp:destination-type} * and delete the routes from its RIBs. * * @param tx DOMDataWriteTransaction to be passed into implementation * @param tablePath YangInstanceIdentifier to be passed into implementation * @param destination ContainerNode DOM representation of NLRI in Update message * @param routesNodeId NodeIdentifier */ private void deleteDestinationRoutes(final DOMDataTreeWriteTransaction tx, final YangInstanceIdentifier tablePath, final ContainerNode destination, final NodeIdentifier routesNodeId) { processDestination(tx, tablePath.node(routesNodeId), destination, null, DELETE_ROUTE); } /** * Given the destination as ContainerNode, implementation needs to parse the DOM model * from this point onward: * * {@code /bgp-mp:mp-reach-nlri/bgp-mp:advertized-routes/bgp-mp:destination-type} * and put the routes to its RIBs. * * @param tx DOMDataWriteTransaction to be passed into implementation * @param tablePath YangInstanceIdentifier to be passed into implementation * @param destination ContainerNode DOM representation of NLRI in Update message * @param attributes ContainerNode to be passed into implementation * @param routesNodeId NodeIdentifier * @return List of processed route identifiers */ private Collection putDestinationRoutes(final DOMDataTreeWriteTransaction tx, final YangInstanceIdentifier tablePath, final ContainerNode destination, final ContainerNode attributes, final NodeIdentifier routesNodeId) { return processDestination(tx, tablePath.node(routesNodeId), destination, attributes, putRoute); } protected abstract Collection processDestination(DOMDataTreeWriteTransaction tx, YangInstanceIdentifier routesPath, ContainerNode destination, ContainerNode attributes, ApplyRoute applyFunction); private static ContainerNode getDestination(final DataContainerChild routes, final NodeIdentifier destinationId) { if (routes instanceof ContainerNode) { final DataContainerChild destination = ((ContainerNode) routes).childByArg(DESTINATION_TYPE); if (destination instanceof ChoiceNode) { final DataContainerChild ret = ((ChoiceNode) destination).childByArg(destinationId); if (ret != null) { if (ret instanceof ContainerNode) { return (ContainerNode) ret; } LOG.debug("Specified node {} is not a container, ignoring it", ret); } else { LOG.debug("Specified container {} is not present in destination {}", destinationId, destination); } } else { LOG.warn("Destination {} is not a choice, ignoring it", destination); } } else { LOG.warn("Advertized routes {} are not a container, ignoring it", routes); } return null; } @Override public final NodeIdentifier routeAttributesIdentifier() { return routeAttributesIdentifier; } @Override public final Collection changedRoutes(final DataTreeCandidateNode routes) { final var myRoutes = routes.modifiedChild(routesContainerIdentifier); if (myRoutes != null) { final var route = myRoutes.modifiedChild(routeNid()); if (route != null) { // Well, given the remote possibility of augmentation, we should perform a filter here, // to make sure the type matches what routeType() reports. return route.childNodes(); } } return Set.of(); } @Override public final YangInstanceIdentifier routesPath(final YangInstanceIdentifier routesTablePaths) { return routesYangInstanceIdentifier(routesTablePaths.node(ROUTES_NID)); } @Override public final List relativeRoutesPath() { return relativeRoutesPath; } @Override public final YangInstanceIdentifier createRouteIdentifier(final YangInstanceIdentifier tablePath, final NodeIdentifierWithPredicates newRouteKey) { return routesPath(tablePath).node(newRouteKey); } @Override public final MapEntryNode createRoute(final MapEntryNode route, final NodeIdentifierWithPredicates key, final ContainerNode attributes) { final DataContainerNodeBuilder builder; if (route != null) { builder = BUILDER_FACTORY.newMapEntryBuilder(route); } else { builder = BUILDER_FACTORY.newMapEntryBuilder(); } return builder .withNodeIdentifier(key) .withChild(ImmutableNodes.leafNode(pathIdNid, extractPathId(key))) .withChild(ImmutableNodes.leafNode(routeKeyQname, extractRouteKey(key))) .withChild(attributes) .build(); } @Override public final NodeIdentifierWithPredicates createRouteListArgument(final Uint32 pathId, final String routeKey) { return NodeIdentifierWithPredicates.of(routeQname, routeKeyTemplate.instantiateWithValues(pathId, routeKey)); } @Override public final void deleteRoutes(final DOMDataTreeWriteTransaction tx, final YangInstanceIdentifier tablePath, final ContainerNode nlri) { deleteRoutes(tx, tablePath, nlri, ROUTES_NID); } @Override public final void deleteRoutes(final DOMDataTreeWriteTransaction tx, final YangInstanceIdentifier tablePath, final ContainerNode nlri, final NodeIdentifier routesNodeId) { final DataContainerChild routes = nlri.childByArg(WITHDRAWN_ROUTES); if (routes != null) { final ContainerNode destination = getDestination(routes, destinationContainerIdentifier()); if (destination != null) { deleteDestinationRoutes(tx, tablePath, destination, routesNodeId); } } else { LOG.debug("Withdrawn routes are not present in NLRI {}", nlri); } } @Override public final Collection putRoutes(final DOMDataTreeWriteTransaction tx, final YangInstanceIdentifier tablePath, final ContainerNode nlri, final ContainerNode attributes) { return putRoutes(tx, tablePath, nlri, attributes, ROUTES_NID); } @Override public final Collection putRoutes(final DOMDataTreeWriteTransaction tx, final YangInstanceIdentifier tablePath, final ContainerNode nlri, final ContainerNode attributes, final NodeIdentifier routesNodeId) { final DataContainerChild routes = nlri.childByArg(ADVERTISED_ROUTES); if (routes != null) { final ContainerNode destination = getDestination(routes, destinationContainerIdentifier()); if (destination != null) { return putDestinationRoutes(tx, tablePath, destination, attributes, routesNodeId); } } else { LOG.debug("Advertized routes are not present in NLRI {}", nlri); } return List.of(); } @Override public final Update buildUpdate(final Collection advertised, final Collection withdrawn, final Attributes attr) { final UpdateBuilder ub = new UpdateBuilder(); final AttributesBuilder ab = new AttributesBuilder(attr); final CNextHop hop = ab.getCNextHop(); LOG.debug("cnextHop before={}", hop); // do not preserve next hop in attributes if we are using MpReach ab.setCNextHop(null); if (!advertised.isEmpty()) { final MpReachNlri mb = buildReach(advertised, hop); ab.addAugmentation(new AttributesReachBuilder().setMpReachNlri(mb).build()); LOG.debug("mpreach nexthop={}", mb); } if (!withdrawn.isEmpty()) { final MpUnreachNlri mb = buildUnreach(withdrawn); ab.addAugmentation(new AttributesUnreachBuilder().setMpUnreachNlri(mb).build()); LOG.debug("mpunrach mb={}", mb); } ub.setAttributes(ab.build()); LOG.debug("update {}", ub.build()); return ub.build(); } private static final class DeleteRoute implements ApplyRoute { @Override public void apply(final DOMDataTreeWriteTransaction tx, final YangInstanceIdentifier base, final NodeIdentifierWithPredicates routeKey, final DataContainerNode route, final ContainerNode attributes) { tx.delete(LogicalDatastoreType.OPERATIONAL, base.node(routeKey)); } } private final class PutRoute implements ApplyRoute { @Override public void apply(final DOMDataTreeWriteTransaction tx, final YangInstanceIdentifier base, final NodeIdentifierWithPredicates routeKey, final DataContainerNode route, final ContainerNode attributes) { // Build the DataContainer data final var b = ImmutableNodes.newMapEntryBuilder().withNodeIdentifier(routeKey); route.body().forEach(b::withChild); // Add attributes b.withChild(BUILDER_FACTORY.newContainerBuilder(attributes) .withNodeIdentifier(routeAttributesIdentifier()) .build()); tx.put(LogicalDatastoreType.OPERATIONAL, base.node(routeKey), b.build()); } } protected final NodeIdentifier routePathIdNid() { return pathIdNid; } protected final ImmutableOffsetMapTemplate routeKeyTemplate() { return routeKeyTemplate; } protected final @NonNull String extractPrefix(final DataContainerNode route) { return (String) route.getChildByArg(prefixTypeNid).body(); } protected final @Nullable RouteDistinguisher extractRouteDistinguisher(final DataContainerNode route) { return RouteDistinguisherUtil.extractRouteDistinguisher(route, rdNid); } protected final YangInstanceIdentifier routesYangInstanceIdentifier(final YangInstanceIdentifier routesTablePaths) { return routesPath.getUnchecked(routesTablePaths); } @Override public R fromNormalizedNode(final YangInstanceIdentifier routePath, final NormalizedNode normalizedNode) { final DataObject node = mappingService.fromNormalizedNode(routePath, normalizedNode).getValue(); verify(node instanceof Route, "node %s is not a Route", node); return (R) node; } @Override public Attributes attributeFromContainerNode(final ContainerNode advertisedAttrs) { final YangInstanceIdentifier path = routeDefaultYii.node(routeAttributesIdentifier()); return (Attributes) verifyNotNull(mappingService.fromNormalizedNode(path, advertisedAttrs).getValue()); } @Override public ContainerNode attributeToContainerNode(final YangInstanceIdentifier attPath, final Attributes attributes) { final var iid = mappingService.fromYangInstanceIdentifier(attPath); return (ContainerNode) verifyNotNull(mappingService.toNormalizedDataObject(iid, attributes).node()); } @Override public final String extractRouteKey(final NodeIdentifierWithPredicates routeListKey) { return verifyNotNull(routeListKey.getValue(routeKeyQname, String.class), "Missing route key in %s", routeListKey); } @Override public final Uint32 extractPathId(final NodeIdentifierWithPredicates routeListKey) { return verifyNotNull(routeListKey.getValue(pathIdNid.getNodeType(), Uint32.class), "Missing path ID in %s", routeListKey); } @Override public final ContainerNode extractAttributes(final MapEntryNode value) { return NormalizedNodes.findNode(value, routeAttributesIdentifier).map(ContainerNode.class::cast).orElse(null); } }