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