1b58884d440e418215f3ba34733ae983d07e3d4a
[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 java.net.URI;
11 import java.time.Instant;
12 import java.time.OffsetDateTime;
13 import java.time.ZoneId;
14 import java.time.format.DateTimeFormatter;
15 import java.util.Collection;
16 import java.util.List;
17 import java.util.Map;
18 import java.util.Optional;
19 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
20 import org.opendaylight.restconf.nb.rfc8040.Rfc8040.IetfYangLibrary;
21 import org.opendaylight.restconf.nb.rfc8040.Rfc8040.MonitoringModule;
22 import org.opendaylight.restconf.nb.rfc8040.Rfc8040.MonitoringModule.QueryParams;
23 import org.opendaylight.restconf.nb.rfc8040.utils.parser.ParserIdentifier;
24 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.library.rev190104.ModulesState;
25 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.library.rev190104.module.list.Module.ConformanceType;
26 import org.opendaylight.yangtools.yang.common.QName;
27 import org.opendaylight.yangtools.yang.common.Revision;
28 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
29 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
30 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
31 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
32 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
33 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
34 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
35 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
36 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
37 import org.opendaylight.yangtools.yang.data.api.schema.OrderedMapNode;
38 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
39 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
40 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.CollectionNodeBuilder;
41 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeBuilder;
42 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.ListNodeBuilder;
43 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
44 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
45 import org.opendaylight.yangtools.yang.model.api.Deviation;
46 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
47 import org.opendaylight.yangtools.yang.model.api.FeatureDefinition;
48 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
49 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
50 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
51 import org.opendaylight.yangtools.yang.model.api.Module;
52 import org.opendaylight.yangtools.yang.model.api.ModuleLike;
53 import org.opendaylight.yangtools.yang.model.api.NotificationDefinition;
54 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
55 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
56 import org.opendaylight.yangtools.yang.model.api.Submodule;
57
58 /**
59  * Util class for mapping nodes.
60  *
61  */
62 public final class RestconfMappingNodeUtil {
63     private RestconfMappingNodeUtil() {
64         // Hidden on purpose
65     }
66
67     /**
68      * Map data from modules to {@link NormalizedNode}.
69      *
70      * @param modules modules for mapping
71      * @param context schema context
72      * @param moduleSetId module-set-id of actual set
73      * @return mapped data as {@link NormalizedNode}
74      */
75     public static ContainerNode mapModulesByIetfYangLibraryYang(final Collection<? extends Module> modules,
76             final SchemaContext context, final String moduleSetId) {
77         final CollectionNodeBuilder<MapEntryNode, OrderedMapNode> mapBuilder = Builders.orderedMapBuilder()
78                 .withNodeIdentifier(new NodeIdentifier(IetfYangLibrary.MODULE_QNAME_LIST));
79         for (final Module module : context.getModules()) {
80             fillMapByModules(mapBuilder, IetfYangLibrary.MODULE_QNAME_LIST, false, module, context);
81         }
82         return Builders.containerBuilder()
83             .withNodeIdentifier(new NodeIdentifier(ModulesState.QNAME))
84             .withChild(ImmutableNodes.leafNode(IetfYangLibrary.MODULE_SET_ID_LEAF_QNAME, moduleSetId))
85             .withChild(mapBuilder.build()).build();
86     }
87
88     /**
89      * Map data by the specific module or submodule.
90      *
91      * @param mapBuilder
92      *             ordered list builder for children
93      * @param mapQName
94      *             QName corresponding to the list builder
95      * @param isSubmodule
96      *             true if module is specified as submodule, false otherwise
97      * @param module
98      *             specific module or submodule
99      * @param context
100      *             schema context
101      */
102     private static void fillMapByModules(final CollectionNodeBuilder<MapEntryNode, OrderedMapNode> mapBuilder,
103             final QName mapQName, final boolean isSubmodule, final ModuleLike module, final SchemaContext context) {
104         final DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> mapEntryBuilder =
105             newCommonLeafsMapEntryBuilder(mapQName, module);
106
107         mapEntryBuilder.withChild(ImmutableNodes.leafNode(IetfYangLibrary.SPECIFIC_MODULE_SCHEMA_LEAF_QNAME,
108             IetfYangLibrary.BASE_URI_OF_SCHEMA + module.getName() + "/"
109             // FIXME: orElse(null) does not seem appropriate here
110             + module.getQNameModule().getRevision().map(Revision::toString).orElse(null)));
111
112         if (!isSubmodule) {
113             mapEntryBuilder.withChild(ImmutableNodes.leafNode(IetfYangLibrary.SPECIFIC_MODULE_NAMESPACE_LEAF_QNAME,
114                 module.getNamespace().toString()));
115
116             // features - not mandatory
117             if (module.getFeatures() != null && !module.getFeatures().isEmpty()) {
118                 addFeatureLeafList(mapEntryBuilder, module.getFeatures());
119             }
120             // deviations - not mandatory
121             final ConformanceType conformance;
122             if (module.getDeviations() != null && !module.getDeviations().isEmpty()) {
123                 addDeviationList(module, mapEntryBuilder, context);
124                 conformance = ConformanceType.Implement;
125             } else {
126                 conformance = ConformanceType.Import;
127             }
128             mapEntryBuilder.withChild(
129                 ImmutableNodes.leafNode(IetfYangLibrary.SPECIFIC_MODULE_CONFORMANCE_LEAF_QNAME, conformance.getName()));
130
131             // submodules - not mandatory
132             if (module.getSubmodules() != null && !module.getSubmodules().isEmpty()) {
133                 addSubmodules(module, mapEntryBuilder, context);
134             }
135         }
136         mapBuilder.withChild(mapEntryBuilder.build());
137     }
138
139     /**
140      * Mapping submodules of specific module.
141      *
142      * @param module
143      *             module with submodules
144      * @param mapEntryBuilder
145      *             mapEntryBuilder of parent for mapping children
146      * @param ietfYangLibraryModule
147      *             ietf-yang-library module
148      * @param context
149      *             schema context
150      */
151     private static void addSubmodules(final ModuleLike module,
152             final DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> mapEntryBuilder,
153             final SchemaContext context) {
154         final CollectionNodeBuilder<MapEntryNode, OrderedMapNode> mapBuilder = Builders.orderedMapBuilder()
155             .withNodeIdentifier(new NodeIdentifier(IetfYangLibrary.SPECIFIC_MODULE_SUBMODULE_LIST_QNAME));
156
157         for (final Submodule submodule : module.getSubmodules()) {
158             fillMapByModules(mapBuilder, IetfYangLibrary.SPECIFIC_MODULE_SUBMODULE_LIST_QNAME, true, submodule,
159                 context);
160         }
161         mapEntryBuilder.withChild(mapBuilder.build());
162     }
163
164     /**
165      * Mapping deviations of specific module.
166      *
167      * @param module
168      *             module with deviations
169      * @param mapEntryBuilder
170      *             mapEntryBuilder of parent for mapping children
171      * @param context
172      *             schema context
173      */
174     private static void addDeviationList(final ModuleLike module,
175             final DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> mapEntryBuilder,
176             final SchemaContext context) {
177         final CollectionNodeBuilder<MapEntryNode, MapNode> deviations = Builders.mapBuilder()
178             .withNodeIdentifier(new NodeIdentifier(IetfYangLibrary.SPECIFIC_MODULE_DEVIATION_LIST_QNAME));
179         for (final Deviation deviation : module.getDeviations()) {
180             final List<QName> ids = deviation.getTargetPath().getNodeIdentifiers();
181             final QName lastComponent = ids.get(ids.size() - 1);
182
183             deviations.withChild(newCommonLeafsMapEntryBuilder(IetfYangLibrary.SPECIFIC_MODULE_DEVIATION_LIST_QNAME,
184                 context.findModule(lastComponent.getModule()).get())
185                 .build());
186         }
187         mapEntryBuilder.withChild(deviations.build());
188     }
189
190     /**
191      * Mapping features of specific module.
192      *
193      * @param mapEntryBuilder mapEntryBuilder of parent for mapping children
194      * @param features features of specific module
195      */
196     private static void addFeatureLeafList(
197             final DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> mapEntryBuilder,
198             final Collection<? extends FeatureDefinition> features) {
199         final ListNodeBuilder<Object, LeafSetEntryNode<Object>> leafSetBuilder = Builders.leafSetBuilder()
200                 .withNodeIdentifier(new NodeIdentifier(IetfYangLibrary.SPECIFIC_MODULE_FEATURE_LEAF_LIST_QNAME));
201         for (final FeatureDefinition feature : features) {
202             final String featureName = feature.getQName().getLocalName();
203             leafSetBuilder.withChild(Builders.leafSetEntryBuilder()
204                 .withNodeIdentifier(
205                     new NodeWithValue<>(IetfYangLibrary.SPECIFIC_MODULE_FEATURE_LEAF_LIST_QNAME, featureName))
206                 .withValue(featureName)
207                 .build());
208         }
209         mapEntryBuilder.withChild(leafSetBuilder.build());
210     }
211
212     private static DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> newCommonLeafsMapEntryBuilder(
213             final QName qname, final ModuleLike module) {
214         final var name = module.getName();
215         final var revision = module.getQNameModule().getRevision().map(Revision::toString).orElse("");
216         return Builders.mapEntryBuilder()
217             .withNodeIdentifier(NodeIdentifierWithPredicates.of(qname, Map.of(
218                 IetfYangLibrary.SPECIFIC_MODULE_NAME_LEAF_QNAME, name,
219                 IetfYangLibrary.SPECIFIC_MODULE_REVISION_LEAF_QNAME, revision)))
220             .withChild(ImmutableNodes.leafNode(IetfYangLibrary.SPECIFIC_MODULE_NAME_LEAF_QNAME, name))
221             .withChild(ImmutableNodes.leafNode(IetfYangLibrary.SPECIFIC_MODULE_REVISION_LEAF_QNAME, revision));
222     }
223
224     /**
225      * Map capabilites by ietf-restconf-monitoring.
226      *
227      * @param monitoringModule
228      *             ietf-restconf-monitoring module
229      * @return mapped capabilites
230      */
231     public static ContainerNode mapCapabilites(final Module monitoringModule) {
232         final DataSchemaNode restconfState =
233                 monitoringModule.getDataChildByName(MonitoringModule.CONT_RESTCONF_STATE_QNAME);
234         final DataSchemaNode capabilitesContSchema =
235                 getChildOfCont((ContainerSchemaNode) restconfState, MonitoringModule.CONT_CAPABILITES_QNAME);
236         final DataContainerNodeBuilder<NodeIdentifier, ContainerNode> capabilitesContBuilder =
237                 Builders.containerBuilder((ContainerSchemaNode) capabilitesContSchema);
238         final DataSchemaNode leafListCapa = getChildOfCont((ContainerSchemaNode) capabilitesContSchema,
239                 MonitoringModule.LEAF_LIST_CAPABILITY_QNAME);
240         final ListNodeBuilder<Object, LeafSetEntryNode<Object>> leafListCapaBuilder =
241                 Builders.orderedLeafSetBuilder((LeafListSchemaNode) leafListCapa);
242         fillLeafListCapa(leafListCapaBuilder, (LeafListSchemaNode) leafListCapa);
243
244         return Builders.containerBuilder()
245             .withNodeIdentifier(new NodeIdentifier(MonitoringModule.CONT_RESTCONF_STATE_QNAME))
246             .withChild(capabilitesContBuilder.withChild(leafListCapaBuilder.build()).build())
247             .build();
248     }
249
250     /**
251      * Map data to leaf-list.
252      *
253      * @param builder
254      *             builder of parent for children
255      * @param leafListSchema
256      *             leaf list schema
257      */
258     private static void fillLeafListCapa(final ListNodeBuilder<Object, LeafSetEntryNode<Object>> builder,
259             final LeafListSchemaNode leafListSchema) {
260         builder.withChild(leafListEntryBuild(leafListSchema, QueryParams.DEPTH));
261         builder.withChild(leafListEntryBuild(leafListSchema, QueryParams.FIELDS));
262         builder.withChild(leafListEntryBuild(leafListSchema, QueryParams.FILTER));
263         builder.withChild(leafListEntryBuild(leafListSchema, QueryParams.REPLAY));
264         builder.withChild(leafListEntryBuild(leafListSchema, QueryParams.WITH_DEFAULTS));
265     }
266
267     /**
268      * Map value to leaf list entry node.
269      *
270      * @param leafListSchema
271      *             leaf list schema of leaf list entry
272      * @param value
273      *             value of leaf entry
274      * @return entry node
275      */
276     private static LeafSetEntryNode<Object> leafListEntryBuild(final LeafListSchemaNode leafListSchema,
277             final String value) {
278         return Builders.leafSetEntryBuilder(leafListSchema).withValue(value).build();
279     }
280
281     /**
282      * Find specific schema node by qname in parent {@link ContainerSchemaNode}.
283      *
284      * @param parent
285      *             schemaNode
286      * @param childQName
287      *             specific qname of child
288      * @return schema node of child by qname
289      */
290     private static DataSchemaNode getChildOfCont(final ContainerSchemaNode parent, final QName childQName) {
291         for (final DataSchemaNode child : parent.getChildNodes()) {
292             if (child.getQName().equals(childQName)) {
293                 return child;
294             }
295         }
296         throw new RestconfDocumentedException(
297                 childQName.getLocalName() + " doesn't exist in container " + MonitoringModule.CONT_RESTCONF_STATE_NAME);
298     }
299
300     /**
301      * Map data of yang notification to normalized node according to
302      * ietf-restconf-monitoring.
303      *
304      * @param notifiQName
305      *             qname of notification from listener
306      * @param notifications
307      *             list of notifications for find schema of notification by
308      *            notifiQName
309      * @param start
310      *             start-time query parameter of notification
311      * @param outputType
312      *             output type of notification
313      * @param uri
314      *             location of registered listener for sending data of
315      *            notification
316      * @param monitoringModule
317      *             ietf-restconf-monitoring module
318      * @return mapped data of notification - map entry node if parent exists,
319      *         container streams with list and map entry node if not
320      */
321     public static MapEntryNode mapYangNotificationStreamByIetfRestconfMonitoring(final QName notifiQName,
322             final Collection<? extends NotificationDefinition> notifications, final Instant start,
323             final String outputType, final URI uri, final Module monitoringModule) {
324         for (final NotificationDefinition notificationDefinition : notifications) {
325             if (notificationDefinition.getQName().equals(notifiQName)) {
326                 final DataSchemaNode streamListSchema = ((ContainerSchemaNode) ((ContainerSchemaNode) monitoringModule
327                         .getDataChildByName(MonitoringModule.CONT_RESTCONF_STATE_QNAME))
328                                 .getDataChildByName(MonitoringModule.CONT_STREAMS_QNAME))
329                                         .getDataChildByName(MonitoringModule.LIST_STREAM_QNAME);
330                 final DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> streamEntry =
331                         Builders.mapEntryBuilder((ListSchemaNode) streamListSchema);
332
333                 final ListSchemaNode listSchema = (ListSchemaNode) streamListSchema;
334                 prepareLeafAndFillEntryBuilder(streamEntry,
335                         listSchema.getDataChildByName(MonitoringModule.LEAF_NAME_STREAM_QNAME),
336                         notificationDefinition.getQName().getLocalName());
337
338                 final Optional<String> optDesc = notificationDefinition.getDescription();
339                 if (optDesc.isPresent()) {
340                     prepareLeafAndFillEntryBuilder(streamEntry,
341                             listSchema.getDataChildByName(MonitoringModule.LEAF_DESCR_STREAM_QNAME), optDesc.get());
342                 }
343                 prepareLeafAndFillEntryBuilder(streamEntry,
344                         listSchema.getDataChildByName(MonitoringModule.LEAF_REPLAY_SUPP_STREAM_QNAME), true);
345                 if (start != null) {
346                     prepareLeafAndFillEntryBuilder(streamEntry,
347                         listSchema.getDataChildByName(MonitoringModule.LEAF_START_TIME_STREAM_QNAME),
348                         DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(OffsetDateTime.ofInstant(start,
349                             ZoneId.systemDefault())));
350                 }
351                 prepareListAndFillEntryBuilder(streamEntry,
352                         (ListSchemaNode) listSchema.getDataChildByName(MonitoringModule.LIST_ACCESS_STREAM_QNAME),
353                         outputType, uri);
354
355                 return streamEntry.build();
356             }
357         }
358
359         throw new RestconfDocumentedException(notifiQName + " doesn't exist in any modul");
360     }
361
362     private static void prepareListAndFillEntryBuilder(
363             final DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> streamEntry,
364             final ListSchemaNode listSchemaNode, final String outputType, final URI uriToWebsocketServer) {
365         final CollectionNodeBuilder<MapEntryNode, MapNode> accessListBuilder = Builders.mapBuilder(listSchemaNode);
366         final DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> entryAccessList =
367                 Builders.mapEntryBuilder(listSchemaNode);
368         prepareLeafAndFillEntryBuilder(entryAccessList,
369                 listSchemaNode.getDataChildByName(MonitoringModule.LEAF_ENCODING_ACCESS_QNAME), outputType);
370         prepareLeafAndFillEntryBuilder(entryAccessList,
371                 listSchemaNode.getDataChildByName(MonitoringModule.LEAF_LOCATION_ACCESS_QNAME),
372                 uriToWebsocketServer.toString());
373         streamEntry.withChild(accessListBuilder.withChild(entryAccessList.build()).build());
374     }
375
376     /**
377      * Prepare leaf and fill entry builder.
378      *
379      * @param streamEntry   Stream entry
380      * @param leafSchema    Leaf schema
381      * @param value         Value
382      */
383     private static void prepareLeafAndFillEntryBuilder(
384             final DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> streamEntry,
385             final DataSchemaNode leafSchema, final Object value) {
386         streamEntry.withChild(Builders.leafBuilder((LeafSchemaNode) leafSchema).withValue(value).build());
387     }
388
389     /**
390      * Map data of data change notification to normalized node according to
391      * ietf-restconf-monitoring.
392      *
393      * @param path
394      *             path of data to listen on
395      * @param start
396      *             start-time query parameter of notification
397      * @param outputType
398      *             output type of notification
399      * @param uri
400      *             location of registered listener for sending data of
401      *            notification
402      * @param monitoringModule
403      *             ietf-restconf-monitoring module
404      * @param schemaContext
405      *             schemaContext for parsing instance identifier to get schema
406      *            node of data
407      * @return mapped data of notification - map entry node if parent exists,
408      *         container streams with list and map entry node if not
409      */
410     public static MapEntryNode mapDataChangeNotificationStreamByIetfRestconfMonitoring(
411             final YangInstanceIdentifier path, final Instant start, final String outputType, final URI uri,
412             final Module monitoringModule, final EffectiveModelContext schemaContext, final String streamName) {
413         final SchemaNode schemaNode = ParserIdentifier
414                 .toInstanceIdentifier(ParserIdentifier.stringFromYangInstanceIdentifier(path, schemaContext),
415                         schemaContext, Optional.empty())
416                 .getSchemaNode();
417         final DataSchemaNode streamListSchema = ((ContainerSchemaNode) ((ContainerSchemaNode) monitoringModule
418                 .getDataChildByName(MonitoringModule.CONT_RESTCONF_STATE_QNAME))
419                         .getDataChildByName(MonitoringModule.CONT_STREAMS_QNAME))
420                                 .getDataChildByName(MonitoringModule.LIST_STREAM_QNAME);
421         final DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> streamEntry =
422                 Builders.mapEntryBuilder((ListSchemaNode) streamListSchema);
423
424         final ListSchemaNode listSchema = (ListSchemaNode) streamListSchema;
425         prepareLeafAndFillEntryBuilder(streamEntry,
426                 listSchema.getDataChildByName(MonitoringModule.LEAF_NAME_STREAM_QNAME), streamName);
427
428         final Optional<String> optDesc = schemaNode.getDescription();
429         if (optDesc.isPresent()) {
430             prepareLeafAndFillEntryBuilder(streamEntry,
431                     listSchema.getDataChildByName(MonitoringModule.LEAF_DESCR_STREAM_QNAME), optDesc.get());
432         }
433         prepareLeafAndFillEntryBuilder(streamEntry,
434                 listSchema.getDataChildByName(MonitoringModule.LEAF_REPLAY_SUPP_STREAM_QNAME), true);
435         prepareLeafAndFillEntryBuilder(streamEntry,
436                 listSchema.getDataChildByName(MonitoringModule.LEAF_START_TIME_STREAM_QNAME),
437                 DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(OffsetDateTime.ofInstant(start, ZoneId.systemDefault())));
438         prepareListAndFillEntryBuilder(streamEntry,
439                 (ListSchemaNode) listSchema.getDataChildByName(MonitoringModule.LIST_ACCESS_STREAM_QNAME), outputType,
440                 uri);
441
442         return streamEntry.build();
443     }
444 }