X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=blobdiff_plain;f=restconf%2Fsal-rest-docgen%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Fnetconf%2Fsal%2Frest%2Fdoc%2Fimpl%2FBaseYangSwaggerGenerator.java;h=5881b2d12199f92aec455e528de724c7bd972ff0;hb=c5b2232060e8e14ae1213a8c017c50a5eff8681f;hp=75a1644e42990be3d123a35354eae180f5be450f;hpb=6da6dc2b16a8314697d72b5cc6ebf4bb89b2066f;p=netconf.git diff --git a/restconf/sal-rest-docgen/src/main/java/org/opendaylight/netconf/sal/rest/doc/impl/BaseYangSwaggerGenerator.java b/restconf/sal-rest-docgen/src/main/java/org/opendaylight/netconf/sal/rest/doc/impl/BaseYangSwaggerGenerator.java index 75a1644e42..5881b2d121 100644 --- a/restconf/sal-rest-docgen/src/main/java/org/opendaylight/netconf/sal/rest/doc/impl/BaseYangSwaggerGenerator.java +++ b/restconf/sal-rest-docgen/src/main/java/org/opendaylight/netconf/sal/rest/doc/impl/BaseYangSwaggerGenerator.java @@ -7,276 +7,405 @@ */ package org.opendaylight.netconf.sal.rest.doc.impl; +import static org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder.TOP; +import static org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder.buildDelete; +import static org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder.buildGet; +import static org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder.buildPost; +import static org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder.buildPostOperation; +import static org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder.buildPut; +import static org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder.getTypeParentNode; +import static org.opendaylight.netconf.sal.rest.doc.util.JsonUtil.addFields; import static org.opendaylight.netconf.sal.rest.doc.util.RestDocgenUtil.resolvePathArgumentsName; + +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; -import com.fasterxml.jackson.datatype.jsonorg.JsonOrgModule; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Range; import java.io.IOException; -import java.net.URI; -import java.text.DateFormat; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.ArrayList; +import java.time.format.DateTimeParseException; import java.util.Arrays; -import java.util.Calendar; import java.util.Collection; import java.util.Collections; -import java.util.Date; -import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.Iterator; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Optional; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import javax.ws.rs.core.UriInfo; -import org.json.JSONException; -import org.json.JSONObject; -import org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder; -import org.opendaylight.netconf.sal.rest.doc.swagger.Api; -import org.opendaylight.netconf.sal.rest.doc.swagger.ApiDeclaration; -import org.opendaylight.netconf.sal.rest.doc.swagger.Operation; -import org.opendaylight.netconf.sal.rest.doc.swagger.Parameter; -import org.opendaylight.netconf.sal.rest.doc.swagger.Resource; -import org.opendaylight.netconf.sal.rest.doc.swagger.ResourceList; -import org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder.Delete; -import org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder.Get; -import org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder.Post; -import org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder.Put; +import org.opendaylight.mdsal.dom.api.DOMSchemaService; +import org.opendaylight.netconf.sal.rest.doc.impl.ApiDocServiceImpl.OAversion; +import org.opendaylight.netconf.sal.rest.doc.swagger.CommonApiObject; +import org.opendaylight.netconf.sal.rest.doc.swagger.Components; +import org.opendaylight.netconf.sal.rest.doc.swagger.Info; +import org.opendaylight.netconf.sal.rest.doc.swagger.OpenApiObject; +import org.opendaylight.netconf.sal.rest.doc.swagger.Server; +import org.opendaylight.netconf.sal.rest.doc.swagger.SwaggerObject; +import org.opendaylight.netconf.sal.rest.doc.util.JsonUtil; import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.common.Revision; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument; +import org.opendaylight.yangtools.yang.model.api.ActionNodeContainer; import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode; import org.opendaylight.yangtools.yang.model.api.DataNodeContainer; import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; -import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode; +import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext; import org.opendaylight.yangtools.yang.model.api.ListSchemaNode; import org.opendaylight.yangtools.yang.model.api.Module; +import org.opendaylight.yangtools.yang.model.api.OperationDefinition; import org.opendaylight.yangtools.yang.model.api.RpcDefinition; -import org.opendaylight.yangtools.yang.model.api.SchemaContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class BaseYangSwaggerGenerator { +public abstract class BaseYangSwaggerGenerator { private static final Logger LOG = LoggerFactory.getLogger(BaseYangSwaggerGenerator.class); - protected static final String API_VERSION = "1.0.0"; - protected static final String SWAGGER_VERSION = "1.2"; - protected static final String RESTCONF_CONTEXT_ROOT = "restconf"; + private static final String API_VERSION = "1.0.0"; + private static final String SWAGGER_VERSION = "2.0"; + private static final String OPEN_API_VERSION = "3.0.3"; + private static final ObjectMapper MAPPER = new ObjectMapper(); - static final String MODULE_NAME_SUFFIX = "_module"; - protected static final DateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd"); - private final ModelGenerator jsonConverter = new ModelGenerator(); + private final DefinitionGenerator jsonConverter = new DefinitionGenerator(); + private final DOMSchemaService schemaService; - // private Map MODULE_DOC_CACHE = new HashMap<>() - private final ObjectMapper mapper = new ObjectMapper(); + public static final String BASE_PATH = "/"; + public static final String MODULE_NAME_SUFFIX = "_module"; - protected BaseYangSwaggerGenerator() { - mapper.registerModule(new JsonOrgModule()); - mapper.configure(SerializationFeature.INDENT_OUTPUT, true); + static { + MAPPER.configure(SerializationFeature.INDENT_OUTPUT, true); } - /** - * Return list of modules converted to swagger compliant resource list. - */ - public ResourceList getResourceListing(UriInfo uriInfo, SchemaContext schemaContext, String context) { + protected BaseYangSwaggerGenerator(final Optional schemaService) { + this.schemaService = schemaService.orElse(null); + } - ResourceList resourceList = createResourceList(); + public DOMSchemaService getSchemaService() { + return schemaService; + } - Set modules = getSortedModules(schemaContext); + public SwaggerObject getAllModulesDoc(final UriInfo uriInfo, final DefinitionNames definitionNames, + final OAversion oaversion) { + final EffectiveModelContext schemaContext = schemaService.getGlobalContext(); + Preconditions.checkState(schemaContext != null); + return getAllModulesDoc(uriInfo, Optional.empty(), schemaContext, Optional.empty(), "", definitionNames, + oaversion); + } - List resources = new ArrayList<>(modules.size()); + public SwaggerObject getAllModulesDoc(final UriInfo uriInfo, final Optional> range, + final EffectiveModelContext schemaContext, final Optional deviceName, final String context, + final DefinitionNames definitionNames, final OAversion oaversion) { + final String schema = createSchemaFromUriInfo(uriInfo); + final String host = createHostFromUriInfo(uriInfo); + String name = "Controller"; + if (deviceName.isPresent()) { + name = deviceName.get(); + } - LOG.info("Modules found [{}]", modules.size()); + final String title = name + " modules of RESTCONF"; + final SwaggerObject doc = createSwaggerObject(schema, host, BASE_PATH, title); + doc.setDefinitions(JsonNodeFactory.instance.objectNode()); + doc.setPaths(JsonNodeFactory.instance.objectNode()); - for (Module module : modules) { - String revisionString = SIMPLE_DATE_FORMAT.format(module.getRevision()); - Resource resource = new Resource(); - LOG.debug("Working on [{},{}]...", module.getName(), revisionString); - ApiDeclaration doc = getApiDeclaration(module.getName(), revisionString, uriInfo, schemaContext, context); + fillDoc(doc, range, schemaContext, context, deviceName, oaversion, definitionNames); - if (doc != null) { - resource.setPath(generatePath(uriInfo, module.getName(), revisionString)); - resources.add(resource); - } else { - LOG.warn("Could not generate doc for {},{}", module.getName(), revisionString); - } + return doc; + } + + public void fillDoc(final SwaggerObject doc, final Optional> range, + final EffectiveModelContext schemaContext, final String context, final Optional deviceName, + final OAversion oaversion, final DefinitionNames definitionNames) { + final SortedSet modules = getSortedModules(schemaContext); + final Set filteredModules; + if (range.isPresent()) { + filteredModules = filterByRange(modules, range.get()); + } else { + filteredModules = modules; } - resourceList.setApis(resources); + for (final Module module : filteredModules) { + final String revisionString = module.getQNameModule().getRevision().map(Revision::toString).orElse(null); - return resourceList; - } + LOG.debug("Working on [{},{}]...", module.getName(), revisionString); - protected ResourceList createResourceList() { - ResourceList resourceList = new ResourceList(); - resourceList.setApiVersion(API_VERSION); - resourceList.setSwaggerVersion(SWAGGER_VERSION); - return resourceList; + getSwaggerDocSpec(module, context, deviceName, schemaContext, oaversion, definitionNames, doc, false); + } } - protected String generatePath(UriInfo uriInfo, String name, String revision) { - URI uri = uriInfo.getRequestUriBuilder().path(generateCacheKey(name, revision)).build(); - return uri.toASCIIString(); - } + private static Set filterByRange(final SortedSet modules, final Range range) { + final int begin = range.lowerEndpoint(); + final int end = range.upperEndpoint(); - public ApiDeclaration getApiDeclaration(String moduleName, String revision, UriInfo uriInfo, SchemaContext schemaContext, String context) { - Date rev = null; + Module firstModule = null; - try { - if (revision != null && !revision.equals("0000-00-00")) { - rev = SIMPLE_DATE_FORMAT.parse(revision); + final Iterator iterator = modules.iterator(); + int counter = 0; + while (iterator.hasNext() && counter < end) { + final Module module = iterator.next(); + if (containsListOrContainer(module.getChildNodes()) || !module.getRpcs().isEmpty()) { + if (counter == begin) { + firstModule = module; + } + counter++; } - } catch (ParseException e) { - throw new IllegalArgumentException(e); } - if (rev != null) { - Calendar cal = new GregorianCalendar(); + if (iterator.hasNext()) { + return modules.subSet(firstModule, iterator.next()); + } else { + return modules.tailSet(firstModule); + } + } + + public CommonApiObject getApiDeclaration(final String module, final String revision, final UriInfo uriInfo, + final OAversion oaversion) { + final EffectiveModelContext schemaContext = schemaService.getGlobalContext(); + Preconditions.checkState(schemaContext != null); + final SwaggerObject doc = getApiDeclaration(module, revision, uriInfo, schemaContext, "", oaversion); + return getAppropriateDoc(doc, oaversion); + } - cal.setTime(rev); + public SwaggerObject getApiDeclaration(final String moduleName, final String revision, final UriInfo uriInfo, + final EffectiveModelContext schemaContext, final String context, final OAversion oaversion) { + final Optional rev; - if (cal.get(Calendar.YEAR) < 1970) { - rev = null; - } + try { + rev = Revision.ofNullable(revision); + } catch (final DateTimeParseException e) { + throw new IllegalArgumentException(e); } - Module module = schemaContext.findModuleByName(moduleName, rev); + final Module module = schemaContext.findModule(moduleName, rev).orElse(null); Preconditions.checkArgument(module != null, "Could not find module by name,revision: " + moduleName + "," + revision); - return getApiDeclaration(module, rev, uriInfo, context, schemaContext); + return getApiDeclaration(module, uriInfo, context, schemaContext, oaversion); } - public ApiDeclaration getApiDeclaration(Module module, Date revision, UriInfo uriInfo, String context, SchemaContext schemaContext) { - String basePath = createBasePathFromUriInfo(uriInfo); + public SwaggerObject getApiDeclaration(final Module module, final UriInfo uriInfo, final String context, + final EffectiveModelContext schemaContext, final OAversion oaversion) { + final String schema = createSchemaFromUriInfo(uriInfo); + final String host = createHostFromUriInfo(uriInfo); - ApiDeclaration doc = getSwaggerDocSpec(module, basePath, context, schemaContext); - if (doc != null) { - return doc; - } - return null; + return getSwaggerDocSpec(module, schema, host, BASE_PATH, context, schemaContext, oaversion); } - protected String createBasePathFromUriInfo(UriInfo uriInfo) { + public String createHostFromUriInfo(final UriInfo uriInfo) { String portPart = ""; - int port = uriInfo.getBaseUri().getPort(); + final int port = uriInfo.getBaseUri().getPort(); if (port != -1) { portPart = ":" + port; } - String basePath = new StringBuilder(uriInfo.getBaseUri().getScheme()).append("://") - .append(uriInfo.getBaseUri().getHost()).append(portPart).append("/").append(RESTCONF_CONTEXT_ROOT) - .toString(); - return basePath; + return uriInfo.getBaseUri().getHost() + portPart; } - public ApiDeclaration getSwaggerDocSpec(Module m, String basePath, String context, SchemaContext schemaContext) { - ApiDeclaration doc = createApiDeclaration(basePath); - - List apis = new ArrayList<>(); - - Collection dataSchemaNodes = m.getChildNodes(); - LOG.debug("child nodes size [{}]", dataSchemaNodes.size()); - for (DataSchemaNode node : dataSchemaNodes) { - if ((node instanceof ListSchemaNode) || (node instanceof ContainerSchemaNode)) { + public String createSchemaFromUriInfo(final UriInfo uriInfo) { + return uriInfo.getBaseUri().getScheme(); + } - LOG.debug("Is Configuration node [{}] [{}]", node.isConfiguration(), node.getQName().getLocalName()); + public SwaggerObject getSwaggerDocSpec(final Module module, final String schema, final String host, + final String basePath, final String context, final EffectiveModelContext schemaContext, + final OAversion oaversion) { + final SwaggerObject doc = createSwaggerObject(schema, host, basePath, module.getName()); + final DefinitionNames definitionNames = new DefinitionNames(); + return getSwaggerDocSpec(module, context, Optional.empty(), schemaContext, oaversion, definitionNames, doc, + true); + } - List pathParams = new ArrayList<>(); - String resourcePath = getDataStorePath("/config/", context); - addRootPostLink(m, (DataNodeContainer) node, pathParams, resourcePath, apis); - addApis(node, apis, resourcePath, pathParams, schemaContext, true); + public SwaggerObject getSwaggerDocSpec(final Module module, final String context, final Optional deviceName, + final EffectiveModelContext schemaContext, final OAversion oaversion, final DefinitionNames definitionNames, + final SwaggerObject doc, final boolean isForSingleModule) { + final ObjectNode definitions; - pathParams = new ArrayList<>(); - resourcePath = getDataStorePath("/operational/", context); - addApis(node, apis, resourcePath, pathParams, schemaContext, false); + try { + if (isForSingleModule) { + definitions = jsonConverter.convertToJsonSchema(module, schemaContext, definitionNames, oaversion, + true); + doc.setDefinitions(definitions); + } else { + definitions = jsonConverter.convertToJsonSchema(module, schemaContext, definitionNames, oaversion, + false); + addFields(doc.getDefinitions(), definitions.fields()); + } + if (LOG.isDebugEnabled()) { + LOG.debug("Document: {}", MAPPER.writeValueAsString(doc)); } + } catch (final IOException e) { + LOG.error("Exception occured in DefinitionGenerator", e); } - Set rpcs = m.getRpcs(); - for (RpcDefinition rpcDefinition : rpcs) { - String resourcePath = getDataStorePath("/operations/", context); - addRpcs(rpcDefinition, apis, resourcePath, schemaContext); - } + final ObjectNode paths = JsonNodeFactory.instance.objectNode(); + final String moduleName = module.getName(); + + boolean hasAddRootPostLink = false; - LOG.debug("Number of APIs found [{}]", apis.size()); + final Collection dataSchemaNodes = module.getChildNodes(); + LOG.debug("child nodes size [{}]", dataSchemaNodes.size()); + for (final DataSchemaNode node : dataSchemaNodes) { + if (node instanceof ListSchemaNode || node instanceof ContainerSchemaNode) { + LOG.debug("Is Configuration node [{}] [{}]", node.isConfiguration(), node.getQName().getLocalName()); - if (!apis.isEmpty()) { - doc.setApis(apis); - JSONObject models = null; + final String localName = module.getName() + ":" + node.getQName().getLocalName(); + ArrayNode pathParams = JsonNodeFactory.instance.arrayNode(); + String resourcePath; + + if (node.isConfiguration()) { // This node's config statement is + // true. + resourcePath = getResourcePath("config", context); + + /* + * When there are two or more top container or list nodes + * whose config statement is true in module, make sure that + * only one root post link is added for this module. + */ + if (isForSingleModule && !hasAddRootPostLink) { + LOG.debug("Has added root post link for module {}", module.getName()); + addRootPostLink(module, deviceName, pathParams, resourcePath, paths, oaversion); + + hasAddRootPostLink = true; + } + + final String resolvedPath = resourcePath + "/" + createPath(node, pathParams, oaversion, localName); + addPaths(node, deviceName, moduleName, paths, pathParams, schemaContext, true, module.getName(), + definitionNames, oaversion, resolvedPath); + } + pathParams = JsonNodeFactory.instance.arrayNode(); + resourcePath = getResourcePath("operational", context); - try { - models = jsonConverter.convertToJsonSchema(m, schemaContext); - doc.setModels(models); - if (LOG.isDebugEnabled()) { - LOG.debug(mapper.writeValueAsString(doc)); + if (!node.isConfiguration()) { + final String resolvedPath = resourcePath + "/" + createPath(node, pathParams, oaversion, localName); + addPaths(node, deviceName, moduleName, paths, pathParams, schemaContext, false, moduleName, + definitionNames, oaversion, resolvedPath); } - } catch (IOException | JSONException e) { - LOG.error("Exception occured in ModelGenerator", e); } + } + + for (final RpcDefinition rpcDefinition : module.getRpcs()) { + final String resolvedPath = getResourcePath("operations", context) + "/" + moduleName + ":" + + rpcDefinition.getQName().getLocalName(); + addOperations(rpcDefinition, moduleName, deviceName, paths, module.getName(), definitionNames, oaversion, + resolvedPath); + } + + LOG.debug("Number of Paths found [{}]", paths.size()); - return doc; + if (isForSingleModule) { + doc.setPaths(paths); + } else { + addFields(doc.getPaths(), paths.fields()); } - return null; + + return doc; } - private void addRootPostLink(final Module module, final DataNodeContainer node, final List pathParams, - final String resourcePath, final List apis) { + private static void addRootPostLink(final Module module, final Optional deviceName, + final ArrayNode pathParams, final String resourcePath, final ObjectNode paths, final OAversion oaversion) { if (containsListOrContainer(module.getChildNodes())) { - final Api apiForRootPostUri = new Api(); - apiForRootPostUri.setPath(resourcePath); - apiForRootPostUri.setOperations(operationPost(module.getName() + MODULE_NAME_SUFFIX, - module.getDescription(), module, pathParams, true)); - apis.add(apiForRootPostUri); + final ObjectNode post = JsonNodeFactory.instance.objectNode(); + final String moduleName = module.getName(); + final String name = moduleName + MODULE_NAME_SUFFIX; + post.set("post", buildPost("", name, "", moduleName, deviceName, + module.getDescription().orElse(""), pathParams, oaversion)); + paths.set(resourcePath, post); } } - protected ApiDeclaration createApiDeclaration(String basePath) { - ApiDeclaration doc = new ApiDeclaration(); - doc.setApiVersion(API_VERSION); - doc.setSwaggerVersion(SWAGGER_VERSION); + public SwaggerObject createSwaggerObject(final String schema, final String host, final String basePath, + final String title) { + final SwaggerObject doc = new SwaggerObject(); + doc.setSwagger(SWAGGER_VERSION); + final Info info = new Info(); + info.setTitle(title); + info.setVersion(API_VERSION); + doc.setInfo(info); + doc.setSchemes(ImmutableList.of(schema)); + doc.setHost(host); doc.setBasePath(basePath); - doc.setProduces(Arrays.asList("application/json", "application/xml")); + doc.setProduces(Arrays.asList("application/xml", "application/json")); return doc; } - protected String getDataStorePath(String dataStore, String context) { - return dataStore + context; + public static CommonApiObject getAppropriateDoc(final SwaggerObject swaggerObject, final OAversion oaversion) { + if (oaversion.equals(OAversion.V3_0)) { + return convertToOpenApi(swaggerObject); + } + return swaggerObject; } - private String generateCacheKey(String module, String revision) { - return module + "(" + revision + ")"; + private static OpenApiObject convertToOpenApi(final SwaggerObject swaggerObject) { + final OpenApiObject doc = new OpenApiObject(); + doc.setOpenapi(OPEN_API_VERSION); + doc.setInfo(swaggerObject.getInfo()); + doc.setServers(convertToServers(swaggerObject.getSchemes(), swaggerObject.getHost(), + swaggerObject.getBasePath())); + doc.setPaths(swaggerObject.getPaths()); + doc.setComponents(new Components(swaggerObject.getDefinitions())); + return doc; } - private void addApis(DataSchemaNode node, List apis, String parentPath, List parentPathParams, SchemaContext schemaContext, - boolean addConfigApi) { - Api api = new Api(); - List pathParams = new ArrayList<>(parentPathParams); + private static List convertToServers(final List schemes, final String host, final String basePath) { + return ImmutableList.of(new Server(schemes.get(0) + "://" + host + basePath)); + } + + protected abstract String getPathVersion(); + + public abstract String getResourcePath(String resourceType, String context); - String resourcePath = parentPath + createPath(node, pathParams, schemaContext) + "/"; + public abstract String getResourcePathPart(String resourceType); + + private void addPaths(final DataSchemaNode node, final Optional deviceName, final String moduleName, + final ObjectNode paths, final ArrayNode parentPathParams, final EffectiveModelContext schemaContext, + final boolean isConfig, final String parentName, final DefinitionNames definitionNames, + final OAversion oaversion, final String resourcePath) { LOG.debug("Adding path: [{}]", resourcePath); - api.setPath(resourcePath); - Iterable childSchemaNodes = Collections.emptySet(); - if ((node instanceof ListSchemaNode) || (node instanceof ContainerSchemaNode)) { - DataNodeContainer dataNodeContainer = (DataNodeContainer) node; + final ArrayNode pathParams = JsonUtil.copy(parentPathParams); + Iterable childSchemaNodes = Collections.emptySet(); + if (node instanceof ListSchemaNode || node instanceof ContainerSchemaNode) { + final DataNodeContainer dataNodeContainer = (DataNodeContainer) node; childSchemaNodes = dataNodeContainer.getChildNodes(); } - api.setOperations(operation(node, pathParams, addConfigApi, childSchemaNodes)); - apis.add(api); - for (DataSchemaNode childNode : childSchemaNodes) { + final ObjectNode path = JsonNodeFactory.instance.objectNode(); + path.setAll(operations(node, moduleName, deviceName, pathParams, isConfig, parentName, definitionNames, + oaversion)); + paths.set(resourcePath, path); + + if (node instanceof ActionNodeContainer) { + ((ActionNodeContainer) node).getActions().forEach(actionDef -> { + final String resolvedPath = "rests/operations" + resourcePath.substring(11) + + "/" + resolvePathArgumentsName(actionDef.getQName(), node.getQName(), schemaContext); + addOperations(actionDef, moduleName, deviceName, paths, parentName, definitionNames, oaversion, + resolvedPath); + }); + } + + for (final DataSchemaNode childNode : childSchemaNodes) { if (childNode instanceof ListSchemaNode || childNode instanceof ContainerSchemaNode) { - // keep config and operation attributes separate. - if (childNode.isConfiguration() == addConfigApi) { - addApis(childNode, apis, resourcePath, pathParams, schemaContext, addConfigApi); - } + final String newParent = parentName + "_" + node.getQName().getLocalName(); + final String localName = resolvePathArgumentsName(childNode.getQName(), node.getQName(), schemaContext); + final String newResourcePath = resourcePath + "/" + createPath(childNode, pathParams, oaversion, + localName); + final boolean newIsConfig = isConfig && childNode.isConfiguration(); + addPaths(childNode, deviceName, moduleName, paths, pathParams, schemaContext, + newIsConfig, newParent, definitionNames, oaversion, newResourcePath); } } - } - private boolean containsListOrContainer(final Iterable nodes) { - for (DataSchemaNode child : nodes) { + private static boolean containsListOrContainer(final Iterable nodes) { + for (final DataSchemaNode child : nodes) { if (child instanceof ListSchemaNode || child instanceof ContainerSchemaNode) { return true; } @@ -284,58 +413,59 @@ public class BaseYangSwaggerGenerator { return false; } - private List operation(DataSchemaNode node, List pathParams, boolean isConfig, Iterable childSchemaNodes) { - List operations = new ArrayList<>(); + private static Map operations(final DataSchemaNode node, final String moduleName, + final Optional deviceName, final ArrayNode pathParams, final boolean isConfig, + final String parentName, final DefinitionNames definitionNames, final OAversion oaversion) { + final Map operations = new HashMap<>(); + final String discriminator = definitionNames.getDiscriminator(node); + + final String nodeName = node.getQName().getLocalName(); + + final String defName = parentName + "_" + nodeName + TOP + discriminator; + final ObjectNode get = buildGet(node, moduleName, deviceName, pathParams, defName, isConfig, oaversion); + operations.put("get", get); - Get getBuilder = new Get(node, isConfig); - operations.add(getBuilder.pathParams(pathParams).build()); if (isConfig) { - Put putBuilder = new Put(node.getQName().getLocalName(), - node.getDescription()); - operations.add(putBuilder.pathParams(pathParams).build()); + final ObjectNode put = buildPut(parentName, nodeName, discriminator, moduleName, deviceName, + node.getDescription().orElse(""), pathParams, oaversion); + operations.put("put", put); - Delete deleteBuilder = new Delete(node); - operations.add(deleteBuilder.pathParams(pathParams).build()); + final ObjectNode delete = buildDelete(node, moduleName, deviceName, pathParams, oaversion); + operations.put("delete", delete); - if (containsListOrContainer(childSchemaNodes)) { - operations.addAll(operationPost(node.getQName().getLocalName(), node.getDescription(), - (DataNodeContainer) node, pathParams, isConfig)); - } + operations.put("post", buildPost(parentName, nodeName, discriminator, moduleName, deviceName, + node.getDescription().orElse(""), pathParams, oaversion)); } return operations; } - private List operationPost(final String name, final String description, final DataNodeContainer dataNodeContainer, List pathParams, boolean isConfig) { - List operations = new ArrayList<>(); - if (isConfig) { - Post postBuilder = new Post(name, description, dataNodeContainer); - operations.add(postBuilder.pathParams(pathParams).build()); - } - return operations; - } + protected abstract ListPathBuilder newListPathBuilder(); - private String createPath(final DataSchemaNode schemaNode, List pathParams, SchemaContext schemaContext) { - ArrayList pathListParams = new ArrayList<>(); - StringBuilder path = new StringBuilder(); - String localName = resolvePathArgumentsName(schemaNode, schemaContext); + private String createPath(final DataSchemaNode schemaNode, final ArrayNode pathParams, + final OAversion oaversion, final String localName) { + final StringBuilder path = new StringBuilder(); path.append(localName); - if ((schemaNode instanceof ListSchemaNode)) { - final List listKeys = ((ListSchemaNode) schemaNode).getKeyDefinition(); - for (final QName listKey : listKeys) { - DataSchemaNode dataChildByName = ((DataNodeContainer) schemaNode).getDataChildByName(listKey); - pathListParams.add(((LeafSchemaNode) dataChildByName)); + if (schemaNode instanceof ListSchemaNode) { + final ListPathBuilder keyBuilder = newListPathBuilder(); + for (final QName listKey : ((ListSchemaNode) schemaNode).getKeyDefinition()) { + final String paramName = createUniquePathParamName(listKey.getLocalName(), pathParams); + final String pathParamIdentifier = keyBuilder.nextParamIdentifier(paramName); - String pathParamIdentifier = new StringBuilder("/{").append(listKey.getLocalName()).append("}") - .toString(); path.append(pathParamIdentifier); - Parameter pathParam = new Parameter(); - pathParam.setName(listKey.getLocalName()); - pathParam.setDescription(dataChildByName.getDescription()); - pathParam.setType("string"); - pathParam.setParamType("path"); + final ObjectNode pathParam = JsonNodeFactory.instance.objectNode(); + pathParam.put("name", paramName); + + ((DataNodeContainer) schemaNode).findDataChildByName(listKey).flatMap(DataSchemaNode::getDescription) + .ifPresent(desc -> pathParam.put("description", desc)); + + final ObjectNode typeParent = getTypeParentNode(pathParam, oaversion); + + typeParent.put("type", "string"); + pathParam.put("in", "path"); + pathParam.put("required", true); pathParams.add(pathParam); } @@ -343,51 +473,46 @@ public class BaseYangSwaggerGenerator { return path.toString(); } - protected void addRpcs(RpcDefinition rpcDefn, List apis, String parentPath, SchemaContext schemaContext) { - Api rpc = new Api(); - String resourcePath = parentPath + resolvePathArgumentsName(rpcDefn, schemaContext); - rpc.setPath(resourcePath); - - Operation operationSpec = new Operation(); - operationSpec.setMethod("POST"); - operationSpec.setNotes(rpcDefn.getDescription()); - operationSpec.setNickname(rpcDefn.getQName().getLocalName()); - if (rpcDefn.getOutput() != null) { - operationSpec.setType("(" + rpcDefn.getQName().getLocalName() + ")output"); - } - if (rpcDefn.getInput() != null) { - Parameter payload = new Parameter(); - payload.setParamType("body"); - payload.setType("(" + rpcDefn.getQName().getLocalName() + ")input"); - operationSpec.setParameters(Collections.singletonList(payload)); - operationSpec.setConsumes(OperationBuilder.CONSUMES_PUT_POST); + private String createUniquePathParamName(final String clearName, final ArrayNode pathParams) { + for (final JsonNode pathParam : pathParams) { + if (isNamePicked(clearName, pathParam)) { + return createUniquePathParamName(clearName, pathParams, 1); + } } + return clearName; + } - rpc.setOperations(Arrays.asList(operationSpec)); + private String createUniquePathParamName(final String clearName, final ArrayNode pathParams, + final int discriminator) { + final String newName = clearName + discriminator; + for (final JsonNode pathParam : pathParams) { + if (isNamePicked(newName, pathParam)) { + return createUniquePathParamName(clearName, pathParams, discriminator + 1); + } + } + return newName; + } - apis.add(rpc); + private static boolean isNamePicked(final String name, final JsonNode pathParam) { + return name.equals(pathParam.get("name").asText()); } - protected SortedSet getSortedModules(SchemaContext schemaContext) { + public SortedSet getSortedModules(final EffectiveModelContext schemaContext) { if (schemaContext == null) { - return new TreeSet<>(); + return Collections.emptySortedSet(); } - Set modules = schemaContext.getModules(); - - SortedSet sortedModules = new TreeSet<>((module1, module2) -> { + final SortedSet sortedModules = new TreeSet<>((module1, module2) -> { int result = module1.getName().compareTo(module2.getName()); if (result == 0) { - Date module1Revision = module1.getRevision() != null ? module1.getRevision() : new Date(0); - Date module2Revision = module2.getRevision() != null ? module2.getRevision() : new Date(0); - result = module1Revision.compareTo(module2Revision); + result = Revision.compare(module1.getRevision(), module2.getRevision()); } if (result == 0) { result = module1.getNamespace().compareTo(module2.getNamespace()); } return result; }); - for (Module m : modules) { + for (final Module m : schemaContext.getModules()) { if (m != null) { sortedModules.add(m); } @@ -395,4 +520,38 @@ public class BaseYangSwaggerGenerator { return sortedModules; } + private static void addOperations(final OperationDefinition operDef, final String moduleName, + final Optional deviceName, final ObjectNode paths, final String parentName, + final DefinitionNames definitionNames, final OAversion oaversion, final String resourcePath) { + final ObjectNode operations = JsonNodeFactory.instance.objectNode(); + operations.set("post", buildPostOperation(operDef, moduleName, deviceName, parentName, definitionNames, + oaversion)); + paths.set(resourcePath, operations); + } + + protected abstract void appendPathKeyValue(StringBuilder builder, Object value); + + public String generateUrlPrefixFromInstanceID(final YangInstanceIdentifier key, final String moduleName) { + final StringBuilder builder = new StringBuilder(); + builder.append("/"); + if (moduleName != null) { + builder.append(moduleName).append(':'); + } + for (final PathArgument arg : key.getPathArguments()) { + final String name = arg.getNodeType().getLocalName(); + if (arg instanceof NodeIdentifierWithPredicates) { + final NodeIdentifierWithPredicates nodeId = (NodeIdentifierWithPredicates) arg; + for (final Entry entry : nodeId.entrySet()) { + appendPathKeyValue(builder, entry.getValue()); + } + } else { + builder.append(name).append('/'); + } + } + return builder.toString(); + } + + protected interface ListPathBuilder { + String nextParamIdentifier(String key); + } }