Remove Bierman02 support from sal-rest-docgen
[netconf.git] / restconf / sal-rest-docgen / src / main / java / org / opendaylight / netconf / sal / rest / doc / impl / BaseYangSwaggerGenerator.java
1 /*
2  * Copyright (c) 2014 Brocade Communications 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.netconf.sal.rest.doc.impl;
9
10 import static org.opendaylight.netconf.sal.rest.doc.impl.ApiDocServiceImpl.DEFAULT_PAGESIZE;
11 import static org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder.TOP;
12 import static org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder.buildDelete;
13 import static org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder.buildGet;
14 import static org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder.buildPost;
15 import static org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder.buildPostOperation;
16 import static org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder.buildPut;
17 import static org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder.getTypeParentNode;
18 import static org.opendaylight.netconf.sal.rest.doc.util.JsonUtil.addFields;
19 import static org.opendaylight.netconf.sal.rest.doc.util.RestDocgenUtil.resolvePathArgumentsName;
20
21 import com.fasterxml.jackson.databind.JsonNode;
22 import com.fasterxml.jackson.databind.ObjectMapper;
23 import com.fasterxml.jackson.databind.SerializationFeature;
24 import com.fasterxml.jackson.databind.node.ArrayNode;
25 import com.fasterxml.jackson.databind.node.JsonNodeFactory;
26 import com.fasterxml.jackson.databind.node.ObjectNode;
27 import com.google.common.base.Preconditions;
28 import com.google.common.collect.ImmutableList;
29 import com.google.common.collect.Range;
30 import java.io.IOException;
31 import java.net.URI;
32 import java.time.format.DateTimeParseException;
33 import java.util.ArrayList;
34 import java.util.Arrays;
35 import java.util.Collection;
36 import java.util.Collections;
37 import java.util.HashMap;
38 import java.util.Iterator;
39 import java.util.List;
40 import java.util.Map;
41 import java.util.Map.Entry;
42 import java.util.Optional;
43 import java.util.Set;
44 import java.util.SortedSet;
45 import java.util.TreeSet;
46 import javax.ws.rs.core.UriInfo;
47 import org.opendaylight.mdsal.dom.api.DOMSchemaService;
48 import org.opendaylight.netconf.sal.rest.doc.impl.ApiDocServiceImpl.OAversion;
49 import org.opendaylight.netconf.sal.rest.doc.swagger.CommonApiObject;
50 import org.opendaylight.netconf.sal.rest.doc.swagger.Components;
51 import org.opendaylight.netconf.sal.rest.doc.swagger.Info;
52 import org.opendaylight.netconf.sal.rest.doc.swagger.OpenApiObject;
53 import org.opendaylight.netconf.sal.rest.doc.swagger.Resource;
54 import org.opendaylight.netconf.sal.rest.doc.swagger.ResourceList;
55 import org.opendaylight.netconf.sal.rest.doc.swagger.Server;
56 import org.opendaylight.netconf.sal.rest.doc.swagger.SwaggerObject;
57 import org.opendaylight.netconf.sal.rest.doc.util.JsonUtil;
58 import org.opendaylight.yangtools.yang.common.QName;
59 import org.opendaylight.yangtools.yang.common.Revision;
60 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
61 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
62 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
63 import org.opendaylight.yangtools.yang.model.api.ActionNodeContainer;
64 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
65 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
66 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
67 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
68 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
69 import org.opendaylight.yangtools.yang.model.api.Module;
70 import org.opendaylight.yangtools.yang.model.api.OperationDefinition;
71 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
72 import org.slf4j.Logger;
73 import org.slf4j.LoggerFactory;
74
75 public abstract class BaseYangSwaggerGenerator {
76
77     private static final Logger LOG = LoggerFactory.getLogger(BaseYangSwaggerGenerator.class);
78
79     private static final String API_VERSION = "1.0.0";
80     private static final String SWAGGER_VERSION = "2.0";
81     private static final String OPEN_API_VERSION = "3.0.3";
82
83     private final DefinitionGenerator jsonConverter = new DefinitionGenerator();
84
85     private final ObjectMapper mapper = new ObjectMapper();
86     private final DOMSchemaService schemaService;
87
88     public static final String BASE_PATH = "/";
89     public static final String MODULE_NAME_SUFFIX = "_module";
90
91     protected BaseYangSwaggerGenerator(final Optional<DOMSchemaService> schemaService) {
92         this.schemaService = schemaService.orElse(null);
93         mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
94     }
95
96     public DOMSchemaService getSchemaService() {
97         return schemaService;
98     }
99
100     public ResourceList getResourceListing(final UriInfo uriInfo, final EffectiveModelContext schemaContext,
101                                            final String context, final OAversion oaversion) {
102         return getResourceListing(uriInfo, schemaContext, context, 0, true, oaversion);
103     }
104
105     /**
106      * Return list of modules converted to swagger compliant resource list.
107      */
108     public ResourceList getResourceListing(final UriInfo uriInfo, final EffectiveModelContext schemaContext,
109                                            final String context, final int pageNum, final boolean all,
110                                            final OAversion oaversion) {
111         final ResourceList resourceList = createResourceList();
112
113         final Set<Module> modules = getSortedModules(schemaContext);
114
115         final List<Resource> resources = new ArrayList<>(DEFAULT_PAGESIZE);
116
117         LOG.info("Modules found [{}]", modules.size());
118         final int start = DEFAULT_PAGESIZE * pageNum;
119         final int end = start + DEFAULT_PAGESIZE;
120         int count = 0;
121         for (final Module module : modules) {
122             final String revisionString = module.getQNameModule().getRevision().map(Revision::toString).orElse(null);
123
124             LOG.debug("Working on [{},{}]...", module.getName(), revisionString);
125             final SwaggerObject doc = getApiDeclaration(module.getName(), revisionString, uriInfo, schemaContext,
126                     context, oaversion);
127             if (doc != null) {
128                 count++;
129                 if (count >= start && count < end || all) {
130                     final Resource resource = new Resource();
131                     resource.setPath(generatePath(uriInfo, module.getName(), revisionString));
132                     resources.add(resource);
133                 }
134
135                 if (count >= end && !all) {
136                     break;
137                 }
138             } else {
139                 LOG.warn("Could not generate doc for {},{}", module.getName(), revisionString);
140             }
141         }
142
143         resourceList.setApis(resources);
144
145         return resourceList;
146     }
147
148     public SwaggerObject getAllModulesDoc(final UriInfo uriInfo, final DefinitionNames definitionNames,
149                                           final OAversion oaversion) {
150         final EffectiveModelContext schemaContext = schemaService.getGlobalContext();
151         Preconditions.checkState(schemaContext != null);
152         return getAllModulesDoc(uriInfo, Optional.empty(), schemaContext, Optional.empty(), "", definitionNames,
153                 oaversion);
154     }
155
156     public SwaggerObject getAllModulesDoc(final UriInfo uriInfo, final Optional<Range<Integer>> range,
157                                           final EffectiveModelContext schemaContext, final Optional<String> deviceName,
158                                           final String context, final DefinitionNames definitionNames,
159                                           final OAversion oaversion) {
160         final String schema = createSchemaFromUriInfo(uriInfo);
161         final String host = createHostFromUriInfo(uriInfo);
162         String name = "Controller";
163         if (deviceName.isPresent()) {
164             name = deviceName.get();
165         }
166
167         final String title = name + " modules of RESTCONF";
168         final SwaggerObject doc = createSwaggerObject(schema, host, BASE_PATH, title);
169         doc.setDefinitions(JsonNodeFactory.instance.objectNode());
170         doc.setPaths(JsonNodeFactory.instance.objectNode());
171
172         fillDoc(doc, range, schemaContext, context, deviceName, oaversion, definitionNames);
173
174         return doc;
175     }
176
177     public void fillDoc(final SwaggerObject doc, final Optional<Range<Integer>> range,
178                         final EffectiveModelContext schemaContext, final String context,
179                         final Optional<String> deviceName, final OAversion oaversion,
180                         final DefinitionNames definitionNames) {
181         final SortedSet<Module> modules = getSortedModules(schemaContext);
182         final Set<Module> filteredModules;
183         if (range.isPresent()) {
184             filteredModules = filterByRange(modules, range.get());
185         } else {
186             filteredModules = modules;
187         }
188
189         for (final Module module : filteredModules) {
190             final String revisionString = module.getQNameModule().getRevision().map(Revision::toString).orElse(null);
191
192             LOG.debug("Working on [{},{}]...", module.getName(), revisionString);
193
194             getSwaggerDocSpec(module, context, deviceName, schemaContext, oaversion, definitionNames, doc, false);
195         }
196     }
197
198     private static Set<Module> filterByRange(final SortedSet<Module> modules, final Range<Integer> range) {
199         final int begin = range.lowerEndpoint();
200         final int end = range.upperEndpoint();
201
202         Module firstModule = null;
203
204         final Iterator<Module> iterator = modules.iterator();
205         int counter = 0;
206         while (iterator.hasNext() && counter < end) {
207             final Module module = iterator.next();
208             if (containsListOrContainer(module.getChildNodes()) || !module.getRpcs().isEmpty()) {
209                 if (counter == begin) {
210                     firstModule = module;
211                 }
212                 counter++;
213             }
214         }
215
216         if (iterator.hasNext()) {
217             return modules.subSet(firstModule, iterator.next());
218         } else {
219             return modules.tailSet(firstModule);
220         }
221     }
222
223     public ResourceList createResourceList() {
224         final ResourceList resourceList = new ResourceList();
225         resourceList.setApiVersion(API_VERSION);
226         resourceList.setSwaggerVersion(SWAGGER_VERSION);
227         return resourceList;
228     }
229
230     public String generatePath(final UriInfo uriInfo, final String name, final String revision) {
231         final URI uri = uriInfo.getRequestUriBuilder().replaceQuery("").path(generateCacheKey(name, revision)).build();
232         return uri.toASCIIString();
233     }
234
235     public CommonApiObject getApiDeclaration(final String module, final String revision, final UriInfo uriInfo,
236                                              final OAversion oaversion) {
237         final EffectiveModelContext schemaContext = schemaService.getGlobalContext();
238         Preconditions.checkState(schemaContext != null);
239         final SwaggerObject doc = getApiDeclaration(module, revision, uriInfo, schemaContext, "", oaversion);
240         return getAppropriateDoc(doc, oaversion);
241     }
242
243     public SwaggerObject getApiDeclaration(final String moduleName, final String revision, final UriInfo uriInfo,
244                                            final EffectiveModelContext schemaContext, final String context,
245                                            final OAversion oaversion) {
246         final Optional<Revision> rev;
247
248         try {
249             rev = Revision.ofNullable(revision);
250         } catch (final DateTimeParseException e) {
251             throw new IllegalArgumentException(e);
252         }
253
254         final Module module = schemaContext.findModule(moduleName, rev).orElse(null);
255         Preconditions.checkArgument(module != null,
256                 "Could not find module by name,revision: " + moduleName + "," + revision);
257
258         return getApiDeclaration(module, uriInfo, context, schemaContext, oaversion);
259     }
260
261     public SwaggerObject getApiDeclaration(final Module module, final UriInfo uriInfo, final String context,
262                                            final EffectiveModelContext schemaContext, final OAversion oaversion) {
263         final String schema = createSchemaFromUriInfo(uriInfo);
264         final String host = createHostFromUriInfo(uriInfo);
265
266         return getSwaggerDocSpec(module, schema, host, BASE_PATH, context, schemaContext, oaversion);
267     }
268
269     public String createHostFromUriInfo(final UriInfo uriInfo) {
270         String portPart = "";
271         final int port = uriInfo.getBaseUri().getPort();
272         if (port != -1) {
273             portPart = ":" + port;
274         }
275         return uriInfo.getBaseUri().getHost() + portPart;
276     }
277
278     public String createSchemaFromUriInfo(final UriInfo uriInfo) {
279         return uriInfo.getBaseUri().getScheme();
280     }
281
282     public SwaggerObject getSwaggerDocSpec(final Module module, final String schema, final String host,
283                                            final String basePath, final String context,
284                                            final EffectiveModelContext schemaContext, final OAversion oaversion) {
285         final SwaggerObject doc = createSwaggerObject(schema, host, basePath, module.getName());
286         final DefinitionNames definitionNames = new DefinitionNames();
287         return getSwaggerDocSpec(module, context, Optional.empty(), schemaContext, oaversion, definitionNames, doc,
288             true);
289     }
290
291
292     public SwaggerObject getSwaggerDocSpec(final Module module, final String context, final Optional<String> deviceName,
293                                            final EffectiveModelContext schemaContext, final OAversion oaversion,
294                                            final DefinitionNames definitionNames, final SwaggerObject doc,
295                                            final boolean isForSingleModule) {
296         final ObjectNode definitions;
297
298         try {
299             if (isForSingleModule) {
300                 definitions = jsonConverter.convertToJsonSchema(module, schemaContext, definitionNames, oaversion,
301                         true);
302                 doc.setDefinitions(definitions);
303             } else {
304                 definitions = jsonConverter.convertToJsonSchema(module, schemaContext, definitionNames, oaversion,
305                         false);
306                 addFields(doc.getDefinitions(), definitions.fields());
307             }
308             if (LOG.isDebugEnabled()) {
309                 LOG.debug("Document: {}", mapper.writeValueAsString(doc));
310             }
311         } catch (final IOException e) {
312             LOG.error("Exception occured in DefinitionGenerator", e);
313         }
314
315         final ObjectNode paths = JsonNodeFactory.instance.objectNode();
316         final String moduleName = module.getName();
317
318         boolean hasAddRootPostLink = false;
319
320         final Collection<? extends DataSchemaNode> dataSchemaNodes = module.getChildNodes();
321         LOG.debug("child nodes size [{}]", dataSchemaNodes.size());
322         for (final DataSchemaNode node : dataSchemaNodes) {
323             if (node instanceof ListSchemaNode || node instanceof ContainerSchemaNode) {
324                 LOG.debug("Is Configuration node [{}] [{}]", node.isConfiguration(), node.getQName().getLocalName());
325
326                 final String localName = module.getName() + ":" + node.getQName().getLocalName();
327                 ArrayNode pathParams = JsonNodeFactory.instance.arrayNode();
328                 String resourcePath;
329
330                 if (node.isConfiguration()) { // This node's config statement is
331                     // true.
332                     resourcePath = getResourcePath("config", context);
333
334                     /*
335                      * When there are two or more top container or list nodes
336                      * whose config statement is true in module, make sure that
337                      * only one root post link is added for this module.
338                      */
339                     if (isForSingleModule && !hasAddRootPostLink) {
340                         LOG.debug("Has added root post link for module {}", module.getName());
341                         addRootPostLink(module, deviceName, pathParams, resourcePath, paths, oaversion);
342
343                         hasAddRootPostLink = true;
344                     }
345
346                     final String resolvedPath = resourcePath + "/" + createPath(node, pathParams, oaversion, localName);
347                     addPaths(node, deviceName, moduleName, paths, pathParams, schemaContext, true, module.getName(),
348                         definitionNames, oaversion, resolvedPath);
349                 }
350                 pathParams = JsonNodeFactory.instance.arrayNode();
351                 resourcePath = getResourcePath("operational", context);
352
353                 if (!node.isConfiguration()) {
354                     final String resolvedPath = resourcePath + "/" + createPath(node, pathParams, oaversion, localName);
355                     addPaths(node, deviceName, moduleName, paths, pathParams, schemaContext, false, moduleName,
356                         definitionNames, oaversion, resolvedPath);
357                 }
358             }
359         }
360
361         for (final RpcDefinition rpcDefinition : module.getRpcs()) {
362             final String resolvedPath = getResourcePath("operations", context) + "/" + moduleName + ":"
363                     + rpcDefinition.getQName().getLocalName();
364             addOperations(rpcDefinition, moduleName, deviceName, paths, module.getName(), definitionNames, oaversion,
365                     resolvedPath);
366         }
367
368         LOG.debug("Number of Paths found [{}]", paths.size());
369
370         if (isForSingleModule) {
371             doc.setPaths(paths);
372         } else {
373             addFields(doc.getPaths(), paths.fields());
374         }
375
376         return doc;
377     }
378
379     private static void addRootPostLink(final Module module, final Optional<String> deviceName,
380             final ArrayNode pathParams, final String resourcePath, final ObjectNode paths, final OAversion oaversion) {
381         if (containsListOrContainer(module.getChildNodes())) {
382             final ObjectNode post = JsonNodeFactory.instance.objectNode();
383             final String moduleName = module.getName();
384             final String name = moduleName + MODULE_NAME_SUFFIX;
385             post.set("post", buildPost("", name, "", moduleName, deviceName,
386                     module.getDescription().orElse(""), pathParams, oaversion));
387             paths.set(resourcePath, post);
388         }
389     }
390
391     public SwaggerObject createSwaggerObject(final String schema, final String host, final String basePath,
392                                              final String title) {
393         final SwaggerObject doc = new SwaggerObject();
394         doc.setSwagger(SWAGGER_VERSION);
395         final Info info = new Info();
396         info.setTitle(title);
397         info.setVersion(API_VERSION);
398         doc.setInfo(info);
399         doc.setSchemes(ImmutableList.of(schema));
400         doc.setHost(host);
401         doc.setBasePath(basePath);
402         doc.setProduces(Arrays.asList("application/xml", "application/json"));
403         return doc;
404     }
405
406     public static CommonApiObject getAppropriateDoc(final SwaggerObject swaggerObject, final OAversion oaversion) {
407         if (oaversion.equals(OAversion.V3_0)) {
408             return convertToOpenApi(swaggerObject);
409         }
410         return swaggerObject;
411     }
412
413     private static OpenApiObject convertToOpenApi(final SwaggerObject swaggerObject) {
414         final OpenApiObject doc = new OpenApiObject();
415         doc.setOpenapi(OPEN_API_VERSION);
416         doc.setInfo(swaggerObject.getInfo());
417         doc.setServers(convertToServers(swaggerObject.getSchemes(), swaggerObject.getHost(),
418                 swaggerObject.getBasePath()));
419         doc.setPaths(swaggerObject.getPaths());
420         doc.setComponents(new Components(swaggerObject.getDefinitions()));
421         return doc;
422     }
423
424
425     private static List<Server> convertToServers(final List<String> schemes, final String host, final String basePath) {
426         return ImmutableList.of(new Server(schemes.get(0) + "://" + host + basePath));
427     }
428
429     protected abstract String getPathVersion();
430
431     public abstract String getResourcePath(String resourceType, String context);
432
433     public abstract String getResourcePathPart(String resourceType);
434
435     private static String generateCacheKey(final String module, final String revision) {
436         return module + "(" + revision + ")";
437     }
438
439     private void addPaths(final DataSchemaNode node, final Optional<String> deviceName, final String moduleName,
440                           final ObjectNode paths, final ArrayNode parentPathParams,
441                           final EffectiveModelContext schemaContext, final boolean isConfig, final String parentName,
442                           final DefinitionNames definitionNames, final OAversion oaversion, final String resourcePath) {
443         LOG.debug("Adding path: [{}]", resourcePath);
444
445         final ArrayNode pathParams = JsonUtil.copy(parentPathParams);
446         Iterable<? extends DataSchemaNode> childSchemaNodes = Collections.emptySet();
447         if (node instanceof ListSchemaNode || node instanceof ContainerSchemaNode) {
448             final DataNodeContainer dataNodeContainer = (DataNodeContainer) node;
449             childSchemaNodes = dataNodeContainer.getChildNodes();
450         }
451
452         final ObjectNode path = JsonNodeFactory.instance.objectNode();
453         path.setAll(operations(node, moduleName, deviceName, pathParams, isConfig, parentName, definitionNames,
454                 oaversion));
455         paths.set(resourcePath, path);
456
457         if (node instanceof ActionNodeContainer) {
458             ((ActionNodeContainer) node).getActions().forEach(actionDef -> {
459                 final String resolvedPath = "rests/operations" + resourcePath.substring(11)
460                         + "/" + resolvePathArgumentsName(actionDef.getQName(), node.getQName(), schemaContext);
461                 addOperations(actionDef, moduleName, deviceName, paths, parentName, definitionNames, oaversion,
462                         resolvedPath);
463             });
464         }
465
466         for (final DataSchemaNode childNode : childSchemaNodes) {
467             if (childNode instanceof ListSchemaNode || childNode instanceof ContainerSchemaNode) {
468                 final String newParent = parentName + "_" + node.getQName().getLocalName();
469                 final String localName = resolvePathArgumentsName(childNode.getQName(), node.getQName(), schemaContext);
470                 final String newResourcePath = resourcePath + "/" + createPath(childNode, pathParams, oaversion,
471                         localName);
472                 final boolean newIsConfig = isConfig && childNode.isConfiguration();
473                 addPaths(childNode, deviceName, moduleName, paths, pathParams, schemaContext,
474                     newIsConfig, newParent, definitionNames, oaversion, newResourcePath);
475             }
476         }
477     }
478
479     private static boolean containsListOrContainer(final Iterable<? extends DataSchemaNode> nodes) {
480         for (final DataSchemaNode child : nodes) {
481             if (child instanceof ListSchemaNode || child instanceof ContainerSchemaNode) {
482                 return true;
483             }
484         }
485         return false;
486     }
487
488     private static Map<String, ObjectNode> operations(final DataSchemaNode node, final String moduleName,
489                                                       final Optional<String> deviceName, final ArrayNode pathParams,
490                                                       final boolean isConfig, final String parentName,
491                                                       final DefinitionNames definitionNames,
492                                                       final OAversion oaversion) {
493         final Map<String, ObjectNode> operations = new HashMap<>();
494         final String discriminator = definitionNames.getDiscriminator(node);
495
496         final String nodeName = node.getQName().getLocalName();
497
498         final String defName = parentName + "_" + nodeName + TOP + discriminator;
499         final ObjectNode get = buildGet(node, moduleName, deviceName, pathParams, defName, isConfig, oaversion);
500         operations.put("get", get);
501
502
503         if (isConfig) {
504             final ObjectNode put = buildPut(parentName, nodeName, discriminator, moduleName, deviceName,
505                     node.getDescription().orElse(""), pathParams, oaversion);
506             operations.put("put", put);
507
508             final ObjectNode delete = buildDelete(node, moduleName, deviceName, pathParams, oaversion);
509             operations.put("delete", delete);
510
511             operations.put("post", buildPost(parentName, nodeName, discriminator, moduleName, deviceName,
512                     node.getDescription().orElse(""), pathParams, oaversion));
513         }
514         return operations;
515     }
516
517     protected abstract ListPathBuilder newListPathBuilder();
518
519     private String createPath(final DataSchemaNode schemaNode, final ArrayNode pathParams,
520                               final OAversion oaversion, final String localName) {
521         final StringBuilder path = new StringBuilder();
522         path.append(localName);
523
524         if (schemaNode instanceof ListSchemaNode) {
525             final ListPathBuilder keyBuilder = newListPathBuilder();
526             for (final QName listKey : ((ListSchemaNode) schemaNode).getKeyDefinition()) {
527                 final String paramName = createUniquePathParamName(listKey.getLocalName(), pathParams);
528                 final String pathParamIdentifier = keyBuilder.nextParamIdentifier(paramName);
529
530                 path.append(pathParamIdentifier);
531
532                 final ObjectNode pathParam = JsonNodeFactory.instance.objectNode();
533                 pathParam.put("name", paramName);
534
535                 ((DataNodeContainer) schemaNode).findDataChildByName(listKey).flatMap(DataSchemaNode::getDescription)
536                         .ifPresent(desc -> pathParam.put("description", desc));
537
538                 final ObjectNode typeParent = getTypeParentNode(pathParam, oaversion);
539
540                 typeParent.put("type", "string");
541                 pathParam.put("in", "path");
542                 pathParam.put("required", true);
543
544                 pathParams.add(pathParam);
545             }
546         }
547         return path.toString();
548     }
549
550     private String createUniquePathParamName(final String clearName, final ArrayNode pathParams) {
551         for (final JsonNode pathParam : pathParams) {
552             if (isNamePicked(clearName, pathParam)) {
553                 return createUniquePathParamName(clearName, pathParams, 1);
554             }
555         }
556         return clearName;
557     }
558
559     private String createUniquePathParamName(final String clearName, final ArrayNode pathParams,
560                                              final int discriminator) {
561         final String newName = clearName + discriminator;
562         for (final JsonNode pathParam : pathParams) {
563             if (isNamePicked(newName, pathParam)) {
564                 return createUniquePathParamName(clearName, pathParams, discriminator + 1);
565             }
566         }
567         return newName;
568     }
569
570     private static boolean isNamePicked(final String name, final JsonNode pathParam) {
571         return name.equals(pathParam.get("name").asText());
572     }
573
574     public SortedSet<Module> getSortedModules(final EffectiveModelContext schemaContext) {
575         if (schemaContext == null) {
576             return Collections.emptySortedSet();
577         }
578
579         final SortedSet<Module> sortedModules = new TreeSet<>((module1, module2) -> {
580             int result = module1.getName().compareTo(module2.getName());
581             if (result == 0) {
582                 result = Revision.compare(module1.getRevision(), module2.getRevision());
583             }
584             if (result == 0) {
585                 result = module1.getNamespace().compareTo(module2.getNamespace());
586             }
587             return result;
588         });
589         for (final Module m : schemaContext.getModules()) {
590             if (m != null) {
591                 sortedModules.add(m);
592             }
593         }
594         return sortedModules;
595     }
596
597     private static void addOperations(final OperationDefinition operDef, final String moduleName,
598             final Optional<String> deviceName, final ObjectNode paths, final String parentName,
599             final DefinitionNames definitionNames, final OAversion oaversion, final String resourcePath) {
600         final ObjectNode operations = JsonNodeFactory.instance.objectNode();
601         operations.set("post", buildPostOperation(operDef, moduleName, deviceName, parentName, definitionNames,
602                 oaversion));
603         paths.set(resourcePath, operations);
604     }
605
606     protected abstract void appendPathKeyValue(StringBuilder builder, Object value);
607
608     public String generateUrlPrefixFromInstanceID(final YangInstanceIdentifier key, final String moduleName) {
609         final StringBuilder builder = new StringBuilder();
610         builder.append("/");
611         if (moduleName != null) {
612             builder.append(moduleName).append(':');
613         }
614         for (final PathArgument arg : key.getPathArguments()) {
615             final String name = arg.getNodeType().getLocalName();
616             if (arg instanceof NodeIdentifierWithPredicates) {
617                 final NodeIdentifierWithPredicates nodeId = (NodeIdentifierWithPredicates) arg;
618                 for (final Entry<QName, Object> entry : nodeId.entrySet()) {
619                     appendPathKeyValue(builder, entry.getValue());
620                 }
621             } else {
622                 builder.append(name).append('/');
623             }
624         }
625         return builder.toString();
626     }
627
628     protected interface ListPathBuilder {
629         String nextParamIdentifier(String key);
630     }
631 }