Merge "Renamed MD-SAL all feature to follow proper odl prefix"
[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 java.io.IOException;
11 import java.net.URI;
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;
20 import java.util.Map;
21 import java.util.Set;
22
23 import javax.ws.rs.core.UriInfo;
24
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;
46
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;
51
52 /**
53  * This class gathers all yang defined {@link Module}s and generates Swagger compliant documentation.
54  */
55 public class ApiDocGenerator {
56
57     private static final Logger _logger = LoggerFactory.getLogger(ApiDocGenerator.class);
58
59     private static final ApiDocGenerator INSTANCE = new ApiDocGenerator();
60     private final ObjectMapper mapper = new ObjectMapper();
61     private final ModelGenerator jsonConverter = new ModelGenerator();
62
63     private SchemaService schemaService;
64
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");
69
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>();
72
73     private ApiDocGenerator(){
74         mapper.registerModule(new JsonOrgModule());
75         mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
76     }
77
78     /**
79      * Returns singleton instance
80      * @return
81      */
82     public static ApiDocGenerator getInstance() {
83         return INSTANCE;
84     }
85
86     /**
87      *
88      * @param schemaService
89      */
90     public void setSchemaService(final SchemaService schemaService) {
91         this.schemaService = schemaService;
92     }
93     /**
94      *
95      * @param uriInfo
96      * @return  list of modules converted to swagger compliant resource list.
97      */
98     public ResourceList getResourceListing(final UriInfo uriInfo) {
99
100         Preconditions.checkState(schemaService != null);
101         SchemaContext schemaContext = schemaService.getGlobalContext();
102         Preconditions.checkState(schemaContext != null);
103
104         Set<Module> modules = schemaContext.getModules();
105
106         ResourceList resourceList = new ResourceList();
107         resourceList.setApiVersion(API_VERSION);
108         resourceList.setSwaggerVersion(SWAGGER_VERSION);
109
110         List<Resource> resources = new ArrayList<>(modules.size());
111         _logger.info("Modules found [{}]", modules.size());
112
113         for (Module module : modules) {
114             Resource resource = new Resource();
115             String revisionString = SIMPLE_DATE_FORMAT.format(module.getRevision());
116
117             _logger.debug("Working on [{},{}]...", module.getName(), revisionString);
118             ApiDeclaration doc = getApiDeclaration(module.getName(), revisionString, uriInfo);
119
120             if (doc != null) {
121                 URI uri = uriInfo.getRequestUriBuilder().
122                         path(generateCacheKey(module.getName(), revisionString)).
123                         build();
124
125                 resource.setPath(uri.toASCIIString());
126                 resources.add(resource);
127             } else {
128                 _logger.debug("Could not generate doc for {},{}", module.getName(), revisionString);
129             }
130         }
131
132         resourceList.setApis(resources);
133
134         return resourceList;
135     }
136
137     public ApiDeclaration getApiDeclaration(final String module, final String revision, final UriInfo uriInfo) {
138
139         //Lookup cache
140         String cacheKey = generateCacheKey(module, revision);
141
142         if (MODULE_DOC_CACHE.containsKey(cacheKey)) {
143             _logger.debug("Serving from cache for {}", cacheKey);
144             return MODULE_DOC_CACHE.get(cacheKey);
145         }
146
147         Date rev = null;
148         try {
149             rev = SIMPLE_DATE_FORMAT.parse(revision);
150         } catch (ParseException e) {
151             throw new IllegalArgumentException(e);
152         }
153
154         SchemaContext schemaContext = schemaService.getGlobalContext();
155         Preconditions.checkState(schemaContext != null);
156
157         Module m = schemaContext.findModuleByName(module, rev);
158         Preconditions.checkArgument(m != null, "Could not find module by name,revision: " + module + "," + revision);
159
160         String basePath = new StringBuilder(uriInfo.getBaseUri().getScheme())
161         .append("://")
162         .append(uriInfo.getBaseUri().getHost())
163         .append(":")
164         .append(uriInfo.getBaseUri().getPort())
165         .append("/")
166         .append(RESTCONF_CONTEXT_ROOT)
167         .toString();
168
169         ApiDeclaration doc = getSwaggerDocSpec(m, basePath);
170         MODULE_DOC_CACHE.put(cacheKey, doc);
171         return doc;
172     }
173
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"));
180
181         List<Api> apis = new ArrayList<Api>();
182
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)) {
187
188                 _logger.debug("Is Configuration node [{}] [{}]", node.isConfiguration(), node.getQName().getLocalName());
189
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);
195
196                 }
197
198                 pathParams = new ArrayList<Parameter>();
199                 String resourcePath = "/operational/" + m.getName() + ":";
200                 addApis(node, apis, resourcePath, pathParams, false);
201             }
202         }
203
204         Set<RpcDefinition> rpcs = m.getRpcs();
205         for (RpcDefinition rpcDefinition : rpcs) {
206             String resourcePath = "/operations/" + m.getName() + ":";
207             addRpcs(rpcDefinition, apis, resourcePath);
208
209         }
210         _logger.debug("Number of APIs found [{}]", apis.size());
211         doc.setApis(apis);
212         JSONObject models = null;
213
214         try {
215             models = jsonConverter.convertToJsonSchema(m);
216             doc.setModels(models);
217             _logger.debug(mapper.writeValueAsString(doc));
218         } catch (IOException | JSONException e) {
219             e.printStackTrace();
220         }
221
222         return doc;
223     }
224
225     private String generateCacheKey(final Module m) {
226         return generateCacheKey(m.getName(), SIMPLE_DATE_FORMAT.format(m.getRevision()));
227     }
228
229     private String generateCacheKey(final String module, final String revision) {
230         return module + "," + revision;
231     }
232
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) {
238
239         Api api = new Api();
240         List<Parameter> pathParams = new ArrayList<Parameter>(parentPathParams);
241
242         String resourcePath = parentPath + createPath(node, pathParams) + "/";
243         _logger.debug("Adding path: [{}]", resourcePath);
244         api.setPath(resourcePath);
245         api.setOperations(operations(node, pathParams, addConfigApi));
246         apis.add(api);
247         if ((node instanceof ListSchemaNode) || (node instanceof ContainerSchemaNode)) {
248             DataNodeContainer schemaNode = (DataNodeContainer) node;
249             Set<DataSchemaNode> dataSchemaNodes = schemaNode.getChildNodes();
250
251             for (DataSchemaNode childNode : dataSchemaNodes) {
252                 addApis(childNode, apis, resourcePath, pathParams, addConfigApi);
253             }
254         }
255
256     }
257
258     private void addRpcs(final RpcDefinition rpcDefn, final List<Api> apis, final String parentPath) {
259         Api rpc = new Api();
260         String resourcePath = parentPath + rpcDefn.getQName().getLocalName();
261         rpc.setPath(resourcePath);
262
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));
268
269         apis.add(rpc);
270     }
271
272     /**
273      * @param node
274      * @param pathParams
275      * @return
276      */
277     private List<Operation> operations(final DataSchemaNode node, final List<Parameter> pathParams, final boolean isConfig) {
278         List<Operation> operations = new ArrayList<>();
279
280         OperationBuilder.Get getBuilder = new OperationBuilder.Get(node);
281         operations.add(getBuilder.pathParams(pathParams).build());
282
283         if (isConfig) {
284             OperationBuilder.Post postBuilder = new OperationBuilder.Post(node);
285             operations.add(postBuilder.pathParams(pathParams).build());
286
287             OperationBuilder.Put putBuilder = new OperationBuilder.Put(node);
288             operations.add(putBuilder.pathParams(pathParams).build());
289
290             OperationBuilder.Delete deleteBuilder = new OperationBuilder.Delete(node);
291             operations.add(deleteBuilder.pathParams(pathParams).build());
292         }
293         return operations;
294     }
295
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);
302
303         if ((schemaNode instanceof ListSchemaNode)) {
304             final List<QName> listKeys = ((ListSchemaNode) schemaNode).getKeyDefinition();
305             for (final QName listKey : listKeys) {
306                 {
307                     DataSchemaNode _dataChildByName = ((DataNodeContainer) schemaNode).getDataChildByName(listKey);
308                     pathListParams.add(((LeafSchemaNode) _dataChildByName));
309
310                     String pathParamIdentifier = new StringBuilder("/{").append(listKey.getLocalName()).append("}").toString();
311                     path.append(pathParamIdentifier);
312
313                     Parameter pathParam = new Parameter();
314                     pathParam.setName(listKey.getLocalName());
315                     pathParam.setDescription(_dataChildByName.getDescription());
316                     pathParam.setType("string");
317                     pathParam.setParamType("path");
318
319                     pathParams.add(pathParam);
320                 }
321             }
322         }
323         return path.toString();
324     }
325
326 }