summaryrefslogtreecommitdiffstats
path: root/netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/OperationsContent.java
diff options
context:
space:
mode:
Diffstat (limited to 'netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/OperationsContent.java')
-rw-r--r--netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/OperationsContent.java156
1 files changed, 156 insertions, 0 deletions
diff --git a/netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/OperationsContent.java b/netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/OperationsContent.java
new file mode 100644
index 0000000..199728f
--- /dev/null
+++ b/netconf/restconf/restconf-common/src/main/java/org/opendaylight/restconf/common/OperationsContent.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) 2021 PANTHEON.tech, s.r.o. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.restconf.common;
+
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.collect.HashBasedTable;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.opendaylight.yangtools.yang.common.Revision;
+import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
+import org.opendaylight.yangtools.yang.model.api.stmt.ModuleEffectiveStatement;
+import org.opendaylight.yangtools.yang.model.api.stmt.RpcEffectiveStatement;
+
+/**
+ * RESTCONF {@code /operations} content for a {@code GET} operation as per
+ * <a href="https://datatracker.ietf.org/doc/html/rfc8040#section-3.3.2">RFC8040</a>.
+ */
+// FIXME: when bierman02 is gone, this should be folded to nb-rfc8040, as it is a server-side thing.
+public enum OperationsContent {
+ JSON("{ \"ietf-restconf:operations\" : { } }") {
+ @Override
+ String createBody(final List<Entry<String, List<String>>> rpcsByPrefix) {
+ final var sb = new StringBuilder("{\n"
+ + " \"ietf-restconf:operations\" : {\n");
+ var entryIt = rpcsByPrefix.iterator();
+ var entry = entryIt.next();
+ var nameIt = entry.getValue().iterator();
+ while (true) {
+ sb.append(" \"").append(entry.getKey()).append(':').append(nameIt.next()).append("\": [null]");
+ if (nameIt.hasNext()) {
+ sb.append(",\n");
+ continue;
+ }
+
+ if (entryIt.hasNext()) {
+ sb.append(",\n");
+ entry = entryIt.next();
+ nameIt = entry.getValue().iterator();
+ continue;
+ }
+
+ break;
+ }
+
+ return sb.append("\n }\n}").toString();
+ }
+
+ @Override
+ String prefix(final ModuleEffectiveStatement module) {
+ return module.argument().getLocalName();
+ }
+ },
+
+ XML("<operations xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\"/>") {
+ @Override
+ String createBody(final List<Entry<String, List<String>>> rpcsByPrefix) {
+ // Header with namespace declarations for each module
+ final var sb = new StringBuilder("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ + "<operations xmlns=\"urn:ietf:params:xml:ns:yang:ietf-restconf\"");
+ for (int i = 0; i < rpcsByPrefix.size(); ++i) {
+ final var prefix = "ns" + i;
+ sb.append("\n xmlns:").append(prefix).append("=\"").append(rpcsByPrefix.get(i).getKey())
+ .append("\"");
+ }
+ sb.append(" >");
+
+ // Second pass: emit all leaves
+ for (int i = 0; i < rpcsByPrefix.size(); ++i) {
+ final var prefix = "ns" + i;
+ for (var localName : rpcsByPrefix.get(i).getValue()) {
+ sb.append("\n <").append(prefix).append(':').append(localName).append("/>");
+ }
+ }
+
+ return sb.append("\n</operations>").toString();
+ }
+
+ @Override
+ String prefix(final ModuleEffectiveStatement module) {
+ return module.localQNameModule().getNamespace().toString();
+ }
+ };
+
+ private final @NonNull String emptyBody;
+
+ OperationsContent(final String emptyBody) {
+ this.emptyBody = requireNonNull(emptyBody);
+ }
+
+ /**
+ * Return the content for a particular {@link EffectiveModelContext}.
+ *
+ * @param context Context to use
+ * @return Content of HTTP GET operation as a String
+ */
+ public final @NonNull String bodyFor(final @Nullable EffectiveModelContext context) {
+ if (context == null) {
+ return emptyBody;
+ }
+ final var modules = context.getModuleStatements();
+ if (modules.isEmpty()) {
+ return emptyBody;
+ }
+
+ // Index into prefix -> revision -> module table
+ final var prefixRevModule = HashBasedTable.<String, Optional<Revision>, ModuleEffectiveStatement>create();
+ for (var module : modules.values()) {
+ prefixRevModule.put(prefix(module), module.localQNameModule().getRevision(), module);
+ }
+
+ // Now extract RPC names for each module with highest revision. This needed so we expose the right set of RPCs,
+ // as we always pick the latest revision to resolve prefix (or module name)
+ // TODO: Simplify this once we have yangtools-7.0.9+
+ final var moduleRpcs = new ArrayList<Entry<String, List<String>>>();
+ for (var moduleEntry : prefixRevModule.rowMap().entrySet()) {
+ final var revisions = new ArrayList<>(moduleEntry.getValue().keySet());
+ revisions.sort(Revision::compare);
+ final var selectedRevision = revisions.get(revisions.size() - 1);
+
+ final var rpcNames = moduleEntry.getValue().get(selectedRevision)
+ .streamEffectiveSubstatements(RpcEffectiveStatement.class)
+ .map(rpc -> rpc.argument().getLocalName())
+ .collect(Collectors.toUnmodifiableList());
+ if (!rpcNames.isEmpty()) {
+ moduleRpcs.add(Map.entry(moduleEntry.getKey(), rpcNames));
+ }
+ }
+
+ if (moduleRpcs.isEmpty()) {
+ // No RPCs, return empty content
+ return emptyBody;
+ }
+
+ // Ensure stability: sort by prefix
+ moduleRpcs.sort(Comparator.comparing(Entry::getKey));
+
+ return modules.isEmpty() ? emptyBody : createBody(moduleRpcs);
+ }
+
+ abstract @NonNull String createBody(List<Entry<String, List<String>>> rpcsByPrefix);
+
+ abstract @NonNull String prefix(ModuleEffectiveStatement module);
+}