Bump upstreams
[netconf.git] / restconf / restconf-nb / src / main / java / org / opendaylight / restconf / server / mdsal / CapabilitiesWriter.java
1 /*
2  * Copyright (c) 2022 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.mdsal;
9
10 import static java.util.Objects.requireNonNull;
11 import static org.opendaylight.yang.svc.v1.urn.ietf.params.xml.ns.yang.ietf.restconf.monitoring.rev170126.YangModuleInfoImpl.qnameOf;
12
13 import com.google.common.annotations.VisibleForTesting;
14 import com.google.common.util.concurrent.FutureCallback;
15 import com.google.common.util.concurrent.MoreExecutors;
16 import javax.annotation.PreDestroy;
17 import javax.inject.Inject;
18 import javax.inject.Singleton;
19 import org.checkerframework.checker.lock.qual.Holding;
20 import org.eclipse.jdt.annotation.NonNull;
21 import org.opendaylight.mdsal.common.api.CommitInfo;
22 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
23 import org.opendaylight.mdsal.dom.api.DOMDataBroker;
24 import org.opendaylight.mdsal.dom.api.DOMSchemaService;
25 import org.opendaylight.mdsal.dom.api.DOMTransactionChain;
26 import org.opendaylight.restconf.api.query.AbstractReplayParam;
27 import org.opendaylight.restconf.api.query.ChangedLeafNodesOnlyParam;
28 import org.opendaylight.restconf.api.query.ChildNodesOnlyParam;
29 import org.opendaylight.restconf.api.query.DepthParam;
30 import org.opendaylight.restconf.api.query.FieldsParam;
31 import org.opendaylight.restconf.api.query.FilterParam;
32 import org.opendaylight.restconf.api.query.LeafNodesOnlyParam;
33 import org.opendaylight.restconf.api.query.PrettyPrintParam;
34 import org.opendaylight.restconf.api.query.SkipNotificationDataParam;
35 import org.opendaylight.restconf.api.query.WithDefaultsParam;
36 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.restconf.monitoring.rev170126.RestconfState;
37 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.restconf.monitoring.rev170126.restconf.state.Capabilities;
38 import org.opendaylight.yangtools.concepts.Registration;
39 import org.opendaylight.yangtools.yang.common.Empty;
40 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
41 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
42 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
43 import org.opendaylight.yangtools.yang.data.spi.node.ImmutableNodes;
44 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
45 import org.osgi.service.component.annotations.Activate;
46 import org.osgi.service.component.annotations.Component;
47 import org.osgi.service.component.annotations.Deactivate;
48 import org.osgi.service.component.annotations.Reference;
49 import org.slf4j.Logger;
50 import org.slf4j.LoggerFactory;
51
52 /**
53  * A simple component which maintains {@link Capabilities} in the operational datastore.
54  */
55 @Singleton
56 @Component(service = { })
57 public final class CapabilitiesWriter implements AutoCloseable, FutureCallback<Empty> {
58     private static final Logger LOG = LoggerFactory.getLogger(CapabilitiesWriter.class);
59
60     @VisibleForTesting
61     static final @NonNull NodeIdentifier CAPABILITY = NodeIdentifier.create(qnameOf("capability"));
62
63     private static final YangInstanceIdentifier PATH = YangInstanceIdentifier.of(
64         NodeIdentifier.create(RestconfState.QNAME), NodeIdentifier.create(Capabilities.QNAME), CAPABILITY);
65
66     private final DOMDataBroker dataBroker;
67
68     private DOMTransactionChain txChain;
69     private Registration reg;
70
71     private boolean written;
72
73     @Inject
74     @Activate
75     public CapabilitiesWriter(@Reference final DOMDataBroker dataBroker,
76             @Reference final DOMSchemaService schemaService) {
77         this.dataBroker = requireNonNull(dataBroker);
78         reg = schemaService.registerSchemaContextListener(this::onModelContextUpdated);
79     }
80
81     @PreDestroy
82     @Deactivate
83     @Override
84     public synchronized void close() {
85         if (reg == null) {
86             return;
87         }
88         reg.close();
89         reg = null;
90         deleteRestconfState();
91         if (txChain != null) {
92             txChain.close();
93         }
94     }
95
96     @Override
97     public synchronized void onFailure(final Throwable cause) {
98         LOG.warn("Transaction chain failed, updates may not have been propagated", cause);
99         txChain = null;
100     }
101
102     @Override
103     public synchronized void onSuccess(final Empty result) {
104         LOG.debug("Transaction chain closed successfully");
105         txChain = null;
106     }
107
108     private synchronized void onModelContextUpdated(final EffectiveModelContext newModelContext) {
109         if (reg != null) {
110             LOG.debug("Ignoring model context update");
111             return;
112         }
113
114         if (newModelContext.findModuleStatement(RestconfState.QNAME.getModule()).isPresent()) {
115             writeRestconfState();
116         } else {
117             deleteRestconfState();
118         }
119     }
120
121     @Holding("this")
122     private void deleteRestconfState() {
123         if (!written) {
124             LOG.debug("No state recorded as written, not attempting removal");
125             return;
126         }
127
128         LOG.debug("Removing ietf-restconf-monitoring state");
129         if (txChain == null) {
130             txChain = dataBroker.createMergingTransactionChain();
131             txChain.addCallback(this);
132         }
133
134         final var tx = txChain.newWriteOnlyTransaction();
135         tx.delete(LogicalDatastoreType.OPERATIONAL, PATH);
136         tx.commit().addCallback(new FutureCallback<CommitInfo>() {
137             @Override
138             public void onSuccess(final CommitInfo result) {
139                 markUnwritten();
140             }
141
142             @Override
143             public void onFailure(final Throwable cause) {
144                 // Ignored, will be reported on the transaction chain
145             }
146         }, MoreExecutors.directExecutor());
147     }
148
149     @Holding("this")
150     private void writeRestconfState() {
151         if (written) {
152             LOG.debug("State recorded as written, not updating it");
153             return;
154         }
155
156         LOG.debug("Updating state of ietf-restconf-monitoring");
157         if (txChain == null) {
158             txChain = dataBroker.createMergingTransactionChain();
159             txChain.addCallback(this);
160         }
161
162         final var tx = txChain.newWriteOnlyTransaction();
163         tx.put(LogicalDatastoreType.OPERATIONAL, PATH, mapCapabilities());
164         tx.commit().addCallback(new FutureCallback<CommitInfo>() {
165             @Override
166             public void onSuccess(final CommitInfo result) {
167                 markWritten();
168             }
169
170             @Override
171             public void onFailure(final Throwable cause) {
172                 // Ignored, will be reported on the transaction chain
173             }
174         }, MoreExecutors.directExecutor());
175     }
176
177     private synchronized void markWritten() {
178         LOG.debug("State of ietf-restconf-monitoring updated");
179         written = true;
180     }
181
182     private synchronized void markUnwritten() {
183         LOG.debug("State of ietf-restconf-monitoring removed");
184         written = false;
185     }
186
187     /**
188      * Create a {@code restconf-state} container.
189      *
190      * @return A container holding capabilities
191      */
192     @VisibleForTesting
193     static @NonNull LeafSetNode<String> mapCapabilities() {
194         return ImmutableNodes.<String>newSystemLeafSetBuilder()
195             .withNodeIdentifier(CAPABILITY)
196             .withChildValue(DepthParam.capabilityUri().toString())
197             .withChildValue(FieldsParam.capabilityUri().toString())
198             .withChildValue(FilterParam.capabilityUri().toString())
199             .withChildValue(AbstractReplayParam.capabilityUri().toString())
200             .withChildValue(WithDefaultsParam.capabilityUri().toString())
201             .withChildValue(PrettyPrintParam.capabilityUri().toString())
202             .withChildValue(LeafNodesOnlyParam.capabilityUri().toString())
203             .withChildValue(ChangedLeafNodesOnlyParam.capabilityUri().toString())
204             .withChildValue(SkipNotificationDataParam.capabilityUri().toString())
205             .withChildValue(ChildNodesOnlyParam.capabilityUri().toString())
206             .build();
207     }
208 }