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