/* * 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.nb.rfc8040.rests.services.impl; 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 * RFC8040. */ enum OperationsContent { JSON("{ \"ietf-restconf:operations\" : { } }") { @Override String createBody(final List>> 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("") { @Override String createBody(final List>> rpcsByPrefix) { // Header with namespace declarations for each module final var sb = new StringBuilder("\n" + ""); // 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").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., 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>>(); 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>> rpcsByPrefix); abstract @NonNull String prefix(ModuleEffectiveStatement module); }