Merge "Bug 809: Enhancements to the toaster example"
[controller.git] / opendaylight / md-sal / sal-rest-docgen / src / main / java / org / opendaylight / controller / sal / rest / doc / impl / ApiDocGenerator.java
1 /*
2  * Copyright (c) 2014 Cisco 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.controller.sal.rest.doc.impl;
9
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;
23
24 import javax.ws.rs.core.UriInfo;
25 import java.io.IOException;
26 import java.net.URI;
27 import java.text.DateFormat;
28 import java.text.ParseException;
29 import java.text.SimpleDateFormat;
30 import java.util.*;
31
32 /**
33  * This class gathers all yang defined {@link Module}s and generates Swagger compliant documentation.
34  */
35 public class ApiDocGenerator {
36
37   private static Logger _logger = LoggerFactory.getLogger(ApiDocGenerator.class);
38
39   private static final ApiDocGenerator INSTANCE = new ApiDocGenerator();
40   private ObjectMapper mapper = new ObjectMapper();
41   private final ModelGenerator jsonConverter = new ModelGenerator();
42
43   private SchemaService schemaService;
44
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");
49
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>();
52
53   private ApiDocGenerator(){
54     mapper.registerModule(new JsonOrgModule());
55     mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
56   }
57
58   /**
59    * Returns singleton instance
60    * @return
61    */
62   public static ApiDocGenerator getInstance() {
63     return INSTANCE;
64   }
65
66   /**
67    *
68    * @param schemaService
69    */
70   public void setSchemaService(SchemaService schemaService) {
71     this.schemaService = schemaService;
72   }
73   /**
74    *
75    * @param uriInfo
76    * @return  list of modules converted to swagger compliant resource list.
77    */
78   public ResourceList getResourceListing(UriInfo uriInfo) {
79
80     Preconditions.checkState(schemaService != null);
81     SchemaContext schemaContext = schemaService.getGlobalContext();
82     Preconditions.checkState(schemaContext != null);
83
84     Set<Module> modules = schemaContext.getModules();
85
86     ResourceList resourceList = new ResourceList();
87     resourceList.setApiVersion(API_VERSION);
88     resourceList.setSwaggerVersion(SWAGGER_VERSION);
89
90     List<Resource> resources = new ArrayList<>(modules.size());
91     _logger.info("Modules found [{}]", modules.size());
92
93     for (Module module : modules) {
94       Resource resource = new Resource();
95       String revisionString = SIMPLE_DATE_FORMAT.format(module.getRevision());
96
97       _logger.debug("Working on [{},{}]...", module.getName(), revisionString);
98       ApiDeclaration doc = getApiDeclaration(module.getName(), revisionString, uriInfo);
99
100       if (doc != null) {
101         URI uri = uriInfo.getRequestUriBuilder().
102             path(generateCacheKey(module.getName(), revisionString)).
103             build();
104
105         resource.setPath(uri.toASCIIString());
106         resources.add(resource);
107       } else {
108         _logger.debug("Could not generate doc for {},{}", module.getName(), revisionString);
109       }
110     }
111
112     resourceList.setApis(resources);
113
114     return resourceList;
115   }
116
117   public ApiDeclaration getApiDeclaration(String module, String revision, UriInfo uriInfo) {
118
119     //Lookup cache
120     String cacheKey = generateCacheKey(module, revision);
121
122     if (MODULE_DOC_CACHE.containsKey(cacheKey)) {
123       _logger.debug("Serving from cache for {}", cacheKey);
124       return MODULE_DOC_CACHE.get(cacheKey);
125     }
126
127     Date rev = null;
128     try {
129       rev = SIMPLE_DATE_FORMAT.parse(revision);
130     } catch (ParseException e) {
131       throw new IllegalArgumentException(e);
132     }
133
134     SchemaContext schemaContext = schemaService.getGlobalContext();
135     Preconditions.checkState(schemaContext != null);
136
137     Module m = schemaContext.findModuleByName(module, rev);
138     Preconditions.checkArgument(m != null, "Could not find module by name,revision: " + module + "," + revision);
139
140     String basePath = new StringBuilder(uriInfo.getBaseUri().getScheme())
141         .append("://")
142         .append(uriInfo.getBaseUri().getHost())
143         .append(":")
144         .append(uriInfo.getBaseUri().getPort())
145         .append("/")
146         .append(RESTCONF_CONTEXT_ROOT)
147         .toString();
148
149     ApiDeclaration doc = getSwaggerDocSpec(m, basePath);
150     MODULE_DOC_CACHE.put(cacheKey, doc);
151     return doc;
152   }
153
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"));
160
161     List<Api> apis = new ArrayList<Api>();
162
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)) {
167
168         _logger.debug("Is Configuration node [{}] [{}]", node.isConfiguration(), node.getQName().getLocalName());
169
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);
175
176         }
177
178         pathParams = new ArrayList<Parameter>();
179         String resourcePath = "/operational/" + m.getName() + ":";
180         addApis(node, apis, resourcePath, pathParams, false);
181       }
182     }
183
184     Set<RpcDefinition> rpcs = m.getRpcs();
185     for (RpcDefinition rpcDefinition : rpcs) {
186       String resourcePath = "/operations/" + m.getName() + ":";
187       addRpcs(rpcDefinition, apis, resourcePath);
188
189     }
190     _logger.debug("Number of APIs found [{}]", apis.size());
191     doc.setApis(apis);
192     JSONObject models = null;
193
194     try {
195       models = jsonConverter.convertToJsonSchema(m);
196       doc.setModels(models);
197       _logger.debug(mapper.writeValueAsString(doc));
198     } catch (IOException | JSONException e) {
199       e.printStackTrace();
200     }
201
202     return doc;
203   }
204
205   private String generateCacheKey(Module m) {
206     return generateCacheKey(m.getName(), SIMPLE_DATE_FORMAT.format(m.getRevision()));
207   }
208
209   private String generateCacheKey(String module, String revision) {
210     return module + "," + revision;
211   }
212
213   private void addApis(DataSchemaNode node,
214                        List<Api> apis,
215                        String parentPath,
216                        List<Parameter> parentPathParams,
217                        boolean addConfigApi) {
218
219     Api api = new Api();
220     List<Parameter> pathParams = new ArrayList<Parameter>(parentPathParams);
221
222     String resourcePath = parentPath + createPath(node, pathParams) + "/";
223     _logger.debug("Adding path: [{}]", resourcePath);
224     api.setPath(resourcePath);
225     api.setOperations(operations(node, pathParams, addConfigApi));
226     apis.add(api);
227     if ((node instanceof ListSchemaNode) || (node instanceof ContainerSchemaNode)) {
228       DataNodeContainer schemaNode = (DataNodeContainer) node;
229       Set<DataSchemaNode> dataSchemaNodes = schemaNode.getChildNodes();
230
231       for (DataSchemaNode childNode : dataSchemaNodes) {
232         addApis(childNode, apis, resourcePath, pathParams, addConfigApi);
233       }
234     }
235
236   }
237
238   private void addRpcs(RpcDefinition rpcDefn, List<Api> apis, String parentPath) {
239     Api rpc = new Api();
240     String resourcePath = parentPath + rpcDefn.getQName().getLocalName();
241     rpc.setPath(resourcePath);
242
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));
248
249     apis.add(rpc);
250   }
251
252   /**
253    * @param node
254    * @param pathParams
255    * @return
256    */
257   private List<Operation> operations(DataSchemaNode node, List<Parameter> pathParams, boolean isConfig) {
258     List<Operation> operations = new ArrayList<>();
259
260     OperationBuilder.Get getBuilder = new OperationBuilder.Get(node);
261     operations.add(getBuilder.pathParams(pathParams).build());
262
263     if (isConfig) {
264       OperationBuilder.Post postBuilder = new OperationBuilder.Post(node);
265       operations.add(postBuilder.pathParams(pathParams).build());
266
267       OperationBuilder.Put putBuilder = new OperationBuilder.Put(node);
268       operations.add(putBuilder.pathParams(pathParams).build());
269
270       OperationBuilder.Delete deleteBuilder = new OperationBuilder.Delete(node);
271       operations.add(deleteBuilder.pathParams(pathParams).build());
272     }
273     return operations;
274   }
275
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);
282
283     if ((schemaNode instanceof ListSchemaNode)) {
284       final List<QName> listKeys = ((ListSchemaNode) schemaNode).getKeyDefinition();
285       for (final QName listKey : listKeys) {
286         {
287           DataSchemaNode _dataChildByName = ((DataNodeContainer) schemaNode).getDataChildByName(listKey);
288           pathListParams.add(((LeafSchemaNode) _dataChildByName));
289
290           String pathParamIdentifier = new StringBuilder("/{").append(listKey.getLocalName()).append("}").toString();
291           path.append(pathParamIdentifier);
292
293           Parameter pathParam = new Parameter();
294           pathParam.setName(listKey.getLocalName());
295           pathParam.setDescription(_dataChildByName.getDescription());
296           pathParam.setType("string");
297           pathParam.setParamType("path");
298
299           pathParams.add(pathParam);
300         }
301       }
302     }
303     return path.toString();
304   }
305
306 }