Rework body formatting wiring
[netconf.git] / restconf / restconf-nb / src / main / java / org / opendaylight / restconf / server / spi / OperationsResource.java
1 /*
2  * Copyright (c) 2021 PANTHEON.tech, s.r.o. and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.restconf.server.spi;
9
10 import static java.util.Objects.requireNonNull;
11
12 import com.google.common.collect.ImmutableSet;
13 import com.google.common.collect.ImmutableSetMultimap;
14 import java.util.Comparator;
15 import java.util.HashMap;
16 import java.util.Map;
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;
30
31 /**
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>.
34  */
35 @NonNullByDefault
36 public final class OperationsResource implements HttpGetResource {
37     private final ApiPathNormalizer pathNormalizer;
38
39     public OperationsResource(final ApiPathNormalizer pathNormalizer) {
40         this.pathNormalizer = requireNonNull(pathNormalizer);
41     }
42
43     @Override
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);
57             }
58         }
59
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)))
65                 .findFirst()
66                 .ifPresent(row -> rpcs.putAll(QNameModule.of(entry.getKey(), row.getKey()), row.getValue()));
67         }
68         return RestconfFuture.of(new AllOperations(modelContext, rpcs.build()));
69     }
70
71     @Override
72     public RestconfFuture<FormattableBody> httpGET(final ServerRequest request, final ApiPath apiPath) {
73         final Rpc path;
74         try {
75             path = pathNormalizer.normalizeRpcPath(apiPath);
76         } catch (RestconfDocumentedException e) {
77             return RestconfFuture.failed(e);
78         }
79         return RestconfFuture.of(new OneOperation(path.inference().modelContext(), path.rpc().argument()));
80     }
81 }