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.yangtools.yang.model.api.EffectiveModelContext;
18 import org.opendaylight.yangtools.yang.model.api.stmt.ModuleEffectiveStatement;
19 import org.opendaylight.yangtools.yang.model.api.stmt.RpcEffectiveStatement;
22 * RESTCONF {@code /operations} content for a {@code GET} operation as per
23 * <a href="https://datatracker.ietf.org/doc/html/rfc8040#section-3.3.2">RFC8040</a>.
25 enum OperationsContent {
26 JSON("{ \"ietf-restconf:operations\" : { } }") {
28 String createBody(final List<Entry<String, List<String>>> rpcsByPrefix) {
29 final var sb = new StringBuilder("{\n"
30 + " \"ietf-restconf:operations\" : {\n");
31 var entryIt = rpcsByPrefix.iterator();
32 var entry = entryIt.next();
33 var nameIt = entry.getValue().iterator();
35 sb.append(" \"").append(entry.getKey()).append(':').append(nameIt.next()).append("\": [null]");
36 if (nameIt.hasNext()) {
41 if (entryIt.hasNext()) {
43 entry = entryIt.next();
44 nameIt = entry.getValue().iterator();
51 return sb.append("\n }\n}").toString();
55 String prefix(final ModuleEffectiveStatement module) {
56 return module.argument().getLocalName();
60 XML("<operations xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\"/>") {
62 String createBody(final List<Entry<String, List<String>>> rpcsByPrefix) {
63 // Header with namespace declarations for each module
64 final var sb = new StringBuilder("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
65 + "<operations xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\"");
66 for (int i = 0; i < rpcsByPrefix.size(); ++i) {
67 final var prefix = "ns" + i;
68 sb.append("\n xmlns:").append(prefix).append("=\"").append(rpcsByPrefix.get(i).getKey())
73 // Second pass: emit all leaves
74 for (int i = 0; i < rpcsByPrefix.size(); ++i) {
75 final var prefix = "ns" + i;
76 for (var localName : rpcsByPrefix.get(i).getValue()) {
77 sb.append("\n <").append(prefix).append(':').append(localName).append("/>");
81 return sb.append("\n</operations>").toString();
85 String prefix(final ModuleEffectiveStatement module) {
86 return module.localQNameModule().getNamespace().toString();
90 private final @NonNull String emptyBody;
92 OperationsContent(final String emptyBody) {
93 this.emptyBody = requireNonNull(emptyBody);
97 * Return the content for a particular {@link EffectiveModelContext}.
99 * @param context Context to use
100 * @return Content of HTTP GET operation as a String
102 public final @NonNull String bodyFor(final @Nullable EffectiveModelContext context) {
103 if (context == null) {
104 // Defensive, return empty content
107 final var modules = context.getModuleStatements();
108 if (modules.isEmpty()) {
109 // No modules, return empty content
113 final var moduleRpcs = modules.values().stream()
114 // Extract XMLNamespaces
115 .map(module -> module.localQNameModule().getNamespace())
116 // Make sure each is XMLNamespace unique
118 // Find the most recent module with that namespace. This needed so we expose the right set of RPCs,
119 // as we always pick the latest revision to resolve prefix (or module name).
120 .map(namespace -> context.findModuleStatements(namespace).iterator().next())
121 // Convert to module prefix + List<String> with RPC names
122 .map(module -> Map.entry(prefix(module), module.streamEffectiveSubstatements(RpcEffectiveStatement.class)
123 .map(rpc -> rpc.argument().getLocalName())
125 // Skip prefixes which do not have any RPCs
126 .filter(entry -> !entry.getValue().isEmpty())
127 // Ensure stability: sort by prefix
128 .sorted(Entry.comparingByKey())
131 return moduleRpcs.isEmpty() ? emptyBody : createBody(moduleRpcs);
134 abstract @NonNull String createBody(List<Entry<String, List<String>>> rpcsByPrefix);
136 abstract @NonNull String prefix(ModuleEffectiveStatement module);