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.SECURITY;
13 import static org.opendaylight.restconf.openapi.impl.OpenApiServiceImpl.DEFAULT_PAGESIZE;
15 import java.io.IOException;
16 import java.util.HashMap;
18 import java.util.Map.Entry;
20 import java.util.TreeSet;
21 import java.util.concurrent.ConcurrentHashMap;
22 import java.util.concurrent.ConcurrentSkipListMap;
23 import java.util.concurrent.atomic.AtomicLong;
24 import javax.ws.rs.core.UriInfo;
25 import org.eclipse.jdt.annotation.Nullable;
26 import org.opendaylight.mdsal.dom.api.DOMMountPointListener;
27 import org.opendaylight.mdsal.dom.api.DOMMountPointService;
28 import org.opendaylight.mdsal.dom.api.DOMSchemaService;
29 import org.opendaylight.restconf.openapi.impl.BaseYangOpenApiGenerator;
30 import org.opendaylight.restconf.openapi.impl.OpenApiInputStream;
31 import org.opendaylight.yangtools.concepts.ListenerRegistration;
32 import org.opendaylight.yangtools.yang.common.QName;
33 import org.opendaylight.yangtools.yang.common.Revision;
34 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
35 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
36 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
37 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
38 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
39 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
40 import org.opendaylight.yangtools.yang.model.api.Module;
41 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
45 public class MountPointOpenApi implements DOMMountPointListener, AutoCloseable {
47 private static final Logger LOG = LoggerFactory.getLogger(MountPointOpenApi.class);
49 private static final String DATASTORES_REVISION = "-";
50 private static final String DATASTORES_LABEL = "Datastores";
52 private final DOMSchemaService globalSchema;
53 private final DOMMountPointService mountService;
54 private final BaseYangOpenApiGenerator openApiGenerator;
55 private final Map<YangInstanceIdentifier, Long> instanceIdToLongId =
56 new ConcurrentSkipListMap<>((o1, o2) -> o1.toString().compareToIgnoreCase(o2.toString()));
57 private final Map<Long, YangInstanceIdentifier> longIdToInstanceId = new ConcurrentHashMap<>();
59 private final AtomicLong idKey = new AtomicLong(0);
61 private ListenerRegistration<DOMMountPointListener> registration;
63 public MountPointOpenApi(final DOMSchemaService globalSchema, final DOMMountPointService mountService,
64 final BaseYangOpenApiGenerator openApiGenerator) {
65 this.globalSchema = requireNonNull(globalSchema);
66 this.mountService = requireNonNull(mountService);
67 this.openApiGenerator = requireNonNull(openApiGenerator);
71 registration = mountService.registerProvisionListener(this);
76 if (registration != null) {
81 public Map<String, Long> getInstanceIdentifiers() {
82 final Map<String, Long> urlToId = new HashMap<>();
83 final SchemaContext context = globalSchema.getGlobalContext();
84 for (final Entry<YangInstanceIdentifier, Long> entry : instanceIdToLongId.entrySet()) {
85 final String modName = findModuleName(entry.getKey(), context);
86 urlToId.put(generateUrlPrefixFromInstanceID(entry.getKey(), modName), entry.getValue());
91 private static String findModuleName(final YangInstanceIdentifier id, final SchemaContext context) {
92 final PathArgument rootQName = id.getPathArguments().iterator().next();
93 for (final Module mod : context.getModules()) {
94 if (mod.findDataChildByName(rootQName.getNodeType()).isPresent()) {
101 private String getYangMountUrl(final YangInstanceIdentifier key) {
102 final String modName = findModuleName(key, globalSchema.getGlobalContext());
103 return generateUrlPrefixFromInstanceID(key, modName) + "yang-ext:mount";
106 private static String generateUrlPrefixFromInstanceID(final YangInstanceIdentifier key, final String moduleName) {
107 final StringBuilder builder = new StringBuilder();
109 if (moduleName != null) {
110 builder.append(moduleName).append(':');
112 for (final PathArgument arg : key.getPathArguments()) {
113 final String name = arg.getNodeType().getLocalName();
114 if (arg instanceof YangInstanceIdentifier.NodeIdentifierWithPredicates nodeId) {
115 for (final Entry<QName, Object> entry : nodeId.entrySet()) {
116 builder.deleteCharAt(builder.length() - 1).append("=").append(entry.getValue()).append('/');
119 builder.append(name).append('/');
122 return builder.toString();
125 private EffectiveModelContext getSchemaContext(final YangInstanceIdentifier id) {
130 checkState(mountService != null);
131 return mountService.getMountPoint(id)
132 .flatMap(mountPoint -> mountPoint.getService(DOMSchemaService.class))
133 .map(DOMSchemaService::getGlobalContext)
137 public OpenApiInputStream getMountPointApi(final UriInfo uriInfo, final Long id, final String module,
138 final String revision) throws IOException {
139 final YangInstanceIdentifier iid = longIdToInstanceId.get(id);
140 final EffectiveModelContext context = getSchemaContext(iid);
141 final String urlPrefix = getYangMountUrl(iid);
142 final String deviceName = extractDeviceName(iid);
144 if (context == null) {
148 if (DATASTORES_LABEL.equals(module) && DATASTORES_REVISION.equals(revision)) {
149 return generateDataStoreOpenApi(context, uriInfo, urlPrefix, deviceName);
151 return openApiGenerator.getApiDeclaration(module, revision, uriInfo, context, urlPrefix, deviceName);
154 public OpenApiInputStream getMountPointApi(final UriInfo uriInfo, final Long id, final @Nullable String strPageNum)
156 final var iid = longIdToInstanceId.get(id);
157 final var context = getSchemaContext(iid);
158 final var urlPrefix = getYangMountUrl(iid);
159 final var deviceName = extractDeviceName(iid);
161 if (context == null) {
165 boolean includeDataStore = true;
166 var modules = context.getModules();
167 if (strPageNum != null) {
168 final var pageNum = Integer.parseInt(strPageNum);
169 final var end = DEFAULT_PAGESIZE * pageNum - 1;
170 int start = end - DEFAULT_PAGESIZE;
174 includeDataStore = false;
176 modules = filterByRange(context, start);
179 final var schema = openApiGenerator.createSchemaFromUriInfo(uriInfo);
180 final var host = openApiGenerator.createHostFromUriInfo(uriInfo);
181 final var title = deviceName + " modules of RESTCONF";
182 final var url = schema + "://" + host + "/";
183 final var basePath = openApiGenerator.getBasePath();
184 return new OpenApiInputStream(context, title, url, SECURITY, deviceName, urlPrefix, false, includeDataStore,
188 private static String extractDeviceName(final YangInstanceIdentifier iid) {
189 return ((YangInstanceIdentifier.NodeIdentifierWithPredicates.Singleton)iid.getLastPathArgument())
190 .values().getElement().toString();
193 private OpenApiInputStream generateDataStoreOpenApi(EffectiveModelContext modelContext,
194 final UriInfo uriInfo, final String urlPrefix, final String deviceName) throws IOException {
195 final var schema = openApiGenerator.createSchemaFromUriInfo(uriInfo);
196 final var host = openApiGenerator.createHostFromUriInfo(uriInfo);
197 final var url = schema + "://" + host + "/";
198 final var basePath = openApiGenerator.getBasePath();
199 final var modules = modelContext.getModules();
200 return new OpenApiInputStream(modelContext, urlPrefix, url, SECURITY, deviceName, urlPrefix, true, false,
205 public void onMountPointCreated(final YangInstanceIdentifier path) {
206 LOG.debug("Mount point {} created", path);
207 final Long idLong = idKey.incrementAndGet();
208 instanceIdToLongId.put(path, idLong);
209 longIdToInstanceId.put(idLong, path);
213 public void onMountPointRemoved(final YangInstanceIdentifier path) {
214 LOG.debug("Mount point {} removed", path);
215 final Long id = instanceIdToLongId.remove(path);
216 longIdToInstanceId.remove(id);
219 private static Set<Module> filterByRange(final EffectiveModelContext schemaContext, final Integer start) {
220 final var sortedModules = new TreeSet<Module>((module1, module2) -> {
221 int result = module1.getName().compareTo(module2.getName());
223 result = Revision.compare(module1.getRevision(), module2.getRevision());
226 result = module1.getNamespace().compareTo(module2.getNamespace());
230 sortedModules.addAll(schemaContext.getModules());
232 final int end = start + DEFAULT_PAGESIZE - 1;
234 var firstModule = sortedModules.first();
236 final var iterator = sortedModules.iterator();
238 while (iterator.hasNext() && counter < end) {
239 final var module = iterator.next();
240 if (containsListOrContainer(module.getChildNodes()) || !module.getRpcs().isEmpty()) {
241 if (counter == start) {
242 firstModule = module;
248 return iterator.hasNext()
249 ? sortedModules.subSet(firstModule, iterator.next()) : sortedModules.tailSet(firstModule);
252 private static boolean containsListOrContainer(final Iterable<? extends DataSchemaNode> nodes) {
253 for (final var child : nodes) {
254 if (child instanceof ListSchemaNode || child instanceof ContainerSchemaNode) {