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_TEMPLATE;
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.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;
60 public class MountPointOpenApi implements DOMMountPointListener, AutoCloseable {
62 private static final Logger LOG = LoggerFactory.getLogger(MountPointOpenApi.class);
64 private static final String DATASTORES_REVISION = "-";
65 private static final String DATASTORES_LABEL = "Datastores";
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<>();
74 private final Object lock = new Object();
76 private final AtomicLong idKey = new AtomicLong(0);
78 private ListenerRegistration<DOMMountPointListener> registration;
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);
88 registration = mountService.registerProvisionListener(this);
93 if (registration != null) {
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());
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 generateUrlPrefixFromInstanceID(key, modName) + "yang-ext:mount";
125 private static String generateUrlPrefixFromInstanceID(final YangInstanceIdentifier key, final String moduleName) {
126 final StringBuilder builder = new StringBuilder();
128 if (moduleName != null) {
129 builder.append(moduleName).append(':');
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('/');
138 builder.append(name).append('/');
141 return builder.toString();
144 private YangInstanceIdentifier getInstanceId(final Long id) {
145 final YangInstanceIdentifier instanceId;
146 synchronized (lock) {
147 instanceId = longIdToInstanceId.get(id);
152 private EffectiveModelContext getSchemaContext(final YangInstanceIdentifier id) {
157 checkState(mountService != null);
158 return mountService.getMountPoint(id)
159 .flatMap(mountPoint -> mountPoint.getService(DOMSchemaService.class))
160 .flatMap(svc -> Optional.ofNullable(svc.getGlobalContext()))
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);
171 if (context == null) {
175 if (DATASTORES_LABEL.equals(module) && DATASTORES_REVISION.equals(revision)) {
176 return generateDataStoreOpenApi(uriInfo, urlPrefix, deviceName);
178 return openApiGenerator.getApiDeclaration(module, revision, uriInfo, context, urlPrefix, deviceName);
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);
187 if (context == null) {
190 final var definitionNames = new DefinitionNames();
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;
201 includeDataStore = false;
203 range = Range.closed(start, end);
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));
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));
221 final var components = new Components(schemas, new SecuritySchemes(OPEN_API_BASIC_AUTH));
222 if (includeDataStore) {
223 paths.putAll(getDataStoreApiPaths(urlPrefix, deviceName));
225 return new OpenApiObject(OPEN_API_VERSION, info, servers, paths, components, SECURITY);
228 private static String extractDeviceName(final YangInstanceIdentifier iid) {
229 return ((YangInstanceIdentifier.NodeIdentifierWithPredicates.Singleton)iid.getLastPathArgument())
230 .values().getElement().toString();
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);
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));
249 final var operationsBuilder = new Path.Builder();
250 operationsBuilder.get(createGetPathItem("operations",
251 "Queries the available operations (RPC calls) on the mounted hosted.", deviceName));
253 return Map.of(openApiGenerator.getResourcePath("data", context), dataBuilder.build(),
254 openApiGenerator.getResourcePath("operations", context), operationsBuilder.build());
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()
267 .responses(responses)
268 .description(description)
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);
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);