2 * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.controller.sal.rest.doc.impl;
10 import java.io.IOException;
12 import java.text.DateFormat;
13 import java.text.ParseException;
14 import java.text.SimpleDateFormat;
15 import java.util.ArrayList;
16 import java.util.Arrays;
17 import java.util.Date;
18 import java.util.HashMap;
19 import java.util.List;
23 import javax.ws.rs.core.UriInfo;
25 import org.json.JSONException;
26 import org.json.JSONObject;
27 import org.opendaylight.controller.sal.core.api.model.SchemaService;
28 import org.opendaylight.controller.sal.rest.doc.model.builder.OperationBuilder;
29 import org.opendaylight.controller.sal.rest.doc.swagger.Api;
30 import org.opendaylight.controller.sal.rest.doc.swagger.ApiDeclaration;
31 import org.opendaylight.controller.sal.rest.doc.swagger.Operation;
32 import org.opendaylight.controller.sal.rest.doc.swagger.Parameter;
33 import org.opendaylight.controller.sal.rest.doc.swagger.Resource;
34 import org.opendaylight.controller.sal.rest.doc.swagger.ResourceList;
35 import org.opendaylight.yangtools.yang.common.QName;
36 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
37 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
38 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
39 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
40 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
41 import org.opendaylight.yangtools.yang.model.api.Module;
42 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
43 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
47 import com.fasterxml.jackson.databind.ObjectMapper;
48 import com.fasterxml.jackson.databind.SerializationFeature;
49 import com.fasterxml.jackson.datatype.jsonorg.JsonOrgModule;
50 import com.google.common.base.Preconditions;
53 * This class gathers all yang defined {@link Module}s and generates Swagger compliant documentation.
55 public class ApiDocGenerator {
57 private static final Logger _logger = LoggerFactory.getLogger(ApiDocGenerator.class);
59 private static final ApiDocGenerator INSTANCE = new ApiDocGenerator();
60 private final ObjectMapper mapper = new ObjectMapper();
61 private final ModelGenerator jsonConverter = new ModelGenerator();
63 private SchemaService schemaService;
65 private static final String API_VERSION = "1.0.0";
66 private static final String SWAGGER_VERSION = "1.2";
67 private static final String RESTCONF_CONTEXT_ROOT = "restconf";
68 private final DateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");
70 //For now its {@link HashMap}. It will be changed to thread-safe Map when schema change listener is implemented.
71 private final Map<String, ApiDeclaration> MODULE_DOC_CACHE = new HashMap<String, ApiDeclaration>();
73 private ApiDocGenerator(){
74 mapper.registerModule(new JsonOrgModule());
75 mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
79 * Returns singleton instance
82 public static ApiDocGenerator getInstance() {
88 * @param schemaService
90 public void setSchemaService(final SchemaService schemaService) {
91 this.schemaService = schemaService;
96 * @return list of modules converted to swagger compliant resource list.
98 public ResourceList getResourceListing(final UriInfo uriInfo) {
100 Preconditions.checkState(schemaService != null);
101 SchemaContext schemaContext = schemaService.getGlobalContext();
102 Preconditions.checkState(schemaContext != null);
104 Set<Module> modules = schemaContext.getModules();
106 ResourceList resourceList = new ResourceList();
107 resourceList.setApiVersion(API_VERSION);
108 resourceList.setSwaggerVersion(SWAGGER_VERSION);
110 List<Resource> resources = new ArrayList<>(modules.size());
111 _logger.info("Modules found [{}]", modules.size());
113 for (Module module : modules) {
114 Resource resource = new Resource();
115 String revisionString = SIMPLE_DATE_FORMAT.format(module.getRevision());
117 _logger.debug("Working on [{},{}]...", module.getName(), revisionString);
118 ApiDeclaration doc = getApiDeclaration(module.getName(), revisionString, uriInfo);
121 URI uri = uriInfo.getRequestUriBuilder().
122 path(generateCacheKey(module.getName(), revisionString)).
125 resource.setPath(uri.toASCIIString());
126 resources.add(resource);
128 _logger.debug("Could not generate doc for {},{}", module.getName(), revisionString);
132 resourceList.setApis(resources);
137 public ApiDeclaration getApiDeclaration(final String module, final String revision, final UriInfo uriInfo) {
140 String cacheKey = generateCacheKey(module, revision);
142 if (MODULE_DOC_CACHE.containsKey(cacheKey)) {
143 _logger.debug("Serving from cache for {}", cacheKey);
144 return MODULE_DOC_CACHE.get(cacheKey);
149 rev = SIMPLE_DATE_FORMAT.parse(revision);
150 } catch (ParseException e) {
151 throw new IllegalArgumentException(e);
154 SchemaContext schemaContext = schemaService.getGlobalContext();
155 Preconditions.checkState(schemaContext != null);
157 Module m = schemaContext.findModuleByName(module, rev);
158 Preconditions.checkArgument(m != null, "Could not find module by name,revision: " + module + "," + revision);
160 String basePath = new StringBuilder(uriInfo.getBaseUri().getScheme())
162 .append(uriInfo.getBaseUri().getHost())
164 .append(uriInfo.getBaseUri().getPort())
166 .append(RESTCONF_CONTEXT_ROOT)
169 ApiDeclaration doc = getSwaggerDocSpec(m, basePath);
170 MODULE_DOC_CACHE.put(cacheKey, doc);
174 public ApiDeclaration getSwaggerDocSpec(final Module m, final String basePath) {
175 ApiDeclaration doc = new ApiDeclaration();
176 doc.setApiVersion(API_VERSION);
177 doc.setSwaggerVersion(SWAGGER_VERSION);
178 doc.setBasePath(basePath);
179 doc.setProduces(Arrays.asList("application/json", "application/xml"));
181 List<Api> apis = new ArrayList<Api>();
183 Set<DataSchemaNode> dataSchemaNodes = m.getChildNodes();
184 _logger.debug("child nodes size [{}]", dataSchemaNodes.size());
185 for (DataSchemaNode node : dataSchemaNodes) {
186 if ((node instanceof ListSchemaNode) || (node instanceof ContainerSchemaNode)) {
188 _logger.debug("Is Configuration node [{}] [{}]", node.isConfiguration(), node.getQName().getLocalName());
190 List<Parameter> pathParams = null;
191 if (node.isConfiguration()) {
192 pathParams = new ArrayList<Parameter>();
193 String resourcePath = "/config/" + m.getName() + ":";
194 addApis(node, apis, resourcePath, pathParams, true);
198 pathParams = new ArrayList<Parameter>();
199 String resourcePath = "/operational/" + m.getName() + ":";
200 addApis(node, apis, resourcePath, pathParams, false);
204 Set<RpcDefinition> rpcs = m.getRpcs();
205 for (RpcDefinition rpcDefinition : rpcs) {
206 String resourcePath = "/operations/" + m.getName() + ":";
207 addRpcs(rpcDefinition, apis, resourcePath);
210 _logger.debug("Number of APIs found [{}]", apis.size());
212 JSONObject models = null;
215 models = jsonConverter.convertToJsonSchema(m);
216 doc.setModels(models);
217 _logger.debug(mapper.writeValueAsString(doc));
218 } catch (IOException | JSONException e) {
225 private String generateCacheKey(final Module m) {
226 return generateCacheKey(m.getName(), SIMPLE_DATE_FORMAT.format(m.getRevision()));
229 private String generateCacheKey(final String module, final String revision) {
230 return module + "," + revision;
233 private void addApis(final DataSchemaNode node,
234 final List<Api> apis,
235 final String parentPath,
236 final List<Parameter> parentPathParams,
237 final boolean addConfigApi) {
240 List<Parameter> pathParams = new ArrayList<Parameter>(parentPathParams);
242 String resourcePath = parentPath + createPath(node, pathParams) + "/";
243 _logger.debug("Adding path: [{}]", resourcePath);
244 api.setPath(resourcePath);
245 api.setOperations(operations(node, pathParams, addConfigApi));
247 if ((node instanceof ListSchemaNode) || (node instanceof ContainerSchemaNode)) {
248 DataNodeContainer schemaNode = (DataNodeContainer) node;
249 Set<DataSchemaNode> dataSchemaNodes = schemaNode.getChildNodes();
251 for (DataSchemaNode childNode : dataSchemaNodes) {
252 addApis(childNode, apis, resourcePath, pathParams, addConfigApi);
258 private void addRpcs(final RpcDefinition rpcDefn, final List<Api> apis, final String parentPath) {
260 String resourcePath = parentPath + rpcDefn.getQName().getLocalName();
261 rpc.setPath(resourcePath);
263 Operation operationSpec = new Operation();
264 operationSpec.setMethod("POST");
265 operationSpec.setNotes(rpcDefn.getDescription());
266 operationSpec.setNickname(rpcDefn.getQName().getLocalName());
267 rpc.setOperations(Arrays.asList(operationSpec));
277 private List<Operation> operations(final DataSchemaNode node, final List<Parameter> pathParams, final boolean isConfig) {
278 List<Operation> operations = new ArrayList<>();
280 OperationBuilder.Get getBuilder = new OperationBuilder.Get(node);
281 operations.add(getBuilder.pathParams(pathParams).build());
284 OperationBuilder.Post postBuilder = new OperationBuilder.Post(node);
285 operations.add(postBuilder.pathParams(pathParams).build());
287 OperationBuilder.Put putBuilder = new OperationBuilder.Put(node);
288 operations.add(putBuilder.pathParams(pathParams).build());
290 OperationBuilder.Delete deleteBuilder = new OperationBuilder.Delete(node);
291 operations.add(deleteBuilder.pathParams(pathParams).build());
296 private String createPath(final DataSchemaNode schemaNode, final List<Parameter> pathParams) {
297 ArrayList<LeafSchemaNode> pathListParams = new ArrayList<LeafSchemaNode>();
298 StringBuilder path = new StringBuilder();
299 QName _qName = schemaNode.getQName();
300 String localName = _qName.getLocalName();
301 path.append(localName);
303 if ((schemaNode instanceof ListSchemaNode)) {
304 final List<QName> listKeys = ((ListSchemaNode) schemaNode).getKeyDefinition();
305 for (final QName listKey : listKeys) {
307 DataSchemaNode _dataChildByName = ((DataNodeContainer) schemaNode).getDataChildByName(listKey);
308 pathListParams.add(((LeafSchemaNode) _dataChildByName));
310 String pathParamIdentifier = new StringBuilder("/{").append(listKey.getLocalName()).append("}").toString();
311 path.append(pathParamIdentifier);
313 Parameter pathParam = new Parameter();
314 pathParam.setName(listKey.getLocalName());
315 pathParam.setDescription(_dataChildByName.getDescription());
316 pathParam.setType("string");
317 pathParam.setParamType("path");
319 pathParams.add(pathParam);
323 return path.toString();