Bump upstream versions
[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                 final String localName = module.getName() + ":" + node.getQName().getLocalName();
333                 ArrayNode pathParams = JsonNodeFactory.instance.arrayNode();
334                 String resourcePath;
335
336                 if (node.isConfiguration()) { // This node's config statement is
337                     // true.
338                     resourcePath = getResourcePath("config", context);
339
340                     /*
341                      * When there are two or more top container or list nodes
342                      * whose config statement is true in module, make sure that
343                      * only one root post link is added for this module.
344                      */
345                     if (isForSingleModule && !hasAddRootPostLink) {
346                         LOG.debug("Has added root post link for module {}", module.getName());
347                         addRootPostLink(module, deviceName, pathParams, resourcePath, paths, oaversion);
348
349                         hasAddRootPostLink = true;
350                     }
351
352                     final String resolvedPath = resourcePath + "/" + createPath(node, pathParams, oaversion, localName);
353                     addPaths(node, deviceName, moduleName, paths, pathParams, schemaContext, true,
354                             module.getName(), definitionNames, uriType, oaversion, resolvedPath);
355                 }
356                 pathParams = JsonNodeFactory.instance.arrayNode();
357                 resourcePath = getResourcePath("operational", context);
358
359                 if (uriType.equals(URIType.DRAFT02)
360                         || uriType.equals(URIType.RFC8040) && !node.isConfiguration()) {
361                     final String resolvedPath = resourcePath + "/" + createPath(node, pathParams, oaversion, localName);
362                     addPaths(node, deviceName, moduleName, paths, pathParams, schemaContext, false,
363                             moduleName, definitionNames, uriType, oaversion, resolvedPath);
364                 }
365             }
366         }
367
368         for (final RpcDefinition rpcDefinition : module.getRpcs()) {
369             final String resolvedPath = getResourcePath("operations", context) + "/" + moduleName + ":"
370                     + rpcDefinition.getQName().getLocalName();
371             addOperations(rpcDefinition, moduleName, deviceName, paths, module.getName(), definitionNames, oaversion,
372                     resolvedPath);
373         }
374
375         LOG.debug("Number of Paths found [{}]", paths.size());
376
377         if (isForSingleModule) {
378             doc.setPaths(paths);
379         } else {
380             addFields(doc.getPaths(), paths.fields());
381         }
382
383         return doc;
384     }
385
386     private static void addRootPostLink(final Module module, final Optional<String> deviceName,
387             final ArrayNode pathParams, final String resourcePath, final ObjectNode paths, final OAversion oaversion) {
388         if (containsListOrContainer(module.getChildNodes())) {
389             final ObjectNode post = JsonNodeFactory.instance.objectNode();
390             final String moduleName = module.getName();
391             final String name = moduleName + MODULE_NAME_SUFFIX;
392             post.set("post", buildPost("", name, "", moduleName, deviceName,
393                     module.getDescription().orElse(""), pathParams, oaversion));
394             paths.set(resourcePath, post);
395         }
396     }
397
398     public SwaggerObject createSwaggerObject(final String schema, final String host, final String basePath,
399                                              final String title) {
400         final SwaggerObject doc = new SwaggerObject();
401         doc.setSwagger(SWAGGER_VERSION);
402         final Info info = new Info();
403         info.setTitle(title);
404         info.setVersion(API_VERSION);
405         doc.setInfo(info);
406         doc.setSchemes(ImmutableList.of(schema));
407         doc.setHost(host);
408         doc.setBasePath(basePath);
409         doc.setProduces(Arrays.asList("application/xml", "application/json"));
410         return doc;
411     }
412
413     public static CommonApiObject getAppropriateDoc(final SwaggerObject swaggerObject, final OAversion oaversion) {
414         if (oaversion.equals(OAversion.V3_0)) {
415             return convertToOpenApi(swaggerObject);
416         }
417         return swaggerObject;
418     }
419
420     private static OpenApiObject convertToOpenApi(final SwaggerObject swaggerObject) {
421         final OpenApiObject doc = new OpenApiObject();
422         doc.setOpenapi(OPEN_API_VERSION);
423         doc.setInfo(swaggerObject.getInfo());
424         doc.setServers(convertToServers(swaggerObject.getSchemes(), swaggerObject.getHost(),
425                 swaggerObject.getBasePath()));
426         doc.setPaths(swaggerObject.getPaths());
427         doc.setComponents(new Components(swaggerObject.getDefinitions()));
428         return doc;
429     }
430
431
432     private static List<Server> convertToServers(final List<String> schemes, final String host, final String basePath) {
433         return ImmutableList.of(new Server(schemes.get(0) + "://" + host + basePath));
434     }
435
436     protected abstract String getPathVersion();
437
438     public abstract String getResourcePath(String resourceType, String context);
439
440     public abstract String getResourcePathPart(String resourceType);
441
442     private static String generateCacheKey(final String module, final String revision) {
443         return module + "(" + revision + ")";
444     }
445
446     private void addPaths(final DataSchemaNode node, final Optional<String> deviceName, final String moduleName,
447                           final ObjectNode paths, final ArrayNode parentPathParams,
448                           final EffectiveModelContext schemaContext, final boolean isConfig, final String parentName,
449                           final DefinitionNames definitionNames, final URIType uriType, final OAversion oaversion,
450                           final String resourcePath) {
451         LOG.debug("Adding path: [{}]", resourcePath);
452
453         final ArrayNode pathParams = JsonUtil.copy(parentPathParams);
454         Iterable<? extends DataSchemaNode> childSchemaNodes = Collections.emptySet();
455         if (node instanceof ListSchemaNode || node instanceof ContainerSchemaNode) {
456             final DataNodeContainer dataNodeContainer = (DataNodeContainer) node;
457             childSchemaNodes = dataNodeContainer.getChildNodes();
458         }
459
460         final ObjectNode path = JsonNodeFactory.instance.objectNode();
461         path.setAll(operations(node, moduleName, deviceName, pathParams, isConfig, parentName, definitionNames,
462                 uriType, oaversion));
463         paths.set(resourcePath, path);
464
465         if (uriType.equals(URIType.RFC8040)) {
466             ((ActionNodeContainer) node).getActions().forEach(actionDef -> {
467                 final String resolvedPath = "rests/operations" + resourcePath.substring(11)
468                         + "/" + resolvePathArgumentsName(actionDef.getQName(), node.getQName(), schemaContext);
469                 addOperations(actionDef, moduleName, deviceName, paths, parentName, definitionNames, oaversion,
470                         resolvedPath);
471             });
472         }
473
474         for (final DataSchemaNode childNode : childSchemaNodes) {
475             if (childNode instanceof ListSchemaNode || childNode instanceof ContainerSchemaNode) {
476                 final String newParent = parentName + "_" + node.getQName().getLocalName();
477                 final String localName = resolvePathArgumentsName(childNode.getQName(), node.getQName(), schemaContext);
478                 final String newResourcePath = resourcePath + "/" + createPath(childNode, pathParams, oaversion,
479                         localName);
480                 if (uriType.equals(URIType.RFC8040)) {
481                     final boolean newIsConfig = isConfig && childNode.isConfiguration();
482                     addPaths(childNode, deviceName, moduleName, paths, pathParams, schemaContext,
483                             newIsConfig, newParent, definitionNames, uriType, oaversion, newResourcePath);
484                 } else {
485                     if (!isConfig || childNode.isConfiguration()) {
486                         addPaths(childNode, deviceName, moduleName, paths, pathParams, schemaContext,
487                                 isConfig, newParent, definitionNames, uriType, oaversion, newResourcePath);
488                     }
489                 }
490             }
491         }
492     }
493
494     private static boolean containsListOrContainer(final Iterable<? extends DataSchemaNode> nodes) {
495         for (final DataSchemaNode child : nodes) {
496             if (child instanceof ListSchemaNode || child instanceof ContainerSchemaNode) {
497                 return true;
498             }
499         }
500         return false;
501     }
502
503     private static Map<String, ObjectNode> operations(final DataSchemaNode node, final String moduleName,
504                                                       final Optional<String> deviceName, final ArrayNode pathParams,
505                                                       final boolean isConfig, final String parentName,
506                                                       final DefinitionNames definitionNames, final URIType uriType,
507                                                       final OAversion oaversion) {
508         final Map<String, ObjectNode> operations = new HashMap<>();
509         final String discriminator = definitionNames.getDiscriminator(node);
510
511         final String nodeName = node.getQName().getLocalName();
512
513         String prefix = "_";
514         if (isConfig && uriType.equals(URIType.DRAFT02)) {
515             prefix = CONFIG + "_";
516         }
517
518         final String defName = parentName + prefix + nodeName + TOP + discriminator;
519         final ObjectNode get = buildGet(node, moduleName, deviceName, pathParams, defName, isConfig, uriType,
520                 oaversion);
521         operations.put("get", get);
522
523
524         if (isConfig) {
525             final ObjectNode put = buildPut(parentName, nodeName, discriminator, moduleName, deviceName,
526                     node.getDescription().orElse(""), pathParams, oaversion);
527             operations.put("put", put);
528
529             final ObjectNode delete = buildDelete(node, moduleName, deviceName, pathParams, oaversion);
530             operations.put("delete", delete);
531
532             operations.put("post", buildPost(parentName, nodeName, discriminator, moduleName, deviceName,
533                     node.getDescription().orElse(""), pathParams, oaversion));
534         }
535         return operations;
536     }
537
538     protected abstract ListPathBuilder newListPathBuilder();
539
540     private String createPath(final DataSchemaNode schemaNode, final ArrayNode pathParams,
541                               final OAversion oaversion, final String localName) {
542         final StringBuilder path = new StringBuilder();
543         path.append(localName);
544
545         if (schemaNode instanceof ListSchemaNode) {
546             final ListPathBuilder keyBuilder = newListPathBuilder();
547             for (final QName listKey : ((ListSchemaNode) schemaNode).getKeyDefinition()) {
548                 final String paramName = createUniquePathParamName(listKey.getLocalName(), pathParams);
549                 final String pathParamIdentifier = keyBuilder.nextParamIdentifier(paramName);
550
551                 path.append(pathParamIdentifier);
552
553                 final ObjectNode pathParam = JsonNodeFactory.instance.objectNode();
554                 pathParam.put("name", paramName);
555
556                 ((DataNodeContainer) schemaNode).findDataChildByName(listKey).flatMap(DataSchemaNode::getDescription)
557                         .ifPresent(desc -> pathParam.put("description", desc));
558
559                 final ObjectNode typeParent = getTypeParentNode(pathParam, oaversion);
560
561                 typeParent.put("type", "string");
562                 pathParam.put("in", "path");
563                 pathParam.put("required", true);
564
565                 pathParams.add(pathParam);
566             }
567         }
568         return path.toString();
569     }
570
571     private String createUniquePathParamName(final String clearName, final ArrayNode pathParams) {
572         for (final JsonNode pathParam : pathParams) {
573             if (isNamePicked(clearName, pathParam)) {
574                 return createUniquePathParamName(clearName, pathParams, 1);
575             }
576         }
577         return clearName;
578     }
579
580     private String createUniquePathParamName(final String clearName, final ArrayNode pathParams,
581                                              final int discriminator) {
582         final String newName = clearName + discriminator;
583         for (final JsonNode pathParam : pathParams) {
584             if (isNamePicked(newName, pathParam)) {
585                 return createUniquePathParamName(clearName, pathParams, discriminator + 1);
586             }
587         }
588         return newName;
589     }
590
591     private static boolean isNamePicked(final String name, final JsonNode pathParam) {
592         return name.equals(pathParam.get("name").asText());
593     }
594
595     public SortedSet<Module> getSortedModules(final EffectiveModelContext schemaContext) {
596         if (schemaContext == null) {
597             return Collections.emptySortedSet();
598         }
599
600         final SortedSet<Module> sortedModules = new TreeSet<>((module1, module2) -> {
601             int result = module1.getName().compareTo(module2.getName());
602             if (result == 0) {
603                 result = Revision.compare(module1.getRevision(), module2.getRevision());
604             }
605             if (result == 0) {
606                 result = module1.getNamespace().compareTo(module2.getNamespace());
607             }
608             return result;
609         });
610         for (final Module m : schemaContext.getModules()) {
611             if (m != null) {
612                 sortedModules.add(m);
613             }
614         }
615         return sortedModules;
616     }
617
618     private static void addOperations(final OperationDefinition operDef, final String moduleName,
619             final Optional<String> deviceName, final ObjectNode paths, final String parentName,
620             final DefinitionNames definitionNames, final OAversion oaversion, final String resourcePath) {
621         final ObjectNode operations = JsonNodeFactory.instance.objectNode();
622         operations.set("post", buildPostOperation(operDef, moduleName, deviceName, parentName, definitionNames,
623                 oaversion));
624         paths.set(resourcePath, operations);
625     }
626
627     protected abstract void appendPathKeyValue(StringBuilder builder, Object value);
628
629     public String generateUrlPrefixFromInstanceID(final YangInstanceIdentifier key, final String moduleName) {
630         final StringBuilder builder = new StringBuilder();
631         builder.append("/");
632         if (moduleName != null) {
633             builder.append(moduleName).append(':');
634         }
635         for (final PathArgument arg : key.getPathArguments()) {
636             final String name = arg.getNodeType().getLocalName();
637             if (arg instanceof NodeIdentifierWithPredicates) {
638                 final NodeIdentifierWithPredicates nodeId = (NodeIdentifierWithPredicates) arg;
639                 for (final Entry<QName, Object> entry : nodeId.entrySet()) {
640                     appendPathKeyValue(builder, entry.getValue());
641                 }
642             } else {
643                 builder.append(name).append('/');
644             }
645         }
646         return builder.toString();
647     }
648
649     protected interface ListPathBuilder {
650         String nextParamIdentifier(String key);
651     }
652 }