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 com.fasterxml.jackson.databind.ObjectMapper;
11 import com.fasterxml.jackson.databind.SerializationFeature;
12 import com.fasterxml.jackson.datatype.jsonorg.JsonOrgModule;
13 import com.google.common.base.Preconditions;
14 import org.json.JSONException;
15 import org.json.JSONObject;
16 import org.opendaylight.controller.sal.core.api.model.SchemaService;
17 import org.opendaylight.controller.sal.rest.doc.model.builder.OperationBuilder;
18 import org.opendaylight.controller.sal.rest.doc.swagger.*;
19 import org.opendaylight.yangtools.yang.common.QName;
20 import org.opendaylight.yangtools.yang.model.api.*;
21 import org.slf4j.Logger;
22 import org.slf4j.LoggerFactory;
24 import javax.ws.rs.core.UriInfo;
25 import java.io.IOException;
27 import java.text.DateFormat;
28 import java.text.ParseException;
29 import java.text.SimpleDateFormat;
33 * This class gathers all yang defined {@link Module}s and generates Swagger compliant documentation.
35 public class ApiDocGenerator {
37 private static Logger _logger = LoggerFactory.getLogger(ApiDocGenerator.class);
39 private static final ApiDocGenerator INSTANCE = new ApiDocGenerator();
40 private ObjectMapper mapper = new ObjectMapper();
41 private final ModelGenerator jsonConverter = new ModelGenerator();
43 private SchemaService schemaService;
45 private final String API_VERSION = "1.0.0";
46 private final String SWAGGER_VERSION = "1.2";
47 private final String RESTCONF_CONTEXT_ROOT = "restconf";
48 private final DateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");
50 //For now its {@link HashMap}. It will be changed to thread-safe Map when schema change listener is implemented.
51 private final Map<String, ApiDeclaration> MODULE_DOC_CACHE = new HashMap<String, ApiDeclaration>();
53 private ApiDocGenerator(){
54 mapper.registerModule(new JsonOrgModule());
55 mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
59 * Returns singleton instance
62 public static ApiDocGenerator getInstance() {
68 * @param schemaService
70 public void setSchemaService(SchemaService schemaService) {
71 this.schemaService = schemaService;
76 * @return list of modules converted to swagger compliant resource list.
78 public ResourceList getResourceListing(UriInfo uriInfo) {
80 Preconditions.checkState(schemaService != null);
81 SchemaContext schemaContext = schemaService.getGlobalContext();
82 Preconditions.checkState(schemaContext != null);
84 Set<Module> modules = schemaContext.getModules();
86 ResourceList resourceList = new ResourceList();
87 resourceList.setApiVersion(API_VERSION);
88 resourceList.setSwaggerVersion(SWAGGER_VERSION);
90 List<Resource> resources = new ArrayList<>(modules.size());
91 _logger.info("Modules found [{}]", modules.size());
93 for (Module module : modules) {
94 Resource resource = new Resource();
95 String revisionString = SIMPLE_DATE_FORMAT.format(module.getRevision());
97 _logger.debug("Working on [{},{}]...", module.getName(), revisionString);
98 ApiDeclaration doc = getApiDeclaration(module.getName(), revisionString, uriInfo);
101 URI uri = uriInfo.getRequestUriBuilder().
102 path(generateCacheKey(module.getName(), revisionString)).
105 resource.setPath(uri.toASCIIString());
106 resources.add(resource);
108 _logger.debug("Could not generate doc for {},{}", module.getName(), revisionString);
112 resourceList.setApis(resources);
117 public ApiDeclaration getApiDeclaration(String module, String revision, UriInfo uriInfo) {
120 String cacheKey = generateCacheKey(module, revision);
122 if (MODULE_DOC_CACHE.containsKey(cacheKey)) {
123 _logger.debug("Serving from cache for {}", cacheKey);
124 return MODULE_DOC_CACHE.get(cacheKey);
129 rev = SIMPLE_DATE_FORMAT.parse(revision);
130 } catch (ParseException e) {
131 throw new IllegalArgumentException(e);
134 SchemaContext schemaContext = schemaService.getGlobalContext();
135 Preconditions.checkState(schemaContext != null);
137 Module m = schemaContext.findModuleByName(module, rev);
138 Preconditions.checkArgument(m != null, "Could not find module by name,revision: " + module + "," + revision);
140 String basePath = new StringBuilder(uriInfo.getBaseUri().getScheme())
142 .append(uriInfo.getBaseUri().getHost())
144 .append(uriInfo.getBaseUri().getPort())
146 .append(RESTCONF_CONTEXT_ROOT)
149 ApiDeclaration doc = getSwaggerDocSpec(m, basePath);
150 MODULE_DOC_CACHE.put(cacheKey, doc);
154 public ApiDeclaration getSwaggerDocSpec(Module m, String basePath) {
155 ApiDeclaration doc = new ApiDeclaration();
156 doc.setApiVersion(API_VERSION);
157 doc.setSwaggerVersion(SWAGGER_VERSION);
158 doc.setBasePath(basePath);
159 doc.setProduces(Arrays.asList("application/json", "application/xml"));
161 List<Api> apis = new ArrayList<Api>();
163 Set<DataSchemaNode> dataSchemaNodes = m.getChildNodes();
164 _logger.debug("child nodes size [{}]", dataSchemaNodes.size());
165 for (DataSchemaNode node : dataSchemaNodes) {
166 if ((node instanceof ListSchemaNode) || (node instanceof ContainerSchemaNode)) {
168 _logger.debug("Is Configuration node [{}] [{}]", node.isConfiguration(), node.getQName().getLocalName());
170 List<Parameter> pathParams = null;
171 if (node.isConfiguration()) {
172 pathParams = new ArrayList<Parameter>();
173 String resourcePath = "/config/" + m.getName() + ":";
174 addApis(node, apis, resourcePath, pathParams, true);
178 pathParams = new ArrayList<Parameter>();
179 String resourcePath = "/operational/" + m.getName() + ":";
180 addApis(node, apis, resourcePath, pathParams, false);
184 Set<RpcDefinition> rpcs = m.getRpcs();
185 for (RpcDefinition rpcDefinition : rpcs) {
186 String resourcePath = "/operations/" + m.getName() + ":";
187 addRpcs(rpcDefinition, apis, resourcePath);
190 _logger.debug("Number of APIs found [{}]", apis.size());
192 JSONObject models = null;
195 models = jsonConverter.convertToJsonSchema(m);
196 doc.setModels(models);
197 _logger.debug(mapper.writeValueAsString(doc));
198 } catch (IOException | JSONException e) {
205 private String generateCacheKey(Module m) {
206 return generateCacheKey(m.getName(), SIMPLE_DATE_FORMAT.format(m.getRevision()));
209 private String generateCacheKey(String module, String revision) {
210 return module + "," + revision;
213 private void addApis(DataSchemaNode node,
216 List<Parameter> parentPathParams,
217 boolean addConfigApi) {
220 List<Parameter> pathParams = new ArrayList<Parameter>(parentPathParams);
222 String resourcePath = parentPath + createPath(node, pathParams) + "/";
223 _logger.debug("Adding path: [{}]", resourcePath);
224 api.setPath(resourcePath);
225 api.setOperations(operations(node, pathParams, addConfigApi));
227 if ((node instanceof ListSchemaNode) || (node instanceof ContainerSchemaNode)) {
228 DataNodeContainer schemaNode = (DataNodeContainer) node;
229 Set<DataSchemaNode> dataSchemaNodes = schemaNode.getChildNodes();
231 for (DataSchemaNode childNode : dataSchemaNodes) {
232 addApis(childNode, apis, resourcePath, pathParams, addConfigApi);
238 private void addRpcs(RpcDefinition rpcDefn, List<Api> apis, String parentPath) {
240 String resourcePath = parentPath + rpcDefn.getQName().getLocalName();
241 rpc.setPath(resourcePath);
243 Operation operationSpec = new Operation();
244 operationSpec.setMethod("POST");
245 operationSpec.setNotes(rpcDefn.getDescription());
246 operationSpec.setNickname(rpcDefn.getQName().getLocalName());
247 rpc.setOperations(Arrays.asList(operationSpec));
257 private List<Operation> operations(DataSchemaNode node, List<Parameter> pathParams, boolean isConfig) {
258 List<Operation> operations = new ArrayList<>();
260 OperationBuilder.Get getBuilder = new OperationBuilder.Get(node);
261 operations.add(getBuilder.pathParams(pathParams).build());
264 OperationBuilder.Post postBuilder = new OperationBuilder.Post(node);
265 operations.add(postBuilder.pathParams(pathParams).build());
267 OperationBuilder.Put putBuilder = new OperationBuilder.Put(node);
268 operations.add(putBuilder.pathParams(pathParams).build());
270 OperationBuilder.Delete deleteBuilder = new OperationBuilder.Delete(node);
271 operations.add(deleteBuilder.pathParams(pathParams).build());
276 private String createPath(final DataSchemaNode schemaNode, List<Parameter> pathParams) {
277 ArrayList<LeafSchemaNode> pathListParams = new ArrayList<LeafSchemaNode>();
278 StringBuilder path = new StringBuilder();
279 QName _qName = schemaNode.getQName();
280 String localName = _qName.getLocalName();
281 path.append(localName);
283 if ((schemaNode instanceof ListSchemaNode)) {
284 final List<QName> listKeys = ((ListSchemaNode) schemaNode).getKeyDefinition();
285 for (final QName listKey : listKeys) {
287 DataSchemaNode _dataChildByName = ((DataNodeContainer) schemaNode).getDataChildByName(listKey);
288 pathListParams.add(((LeafSchemaNode) _dataChildByName));
290 String pathParamIdentifier = new StringBuilder("/{").append(listKey.getLocalName()).append("}").toString();
291 path.append(pathParamIdentifier);
293 Parameter pathParam = new Parameter();
294 pathParam.setName(listKey.getLocalName());
295 pathParam.setDescription(_dataChildByName.getDescription());
296 pathParam.setType("string");
297 pathParam.setParamType("path");
299 pathParams.add(pathParam);
303 return path.toString();