2 * Copyright (c) 2023 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.nb.rfc8040.rests.services.impl;
10 import static com.google.common.base.Verify.verifyNotNull;
11 import static java.util.Objects.requireNonNull;
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;
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.rests.transactions.MdsalRestconfStrategy;
36 import org.opendaylight.restconf.nb.rfc8040.rests.transactions.RestconfStrategy;
37 import org.opendaylight.restconf.nb.rfc8040.utils.parser.ParserIdentifier;
38 import org.opendaylight.restconf.server.api.RestconfServer;
39 import org.opendaylight.restconf.server.spi.OperationInput;
40 import org.opendaylight.restconf.server.spi.OperationOutput;
41 import org.opendaylight.restconf.server.spi.RpcImplementation;
42 import org.opendaylight.yangtools.yang.common.ErrorTag;
43 import org.opendaylight.yangtools.yang.common.ErrorType;
44 import org.opendaylight.yangtools.yang.common.QName;
45 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
46 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
47 import org.osgi.service.component.annotations.Activate;
48 import org.osgi.service.component.annotations.Component;
49 import org.osgi.service.component.annotations.Reference;
50 import org.slf4j.Logger;
51 import org.slf4j.LoggerFactory;
54 * A RESTCONF server implemented on top of MD-SAL.
56 // FIXME: this should live in 'org.opendaylight.restconf.server.mdsal' package
58 @Component(service = { MdsalRestconfServer.class, RestconfServer.class })
59 public final class MdsalRestconfServer implements RestconfServer {
60 private static final Logger LOG = LoggerFactory.getLogger(MdsalRestconfServer.class);
61 private static final VarHandle LOCAL_STRATEGY;
65 LOCAL_STRATEGY = MethodHandles.lookup()
66 .findVarHandle(MdsalRestconfServer.class, "localStrategy", RestconfStrategy.class);
67 } catch (NoSuchFieldException | IllegalAccessException e) {
68 throw new ExceptionInInitializerError(e);
72 private final @NonNull ImmutableMap<QName, RpcImplementation> localRpcs;
73 private final @NonNull DOMMountPointService mountPointService;
74 private final @NonNull DatabindProvider databindProvider;
75 private final @NonNull DOMDataBroker dataBroker;
76 private final @Nullable DOMRpcService rpcService;
78 @SuppressWarnings("unused")
79 private volatile RestconfStrategy localStrategy;
83 public MdsalRestconfServer(@Reference final DatabindProvider databindProvider,
84 @Reference final DOMDataBroker dataBroker, @Reference final DOMRpcService rpcService,
85 @Reference final DOMMountPointService mountPointService,
86 @Reference final List<RpcImplementation> localRpcs) {
87 this.databindProvider = requireNonNull(databindProvider);
88 this.dataBroker = requireNonNull(dataBroker);
89 this.rpcService = requireNonNull(rpcService);
90 this.mountPointService = requireNonNull(mountPointService);
91 this.localRpcs = Maps.uniqueIndex(localRpcs, RpcImplementation::qname);
94 public MdsalRestconfServer(final DatabindProvider databind, final DOMDataBroker dataBroker,
95 final DOMRpcService rpcService, final DOMMountPointService mountPointService,
96 final RpcImplementation... localRpcs) {
97 this(databind, dataBroker, rpcService, mountPointService, List.of(localRpcs));
100 @NonNull InstanceIdentifierContext bindRequestPath(final String identifier) {
101 return bindRequestPath(databindProvider.currentContext(), identifier);
105 @NonNull InstanceIdentifierContext bindRequestPath(final DatabindContext databind, final String identifier) {
106 // FIXME: go through ApiPath first. That part should eventually live in callers
107 // FIXME: DatabindContext looks like it should be internal
108 return verifyNotNull(ParserIdentifier.toInstanceIdentifier(requireNonNull(identifier), databind.modelContext(),
113 public RestconfFuture<OperationOutput> invokeRpc(final URI restconfURI, final String apiPath,
114 final OperationInputBody body) {
115 final var currentContext = databindProvider.currentContext();
116 final var reqPath = bindRequestPath(currentContext, apiPath);
117 final var inference = reqPath.inference();
118 final ContainerNode input;
120 input = body.toContainerNode(inference);
121 } catch (IOException e) {
122 LOG.debug("Error reading input", e);
123 return RestconfFuture.failed(new RestconfDocumentedException("Error parsing input: " + e.getMessage(),
124 ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE, e));
127 return getRestconfStrategy(reqPath.getSchemaContext(), reqPath.getMountPoint())
128 .invokeRpc(restconfURI, reqPath.getSchemaNode().getQName(),
129 new OperationInput(currentContext, inference, input));
132 @NonNull InstanceIdentifierContext bindRequestRoot() {
133 return InstanceIdentifierContext.ofLocalRoot(databindProvider.currentContext().modelContext());
137 @NonNull RestconfStrategy getRestconfStrategy(final EffectiveModelContext modelContext,
138 final @Nullable DOMMountPoint mountPoint) {
139 if (mountPoint == null) {
140 return localStrategy(modelContext);
143 final var ret = RestconfStrategy.forMountPoint(modelContext, mountPoint);
145 final var mountId = mountPoint.getIdentifier();
146 LOG.warn("Mount point {} does not expose a suitable access interface", mountId);
147 throw new RestconfDocumentedException("Could not find a supported access interface in mount point "
153 private @NonNull RestconfStrategy localStrategy(final EffectiveModelContext modelContext) {
154 final var local = (RestconfStrategy) LOCAL_STRATEGY.getAcquire(this);
155 if (local != null && modelContext.equals(local.modelContext())) {
159 final var created = new MdsalRestconfStrategy(modelContext, dataBroker, rpcService, localRpcs);
160 LOCAL_STRATEGY.setRelease(this, created);