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.server.spi;
10 import static java.util.Objects.requireNonNull;
12 import com.google.common.collect.ImmutableSet;
13 import com.google.common.collect.ImmutableSetMultimap;
14 import java.util.Comparator;
15 import java.util.HashMap;
17 import java.util.Map.Entry;
18 import org.eclipse.jdt.annotation.NonNullByDefault;
19 import org.opendaylight.restconf.api.ApiPath;
20 import org.opendaylight.restconf.api.FormattableBody;
21 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
22 import org.opendaylight.restconf.common.errors.RestconfFuture;
23 import org.opendaylight.restconf.server.api.DatabindPath.Rpc;
24 import org.opendaylight.restconf.server.api.ServerRequest;
25 import org.opendaylight.yangtools.yang.common.QName;
26 import org.opendaylight.yangtools.yang.common.QNameModule;
27 import org.opendaylight.yangtools.yang.common.Revision;
28 import org.opendaylight.yangtools.yang.common.XMLNamespace;
29 import org.opendaylight.yangtools.yang.model.api.stmt.RpcEffectiveStatement;
32 * RESTCONF {@code /operations} content for a {@code GET} operation as per
33 * <a href="https://www.rfc-editor.org/rfc/rfc8040#section-3.3.2">RFC8040</a>.
36 public final class OperationsResource implements HttpGetResource {
37 private final ApiPathNormalizer pathNormalizer;
39 public OperationsResource(final ApiPathNormalizer pathNormalizer) {
40 this.pathNormalizer = requireNonNull(pathNormalizer);
44 public RestconfFuture<FormattableBody> httpGET(final ServerRequest request) {
45 // RPC QNames by their XMLNamespace/Revision. This should be a Table, but Revision can be null, which wrecks us.
46 final var table = new HashMap<XMLNamespace, Map<Revision, ImmutableSet<QName>>>();
47 final var modelContext = pathNormalizer.databind().modelContext();
48 for (var entry : modelContext.getModuleStatements().entrySet()) {
49 final var module = entry.getValue();
50 final var rpcNames = module.streamEffectiveSubstatements(RpcEffectiveStatement.class)
51 .map(RpcEffectiveStatement::argument)
52 .collect(ImmutableSet.toImmutableSet());
53 if (!rpcNames.isEmpty()) {
54 final var namespace = entry.getKey();
55 table.computeIfAbsent(namespace.namespace(), ignored -> new HashMap<>())
56 .put(namespace.revision(), rpcNames);
60 // Now pick the latest revision for each namespace
61 final var rpcs = ImmutableSetMultimap.<QNameModule, QName>builder();
62 for (var entry : table.entrySet()) {
63 entry.getValue().entrySet().stream()
64 .sorted(Comparator.comparing(Entry::getKey, (first, second) -> Revision.compare(second, first)))
66 .ifPresent(row -> rpcs.putAll(QNameModule.ofRevision(entry.getKey(), row.getKey()), row.getValue()));
68 return RestconfFuture.of(new AllOperations(modelContext, rpcs.build()));
72 public RestconfFuture<FormattableBody> httpGET(final ServerRequest request, final ApiPath apiPath) {
75 path = pathNormalizer.normalizeRpcPath(apiPath);
76 } catch (RestconfDocumentedException e) {
77 return RestconfFuture.failed(e);
79 return RestconfFuture.of(new OneOperation(path.inference().modelContext(), path.rpc().argument()));