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