2 * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.netconf.sal.connect.netconf.schema.mapping;
10 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.IETF_NETCONF_NOTIFICATIONS;
11 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_URI;
12 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.toPath;
14 import com.google.common.base.Preconditions;
15 import com.google.common.collect.Maps;
16 import com.google.common.collect.Multimap;
17 import com.google.common.collect.Multimaps;
18 import java.io.IOException;
19 import java.util.Collection;
20 import java.util.Collections;
21 import java.util.Comparator;
22 import java.util.Date;
24 import javax.annotation.Nonnull;
25 import javax.xml.stream.XMLStreamException;
26 import javax.xml.transform.dom.DOMResult;
27 import org.opendaylight.controller.config.util.xml.MissingNameSpaceException;
28 import org.opendaylight.controller.config.util.xml.XmlElement;
29 import org.opendaylight.controller.md.sal.dom.api.DOMEvent;
30 import org.opendaylight.controller.md.sal.dom.api.DOMNotification;
31 import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult;
32 import org.opendaylight.controller.md.sal.dom.spi.DefaultDOMRpcResult;
33 import org.opendaylight.netconf.api.NetconfMessage;
34 import org.opendaylight.netconf.sal.connect.api.MessageTransformer;
35 import org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil;
36 import org.opendaylight.netconf.sal.connect.util.MessageCounter;
37 import org.opendaylight.yangtools.yang.common.QName;
38 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
39 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
40 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
41 import org.opendaylight.yangtools.yang.data.impl.codec.xml.XmlUtils;
42 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
43 import org.opendaylight.yangtools.yang.data.impl.schema.transform.dom.parser.DomToNormalizedNodeParserFactory;
44 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
45 import org.opendaylight.yangtools.yang.model.api.NotificationDefinition;
46 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
47 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
48 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
49 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
50 import org.slf4j.Logger;
51 import org.slf4j.LoggerFactory;
52 import org.w3c.dom.Document;
53 import org.w3c.dom.Element;
55 public class NetconfMessageTransformer implements MessageTransformer<NetconfMessage> {
57 private static final Logger LOG = LoggerFactory.getLogger(NetconfMessageTransformer.class);
59 private final SchemaContext schemaContext;
60 private final BaseSchema baseSchema;
61 private final MessageCounter counter;
62 private final Map<QName, RpcDefinition> mappedRpcs;
63 private final Multimap<QName, NotificationDefinition> mappedNotifications;
64 private final DomToNormalizedNodeParserFactory parserFactory;
66 public NetconfMessageTransformer(final SchemaContext schemaContext, final boolean strictParsing) {
67 this(schemaContext, strictParsing, BaseSchema.BASE_NETCONF_CTX);
70 public NetconfMessageTransformer(final SchemaContext schemaContext, final boolean strictParsing,
71 final BaseSchema baseSchema) {
72 this.counter = new MessageCounter();
73 this.schemaContext = schemaContext;
74 parserFactory = DomToNormalizedNodeParserFactory
75 .getInstance(XmlUtils.DEFAULT_XML_CODEC_PROVIDER, schemaContext, strictParsing);
76 mappedRpcs = Maps.uniqueIndex(schemaContext.getOperations(), SchemaNode::getQName);
77 mappedNotifications = Multimaps.index(schemaContext.getNotifications(),
78 node -> node.getQName().withoutRevision());
79 this.baseSchema = baseSchema;
83 public synchronized DOMNotification toNotification(final NetconfMessage message) {
84 final Map.Entry<Date, XmlElement> stripped = NetconfMessageTransformUtil.stripNotification(message);
85 final QName notificationNoRev;
87 notificationNoRev = QName.create(
88 stripped.getValue().getNamespace(), stripped.getValue().getName()).withoutRevision();
89 } catch (final MissingNameSpaceException e) {
90 throw new IllegalArgumentException(
91 "Unable to parse notification " + message + ", cannot find namespace", e);
93 final Collection<NotificationDefinition> notificationDefinitions = mappedNotifications.get(notificationNoRev);
94 Preconditions.checkArgument(notificationDefinitions.size() > 0,
95 "Unable to parse notification %s, unknown notification. Available notifications: %s",
96 notificationDefinitions, mappedNotifications.keySet());
98 final NotificationDefinition mostRecentNotification = getMostRecentNotification(notificationDefinitions);
100 final ContainerSchemaNode notificationAsContainerSchemaNode =
101 NetconfMessageTransformUtil.createSchemaForNotification(mostRecentNotification);
103 final Element element = stripped.getValue().getDomElement();
104 final ContainerNode content;
106 content = parserFactory.getContainerNodeParser().parse(Collections.singleton(element),
107 notificationAsContainerSchemaNode);
108 } catch (IllegalArgumentException e) {
109 throw new IllegalArgumentException(String.format("Failed to parse notification %s", element), e);
111 return new NetconfDeviceNotification(content, stripped.getKey());
114 private static NotificationDefinition getMostRecentNotification(
115 final Collection<NotificationDefinition> notificationDefinitions) {
116 Comparator<NotificationDefinition> cmp = (o1, o2) ->
117 o1.getQName().getRevision().compareTo(o2.getQName().getRevision());
119 return Collections.max(notificationDefinitions, cmp);
123 public NetconfMessage toRpcRequest(SchemaPath rpc, final NormalizedNode<?, ?> payload) {
124 // In case no input for rpc is defined, we can simply construct the payload here
125 final QName rpcQName = rpc.getLastComponent();
126 Map<QName, RpcDefinition> currentMappedRpcs = mappedRpcs;
128 // Determine whether a base netconf operation is being invoked
129 // and also check if the device exposed model for base netconf.
130 // If no, use pre built base netconf operations model
131 final boolean needToUseBaseCtx = mappedRpcs.get(rpcQName) == null && isBaseOrNotificationRpc(rpcQName);
132 if (needToUseBaseCtx) {
133 currentMappedRpcs = baseSchema.getMappedRpcs();
136 Preconditions.checkNotNull(currentMappedRpcs.get(rpcQName),
137 "Unknown rpc %s, available rpcs: %s", rpcQName, currentMappedRpcs.keySet());
138 if (currentMappedRpcs.get(rpcQName).getInput().getChildNodes().isEmpty()) {
139 return new NetconfMessage(NetconfMessageTransformUtil
140 .prepareDomResultForRpcRequest(rpcQName, counter).getNode().getOwnerDocument());
143 Preconditions.checkNotNull(payload, "Transforming an rpc with input: %s, payload cannot be null", rpcQName);
144 Preconditions.checkArgument(payload instanceof ContainerNode,
145 "Transforming an rpc with input: %s, payload has to be a container, but was: %s", rpcQName, payload);
147 // Set the path to the input of rpc for the node stream writer
148 rpc = rpc.createChild(QName.create(rpcQName, "input").intern());
149 final DOMResult result = NetconfMessageTransformUtil.prepareDomResultForRpcRequest(rpcQName, counter);
152 // If the schema context for netconf device does not contain model for base netconf operations,
153 // use default pre build context with just the base model
154 // This way operations like lock/unlock are supported even if the source for base model was not provided
155 SchemaContext ctx = needToUseBaseCtx ? baseSchema.getSchemaContext() : schemaContext;
156 NetconfMessageTransformUtil.writeNormalizedRpc((ContainerNode) payload, result, rpc, ctx);
157 } catch (final XMLStreamException | IOException | IllegalStateException e) {
158 throw new IllegalStateException("Unable to serialize " + rpc, e);
161 final Document node = result.getNode().getOwnerDocument();
163 return new NetconfMessage(node);
166 private static boolean isBaseOrNotificationRpc(final QName rpc) {
167 return rpc.getNamespace().equals(NETCONF_URI)
168 || rpc.getNamespace().equals(IETF_NETCONF_NOTIFICATIONS.getNamespace())
169 || rpc.getNamespace().equals(NetconfMessageTransformUtil.CREATE_SUBSCRIPTION_RPC_QNAME.getNamespace());
174 public synchronized DOMRpcResult toRpcResult(final NetconfMessage message, final SchemaPath rpc) {
175 final NormalizedNode<?, ?> normalizedNode;
176 final QName rpcQName = rpc.getLastComponent();
177 if (NetconfMessageTransformUtil.isDataRetrievalOperation(rpcQName)) {
178 final Element xmlData = NetconfMessageTransformUtil.getDataSubtree(message.getDocument());
179 final ContainerSchemaNode schemaForDataRead =
180 NetconfMessageTransformUtil.createSchemaForDataRead(schemaContext);
181 final ContainerNode dataNode;
185 parserFactory.getContainerNodeParser().parse(Collections.singleton(xmlData), schemaForDataRead);
186 } catch (IllegalArgumentException e) {
187 throw new IllegalArgumentException(String.format("Failed to parse data response %s", xmlData), e);
190 normalizedNode = Builders.containerBuilder()
191 .withNodeIdentifier(new YangInstanceIdentifier
192 .NodeIdentifier(NetconfMessageTransformUtil.NETCONF_RPC_REPLY_QNAME))
193 .withChild(dataNode).build();
196 Map<QName, RpcDefinition> currentMappedRpcs = mappedRpcs;
198 // Determine whether a base netconf operation is being invoked
199 // and also check if the device exposed model for base netconf.
200 // If no, use pre built base netconf operations model
201 final boolean needToUseBaseCtx = mappedRpcs.get(rpcQName) == null && isBaseOrNotificationRpc(rpcQName);
202 if (needToUseBaseCtx) {
203 currentMappedRpcs = baseSchema.getMappedRpcs();
206 final RpcDefinition rpcDefinition = currentMappedRpcs.get(rpcQName);
207 Preconditions.checkArgument(rpcDefinition != null,
208 "Unable to parse response of %s, the rpc is unknown", rpcQName);
210 // In case no input for rpc is defined, we can simply construct the payload here
211 if (rpcDefinition.getOutput().getChildNodes().isEmpty()) {
212 Preconditions.checkArgument(XmlElement.fromDomDocument(
213 message.getDocument()).getOnlyChildElementWithSameNamespaceOptionally("ok").isPresent(),
214 "Unexpected content in response of rpc: %s, %s", rpcDefinition.getQName(), message);
215 normalizedNode = null;
217 final Element element = message.getDocument().getDocumentElement();
219 normalizedNode = parserFactory.getContainerNodeParser().parse(Collections.singleton(element),
220 rpcDefinition.getOutput());
221 } catch (IllegalArgumentException e) {
222 throw new IllegalArgumentException(String.format("Failed to parse RPC response %s", element), e);
226 return new DefaultDOMRpcResult(normalizedNode);
229 static class NetconfDeviceNotification implements DOMNotification, DOMEvent {
230 private final ContainerNode content;
231 private final SchemaPath schemaPath;
232 private final Date eventTime;
234 NetconfDeviceNotification(final ContainerNode content, final Date eventTime) {
235 this.content = content;
236 this.eventTime = eventTime;
237 this.schemaPath = toPath(content.getNodeType());
242 public SchemaPath getType() {
249 public ContainerNode getBody() {
254 public Date getEventTime() {