2 * Copyright (c) 2021 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.nb.rfc8040.rests.services.impl;
10 import static java.util.Objects.requireNonNull;
12 import java.util.List;
14 import java.util.Map.Entry;
15 import org.eclipse.jdt.annotation.NonNull;
16 import org.eclipse.jdt.annotation.Nullable;
17 import org.opendaylight.restconf.nb.rfc8040.legacy.InstanceIdentifierContext;
18 import org.opendaylight.yangtools.yang.common.QNameModule;
19 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
20 import org.opendaylight.yangtools.yang.model.api.stmt.ModuleEffectiveStatement;
21 import org.opendaylight.yangtools.yang.model.api.stmt.RpcEffectiveStatement;
24 * RESTCONF {@code /operations} content for a {@code GET} operation as per
25 * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-3.3.2">RFC8040</a>.
27 enum OperationsContent {
28 JSON("{ \"ietf-restconf:operations\" : { } }") {
30 String createBody(final List<Entry<String, List<String>>> rpcsByPrefix) {
31 final var sb = new StringBuilder("{\n"
32 + " \"ietf-restconf:operations\" : {\n");
33 var entryIt = rpcsByPrefix.iterator();
34 var entry = entryIt.next();
35 var nameIt = entry.getValue().iterator();
37 sb.append(" \"").append(entry.getKey()).append(':').append(nameIt.next()).append("\": [null]");
38 if (nameIt.hasNext()) {
43 if (entryIt.hasNext()) {
45 entry = entryIt.next();
46 nameIt = entry.getValue().iterator();
53 return sb.append("\n }\n}").toString();
57 String prefix(final ModuleEffectiveStatement module) {
58 return module.argument().getLocalName();
62 XML("<operations xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\"/>") {
64 String createBody(final List<Entry<String, List<String>>> rpcsByPrefix) {
65 // Header with namespace declarations for each module
66 final var sb = new StringBuilder("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
67 + "<operations xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\"");
68 for (int i = 0; i < rpcsByPrefix.size(); ++i) {
69 final var prefix = "ns" + i;
70 sb.append("\n xmlns:").append(prefix).append("=\"").append(rpcsByPrefix.get(i).getKey())
75 // Second pass: emit all leaves
76 for (int i = 0; i < rpcsByPrefix.size(); ++i) {
77 final var prefix = "ns" + i;
78 for (var localName : rpcsByPrefix.get(i).getValue()) {
79 sb.append("\n <").append(prefix).append(':').append(localName).append("/>");
83 return sb.append("\n</operations>").toString();
87 String prefix(final ModuleEffectiveStatement module) {
88 return module.localQNameModule().getNamespace().toString();
92 private final @NonNull String emptyBody;
94 OperationsContent(final String emptyBody) {
95 this.emptyBody = requireNonNull(emptyBody);
99 * Return the content for a particular {@link EffectiveModelContext}.
101 * @param context Context to use
102 * @return Content of HTTP GET operation as a String
104 public final @NonNull String bodyFor(final @Nullable EffectiveModelContext context) {
105 if (isEmptyContext(context)) {
106 // No modules, or defensive return empty content
110 final var moduleRpcs = getModuleRpcs(context, context.getModuleStatements());
111 return moduleRpcs.isEmpty() ? emptyBody : createBody(moduleRpcs);
115 * Return content with RPCs and actions for a particular {@link InstanceIdentifierContext}.
117 * @param identifierContext InstanceIdentifierContext to use
118 * @return Content of HTTP GET operation as a String
120 public final @NonNull String bodyFor(final @NonNull InstanceIdentifierContext identifierContext) {
121 final var context = identifierContext.getSchemaContext();
122 if (isEmptyContext(context)) {
123 // No modules, or defensive return empty content
127 final var stack = identifierContext.inference().toSchemaInferenceStack();
128 // empty stack == get all RPCs/actions
129 if (stack.isEmpty()) {
130 return createBody(getModuleRpcs(context, context.getModuleStatements()));
133 // get current module RPCs/actions by RPC/action name
134 final var currentModule = stack.currentModule();
135 final var currentModuleKey = Map.of(currentModule.localQNameModule(), currentModule);
136 final var rpcName = identifierContext.getSchemaNode().getQName().getLocalName();
137 return getModuleRpcs(context, currentModuleKey).stream()
139 .map(e -> Map.entry(e.getKey(), e.getValue().stream().filter(rpcName::equals).toList()))
140 .map(e -> createBody(List.of(e)))
144 private static boolean isEmptyContext(final EffectiveModelContext context) {
145 if (context == null) {
148 return context.getModuleStatements().isEmpty();
152 * Returns a list of entries, where each entry contains a module prefix and a list of RPC names.
154 * @param context the effective model context
155 * @param modules the map of QNameModule to ModuleEffectiveStatement
156 * @return a list of entries, where each entry contains a module prefix and a list of RPC names
158 private List<Entry<@NonNull String, List<String>>> getModuleRpcs(final EffectiveModelContext context,
159 final Map<QNameModule, ModuleEffectiveStatement> modules) {
160 return modules.values().stream()
161 // Extract XMLNamespaces
162 .map(module -> module.localQNameModule().getNamespace())
163 // Make sure each is XMLNamespace unique
165 // Find the most recent module with that namespace. This needed so we expose the right set of RPCs,
166 // as we always pick the latest revision to resolve prefix (or module name).
167 .map(namespace -> context.findModuleStatements(namespace).iterator().next())
168 // Convert to module prefix + List<String> with RPC names
169 .map(module -> Map.entry(prefix(module),
170 module.streamEffectiveSubstatements(RpcEffectiveStatement.class)
171 .map(rpc -> rpc.argument().getLocalName())
173 // Skip prefixes which do not have any RPCs
174 .filter(entry -> !entry.getValue().isEmpty())
175 // Ensure stability: sort by prefix
176 .sorted(Entry.comparingByKey())
180 abstract @NonNull String createBody(List<Entry<String, List<String>>> rpcsByPrefix);
182 abstract @NonNull String prefix(ModuleEffectiveStatement module);