Update swagger generator to OpenAPI 3.0
[netconf.git] / restconf / sal-rest-docgen / src / main / java / org / opendaylight / netconf / sal / rest / doc / mountpoints / MountPointSwagger.java
1 /*
2  * Copyright (c) 2020 PANTHEON.tech, s.r.o. 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.netconf.sal.rest.doc.mountpoints;
9
10 import static com.google.common.base.Preconditions.checkState;
11 import static java.util.Objects.requireNonNull;
12 import static org.opendaylight.netconf.sal.rest.doc.impl.ApiDocServiceImpl.DEFAULT_PAGESIZE;
13 import static org.opendaylight.netconf.sal.rest.doc.impl.BaseYangSwaggerGenerator.BASE_PATH;
14 import static org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder.DESCRIPTION_KEY;
15 import static org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder.RESPONSES_KEY;
16 import static org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder.SUMMARY_KEY;
17 import static org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder.SUMMARY_SEPARATOR;
18 import static org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder.TAGS_KEY;
19 import static org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder.buildTagsValue;
20 import static org.opendaylight.netconf.sal.rest.doc.util.JsonUtil.addFields;
21
22 import com.fasterxml.jackson.databind.node.JsonNodeFactory;
23 import com.fasterxml.jackson.databind.node.ObjectNode;
24 import com.google.common.collect.Range;
25 import java.util.HashMap;
26 import java.util.Map;
27 import java.util.Map.Entry;
28 import java.util.Optional;
29 import java.util.TreeMap;
30 import java.util.concurrent.atomic.AtomicLong;
31 import javax.ws.rs.HttpMethod;
32 import javax.ws.rs.core.Response;
33 import javax.ws.rs.core.UriInfo;
34 import org.opendaylight.mdsal.dom.api.DOMMountPoint;
35 import org.opendaylight.mdsal.dom.api.DOMMountPointListener;
36 import org.opendaylight.mdsal.dom.api.DOMMountPointService;
37 import org.opendaylight.mdsal.dom.api.DOMSchemaService;
38 import org.opendaylight.netconf.sal.rest.doc.impl.ApiDocServiceImpl.OAversion;
39 import org.opendaylight.netconf.sal.rest.doc.impl.ApiDocServiceImpl.URIType;
40 import org.opendaylight.netconf.sal.rest.doc.impl.BaseYangSwaggerGenerator;
41 import org.opendaylight.netconf.sal.rest.doc.impl.DefinitionNames;
42 import org.opendaylight.netconf.sal.rest.doc.swagger.CommonApiObject;
43 import org.opendaylight.netconf.sal.rest.doc.swagger.SwaggerObject;
44 import org.opendaylight.yangtools.concepts.ListenerRegistration;
45 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
46 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
47 import org.opendaylight.yangtools.yang.model.api.Module;
48 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
49 import org.slf4j.Logger;
50 import org.slf4j.LoggerFactory;
51
52 public class MountPointSwagger implements DOMMountPointListener, AutoCloseable {
53
54     private static final Logger LOG = LoggerFactory.getLogger(MountPointSwagger.class);
55
56     private static final String DATASTORES_REVISION = "-";
57     private static final String DATASTORES_LABEL = "Datastores";
58
59     private final DOMSchemaService globalSchema;
60     private final DOMMountPointService mountService;
61     private final BaseYangSwaggerGenerator swaggerGenerator;
62     private final Map<YangInstanceIdentifier, Long> instanceIdToLongId =
63             new TreeMap<>((o1, o2) -> o1.toString().compareToIgnoreCase(o2.toString()));
64     private final Map<Long, YangInstanceIdentifier> longIdToInstanceId = new HashMap<>();
65
66     private final Object lock = new Object();
67
68     private final AtomicLong idKey = new AtomicLong(0);
69
70     private ListenerRegistration<DOMMountPointListener> registration;
71
72     public MountPointSwagger(final DOMSchemaService globalSchema, final DOMMountPointService mountService,
73                              final BaseYangSwaggerGenerator swaggerGenerator) {
74         this.globalSchema = requireNonNull(globalSchema);
75         this.mountService = requireNonNull(mountService);
76         this.swaggerGenerator = requireNonNull(swaggerGenerator);
77     }
78
79     public void init() {
80         registration = mountService.registerProvisionListener(this);
81     }
82
83     @Override
84     public void close() {
85         if (registration != null) {
86             registration.close();
87         }
88     }
89
90     public Map<String, Long> getInstanceIdentifiers() {
91         final Map<String, Long> urlToId = new HashMap<>();
92         synchronized (this.lock) {
93             final SchemaContext context = this.globalSchema.getGlobalContext();
94             for (final Entry<YangInstanceIdentifier, Long> entry : this.instanceIdToLongId.entrySet()) {
95                 final String modName = findModuleName(entry.getKey(), context);
96                 urlToId.put(swaggerGenerator.generateUrlPrefixFromInstanceID(entry.getKey(), modName),
97                         entry.getValue());
98             }
99         }
100         return urlToId;
101     }
102
103     private static String findModuleName(final YangInstanceIdentifier id, final SchemaContext context) {
104         final PathArgument rootQName = id.getPathArguments().iterator().next();
105         for (final Module mod : context.getModules()) {
106             if (mod.findDataChildByName(rootQName.getNodeType()).isPresent()) {
107                 return mod.getName();
108             }
109         }
110         return null;
111     }
112
113     private String getYangMountUrl(final YangInstanceIdentifier key) {
114         final String modName = findModuleName(key, this.globalSchema.getGlobalContext());
115         return swaggerGenerator.generateUrlPrefixFromInstanceID(key, modName) + "yang-ext:mount";
116     }
117
118     private YangInstanceIdentifier getInstanceId(final Long id) {
119         final YangInstanceIdentifier instanceId;
120         synchronized (this.lock) {
121             instanceId = this.longIdToInstanceId.get(id);
122         }
123         return instanceId;
124     }
125
126     private SchemaContext getSchemaContext(final YangInstanceIdentifier id) {
127         if (id == null) {
128             return null;
129         }
130
131         checkState(mountService != null);
132         final Optional<DOMMountPoint> mountPoint = this.mountService.getMountPoint(id);
133         if (mountPoint.isEmpty()) {
134             return null;
135         }
136
137         final SchemaContext context = mountPoint.get().getSchemaContext();
138         return context;
139     }
140
141     public CommonApiObject getMountPointApi(final UriInfo uriInfo, final Long id, final String module,
142                                             final String revision, final URIType uriType, final OAversion oaversion) {
143         final YangInstanceIdentifier iid = getInstanceId(id);
144         final SchemaContext context = getSchemaContext(iid);
145         final String urlPrefix = getYangMountUrl(iid);
146         final String deviceName  = extractDeviceName(iid);
147
148         if (context == null) {
149             return null;
150         }
151
152         if (DATASTORES_LABEL.equals(module) && DATASTORES_REVISION.equals(revision)) {
153             return generateDataStoreApiDoc(uriInfo, urlPrefix, deviceName);
154         }
155         final SwaggerObject swaggerObject = swaggerGenerator.getApiDeclaration(module, revision, uriInfo, context,
156                 urlPrefix, uriType, oaversion);
157         return BaseYangSwaggerGenerator.getAppropriateDoc(swaggerObject, oaversion);
158     }
159
160     public CommonApiObject getMountPointApi(final UriInfo uriInfo, final Long id, final Optional<Integer> pageNum,
161                                             final URIType uriType, final OAversion oaversion) {
162         final YangInstanceIdentifier iid = getInstanceId(id);
163         final SchemaContext context = getSchemaContext(iid);
164         final String urlPrefix = getYangMountUrl(iid);
165         final String deviceName  = extractDeviceName(iid);
166
167         if (context == null) {
168             return null;
169         }
170         final DefinitionNames definitionNames = new DefinitionNames();
171
172         boolean includeDataStore = true;
173         Optional<Range<Integer>> range = Optional.empty();
174
175         if (pageNum.isPresent()) {
176             final int pageNumValue = pageNum.get();
177             final int end = DEFAULT_PAGESIZE * pageNumValue - 1;
178             int start = end - DEFAULT_PAGESIZE;
179             if (pageNumValue == 1) {
180                 start++;
181             } else {
182                 includeDataStore = false;
183             }
184             range = Optional.of(Range.closed(start, end));
185         }
186
187         final SwaggerObject doc;
188
189         final SwaggerObject swaggerObject = swaggerGenerator.getAllModulesDoc(uriInfo, range, context,
190                 Optional.of(deviceName), urlPrefix, definitionNames, uriType, oaversion);
191
192         if (includeDataStore) {
193             doc = generateDataStoreApiDoc(uriInfo, urlPrefix, deviceName);
194             addFields(doc.getPaths() ,swaggerObject.getPaths().fields());
195             addFields(doc.getDefinitions() ,swaggerObject.getDefinitions().fields());
196             doc.getInfo().setTitle(swaggerObject.getInfo().getTitle());
197         } else {
198             doc = swaggerObject;
199         }
200
201         return BaseYangSwaggerGenerator.getAppropriateDoc(doc, oaversion);
202     }
203
204     private static String extractDeviceName(final YangInstanceIdentifier iid) {
205         return ((YangInstanceIdentifier.NodeIdentifierWithPredicates.Singleton)iid.getLastPathArgument())
206                 .values().getElement().toString();
207     }
208
209     private SwaggerObject generateDataStoreApiDoc(final UriInfo uriInfo, final String context,
210                                                   final String deviceName) {
211         final SwaggerObject declaration = swaggerGenerator.createSwaggerObject(
212                 swaggerGenerator.createSchemaFromUriInfo(uriInfo),
213                 swaggerGenerator.createHostFromUriInfo(uriInfo),
214                 BASE_PATH,
215                 context);
216
217         final ObjectNode pathsObject = JsonNodeFactory.instance.objectNode();
218         createGetPathItem("config", "Queries the config (startup) datastore on the mounted hosted.",
219                 context, deviceName, pathsObject);
220         createGetPathItem("operational", "Queries the operational (running) datastore on the mounted hosted.",
221                 context, deviceName, pathsObject);
222         createGetPathItem("operations", "Queries the available operations (RPC calls) on the mounted hosted.",
223                 context, deviceName, pathsObject);
224
225         declaration.setPaths(pathsObject);
226         declaration.setDefinitions(JsonNodeFactory.instance.objectNode());
227
228         return declaration;
229     }
230
231     private void createGetPathItem(final String resourceType, final String description, final String context,
232                                    final String deviceName, final ObjectNode pathsObject) {
233         final ObjectNode pathItem = JsonNodeFactory.instance.objectNode();
234         final ObjectNode operationObject = JsonNodeFactory.instance.objectNode();
235         pathItem.set("get", operationObject);
236         operationObject.put(DESCRIPTION_KEY, description);
237         operationObject.put(SUMMARY_KEY, HttpMethod.GET + SUMMARY_SEPARATOR + deviceName + SUMMARY_SEPARATOR
238                 + swaggerGenerator.getResourcePathPart(resourceType));
239         operationObject.set(TAGS_KEY, buildTagsValue(Optional.of(deviceName), "GET root"));
240         final ObjectNode okResponse = JsonNodeFactory.instance.objectNode();
241         okResponse.put(DESCRIPTION_KEY, Response.Status.OK.getReasonPhrase());
242         final ObjectNode responses = JsonNodeFactory.instance.objectNode();
243         responses.set(String.valueOf(Response.Status.OK.getStatusCode()), okResponse);
244         operationObject.set(RESPONSES_KEY, responses);
245         pathsObject.set(swaggerGenerator.getResourcePath(resourceType, context), pathItem);
246     }
247
248     @Override
249     public void onMountPointCreated(final YangInstanceIdentifier path) {
250         synchronized (this.lock) {
251             LOG.debug("Mount point {} created", path);
252             final Long idLong = this.idKey.incrementAndGet();
253             this.instanceIdToLongId.put(path, idLong);
254             this.longIdToInstanceId.put(idLong, path);
255         }
256     }
257
258     @Override
259     public void onMountPointRemoved(final YangInstanceIdentifier path) {
260         synchronized (this.lock) {
261             LOG.debug("Mount point {} removed", path);
262             final Long id = this.instanceIdToLongId.remove(path);
263             this.longIdToInstanceId.remove(id);
264         }
265     }
266 }