Fix processTypeDef method call
[netconf.git] / restconf / restconf-openapi / src / main / java / org / opendaylight / restconf / openapi / 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.restconf.openapi.mountpoints;
9
10 import static com.google.common.base.Preconditions.checkState;
11 import static java.util.Objects.requireNonNull;
12 import static org.opendaylight.restconf.openapi.impl.BaseYangOpenApiGenerator.API_VERSION;
13 import static org.opendaylight.restconf.openapi.impl.BaseYangOpenApiGenerator.BASE_PATH;
14 import static org.opendaylight.restconf.openapi.impl.BaseYangOpenApiGenerator.OPEN_API_BASIC_AUTH;
15 import static org.opendaylight.restconf.openapi.impl.BaseYangOpenApiGenerator.OPEN_API_VERSION;
16 import static org.opendaylight.restconf.openapi.impl.BaseYangOpenApiGenerator.SECURITY;
17 import static org.opendaylight.restconf.openapi.impl.BaseYangOpenApiGenerator.filterByRange;
18 import static org.opendaylight.restconf.openapi.impl.BaseYangOpenApiGenerator.getSortedModules;
19 import static org.opendaylight.restconf.openapi.impl.OpenApiServiceImpl.DEFAULT_PAGESIZE;
20 import static org.opendaylight.restconf.openapi.model.builder.OperationBuilder.DESCRIPTION_KEY;
21 import static org.opendaylight.restconf.openapi.model.builder.OperationBuilder.SUMMARY_TEMPLATE;
22
23 import com.fasterxml.jackson.databind.node.JsonNodeFactory;
24 import com.fasterxml.jackson.databind.node.ObjectNode;
25 import com.google.common.collect.Range;
26 import java.util.HashMap;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.Map.Entry;
30 import java.util.Optional;
31 import java.util.TreeMap;
32 import java.util.concurrent.atomic.AtomicLong;
33 import javax.ws.rs.HttpMethod;
34 import javax.ws.rs.core.Response;
35 import javax.ws.rs.core.UriInfo;
36 import org.eclipse.jdt.annotation.Nullable;
37 import org.opendaylight.mdsal.dom.api.DOMMountPointListener;
38 import org.opendaylight.mdsal.dom.api.DOMMountPointService;
39 import org.opendaylight.mdsal.dom.api.DOMSchemaService;
40 import org.opendaylight.restconf.openapi.impl.BaseYangOpenApiGenerator;
41 import org.opendaylight.restconf.openapi.impl.DefinitionNames;
42 import org.opendaylight.restconf.openapi.model.Components;
43 import org.opendaylight.restconf.openapi.model.Info;
44 import org.opendaylight.restconf.openapi.model.OpenApiObject;
45 import org.opendaylight.restconf.openapi.model.Operation;
46 import org.opendaylight.restconf.openapi.model.Path;
47 import org.opendaylight.restconf.openapi.model.Schema;
48 import org.opendaylight.restconf.openapi.model.SecuritySchemes;
49 import org.opendaylight.restconf.openapi.model.Server;
50 import org.opendaylight.yangtools.concepts.ListenerRegistration;
51 import org.opendaylight.yangtools.yang.common.QName;
52 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
53 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
54 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
55 import org.opendaylight.yangtools.yang.model.api.Module;
56 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
57 import org.slf4j.Logger;
58 import org.slf4j.LoggerFactory;
59
60 public class MountPointOpenApi implements DOMMountPointListener, AutoCloseable {
61
62     private static final Logger LOG = LoggerFactory.getLogger(MountPointOpenApi.class);
63
64     private static final String DATASTORES_REVISION = "-";
65     private static final String DATASTORES_LABEL = "Datastores";
66
67     private final DOMSchemaService globalSchema;
68     private final DOMMountPointService mountService;
69     private final BaseYangOpenApiGenerator openApiGenerator;
70     private final Map<YangInstanceIdentifier, Long> instanceIdToLongId =
71             new TreeMap<>((o1, o2) -> o1.toString().compareToIgnoreCase(o2.toString()));
72     private final Map<Long, YangInstanceIdentifier> longIdToInstanceId = new HashMap<>();
73
74     private final Object lock = new Object();
75
76     private final AtomicLong idKey = new AtomicLong(0);
77
78     private ListenerRegistration<DOMMountPointListener> registration;
79
80     public MountPointOpenApi(final DOMSchemaService globalSchema, final DOMMountPointService mountService,
81             final BaseYangOpenApiGenerator openApiGenerator) {
82         this.globalSchema = requireNonNull(globalSchema);
83         this.mountService = requireNonNull(mountService);
84         this.openApiGenerator = requireNonNull(openApiGenerator);
85     }
86
87     public void init() {
88         registration = mountService.registerProvisionListener(this);
89     }
90
91     @Override
92     public void close() {
93         if (registration != null) {
94             registration.close();
95         }
96     }
97
98     public Map<String, Long> getInstanceIdentifiers() {
99         final Map<String, Long> urlToId = new HashMap<>();
100         synchronized (lock) {
101             final SchemaContext context = globalSchema.getGlobalContext();
102             for (final Entry<YangInstanceIdentifier, Long> entry : instanceIdToLongId.entrySet()) {
103                 final String modName = findModuleName(entry.getKey(), context);
104                 urlToId.put(generateUrlPrefixFromInstanceID(entry.getKey(), modName), entry.getValue());
105             }
106         }
107         return urlToId;
108     }
109
110     private static String findModuleName(final YangInstanceIdentifier id, final SchemaContext context) {
111         final PathArgument rootQName = id.getPathArguments().iterator().next();
112         for (final Module mod : context.getModules()) {
113             if (mod.findDataChildByName(rootQName.getNodeType()).isPresent()) {
114                 return mod.getName();
115             }
116         }
117         return null;
118     }
119
120     private String getYangMountUrl(final YangInstanceIdentifier key) {
121         final String modName = findModuleName(key, globalSchema.getGlobalContext());
122         return generateUrlPrefixFromInstanceID(key, modName) + "yang-ext:mount";
123     }
124
125     private static String generateUrlPrefixFromInstanceID(final YangInstanceIdentifier key, final String moduleName) {
126         final StringBuilder builder = new StringBuilder();
127         builder.append("/");
128         if (moduleName != null) {
129             builder.append(moduleName).append(':');
130         }
131         for (final PathArgument arg : key.getPathArguments()) {
132             final String name = arg.getNodeType().getLocalName();
133             if (arg instanceof YangInstanceIdentifier.NodeIdentifierWithPredicates nodeId) {
134                 for (final Entry<QName, Object> entry : nodeId.entrySet()) {
135                     builder.deleteCharAt(builder.length() - 1).append("=").append(entry.getValue()).append('/');
136                 }
137             } else {
138                 builder.append(name).append('/');
139             }
140         }
141         return builder.toString();
142     }
143
144     private YangInstanceIdentifier getInstanceId(final Long id) {
145         final YangInstanceIdentifier instanceId;
146         synchronized (lock) {
147             instanceId = longIdToInstanceId.get(id);
148         }
149         return instanceId;
150     }
151
152     private EffectiveModelContext getSchemaContext(final YangInstanceIdentifier id) {
153         if (id == null) {
154             return null;
155         }
156
157         checkState(mountService != null);
158         return mountService.getMountPoint(id)
159             .flatMap(mountPoint -> mountPoint.getService(DOMSchemaService.class))
160             .flatMap(svc -> Optional.ofNullable(svc.getGlobalContext()))
161             .orElse(null);
162     }
163
164     public OpenApiObject getMountPointApi(final UriInfo uriInfo, final Long id, final String module,
165             final String revision) {
166         final YangInstanceIdentifier iid = getInstanceId(id);
167         final EffectiveModelContext context = getSchemaContext(iid);
168         final String urlPrefix = getYangMountUrl(iid);
169         final String deviceName  = extractDeviceName(iid);
170
171         if (context == null) {
172             return null;
173         }
174
175         if (DATASTORES_LABEL.equals(module) && DATASTORES_REVISION.equals(revision)) {
176             return generateDataStoreOpenApi(uriInfo, urlPrefix, deviceName);
177         }
178         return openApiGenerator.getApiDeclaration(module, revision, uriInfo, context, urlPrefix, deviceName);
179     }
180
181     public OpenApiObject getMountPointApi(final UriInfo uriInfo, final Long id, final @Nullable String strPageNum) {
182         final var iid = getInstanceId(id);
183         final var context = getSchemaContext(iid);
184         final var urlPrefix = getYangMountUrl(iid);
185         final var deviceName = extractDeviceName(iid);
186
187         if (context == null) {
188             return null;
189         }
190         final var definitionNames = new DefinitionNames();
191
192         boolean includeDataStore = true;
193         Range<Integer> range = Range.all();
194         if (strPageNum != null) {
195             final var pageNum = Integer.parseInt(strPageNum);
196             final var end = DEFAULT_PAGESIZE * pageNum - 1;
197             int start = end - DEFAULT_PAGESIZE;
198             if (pageNum == 1) {
199                 start++;
200             } else {
201                 includeDataStore = false;
202             }
203             range = Range.closed(start, end);
204         }
205
206         final var schema = openApiGenerator.createSchemaFromUriInfo(uriInfo);
207         final var host = openApiGenerator.createHostFromUriInfo(uriInfo);
208         final var title = deviceName + " modules of RESTCONF";
209         final var info = new Info(API_VERSION, title);
210         final var servers = List.of(new Server(schema + "://" + host + BASE_PATH));
211
212         final var modules = getSortedModules(context);
213         final var filteredModules = filterByRange(modules, range);
214         final var paths = new HashMap<String, Path>();
215         final var schemas = new HashMap<String, Schema>();
216         for (final var module : filteredModules) {
217             LOG.debug("Working on [{},{}]...", module.getName(), module.getQNameModule().getRevision().orElse(null));
218             schemas.putAll(openApiGenerator.getSchemas(module, context, definitionNames, false));
219             paths.putAll(openApiGenerator.getPaths(module, urlPrefix, deviceName, context, definitionNames, false));
220         }
221         final var components = new Components(schemas, new SecuritySchemes(OPEN_API_BASIC_AUTH));
222         if (includeDataStore) {
223             paths.putAll(getDataStoreApiPaths(urlPrefix, deviceName));
224         }
225         return new OpenApiObject(OPEN_API_VERSION, info, servers, paths, components, SECURITY);
226     }
227
228     private static String extractDeviceName(final YangInstanceIdentifier iid) {
229         return ((YangInstanceIdentifier.NodeIdentifierWithPredicates.Singleton)iid.getLastPathArgument())
230                 .values().getElement().toString();
231     }
232
233     private OpenApiObject generateDataStoreOpenApi(final UriInfo uriInfo, final String context,
234             final String deviceName) {
235         final var info = new Info(API_VERSION, context);
236         final var schema = openApiGenerator.createSchemaFromUriInfo(uriInfo);
237         final var host = openApiGenerator.createHostFromUriInfo(uriInfo);
238         final var servers = List.of(new Server(schema + "://" + host + BASE_PATH));
239         final var components = new Components(new HashMap<>(), new SecuritySchemes(OPEN_API_BASIC_AUTH));
240         final var paths = getDataStoreApiPaths(context, deviceName);
241         return new OpenApiObject(OPEN_API_VERSION, info, servers, paths, components, SECURITY);
242     }
243
244     private Map<String, Path> getDataStoreApiPaths(final String context, final String deviceName) {
245         final var dataBuilder = new Path.Builder();
246         dataBuilder.get(createGetPathItem("data",
247                 "Queries the config (startup) datastore on the mounted hosted.", deviceName));
248
249         final var operationsBuilder = new Path.Builder();
250         operationsBuilder.get(createGetPathItem("operations",
251                 "Queries the available operations (RPC calls) on the mounted hosted.", deviceName));
252
253         return Map.of(openApiGenerator.getResourcePath("data", context), dataBuilder.build(),
254             openApiGenerator.getResourcePath("operations", context), operationsBuilder.build());
255     }
256
257     private static Operation createGetPathItem(final String resourceType, final String description,
258             final String deviceName) {
259         final String summary = SUMMARY_TEMPLATE.formatted(HttpMethod.GET, deviceName, "datastore", resourceType);
260         final List<String> tags = List.of(deviceName + " GET root");
261         final ObjectNode okResponse = JsonNodeFactory.instance.objectNode();
262         okResponse.put(DESCRIPTION_KEY, Response.Status.OK.getReasonPhrase());
263         final ObjectNode responses = JsonNodeFactory.instance.objectNode();
264         responses.set(String.valueOf(Response.Status.OK.getStatusCode()), okResponse);
265         return new Operation.Builder()
266             .tags(tags)
267             .responses(responses)
268             .description(description)
269             .summary(summary)
270             .build();
271     }
272
273     @Override
274     public void onMountPointCreated(final YangInstanceIdentifier path) {
275         synchronized (lock) {
276             LOG.debug("Mount point {} created", path);
277             final Long idLong = idKey.incrementAndGet();
278             instanceIdToLongId.put(path, idLong);
279             longIdToInstanceId.put(idLong, path);
280         }
281     }
282
283     @Override
284     public void onMountPointRemoved(final YangInstanceIdentifier path) {
285         synchronized (lock) {
286             LOG.debug("Mount point {} removed", path);
287             final Long id = instanceIdToLongId.remove(path);
288             longIdToInstanceId.remove(id);
289         }
290     }
291 }