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