80c875dd7039893bb472ce3eb08bb6a8e18b9c4a
[netconf.git] / restconf / restconf-nb-rfc8040 / src / main / java / org / opendaylight / restconf / nb / rfc8040 / utils / mapping / RestconfMappingNodeUtil.java
1 /*
2  * Copyright (c) 2016 Cisco Systems, Inc. and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.restconf.nb.rfc8040.utils.mapping;
9
10 import static org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.restconf.monitoring.rev170126.$YangModuleInfoImpl.qnameOf;
11
12 import com.google.common.annotations.VisibleForTesting;
13 import java.net.URI;
14 import java.time.Instant;
15 import java.time.OffsetDateTime;
16 import java.time.ZoneId;
17 import java.time.format.DateTimeFormatter;
18 import java.util.Collection;
19 import java.util.List;
20 import java.util.Map;
21 import java.util.Optional;
22 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
23 import org.opendaylight.restconf.nb.rfc8040.Rfc8040;
24 import org.opendaylight.restconf.nb.rfc8040.Rfc8040.IetfYangLibrary;
25 import org.opendaylight.restconf.nb.rfc8040.utils.parser.ParserIdentifier;
26 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.restconf.monitoring.rev170126.RestconfState;
27 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.restconf.monitoring.rev170126.restconf.state.Capabilities;
28 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.restconf.monitoring.rev170126.restconf.state.streams.Stream;
29 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.restconf.monitoring.rev170126.restconf.state.streams.stream.Access;
30 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.library.rev190104.ModulesState;
31 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.library.rev190104.module.list.Module.ConformanceType;
32 import org.opendaylight.yangtools.yang.common.QName;
33 import org.opendaylight.yangtools.yang.common.Revision;
34 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
35 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
36 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
37 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
38 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
39 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
40 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
41 import org.opendaylight.yangtools.yang.data.api.schema.SystemLeafSetNode;
42 import org.opendaylight.yangtools.yang.data.api.schema.SystemMapNode;
43 import org.opendaylight.yangtools.yang.data.api.schema.UserMapNode;
44 import org.opendaylight.yangtools.yang.data.api.schema.builder.CollectionNodeBuilder;
45 import org.opendaylight.yangtools.yang.data.api.schema.builder.DataContainerNodeBuilder;
46 import org.opendaylight.yangtools.yang.data.api.schema.builder.ListNodeBuilder;
47 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
48 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
49 import org.opendaylight.yangtools.yang.model.api.Deviation;
50 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
51 import org.opendaylight.yangtools.yang.model.api.FeatureDefinition;
52 import org.opendaylight.yangtools.yang.model.api.Module;
53 import org.opendaylight.yangtools.yang.model.api.ModuleLike;
54 import org.opendaylight.yangtools.yang.model.api.NotificationDefinition;
55 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
56 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
57 import org.opendaylight.yangtools.yang.model.api.Submodule;
58
59 /**
60  * Util class for mapping nodes.
61  */
62 public final class RestconfMappingNodeUtil {
63     private static final QName CAPABILITY_QNAME = qnameOf("capability");
64     @VisibleForTesting
65     static final QName DESCRIPTION_QNAME = qnameOf("description");
66     @VisibleForTesting
67     static final QName ENCODING_QNAME = qnameOf("encoding");
68     @VisibleForTesting
69     static final QName LOCATION_QNAME = qnameOf("location");
70     @VisibleForTesting
71     static final QName NAME_QNAME = qnameOf("name");
72     @VisibleForTesting
73     static final QName REPLAY_SUPPORT_QNAME = qnameOf("replay-support");
74     @VisibleForTesting
75     static final QName REPLAY_LOG_CREATION_TIME = qnameOf("replay-log-creation-time");
76
77     private RestconfMappingNodeUtil() {
78         // Hidden on purpose
79     }
80
81     /**
82      * Map data from modules to {@link NormalizedNode}.
83      *
84      * @param modules modules for mapping
85      * @param context schema context
86      * @param moduleSetId module-set-id of actual set
87      * @return mapped data as {@link NormalizedNode}
88      */
89     public static ContainerNode mapModulesByIetfYangLibraryYang(final Collection<? extends Module> modules,
90             final SchemaContext context, final String moduleSetId) {
91         final CollectionNodeBuilder<MapEntryNode, UserMapNode> mapBuilder = Builders.orderedMapBuilder()
92                 .withNodeIdentifier(new NodeIdentifier(IetfYangLibrary.MODULE_QNAME_LIST));
93         for (final Module module : context.getModules()) {
94             fillMapByModules(mapBuilder, IetfYangLibrary.MODULE_QNAME_LIST, false, module, context);
95         }
96         return Builders.containerBuilder()
97             .withNodeIdentifier(new NodeIdentifier(ModulesState.QNAME))
98             .withChild(ImmutableNodes.leafNode(IetfYangLibrary.MODULE_SET_ID_LEAF_QNAME, moduleSetId))
99             .withChild(mapBuilder.build()).build();
100     }
101
102     /**
103      * Map data by the specific module or submodule.
104      *
105      * @param mapBuilder
106      *             ordered list builder for children
107      * @param mapQName
108      *             QName corresponding to the list builder
109      * @param isSubmodule
110      *             true if module is specified as submodule, false otherwise
111      * @param module
112      *             specific module or submodule
113      * @param context
114      *             schema context
115      */
116     private static void fillMapByModules(final CollectionNodeBuilder<MapEntryNode, UserMapNode> mapBuilder,
117             final QName mapQName, final boolean isSubmodule, final ModuleLike module, final SchemaContext context) {
118         final DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> mapEntryBuilder =
119             newCommonLeafsMapEntryBuilder(mapQName, module);
120
121         mapEntryBuilder.withChild(ImmutableNodes.leafNode(IetfYangLibrary.SPECIFIC_MODULE_SCHEMA_LEAF_QNAME,
122             IetfYangLibrary.BASE_URI_OF_SCHEMA + module.getName() + "/"
123             // FIXME: orElse(null) does not seem appropriate here
124             + module.getQNameModule().getRevision().map(Revision::toString).orElse(null)));
125
126         if (!isSubmodule) {
127             mapEntryBuilder.withChild(ImmutableNodes.leafNode(IetfYangLibrary.SPECIFIC_MODULE_NAMESPACE_LEAF_QNAME,
128                 module.getNamespace().toString()));
129
130             // features - not mandatory
131             if (module.getFeatures() != null && !module.getFeatures().isEmpty()) {
132                 addFeatureLeafList(mapEntryBuilder, module.getFeatures());
133             }
134             // deviations - not mandatory
135             final ConformanceType conformance;
136             if (module.getDeviations() != null && !module.getDeviations().isEmpty()) {
137                 addDeviationList(module, mapEntryBuilder, context);
138                 conformance = ConformanceType.Implement;
139             } else {
140                 conformance = ConformanceType.Import;
141             }
142             mapEntryBuilder.withChild(
143                 ImmutableNodes.leafNode(IetfYangLibrary.SPECIFIC_MODULE_CONFORMANCE_LEAF_QNAME, conformance.getName()));
144
145             // submodules - not mandatory
146             if (module.getSubmodules() != null && !module.getSubmodules().isEmpty()) {
147                 addSubmodules(module, mapEntryBuilder, context);
148             }
149         }
150         mapBuilder.withChild(mapEntryBuilder.build());
151     }
152
153     /**
154      * Mapping submodules of specific module.
155      *
156      * @param module
157      *             module with submodules
158      * @param mapEntryBuilder
159      *             mapEntryBuilder of parent for mapping children
160      * @param ietfYangLibraryModule
161      *             ietf-yang-library module
162      * @param context
163      *             schema context
164      */
165     private static void addSubmodules(final ModuleLike module,
166             final DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> mapEntryBuilder,
167             final SchemaContext context) {
168         final CollectionNodeBuilder<MapEntryNode, UserMapNode> mapBuilder = Builders.orderedMapBuilder()
169             .withNodeIdentifier(new NodeIdentifier(IetfYangLibrary.SPECIFIC_MODULE_SUBMODULE_LIST_QNAME));
170
171         for (final Submodule submodule : module.getSubmodules()) {
172             fillMapByModules(mapBuilder, IetfYangLibrary.SPECIFIC_MODULE_SUBMODULE_LIST_QNAME, true, submodule,
173                 context);
174         }
175         mapEntryBuilder.withChild(mapBuilder.build());
176     }
177
178     /**
179      * Mapping deviations of specific module.
180      *
181      * @param module
182      *             module with deviations
183      * @param mapEntryBuilder
184      *             mapEntryBuilder of parent for mapping children
185      * @param context
186      *             schema context
187      */
188     private static void addDeviationList(final ModuleLike module,
189             final DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> mapEntryBuilder,
190             final SchemaContext context) {
191         final CollectionNodeBuilder<MapEntryNode, SystemMapNode> deviations = Builders.mapBuilder()
192             .withNodeIdentifier(new NodeIdentifier(IetfYangLibrary.SPECIFIC_MODULE_DEVIATION_LIST_QNAME));
193         for (final Deviation deviation : module.getDeviations()) {
194             final List<QName> ids = deviation.getTargetPath().getNodeIdentifiers();
195             final QName lastComponent = ids.get(ids.size() - 1);
196
197             deviations.withChild(newCommonLeafsMapEntryBuilder(IetfYangLibrary.SPECIFIC_MODULE_DEVIATION_LIST_QNAME,
198                 context.findModule(lastComponent.getModule()).get())
199                 .build());
200         }
201         mapEntryBuilder.withChild(deviations.build());
202     }
203
204     /**
205      * Mapping features of specific module.
206      *
207      * @param mapEntryBuilder mapEntryBuilder of parent for mapping children
208      * @param features features of specific module
209      */
210     private static void addFeatureLeafList(
211             final DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> mapEntryBuilder,
212             final Collection<? extends FeatureDefinition> features) {
213         final ListNodeBuilder<String, SystemLeafSetNode<String>> leafSetBuilder = Builders.<String>leafSetBuilder()
214                 .withNodeIdentifier(new NodeIdentifier(IetfYangLibrary.SPECIFIC_MODULE_FEATURE_LEAF_LIST_QNAME));
215         for (final FeatureDefinition feature : features) {
216             leafSetBuilder.withChildValue(feature.getQName().getLocalName());
217         }
218         mapEntryBuilder.withChild(leafSetBuilder.build());
219     }
220
221     private static DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> newCommonLeafsMapEntryBuilder(
222             final QName qname, final ModuleLike module) {
223         final var name = module.getName();
224         final var revision = module.getQNameModule().getRevision().map(Revision::toString).orElse("");
225         return Builders.mapEntryBuilder()
226             .withNodeIdentifier(NodeIdentifierWithPredicates.of(qname, Map.of(
227                 IetfYangLibrary.SPECIFIC_MODULE_NAME_LEAF_QNAME, name,
228                 IetfYangLibrary.SPECIFIC_MODULE_REVISION_LEAF_QNAME, revision)))
229             .withChild(ImmutableNodes.leafNode(IetfYangLibrary.SPECIFIC_MODULE_NAME_LEAF_QNAME, name))
230             .withChild(ImmutableNodes.leafNode(IetfYangLibrary.SPECIFIC_MODULE_REVISION_LEAF_QNAME, revision));
231     }
232
233     /**
234      * Map capabilites by ietf-restconf-monitoring.
235      *
236      * @param monitoringModule ietf-restconf-monitoring module
237      * @return mapped capabilites
238      */
239     public static ContainerNode mapCapabilites(final Module monitoringModule) {
240         return Builders.containerBuilder()
241             .withNodeIdentifier(new NodeIdentifier(RestconfState.QNAME))
242             .withChild(Builders.containerBuilder()
243                 .withNodeIdentifier(new NodeIdentifier(Capabilities.QNAME))
244                 .withChild(Builders.<String>orderedLeafSetBuilder()
245                     .withNodeIdentifier(new NodeIdentifier(CAPABILITY_QNAME))
246                     .withChildValue(Rfc8040.Capabilities.DEPTH)
247                     .withChildValue(Rfc8040.Capabilities.FIELDS)
248                     .withChildValue(Rfc8040.Capabilities.FILTER)
249                     .withChildValue(Rfc8040.Capabilities.REPLAY)
250                     .withChildValue(Rfc8040.Capabilities.WITH_DEFAULTS)
251                     .build())
252                 .build())
253             .build();
254     }
255
256     /**
257      * Map data of yang notification to normalized node according to ietf-restconf-monitoring.
258      *
259      * @param notifiQName qname of notification from listener
260      * @param notifications list of notifications for find schema of notification by notifiQName
261      * @param start start-time query parameter of notification
262      * @param outputType output type of notification
263      * @param uri location of registered listener for sending data of notification
264      * @return mapped data of notification - map entry node if parent exists,
265      *         container streams with list and map entry node if not
266      */
267     public static MapEntryNode mapYangNotificationStreamByIetfRestconfMonitoring(final QName notifiQName,
268             final Collection<? extends NotificationDefinition> notifications, final Instant start,
269             final String outputType, final URI uri) {
270         for (final NotificationDefinition notificationDefinition : notifications) {
271             if (notificationDefinition.getQName().equals(notifiQName)) {
272                 final String streamName = notifiQName.getLocalName();
273                 final DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> streamEntry =
274                     Builders.mapEntryBuilder()
275                         .withNodeIdentifier(NodeIdentifierWithPredicates.of(Stream.QNAME, NAME_QNAME, streamName))
276                         .withChild(ImmutableNodes.leafNode(NAME_QNAME, streamName));
277
278                 notificationDefinition.getDescription().ifPresent(
279                     desc -> streamEntry.withChild(ImmutableNodes.leafNode(DESCRIPTION_QNAME, desc)));
280                 streamEntry.withChild(ImmutableNodes.leafNode(REPLAY_SUPPORT_QNAME, Boolean.TRUE));
281                 if (start != null) {
282                     streamEntry.withChild(ImmutableNodes.leafNode(REPLAY_LOG_CREATION_TIME,
283                         DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(OffsetDateTime.ofInstant(start,
284                             ZoneId.systemDefault()))));
285                 }
286
287                 return streamEntry
288                     .withChild(createAccessList(outputType, uri))
289                     .build();
290             }
291         }
292
293         throw new RestconfDocumentedException(notifiQName + " doesn't exist in any modul");
294     }
295
296     private static MapNode createAccessList(final String outputType, final URI uriToWebsocketServer) {
297         return Builders.mapBuilder()
298             .withNodeIdentifier(new NodeIdentifier(Access.QNAME))
299             .withChild(Builders.mapEntryBuilder()
300                 .withNodeIdentifier(NodeIdentifierWithPredicates.of(Access.QNAME, ENCODING_QNAME, outputType))
301                 .withChild(ImmutableNodes.leafNode(ENCODING_QNAME, outputType))
302                 .withChild(ImmutableNodes.leafNode(LOCATION_QNAME, uriToWebsocketServer.toString()))
303                 .build())
304             .build();
305     }
306
307     /**
308      * Map data of data change notification to normalized node according to ietf-restconf-monitoring.
309      *
310      * @param path path of data to listen on
311      * @param start start-time query parameter of notification
312      * @param outputType output type of notification
313      * @param uri location of registered listener for sending data of notification
314      * @param schemaContext schemaContext for parsing instance identifier to get schema node of data
315      * @return mapped data of notification - map entry node if parent exists,
316      *         container streams with list and map entry node if not
317      */
318     public static MapEntryNode mapDataChangeNotificationStreamByIetfRestconfMonitoring(
319             final YangInstanceIdentifier path, final Instant start, final String outputType, final URI uri,
320             final EffectiveModelContext schemaContext, final String streamName) {
321         final SchemaNode schemaNode = ParserIdentifier
322                 .toInstanceIdentifier(ParserIdentifier.stringFromYangInstanceIdentifier(path, schemaContext),
323                         schemaContext, Optional.empty())
324                 .getSchemaNode();
325         final DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> streamEntry =
326             Builders.mapEntryBuilder()
327                 .withNodeIdentifier(NodeIdentifierWithPredicates.of(Stream.QNAME, NAME_QNAME, streamName))
328                 .withChild(ImmutableNodes.leafNode(NAME_QNAME, streamName));
329
330         schemaNode.getDescription().ifPresent(desc ->
331             streamEntry.withChild(ImmutableNodes.leafNode(DESCRIPTION_QNAME, desc)));
332
333         return streamEntry
334             .withChild(ImmutableNodes.leafNode(REPLAY_SUPPORT_QNAME, Boolean.TRUE))
335             .withChild(ImmutableNodes.leafNode(REPLAY_LOG_CREATION_TIME,
336                 DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(OffsetDateTime.ofInstant(start, ZoneId.systemDefault()))))
337             .withChild(createAccessList(outputType, uri))
338             .build();
339     }
340 }