Codify operationsGET
[netconf.git] / restconf / restconf-nb / src / main / java / org / opendaylight / restconf / nb / rfc8040 / rests / services / impl / MdsalRestconfServer.java
1 /*
2  * Copyright (c) 2023 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.nb.rfc8040.rests.services.impl;
9
10 import static com.google.common.base.Verify.verifyNotNull;
11 import static java.util.Objects.requireNonNull;
12
13 import com.google.common.annotations.VisibleForTesting;
14 import com.google.common.collect.ImmutableMap;
15 import com.google.common.collect.Maps;
16 import java.io.IOException;
17 import java.lang.invoke.MethodHandles;
18 import java.lang.invoke.VarHandle;
19 import java.net.URI;
20 import java.util.List;
21 import javax.inject.Inject;
22 import javax.inject.Singleton;
23 import org.eclipse.jdt.annotation.NonNull;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.opendaylight.mdsal.dom.api.DOMDataBroker;
26 import org.opendaylight.mdsal.dom.api.DOMMountPoint;
27 import org.opendaylight.mdsal.dom.api.DOMMountPointService;
28 import org.opendaylight.mdsal.dom.api.DOMRpcService;
29 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
30 import org.opendaylight.restconf.common.errors.RestconfFuture;
31 import org.opendaylight.restconf.nb.rfc8040.databind.DatabindContext;
32 import org.opendaylight.restconf.nb.rfc8040.databind.DatabindProvider;
33 import org.opendaylight.restconf.nb.rfc8040.databind.OperationInputBody;
34 import org.opendaylight.restconf.nb.rfc8040.legacy.InstanceIdentifierContext;
35 import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
36 import org.opendaylight.restconf.nb.rfc8040.rests.transactions.MdsalRestconfStrategy;
37 import org.opendaylight.restconf.nb.rfc8040.rests.transactions.RestconfStrategy;
38 import org.opendaylight.restconf.nb.rfc8040.utils.parser.ParserIdentifier;
39 import org.opendaylight.restconf.server.api.RestconfServer;
40 import org.opendaylight.restconf.server.spi.OperationInput;
41 import org.opendaylight.restconf.server.spi.OperationOutput;
42 import org.opendaylight.restconf.server.spi.RpcImplementation;
43 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.restconf.rev170126.YangApi;
44 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.restconf.rev170126.restconf.Restconf;
45 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.library.rev190104.YangLibrary;
46 import org.opendaylight.yangtools.yang.common.ErrorTag;
47 import org.opendaylight.yangtools.yang.common.ErrorType;
48 import org.opendaylight.yangtools.yang.common.QName;
49 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
50 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
51 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
52 import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
53 import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack.Inference;
54 import org.osgi.service.component.annotations.Activate;
55 import org.osgi.service.component.annotations.Component;
56 import org.osgi.service.component.annotations.Reference;
57 import org.slf4j.Logger;
58 import org.slf4j.LoggerFactory;
59
60 /**
61  * A RESTCONF server implemented on top of MD-SAL.
62  */
63 // FIXME: this should live in 'org.opendaylight.restconf.server.mdsal' package
64 @Singleton
65 @Component(service = { MdsalRestconfServer.class, RestconfServer.class })
66 public final class MdsalRestconfServer implements RestconfServer {
67     private static final Logger LOG = LoggerFactory.getLogger(MdsalRestconfServer.class);
68     private static final QName YANG_LIBRARY_VERSION = QName.create(Restconf.QNAME, "yang-library-version").intern();
69     private static final String YANG_LIBRARY_REVISION = YangLibrary.QNAME.getRevision().orElseThrow().toString();
70     private static final VarHandle LOCAL_STRATEGY;
71
72     static {
73         try {
74             LOCAL_STRATEGY = MethodHandles.lookup()
75                 .findVarHandle(MdsalRestconfServer.class, "localStrategy", RestconfStrategy.class);
76         } catch (NoSuchFieldException | IllegalAccessException e) {
77             throw new ExceptionInInitializerError(e);
78         }
79     }
80
81     private final @NonNull ImmutableMap<QName, RpcImplementation> localRpcs;
82     private final @NonNull DOMMountPointService mountPointService;
83     private final @NonNull DatabindProvider databindProvider;
84     private final @NonNull DOMDataBroker dataBroker;
85     private final @Nullable DOMRpcService rpcService;
86
87     @SuppressWarnings("unused")
88     private volatile RestconfStrategy localStrategy;
89
90     @Inject
91     @Activate
92     public MdsalRestconfServer(@Reference final DatabindProvider databindProvider,
93             @Reference final DOMDataBroker dataBroker, @Reference final DOMRpcService rpcService,
94             @Reference final DOMMountPointService mountPointService,
95             @Reference final List<RpcImplementation> localRpcs) {
96         this.databindProvider = requireNonNull(databindProvider);
97         this.dataBroker = requireNonNull(dataBroker);
98         this.rpcService = requireNonNull(rpcService);
99         this.mountPointService = requireNonNull(mountPointService);
100         this.localRpcs = Maps.uniqueIndex(localRpcs, RpcImplementation::qname);
101     }
102
103     public MdsalRestconfServer(final DatabindProvider databind, final DOMDataBroker dataBroker,
104             final DOMRpcService rpcService, final DOMMountPointService mountPointService,
105             final RpcImplementation... localRpcs) {
106         this(databind, dataBroker, rpcService, mountPointService, List.of(localRpcs));
107     }
108
109     @NonNull InstanceIdentifierContext bindRequestPath(final String identifier) {
110         return bindRequestPath(databindProvider.currentContext(), identifier);
111     }
112
113     @Deprecated
114     @NonNull InstanceIdentifierContext bindRequestPath(final DatabindContext databind, final String identifier) {
115         // FIXME: go through ApiPath first. That part should eventually live in callers
116         // FIXME: DatabindContext looks like it should be internal
117         return verifyNotNull(ParserIdentifier.toInstanceIdentifier(requireNonNull(identifier), databind.modelContext(),
118             mountPointService));
119     }
120
121     @Override
122     public NormalizedNodePayload yangLibraryVersionGET() {
123         final var stack = SchemaInferenceStack.of(databindProvider.currentContext().modelContext());
124         stack.enterYangData(YangApi.NAME);
125         stack.enterDataTree(Restconf.QNAME);
126         stack.enterDataTree(YANG_LIBRARY_VERSION);
127         return new NormalizedNodePayload(stack.toInference(),
128             ImmutableNodes.leafNode(YANG_LIBRARY_VERSION, YANG_LIBRARY_REVISION));
129     }
130
131     @Override
132     public String operationsGET(final OperationsContent contentType) {
133         return operationsGET(contentType, bindRequestRoot().inference());
134     }
135
136     @Override
137     public String operationsGET(final OperationsContent contentType, final String operation) {
138         return operationsGET(contentType, bindRequestPath(operation).inference());
139     }
140
141     @VisibleForTesting
142     static @NonNull String operationsGET(final OperationsContent contentType, final @NonNull Inference inference) {
143         return contentType.bodyFor(inference);
144     }
145
146     @Override
147     public RestconfFuture<OperationOutput> operationsPOST(final URI restconfURI, final String apiPath,
148             final OperationInputBody body) {
149         final var currentContext = databindProvider.currentContext();
150         final var reqPath = bindRequestPath(currentContext, apiPath);
151         final var inference = reqPath.inference();
152         final ContainerNode input;
153         try {
154             input = body.toContainerNode(inference);
155         } catch (IOException e) {
156             LOG.debug("Error reading input", e);
157             return RestconfFuture.failed(new RestconfDocumentedException("Error parsing input: " + e.getMessage(),
158                 ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE, e));
159         }
160
161         return getRestconfStrategy(reqPath.getSchemaContext(), reqPath.getMountPoint())
162             .invokeRpc(restconfURI, reqPath.getSchemaNode().getQName(),
163                 new OperationInput(currentContext, inference, input));
164     }
165
166     @NonNull InstanceIdentifierContext bindRequestRoot() {
167         return InstanceIdentifierContext.ofLocalRoot(databindProvider.currentContext().modelContext());
168     }
169
170     @VisibleForTesting
171     @NonNull RestconfStrategy getRestconfStrategy(final EffectiveModelContext modelContext,
172             final @Nullable DOMMountPoint mountPoint) {
173         if (mountPoint == null) {
174             return localStrategy(modelContext);
175         }
176
177         final var ret = RestconfStrategy.forMountPoint(modelContext, mountPoint);
178         if (ret == null) {
179             final var mountId = mountPoint.getIdentifier();
180             LOG.warn("Mount point {} does not expose a suitable access interface", mountId);
181             throw new RestconfDocumentedException("Could not find a supported access interface in mount point "
182                 + mountId);
183         }
184         return ret;
185     }
186
187     private @NonNull RestconfStrategy localStrategy(final EffectiveModelContext modelContext) {
188         final var local = (RestconfStrategy) LOCAL_STRATEGY.getAcquire(this);
189         if (local != null && modelContext.equals(local.modelContext())) {
190             return local;
191         }
192
193         final var created = new MdsalRestconfStrategy(modelContext, dataBroker, rpcService, localRpcs);
194         LOCAL_STRATEGY.setRelease(this, created);
195         return created;
196     }
197 }