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=5c70dd3f680d82f3036dfeeb4e26350e49edde2b;hb=b8f5ebd2f817de1d613d84cf41f3516728767eba;hp=4367d8cf0ab097d09298af911f190104f0d6d303;hpb=476cc2164a06b77e7dd38859e0124d82cd2c8847;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 4367d8cf0a..5c70dd3f68 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 @@ -11,39 +11,58 @@ import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTr import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_URI; import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.toPath; -import com.google.common.base.Function; +import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSet.Builder; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; import com.google.common.collect.Multimaps; import java.io.IOException; +import java.net.URISyntaxException; +import java.time.Instant; import java.util.Collection; import java.util.Collections; -import java.util.Comparator; -import java.util.Date; import java.util.Map; -import javax.annotation.Nonnull; +import java.util.Set; +import javax.xml.parsers.ParserConfigurationException; import javax.xml.stream.XMLStreamException; import javax.xml.transform.dom.DOMResult; -import org.opendaylight.controller.config.util.xml.MissingNameSpaceException; -import org.opendaylight.controller.config.util.xml.XmlElement; -import org.opendaylight.controller.md.sal.dom.api.DOMEvent; -import org.opendaylight.controller.md.sal.dom.api.DOMNotification; -import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult; -import org.opendaylight.controller.md.sal.dom.spi.DefaultDOMRpcResult; +import javax.xml.transform.dom.DOMSource; +import org.opendaylight.mdsal.dom.api.DOMActionResult; +import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier; +import org.opendaylight.mdsal.dom.api.DOMEvent; +import org.opendaylight.mdsal.dom.api.DOMNotification; +import org.opendaylight.mdsal.dom.api.DOMRpcResult; +import org.opendaylight.mdsal.dom.spi.DefaultDOMRpcResult; +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.netconf.util.NetconfMessageTransformUtil; import org.opendaylight.netconf.sal.connect.util.MessageCounter; import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.common.Revision; +import org.opendaylight.yangtools.yang.common.RpcError; +import org.opendaylight.yangtools.yang.common.YangConstants; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.AnyXmlNode; import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; -import org.opendaylight.yangtools.yang.data.impl.codec.xml.XmlUtils; +import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter; +import org.opendaylight.yangtools.yang.data.codec.xml.XmlParserStream; import org.opendaylight.yangtools.yang.data.impl.schema.Builders; -import org.opendaylight.yangtools.yang.data.impl.schema.transform.dom.parser.DomToNormalizedNodeParserFactory; +import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNormalizedNodeStreamWriter; +import org.opendaylight.yangtools.yang.data.impl.schema.NormalizedNodeResult; +import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree; +import org.opendaylight.yangtools.yang.model.api.ActionDefinition; +import org.opendaylight.yangtools.yang.model.api.ActionNodeContainer; import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode; +import org.opendaylight.yangtools.yang.model.api.DataNodeContainer; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; 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; @@ -52,22 +71,19 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Document; import org.w3c.dom.Element; +import org.xml.sax.SAXException; public class NetconfMessageTransformer implements MessageTransformer { private static final Logger LOG = LoggerFactory.getLogger(NetconfMessageTransformer.class); - private static final Function QNAME_FUNCTION = rpcDefinition -> rpcDefinition.getQName(); - - private static final Function QNAME_NOREV_FUNCTION = - notification -> QNAME_FUNCTION.apply(notification).withoutRevision(); - private final SchemaContext schemaContext; private final BaseSchema baseSchema; private final MessageCounter counter; private final Map mappedRpcs; private final Multimap mappedNotifications; - private final DomToNormalizedNodeParserFactory parserFactory; + private final boolean strictParsing; + private final Set actions; public NetconfMessageTransformer(final SchemaContext schemaContext, final boolean strictParsing) { this(schemaContext, strictParsing, BaseSchema.BASE_NETCONF_CTX); @@ -77,16 +93,42 @@ public class NetconfMessageTransformer implements MessageTransformer node.getQName().withoutRevision()); this.baseSchema = baseSchema; + this.strictParsing = strictParsing; + } + + @VisibleForTesting + Set getActions() { + final Builder builder = ImmutableSet.builder(); + for (DataSchemaNode dataSchemaNode : schemaContext.getChildNodes()) { + if (dataSchemaNode instanceof ActionNodeContainer) { + findAction(dataSchemaNode, builder); + } + } + return builder.build(); + } + + private void findAction(final DataSchemaNode dataSchemaNode, final Builder builder) { + if (dataSchemaNode instanceof ActionNodeContainer) { + final ActionNodeContainer containerSchemaNode = (ActionNodeContainer) dataSchemaNode; + for (ActionDefinition actionDefinition : containerSchemaNode.getActions()) { + builder.add(actionDefinition); + } + } + if (dataSchemaNode instanceof DataNodeContainer) { + for (DataSchemaNode innerDataSchemaNode : ((DataNodeContainer) dataSchemaNode).getChildNodes()) { + findAction(innerDataSchemaNode, builder); + } + } } @Override public synchronized DOMNotification toNotification(final NetconfMessage message) { - final Map.Entry stripped = NetconfMessageTransformUtil.stripNotification(message); + final Map.Entry stripped = NetconfMessageTransformUtil.stripNotification(message); final QName notificationNoRev; try { notificationNoRev = QName.create( @@ -108,9 +150,14 @@ public class NetconfMessageTransformer implements MessageTransformer notificationDefinitions) { - Comparator cmp = (o1, o2) -> - o1.getQName().getRevision().compareTo(o2.getQName().getRevision()); - - return Collections.max(notificationDefinitions, cmp); + return Collections.max(notificationDefinitions, (o1, o2) -> + Revision.compare(o1.getQName().getRevision(), o2.getQName().getRevision())); } @Override - public NetconfMessage toRpcRequest(SchemaPath rpc, final NormalizedNode payload) { + public NetconfMessage toRpcRequest(final SchemaPath rpc, final NormalizedNode payload) { // In case no input for rpc is defined, we can simply construct the payload here final QName rpcQName = rpc.getLastComponent(); - Map currentMappedRpcs = mappedRpcs; // Determine whether a base netconf operation is being invoked // and also check if the device exposed model for base netconf. // If no, use pre built base netconf operations model final boolean needToUseBaseCtx = mappedRpcs.get(rpcQName) == null && isBaseOrNotificationRpc(rpcQName); + final Map currentMappedRpcs; if (needToUseBaseCtx) { currentMappedRpcs = baseSchema.getMappedRpcs(); + } else { + currentMappedRpcs = mappedRpcs; } - Preconditions.checkNotNull(currentMappedRpcs.get(rpcQName), + final RpcDefinition mappedRpc = Preconditions.checkNotNull(currentMappedRpcs.get(rpcQName), "Unknown rpc %s, available rpcs: %s", rpcQName, currentMappedRpcs.keySet()); - if (currentMappedRpcs.get(rpcQName).getInput().getChildNodes().isEmpty()) { + if (mappedRpc.getInput().getChildNodes().isEmpty()) { return new NetconfMessage(NetconfMessageTransformUtil .prepareDomResultForRpcRequest(rpcQName, counter).getNode().getOwnerDocument()); } Preconditions.checkNotNull(payload, "Transforming an rpc with input: %s, payload cannot be null", rpcQName); + Preconditions.checkArgument(payload instanceof ContainerNode, "Transforming an rpc with input: %s, payload has to be a container, but was: %s", rpcQName, payload); - // Set the path to the input of rpc for the node stream writer - rpc = rpc.createChild(QName.create(rpcQName, "input").intern()); + final SchemaPath rpcInput = rpc.createChild(YangConstants.operationInputQName(rpcQName.getModule())); final DOMResult result = NetconfMessageTransformUtil.prepareDomResultForRpcRequest(rpcQName, counter); try { // If the schema context for netconf device does not contain model for base netconf operations, // use default pre build context with just the base model // This way operations like lock/unlock are supported even if the source for base model was not provided - SchemaContext ctx = needToUseBaseCtx ? baseSchema.getSchemaContext() : schemaContext; - NetconfMessageTransformUtil.writeNormalizedRpc(((ContainerNode) payload), result, rpc, ctx); + final SchemaContext ctx = needToUseBaseCtx ? baseSchema.getSchemaContext() : schemaContext; + NetconfMessageTransformUtil.writeNormalizedRpc((ContainerNode) payload, result, rpcInput, ctx); } catch (final XMLStreamException | IOException | IllegalStateException e) { - throw new IllegalStateException("Unable to serialize " + rpc, e); + throw new IllegalStateException("Unable to serialize " + rpcInput, e); + } + + final Document node = result.getNode().getOwnerDocument(); + + return new NetconfMessage(node); + } + + @Override + public NetconfMessage toActionRequest(final SchemaPath action, final DOMDataTreeIdentifier domDataTreeIdentifier, + final NormalizedNode payload) { + ActionDefinition actionDefinition = null; + for (ActionDefinition actionDef : actions) { + if (actionDef.getPath().getLastComponent().equals(action.getLastComponent())) { + actionDefinition = actionDef; + } + } + Preconditions.checkNotNull(actionDefinition, "Action does not exist: %s", action.getLastComponent()); + + final ContainerSchemaNode inputDef = actionDefinition.getInput(); + if (inputDef.getChildNodes().isEmpty()) { + return new NetconfMessage(NetconfMessageTransformUtil.prepareDomResultForActionRequest( + DataSchemaContextTree.from(schemaContext), domDataTreeIdentifier, action, counter, + actionDefinition.getQName().getLocalName()).getNode().getOwnerDocument()); + } + + Preconditions.checkNotNull(payload, "Transforming an action with input: %s, payload cannot be null", + action.getLastComponent()); + Preconditions.checkArgument(payload instanceof ContainerNode, + "Transforming an rpc with input: %s, payload has to be a container, but was: %s", + action.getLastComponent(), payload); + + // Set the path to the input of action for the node stream writer + final DOMResult result = NetconfMessageTransformUtil.prepareDomResultForActionRequest( + DataSchemaContextTree.from(schemaContext), domDataTreeIdentifier, inputDef.getPath(), counter, + actionDefinition.getQName().getLocalName()); + + try { + NetconfMessageTransformUtil.writeNormalizedRpc((ContainerNode) payload, result, inputDef.getPath(), + schemaContext); + } catch (final XMLStreamException | IOException | IllegalStateException e) { + throw new IllegalStateException("Unable to serialize " + action, e); } final Document node = result.getNode().getOwnerDocument(); @@ -174,28 +262,18 @@ public class NetconfMessageTransformer implements MessageTransformer normalizedNode; final QName rpcQName = rpc.getLastComponent(); if (NetconfMessageTransformUtil.isDataRetrievalOperation(rpcQName)) { final Element xmlData = NetconfMessageTransformUtil.getDataSubtree(message.getDocument()); - final ContainerSchemaNode schemaForDataRead = - NetconfMessageTransformUtil.createSchemaForDataRead(schemaContext); - final ContainerNode dataNode; - - try { - dataNode = - parserFactory.getContainerNodeParser().parse(Collections.singleton(xmlData), schemaForDataRead); - } catch (IllegalArgumentException e) { - throw new IllegalArgumentException(String.format("Failed to parse data response %s", xmlData), e); - } - - normalizedNode = Builders.containerBuilder() - .withNodeIdentifier(new YangInstanceIdentifier - .NodeIdentifier(NetconfMessageTransformUtil.NETCONF_RPC_REPLY_QNAME)) - .withChild(dataNode).build(); + final AnyXmlNode anyXmlNode = Builders.anyXmlBuilder() + .withNodeIdentifier(NetconfMessageTransformUtil.NETCONF_DATA_NODEID) + .withValue(new DOMSource(xmlData)).build(); + normalizedNode = Builders.containerBuilder().withNodeIdentifier( + new YangInstanceIdentifier.NodeIdentifier(NetconfMessageTransformUtil.NETCONF_RPC_REPLY_QNAME)) + .withChild(anyXmlNode).build(); } else { Map currentMappedRpcs = mappedRpcs; @@ -213,50 +291,75 @@ public class NetconfMessageTransformer implements MessageTransformeremptyList()); + } else { + return new SimpleDOMActionResult(normalizedNode, Collections.emptyList()); + } + } + + private NormalizedNode parseResult(final NetconfMessage message, + final OperationDefinition operationDefinition) { + if (operationDefinition.getOutput().getChildNodes().isEmpty()) { + Preconditions.checkArgument(XmlElement.fromDomDocument( + message.getDocument()).getOnlyChildElementWithSameNamespaceOptionally("ok").isPresent(), + "Unexpected content in response of rpc: %s, %s", operationDefinition.getQName(), message); + return null; + } else { + final Element element = message.getDocument().getDocumentElement(); + try { + final NormalizedNodeResult resultHolder = new NormalizedNodeResult(); + final NormalizedNodeStreamWriter writer = ImmutableNormalizedNodeStreamWriter.from(resultHolder); + final XmlParserStream xmlParser = XmlParserStream.create(writer, schemaContext, + operationDefinition.getOutput(), strictParsing); + xmlParser.traverse(new DOMSource(element)); + return resultHolder.getResult(); + } catch (XMLStreamException | URISyntaxException | IOException | ParserConfigurationException + | SAXException e) { + throw new IllegalArgumentException(String.format("Failed to parse RPC response %s", element), e); + } + } + } + static class NetconfDeviceNotification implements DOMNotification, DOMEvent { private final ContainerNode content; private final SchemaPath schemaPath; - private final Date eventTime; + private final Instant eventTime; - NetconfDeviceNotification(final ContainerNode content, final Date eventTime) { + NetconfDeviceNotification(final ContainerNode content, final Instant eventTime) { this.content = content; this.eventTime = eventTime; this.schemaPath = toPath(content.getNodeType()); } - @Nonnull @Override public SchemaPath getType() { return schemaPath; - } - @Nonnull @Override public ContainerNode getBody() { return content; } @Override - public Date getEventTime() { + public Instant getEventInstant() { return eventTime; } }