X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=blobdiff_plain;f=netconf%2Fsal-netconf-connector%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Fnetconf%2Fsal%2Fconnect%2Fnetconf%2Fschema%2Fmapping%2FNetconfMessageTransformer.java;h=7a2e57017d84656f00f4138113c7bc9cb7c047c5;hb=refs%2Fchanges%2F23%2F103723%2F3;hp=46f206f6d9725e5b096260d3bf954b8add18ef9d;hpb=4ac4b04b18e2667d43109b730dcd362038f9d4f9;p=netconf.git diff --git a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/schema/mapping/NetconfMessageTransformer.java b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/schema/mapping/NetconfMessageTransformer.java index 46f206f6d9..7a2e57017d 100644 --- a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/schema/mapping/NetconfMessageTransformer.java +++ b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/schema/mapping/NetconfMessageTransformer.java @@ -7,6 +7,8 @@ */ package org.opendaylight.netconf.sal.connect.netconf.schema.mapping; +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; import static java.util.Objects.requireNonNull; import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.CREATE_SUBSCRIPTION_RPC_QNAME; import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.IETF_NETCONF_NOTIFICATIONS; @@ -14,7 +16,7 @@ import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTr import com.google.common.annotations.Beta; import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; @@ -23,13 +25,14 @@ import com.google.common.collect.Multimap; import com.google.common.collect.Multimaps; import com.google.common.collect.Streams; import java.io.IOException; -import java.net.URI; import java.net.URISyntaxException; import java.time.Instant; import java.util.AbstractMap; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.Deque; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -42,6 +45,8 @@ import java.util.stream.Collectors; import javax.xml.stream.XMLStreamException; import javax.xml.transform.dom.DOMResult; import javax.xml.transform.dom.DOMSource; +import org.checkerframework.checker.lock.qual.GuardedBy; +import org.eclipse.jdt.annotation.NonNull; import org.opendaylight.mdsal.common.api.LogicalDatastoreType; import org.opendaylight.mdsal.dom.api.DOMActionResult; import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier; @@ -53,13 +58,17 @@ import org.opendaylight.mdsal.dom.spi.SimpleDOMActionResult; import org.opendaylight.netconf.api.NetconfMessage; import org.opendaylight.netconf.api.xml.MissingNameSpaceException; import org.opendaylight.netconf.api.xml.XmlElement; -import org.opendaylight.netconf.sal.connect.api.MessageTransformer; +import org.opendaylight.netconf.sal.connect.api.ActionTransformer; +import org.opendaylight.netconf.sal.connect.api.NotificationTransformer; +import org.opendaylight.netconf.sal.connect.api.RpcTransformer; import org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil; import org.opendaylight.netconf.sal.connect.util.MessageCounter; import org.opendaylight.yangtools.rfc8528.data.api.MountPointContext; import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.common.Revision; +import org.opendaylight.yangtools.yang.common.XMLNamespace; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.InstanceIdentifierBuilder; import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter; @@ -82,9 +91,9 @@ import org.opendaylight.yangtools.yang.model.api.Module; import org.opendaylight.yangtools.yang.model.api.NotificationDefinition; import org.opendaylight.yangtools.yang.model.api.OperationDefinition; import org.opendaylight.yangtools.yang.model.api.RpcDefinition; -import org.opendaylight.yangtools.yang.model.api.SchemaContext; import org.opendaylight.yangtools.yang.model.api.SchemaNode; import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Absolute; +import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Document; @@ -93,10 +102,10 @@ import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; -public class NetconfMessageTransformer implements MessageTransformer { +public class NetconfMessageTransformer implements ActionTransformer, NotificationTransformer, RpcTransformer { private static final Logger LOG = LoggerFactory.getLogger(NetconfMessageTransformer.class); - private static final ImmutableSet BASE_OR_NOTIFICATION_NS = ImmutableSet.of( + private static final ImmutableSet BASE_OR_NOTIFICATION_NS = ImmutableSet.of( NETCONF_URI, IETF_NETCONF_NOTIFICATIONS.getNamespace(), CREATE_SUBSCRIPTION_RPC_QNAME.getNamespace()); @@ -112,43 +121,49 @@ public class NetconfMessageTransformer implements MessageTransformer action.getPath().asAbsolute()); + mappedRpcs = Maps.uniqueIndex(schemaContext.getOperations(), SchemaNode::getQName); + actions = getActions(schemaContext); // RFC6020 normal notifications - this.mappedNotifications = Multimaps.index(schemaContext.getNotifications(), + mappedNotifications = Multimaps.index(schemaContext.getNotifications(), node -> node.getQName().withoutRevision()); this.baseSchema = baseSchema; this.strictParsing = strictParsing; } @VisibleForTesting - // FIXME: return Map by using only - static List getActions(final SchemaContext schemaContext) { - final List builder = new ArrayList<>(); - findAction(schemaContext, builder); - return builder; + static ImmutableMap getActions(final EffectiveModelContext schemaContext) { + final var builder = ImmutableMap.builder(); + findAction(schemaContext, new ArrayDeque(), builder); + return builder.build(); } - private static void findAction(final DataSchemaNode dataSchemaNode, final List builder) { + private static void findAction(final DataSchemaNode dataSchemaNode, final Deque path, + final ImmutableMap.Builder builder) { if (dataSchemaNode instanceof ActionNodeContainer) { for (ActionDefinition actionDefinition : ((ActionNodeContainer) dataSchemaNode).getActions()) { - builder.add(actionDefinition); + path.addLast(actionDefinition.getQName()); + builder.put(Absolute.of(path), actionDefinition); + path.removeLast(); } } if (dataSchemaNode instanceof DataNodeContainer) { for (DataSchemaNode innerDataSchemaNode : ((DataNodeContainer) dataSchemaNode).getChildNodes()) { - findAction(innerDataSchemaNode, builder); + path.addLast(innerDataSchemaNode.getQName()); + findAction(innerDataSchemaNode, path, builder); + path.removeLast(); } } else if (dataSchemaNode instanceof ChoiceSchemaNode) { for (CaseSchemaNode caze : ((ChoiceSchemaNode) dataSchemaNode).getCases()) { - findAction(caze, builder); + path.addLast(caze.getQName()); + findAction(caze, path, builder); + path.removeLast(); } } } @@ -165,56 +180,43 @@ public class NetconfMessageTransformer implements MessageTransformer notificationDefinitions = - mappedNotifications.get(notificationNoRev); - Element element = stripped.getValue().getDomElement(); - - NestedNotificationInfo nestedNotificationInfo = null; - if (notificationDefinitions.isEmpty()) { - // check if notification is nested notification - Optional nestedNotificationOptional = findNestedNotification(message, element); - if (nestedNotificationOptional.isPresent()) { - nestedNotificationInfo = nestedNotificationOptional.get(); - notificationDefinitions = Collections.singletonList(nestedNotificationInfo.notificationDefinition); - element = (Element) nestedNotificationInfo.notificationNode; - } + final var matchingTopLevel = mappedNotifications.get(notificationNoRev); + final var element = stripped.getValue().getDomElement(); + if (!matchingTopLevel.isEmpty()) { + final var notification = getMostRecentNotification(matchingTopLevel); + // FIXME: we really should have a pre-constructed identifier here + return new NetconfDeviceNotification(toNotification(Absolute.of(notification.getQName()), element), + stripped.getKey()); } - Preconditions.checkArgument(notificationDefinitions.size() > 0, - "Unable to parse notification %s, unknown notification. Available notifications: %s", - notificationDefinitions, mappedNotifications.keySet()); - - final NotificationDefinition mostRecentNotification = getMostRecentNotification(notificationDefinitions); - final ContainerSchemaNode notificationAsContainerSchemaNode = - NetconfMessageTransformUtil.createSchemaForNotification(mostRecentNotification); + final var nestedInfo = findNestedNotification(message, element) + .orElseThrow(() -> new IllegalArgumentException("Unable to parse notification for " + element + + ". Available notifications: " + mappedNotifications.keySet())); + final var schemaPath = nestedInfo.schemaPath; + return new NetconfDeviceTreeNotification(toNotification(schemaPath, nestedInfo.element), schemaPath, + stripped.getKey(), nestedInfo.instancePath); + } - final ContainerNode content; + @GuardedBy("this") + private ContainerNode toNotification(final Absolute notificationPath, final Element element) { + final NormalizedNodeResult resultHolder = new NormalizedNodeResult(); try { - final NormalizedNodeResult resultHolder = new NormalizedNodeResult(); final NormalizedNodeStreamWriter writer = ImmutableNormalizedNodeStreamWriter.from(resultHolder); final XmlParserStream xmlParser = XmlParserStream.create(writer, mountContext, - notificationAsContainerSchemaNode, strictParsing); + SchemaInferenceStack.of(mountContext.getEffectiveModelContext(), notificationPath).toInference(), + strictParsing); xmlParser.traverse(new DOMSource(element)); - content = (ContainerNode) resultHolder.getResult(); } catch (XMLStreamException | URISyntaxException | IOException | SAXException | UnsupportedOperationException e) { throw new IllegalArgumentException(String.format("Failed to parse notification %s", element), e); } - - if (nestedNotificationInfo != null) { - return new NetconfDeviceTreeNotification(content, - // FIXME: improve this to cache the path - mostRecentNotification.getPath().asAbsolute(), - stripped.getKey(), nestedNotificationInfo.domDataTreeIdentifier); - } - - return new NetconfDeviceNotification(content, stripped.getKey()); + return (ContainerNode) resultHolder.getResult(); } private Optional findNestedNotification(final NetconfMessage message, final Element element) { final Iterator modules = mountContext.getEffectiveModelContext() - .findModules(URI.create(element.getNamespaceURI())).iterator(); + .findModules(XMLNamespace.of(element.getNamespaceURI())).iterator(); if (!modules.hasNext()) { throw new IllegalArgumentException( "Unable to parse notification " + message + ", cannot find top level module"); @@ -223,38 +225,42 @@ public class NetconfMessageTransformer implements MessageTransformer(), YangInstanceIdentifier.builder())); } } return Optional.empty(); } + // FIXME: this method is using QNames which are not bound to a Revision. Why is that? + // FIXME: furthermore this does not handle the entirety of schema layout: notably missing a choice/case schema nodes private NestedNotificationInfo traverseXmlNodeContainingNotification(final Node xmlNode, - final SchemaNode schemaNode, final YangInstanceIdentifier.InstanceIdentifierBuilder builder) { - if (schemaNode instanceof ContainerSchemaNode) { - ContainerSchemaNode dataContainerNode = (ContainerSchemaNode) schemaNode; - builder.node(QName.create(xmlNode.getNamespaceURI(), xmlNode.getLocalName())); + final SchemaNode schemaNode, final List schemaBuilder, + final InstanceIdentifierBuilder instanceBuilder) { + if (schemaNode instanceof ContainerSchemaNode containerSchema) { + instanceBuilder.node(QName.create(xmlNode.getNamespaceURI(), xmlNode.getLocalName())); + schemaBuilder.add(containerSchema.getQName()); - Entry xmlContainerChildPair = findXmlContainerChildPair(xmlNode, dataContainerNode); + Entry xmlContainerChildPair = findXmlContainerChildPair(xmlNode, containerSchema); return traverseXmlNodeContainingNotification(xmlContainerChildPair.getKey(), - xmlContainerChildPair.getValue(), builder); - } else if (schemaNode instanceof ListSchemaNode) { - ListSchemaNode listSchemaNode = (ListSchemaNode) schemaNode; - builder.node(QName.create(xmlNode.getNamespaceURI(), xmlNode.getLocalName())); + xmlContainerChildPair.getValue(), schemaBuilder, instanceBuilder); + } else if (schemaNode instanceof ListSchemaNode listSchema) { + instanceBuilder.node(QName.create(xmlNode.getNamespaceURI(), xmlNode.getLocalName())); + schemaBuilder.add(listSchema.getQName()); - Map listKeys = findXmlListKeys(xmlNode, listSchemaNode); - builder.nodeWithKey(QName.create(xmlNode.getNamespaceURI(), xmlNode.getLocalName()), listKeys); + Map listKeys = findXmlListKeys(xmlNode, listSchema); + instanceBuilder.nodeWithKey(QName.create(xmlNode.getNamespaceURI(), xmlNode.getLocalName()), listKeys); - Entry xmlListChildPair = findXmlListChildPair(xmlNode, listSchemaNode); + Entry xmlListChildPair = findXmlListChildPair(xmlNode, listSchema); return traverseXmlNodeContainingNotification(xmlListChildPair.getKey(), - xmlListChildPair.getValue(), builder); + xmlListChildPair.getValue(), schemaBuilder, instanceBuilder); } else if (schemaNode instanceof NotificationDefinition) { - builder.node(QName.create(xmlNode.getNamespaceURI(), xmlNode.getLocalName())); + // FIXME: this should not be here: it does not form a valid YangInstanceIdentifier + instanceBuilder.node(QName.create(xmlNode.getNamespaceURI(), xmlNode.getLocalName())); + schemaBuilder.add(schemaNode.getQName()); - NotificationDefinition notificationDefinition = (NotificationDefinition) schemaNode; - return new NestedNotificationInfo(notificationDefinition, - new DOMDataTreeIdentifier(LogicalDatastoreType.CONFIGURATION, builder.build()), xmlNode); + return new NestedNotificationInfo(Absolute.of(schemaBuilder), + new DOMDataTreeIdentifier(LogicalDatastoreType.CONFIGURATION, instanceBuilder.build()), xmlNode); } throw new IllegalStateException("No notification found"); } @@ -272,7 +278,7 @@ public class NetconfMessageTransformer implements MessageTransformer(currentNode, schemaChildNode); + return Map.entry(currentNode, schemaChildNode); } } } @@ -323,7 +329,7 @@ public class NetconfMessageTransformer implements MessageTransformer payload) { + public NetconfMessage toRpcRequest(final QName rpc, final NormalizedNode payload) { // In case no input for rpc is defined, we can simply construct the payload here // Determine whether a base netconf operation is being invoked @@ -337,16 +343,16 @@ public class NetconfMessageTransformer implements MessageTransformer payload) { + final NormalizedNode payload) { final ActionDefinition actionDef = actions.get(action); - Preconditions.checkArgument(actionDef != null, "Action does not exist: %s", action); + checkArgument(actionDef != null, "Action does not exist: %s", action); final InputSchemaNode inputDef = actionDef.getInput(); if (inputDef.getChildNodes().isEmpty()) { @@ -378,8 +384,8 @@ public class NetconfMessageTransformer implements MessageTransformer normalizedNode; + final ContainerNode normalizedNode; if (NetconfMessageTransformUtil.isDataRetrievalOperation(rpc)) { normalizedNode = Builders.containerBuilder() .withNodeIdentifier(NetconfMessageTransformUtil.NETCONF_RPC_REPLY_NODEID) @@ -421,11 +427,10 @@ public class NetconfMessageTransformer implements MessageTransformer parseResult(final NetconfMessage message, - final OperationDefinition operationDefinition) { + private ContainerNode parseResult(final NetconfMessage message, final Absolute operationPath, + final OperationDefinition operationDef) { final Optional okResponseElement = XmlElement.fromDomDocument(message.getDocument()) .getOnlyChildElementWithSameNamespaceOptionally("ok"); - if (operationDefinition.getOutput().getChildNodes().isEmpty()) { - Preconditions.checkArgument(okResponseElement.isPresent(), - "Unexpected content in response of rpc: %s, %s", operationDefinition.getQName(), message); + final var operOutput = operationDef.getOutput(); + if (operOutput.getChildNodes().isEmpty()) { + checkArgument(okResponseElement.isPresent(), "Unexpected content in response of operation: %s, %s", + operationDef.getQName(), message); return null; - } else { - if (okResponseElement.isPresent()) { - LOG.debug("Received response for RPC with defined Output"); - return null; - } + } + if (okResponseElement.isPresent()) { + // FIXME: could be an action as well + LOG.debug("Received response for RPC with defined Output"); + return null; + } - Element element = message.getDocument().getDocumentElement(); - try { - final NormalizedNodeResult resultHolder = new NormalizedNodeResult(); - final NormalizedNodeStreamWriter writer = ImmutableNormalizedNodeStreamWriter.from(resultHolder); - final XmlParserStream xmlParser = XmlParserStream.create(writer, mountContext, - operationDefinition.getOutput(), strictParsing); - xmlParser.traverse(new DOMSource(element)); - return resultHolder.getResult(); - } catch (XMLStreamException | URISyntaxException | IOException | SAXException e) { - throw new IllegalArgumentException(String.format("Failed to parse RPC response %s", element), e); - } + final var operSteps = operationPath.getNodeIdentifiers(); + final var outputPath = Absolute.of(ImmutableList.builderWithExpectedSize(operSteps.size() + 1) + .addAll(operSteps) + .add(operOutput.getQName()) + .build()); + // FIXME: we should have a cached inference here, or XMLParserStream should accept Absolute instead + final var inference = SchemaInferenceStack.of(mountContext.getEffectiveModelContext(), outputPath) + .toInference(); + + final NormalizedNodeResult resultHolder = new NormalizedNodeResult(); + final Element element = message.getDocument().getDocumentElement(); + try { + final NormalizedNodeStreamWriter writer = ImmutableNormalizedNodeStreamWriter.from(resultHolder); + final XmlParserStream xmlParser = XmlParserStream.create(writer, mountContext, inference, strictParsing); + xmlParser.traverse(new DOMSource(element)); + } catch (XMLStreamException | URISyntaxException | IOException | SAXException e) { + throw new IllegalArgumentException(String.format("Failed to parse RPC response %s", element), e); } + return (ContainerNode) resultHolder.getResult(); } @Beta @@ -480,7 +494,7 @@ public class NetconfMessageTransformer implements MessageTransformer