2 * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others. All rights reserved.
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
8 package org.opendaylight.restconf.openapi.mountpoints;
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_SEPARATOR;
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;
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.data.api.YangInstanceIdentifier;
52 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
53 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
54 import org.opendaylight.yangtools.yang.model.api.Module;
55 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
56 import org.slf4j.Logger;
57 import org.slf4j.LoggerFactory;
59 public class MountPointOpenApi implements DOMMountPointListener, AutoCloseable {
61 private static final Logger LOG = LoggerFactory.getLogger(MountPointOpenApi.class);
63 private static final String DATASTORES_REVISION = "-";
64 private static final String DATASTORES_LABEL = "Datastores";
66 private final DOMSchemaService globalSchema;
67 private final DOMMountPointService mountService;
68 private final BaseYangOpenApiGenerator openApiGenerator;
69 private final Map<YangInstanceIdentifier, Long> instanceIdToLongId =
70 new TreeMap<>((o1, o2) -> o1.toString().compareToIgnoreCase(o2.toString()));
71 private final Map<Long, YangInstanceIdentifier> longIdToInstanceId = new HashMap<>();
73 private final Object lock = new Object();
75 private final AtomicLong idKey = new AtomicLong(0);
77 private ListenerRegistration<DOMMountPointListener> registration;
79 public MountPointOpenApi(final DOMSchemaService globalSchema, final DOMMountPointService mountService,
80 final BaseYangOpenApiGenerator openApiGenerator) {
81 this.globalSchema = requireNonNull(globalSchema);
82 this.mountService = requireNonNull(mountService);
83 this.openApiGenerator = requireNonNull(openApiGenerator);
87 registration = mountService.registerProvisionListener(this);
92 if (registration != null) {
97 public Map<String, Long> getInstanceIdentifiers() {
98 final Map<String, Long> urlToId = new HashMap<>();
100 final SchemaContext context = globalSchema.getGlobalContext();
101 for (final Entry<YangInstanceIdentifier, Long> entry : instanceIdToLongId.entrySet()) {
102 final String modName = findModuleName(entry.getKey(), context);
103 urlToId.put(openApiGenerator.generateUrlPrefixFromInstanceID(entry.getKey(), modName),
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();
120 private String getYangMountUrl(final YangInstanceIdentifier key) {
121 final String modName = findModuleName(key, globalSchema.getGlobalContext());
122 return openApiGenerator.generateUrlPrefixFromInstanceID(key, modName) + "yang-ext:mount";
125 private YangInstanceIdentifier getInstanceId(final Long id) {
126 final YangInstanceIdentifier instanceId;
127 synchronized (lock) {
128 instanceId = longIdToInstanceId.get(id);
133 private EffectiveModelContext getSchemaContext(final YangInstanceIdentifier id) {
138 checkState(mountService != null);
139 return mountService.getMountPoint(id)
140 .flatMap(mountPoint -> mountPoint.getService(DOMSchemaService.class))
141 .flatMap(svc -> Optional.ofNullable(svc.getGlobalContext()))
145 public OpenApiObject getMountPointApi(final UriInfo uriInfo, final Long id, final String module,
146 final String revision) {
147 final YangInstanceIdentifier iid = getInstanceId(id);
148 final EffectiveModelContext context = getSchemaContext(iid);
149 final String urlPrefix = getYangMountUrl(iid);
150 final String deviceName = extractDeviceName(iid);
152 if (context == null) {
156 if (DATASTORES_LABEL.equals(module) && DATASTORES_REVISION.equals(revision)) {
157 return generateDataStoreOpenApi(uriInfo, urlPrefix, deviceName);
159 return openApiGenerator.getApiDeclaration(module, revision, uriInfo, context, urlPrefix, deviceName);
162 public OpenApiObject getMountPointApi(final UriInfo uriInfo, final Long id, final @Nullable String strPageNum) {
163 final var iid = getInstanceId(id);
164 final var context = getSchemaContext(iid);
165 final var urlPrefix = getYangMountUrl(iid);
166 final var deviceName = extractDeviceName(iid);
168 if (context == null) {
171 final var definitionNames = new DefinitionNames();
173 boolean includeDataStore = true;
174 Range<Integer> range = Range.all();
175 if (strPageNum != null) {
176 final var pageNum = Integer.parseInt(strPageNum);
177 final var end = DEFAULT_PAGESIZE * pageNum - 1;
178 int start = end - DEFAULT_PAGESIZE;
182 includeDataStore = false;
184 range = Range.closed(start, end);
187 final var schema = openApiGenerator.createSchemaFromUriInfo(uriInfo);
188 final var host = openApiGenerator.createHostFromUriInfo(uriInfo);
189 final var title = deviceName + " modules of RESTCONF";
190 final var info = new Info(API_VERSION, title);
191 final var servers = List.of(new Server(schema + "://" + host + BASE_PATH));
193 final var modules = getSortedModules(context);
194 final var filteredModules = filterByRange(modules, range);
195 final var paths = new HashMap<String, Path>();
196 final var schemas = new HashMap<String, Schema>();
197 for (final var module : filteredModules) {
198 LOG.debug("Working on [{},{}]...", module.getName(), module.getQNameModule().getRevision().orElse(null));
199 schemas.putAll(openApiGenerator.getSchemas(module, context, definitionNames, false));
200 paths.putAll(openApiGenerator.getPaths(module, urlPrefix, deviceName, context, definitionNames, false));
202 final var components = new Components(schemas, new SecuritySchemes(OPEN_API_BASIC_AUTH));
203 if (includeDataStore) {
204 paths.putAll(getDataStoreApiPaths(urlPrefix, deviceName));
206 return new OpenApiObject(OPEN_API_VERSION, info, servers, paths, components, SECURITY);
209 private static String extractDeviceName(final YangInstanceIdentifier iid) {
210 return ((YangInstanceIdentifier.NodeIdentifierWithPredicates.Singleton)iid.getLastPathArgument())
211 .values().getElement().toString();
214 private OpenApiObject generateDataStoreOpenApi(final UriInfo uriInfo, final String context,
215 final String deviceName) {
216 final var info = new Info(API_VERSION, context);
217 final var schema = openApiGenerator.createSchemaFromUriInfo(uriInfo);
218 final var host = openApiGenerator.createHostFromUriInfo(uriInfo);
219 final var servers = List.of(new Server(schema + "://" + host + BASE_PATH));
220 final var components = new Components(new HashMap<>(), new SecuritySchemes(OPEN_API_BASIC_AUTH));
221 final var paths = getDataStoreApiPaths(context, deviceName);
222 return new OpenApiObject(OPEN_API_VERSION, info, servers, paths, components, SECURITY);
225 private Map<String, Path> getDataStoreApiPaths(final String context, final String deviceName) {
226 final var dataBuilder = new Path.Builder();
227 dataBuilder.get(createGetPathItem("data",
228 "Queries the config (startup) datastore on the mounted hosted.", deviceName));
230 final var operationsBuilder = new Path.Builder();
231 operationsBuilder.get(createGetPathItem("operations",
232 "Queries the available operations (RPC calls) on the mounted hosted.", deviceName));
234 return Map.of(openApiGenerator.getResourcePath("data", context), dataBuilder.build(),
235 openApiGenerator.getResourcePath("operations", context), operationsBuilder.build());
238 private static Operation createGetPathItem(final String resourceType, final String description,
239 final String deviceName) {
240 final String summary = HttpMethod.GET + SUMMARY_SEPARATOR + deviceName + SUMMARY_SEPARATOR + resourceType;
241 final List<String> tags = List.of(deviceName + " GET root");
242 final ObjectNode okResponse = JsonNodeFactory.instance.objectNode();
243 okResponse.put(DESCRIPTION_KEY, Response.Status.OK.getReasonPhrase());
244 final ObjectNode responses = JsonNodeFactory.instance.objectNode();
245 responses.set(String.valueOf(Response.Status.OK.getStatusCode()), okResponse);
246 return new Operation.Builder()
248 .responses(responses)
249 .description(description)
255 public void onMountPointCreated(final YangInstanceIdentifier path) {
256 synchronized (lock) {
257 LOG.debug("Mount point {} created", path);
258 final Long idLong = idKey.incrementAndGet();
259 instanceIdToLongId.put(path, idLong);
260 longIdToInstanceId.put(idLong, path);
265 public void onMountPointRemoved(final YangInstanceIdentifier path) {
266 synchronized (lock) {
267 LOG.debug("Mount point {} removed", path);
268 final Long id = instanceIdToLongId.remove(path);
269 longIdToInstanceId.remove(id);