Use ImmutableNodes.fromInstanceId in restconf
[controller.git] / opendaylight / md-sal / sal-rest-connector / src / main / java / org / opendaylight / controller / sal / restconf / impl / BrokerFacade.java
1 /**
2  * Copyright (c) 2014 Cisco Systems, Inc. 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.controller.sal.restconf.impl;
9
10 import static org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType.CONFIGURATION;
11 import static org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType.OPERATIONAL;
12
13 import com.google.common.base.Optional;
14 import com.google.common.base.Preconditions;
15 import com.google.common.util.concurrent.CheckedFuture;
16 import com.google.common.util.concurrent.ListenableFuture;
17 import java.util.ArrayList;
18 import java.util.Iterator;
19 import java.util.List;
20 import java.util.concurrent.ExecutionException;
21 import javax.ws.rs.core.Response.Status;
22 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
23 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
24 import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
25 import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker;
26 import org.opendaylight.controller.md.sal.dom.api.DOMDataChangeListener;
27 import org.opendaylight.controller.md.sal.dom.api.DOMDataReadTransaction;
28 import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
29 import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
30 import org.opendaylight.controller.md.sal.dom.api.DOMMountPoint;
31 import org.opendaylight.controller.md.sal.dom.api.DOMRpcException;
32 import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult;
33 import org.opendaylight.controller.md.sal.dom.api.DOMRpcService;
34 import org.opendaylight.controller.sal.core.api.Broker.ConsumerSession;
35 import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorTag;
36 import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorType;
37 import org.opendaylight.controller.sal.streams.listeners.ListenerAdapter;
38 import org.opendaylight.yangtools.concepts.ListenerRegistration;
39 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
40 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
41 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
42 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
43 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
44 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
45 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
48
49 public class BrokerFacade {
50     private final static Logger LOG = LoggerFactory.getLogger(BrokerFacade.class);
51
52     private final static BrokerFacade INSTANCE = new BrokerFacade();
53     private volatile DOMRpcService rpcService;
54     private volatile ConsumerSession context;
55     private DOMDataBroker domDataBroker;
56
57     private BrokerFacade() {
58     }
59
60     public void setRpcService(final DOMRpcService router) {
61         rpcService = router;
62     }
63
64     public void setContext(final ConsumerSession context) {
65         this.context = context;
66     }
67
68     public static BrokerFacade getInstance() {
69         return BrokerFacade.INSTANCE;
70     }
71
72     private void checkPreconditions() {
73         if (context == null || domDataBroker == null) {
74             throw new RestconfDocumentedException(Status.SERVICE_UNAVAILABLE);
75         }
76     }
77
78     // READ configuration
79     public NormalizedNode<?, ?> readConfigurationData(final YangInstanceIdentifier path) {
80         checkPreconditions();
81         return readDataViaTransaction(domDataBroker.newReadOnlyTransaction(), CONFIGURATION, path);
82     }
83
84     public NormalizedNode<?, ?> readConfigurationData(final DOMMountPoint mountPoint, final YangInstanceIdentifier path) {
85         final Optional<DOMDataBroker> domDataBrokerService = mountPoint.getService(DOMDataBroker.class);
86         if (domDataBrokerService.isPresent()) {
87             return readDataViaTransaction(domDataBrokerService.get().newReadOnlyTransaction(), CONFIGURATION, path);
88         }
89         throw new RestconfDocumentedException("DOM data broker service isn't available for mount point.");
90     }
91
92     // READ operational
93     public NormalizedNode<?, ?> readOperationalData(final YangInstanceIdentifier path) {
94         checkPreconditions();
95         return readDataViaTransaction(domDataBroker.newReadOnlyTransaction(), OPERATIONAL, path);
96     }
97
98     public NormalizedNode<?, ?> readOperationalData(final DOMMountPoint mountPoint, final YangInstanceIdentifier path) {
99         final Optional<DOMDataBroker> domDataBrokerService = mountPoint.getService(DOMDataBroker.class);
100         if (domDataBrokerService.isPresent()) {
101             return readDataViaTransaction(domDataBrokerService.get().newReadOnlyTransaction(), OPERATIONAL, path);
102         }
103         throw new RestconfDocumentedException("DOM data broker service isn't available for mount point.");
104     }
105
106     // PUT configuration
107     public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataPut(
108             final SchemaContext globalSchema, final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload) {
109         checkPreconditions();
110         return putDataViaTransaction(domDataBroker.newReadWriteTransaction(), CONFIGURATION, path, payload, globalSchema);
111     }
112
113     public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataPut(
114             final DOMMountPoint mountPoint, final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload) {
115         final Optional<DOMDataBroker> domDataBrokerService = mountPoint.getService(DOMDataBroker.class);
116         if (domDataBrokerService.isPresent()) {
117             return putDataViaTransaction(domDataBrokerService.get().newReadWriteTransaction(), CONFIGURATION, path,
118                     payload, mountPoint.getSchemaContext());
119         }
120         throw new RestconfDocumentedException("DOM data broker service isn't available for mount point.");
121     }
122
123     // POST configuration
124     public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataPost(
125             final SchemaContext globalSchema, final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload) {
126         checkPreconditions();
127         return postDataViaTransaction(domDataBroker.newReadWriteTransaction(), CONFIGURATION, path, payload, globalSchema);
128     }
129
130     public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataPost(
131             final DOMMountPoint mountPoint, final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload) {
132         final Optional<DOMDataBroker> domDataBrokerService = mountPoint.getService(DOMDataBroker.class);
133         if (domDataBrokerService.isPresent()) {
134             return postDataViaTransaction(domDataBrokerService.get().newReadWriteTransaction(), CONFIGURATION, path,
135                     payload, mountPoint.getSchemaContext());
136         }
137         throw new RestconfDocumentedException("DOM data broker service isn't available for mount point.");
138     }
139
140     // DELETE configuration
141     public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataDelete(
142             final YangInstanceIdentifier path) {
143         checkPreconditions();
144         return deleteDataViaTransaction(domDataBroker.newWriteOnlyTransaction(), CONFIGURATION, path);
145     }
146
147     public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataDelete(
148             final DOMMountPoint mountPoint, final YangInstanceIdentifier path) {
149         final Optional<DOMDataBroker> domDataBrokerService = mountPoint.getService(DOMDataBroker.class);
150         if (domDataBrokerService.isPresent()) {
151             return deleteDataViaTransaction(domDataBrokerService.get().newWriteOnlyTransaction(), CONFIGURATION, path);
152         }
153         throw new RestconfDocumentedException("DOM data broker service isn't available for mount point.");
154     }
155
156     // RPC
157     public CheckedFuture<DOMRpcResult, DOMRpcException> invokeRpc(final SchemaPath type, final NormalizedNode<?, ?> input) {
158         checkPreconditions();
159         if (rpcService == null) {
160             throw new RestconfDocumentedException(Status.SERVICE_UNAVAILABLE);
161         }
162         return rpcService.invokeRpc(type, input);
163     }
164
165     public void registerToListenDataChanges(final LogicalDatastoreType datastore, final DataChangeScope scope,
166             final ListenerAdapter listener) {
167         checkPreconditions();
168
169         if (listener.isListening()) {
170             return;
171         }
172
173         final YangInstanceIdentifier path = listener.getPath();
174         final ListenerRegistration<DOMDataChangeListener> registration = domDataBroker.registerDataChangeListener(
175                 datastore, path, listener, scope);
176
177         listener.setRegistration(registration);
178     }
179
180     private NormalizedNode<?, ?> readDataViaTransaction(final DOMDataReadTransaction transaction,
181             final LogicalDatastoreType datastore, final YangInstanceIdentifier path) {
182         LOG.trace("Read " + datastore.name() + " via Restconf: {}", path);
183         final ListenableFuture<Optional<NormalizedNode<?, ?>>> listenableFuture = transaction.read(datastore, path);
184         if (listenableFuture != null) {
185             Optional<NormalizedNode<?, ?>> optional;
186             try {
187                 LOG.debug("Reading result data from transaction.");
188                 optional = listenableFuture.get();
189             } catch (InterruptedException | ExecutionException e) {
190                 throw new RestconfDocumentedException("Problem to get data from transaction.", e.getCause());
191
192             }
193             if (optional != null) {
194                 if (optional.isPresent()) {
195                     return optional.get();
196                 }
197             }
198         }
199         return null;
200     }
201
202     private CheckedFuture<Void, TransactionCommitFailedException> postDataViaTransaction(
203             final DOMDataReadWriteTransaction rWTransaction, final LogicalDatastoreType datastore,
204             final YangInstanceIdentifier parentPath, final NormalizedNode<?, ?> payload, final SchemaContext schemaContext) {
205         // FIXME: This is doing correct post for container and list children
206         //        not sure if this will work for choice case
207         final YangInstanceIdentifier path;
208         if(payload instanceof MapEntryNode) {
209             path = parentPath.node(payload.getNodeType()).node(payload.getIdentifier());
210         } else {
211             path = parentPath.node(payload.getIdentifier());
212         }
213
214         final ListenableFuture<Optional<NormalizedNode<?, ?>>> futureDatastoreData = rWTransaction.read(datastore, path);
215         try {
216             final Optional<NormalizedNode<?, ?>> optionalDatastoreData = futureDatastoreData.get();
217             if (optionalDatastoreData.isPresent() && payload.equals(optionalDatastoreData.get())) {
218                 final String errMsg = "Post Configuration via Restconf was not executed because data already exists";
219                 LOG.trace(errMsg + ":{}", path);
220                 rWTransaction.cancel();
221                 throw new RestconfDocumentedException("Data already exists for path: " + path, ErrorType.PROTOCOL,
222                         ErrorTag.DATA_EXISTS);
223             }
224         } catch (InterruptedException | ExecutionException e) {
225             LOG.trace("It wasn't possible to get data loaded from datastore at path " + path);
226         }
227
228         ensureParentsByMerge(datastore, path, rWTransaction, schemaContext);
229         rWTransaction.merge(datastore, path, payload);
230         LOG.trace("Post " + datastore.name() + " via Restconf: {}", path);
231         return rWTransaction.submit();
232     }
233
234     private CheckedFuture<Void, TransactionCommitFailedException> putDataViaTransaction(
235             final DOMDataReadWriteTransaction writeTransaction, final LogicalDatastoreType datastore,
236             final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload, final SchemaContext schemaContext) {
237         LOG.trace("Put " + datastore.name() + " via Restconf: {}", path);
238         ensureParentsByMerge(datastore, path, writeTransaction, schemaContext);
239         writeTransaction.put(datastore, path, payload);
240         return writeTransaction.submit();
241     }
242
243     private CheckedFuture<Void, TransactionCommitFailedException> deleteDataViaTransaction(
244             final DOMDataWriteTransaction writeTransaction, final LogicalDatastoreType datastore,
245             final YangInstanceIdentifier path) {
246         LOG.trace("Delete " + datastore.name() + " via Restconf: {}", path);
247         writeTransaction.delete(datastore, path);
248         return writeTransaction.submit();
249     }
250
251     public void setDomDataBroker(final DOMDataBroker domDataBroker) {
252         this.domDataBroker = domDataBroker;
253     }
254
255     private void ensureParentsByMerge(final LogicalDatastoreType store,
256                                       final YangInstanceIdentifier normalizedPath, final DOMDataReadWriteTransaction rwTx, final SchemaContext schemaContext) {
257         final List<PathArgument> normalizedPathWithoutChildArgs = new ArrayList<>();
258         YangInstanceIdentifier rootNormalizedPath = null;
259
260         final Iterator<PathArgument> it = normalizedPath.getPathArguments().iterator();
261
262         while(it.hasNext()) {
263             final PathArgument pathArgument = it.next();
264             if(rootNormalizedPath == null) {
265                 rootNormalizedPath = YangInstanceIdentifier.create(pathArgument);
266             }
267
268             // Skip last element, its not a parent
269             if(it.hasNext()) {
270                 normalizedPathWithoutChildArgs.add(pathArgument);
271             }
272         }
273
274         // No parent structure involved, no need to ensure parents
275         if(normalizedPathWithoutChildArgs.isEmpty()) {
276             return;
277         }
278
279         Preconditions.checkArgument(rootNormalizedPath != null, "Empty path received");
280
281         final NormalizedNode<?, ?> parentStructure =
282                 ImmutableNodes.fromInstanceId(schemaContext, YangInstanceIdentifier.create(normalizedPathWithoutChildArgs));
283         rwTx.merge(store, rootNormalizedPath, parentStructure);
284     }
285 }