Bug 3104 - Sal Rest Connector: Data already exists for path when adding
[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 import com.google.common.base.Optional;
13 import com.google.common.base.Preconditions;
14 import com.google.common.util.concurrent.CheckedFuture;
15 import com.google.common.util.concurrent.ListenableFuture;
16 import java.util.ArrayList;
17 import java.util.Iterator;
18 import java.util.List;
19 import java.util.concurrent.ExecutionException;
20 import javax.ws.rs.core.Response.Status;
21 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
22 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
23 import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
24 import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker;
25 import org.opendaylight.controller.md.sal.dom.api.DOMDataChangeListener;
26 import org.opendaylight.controller.md.sal.dom.api.DOMDataReadTransaction;
27 import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
28 import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
29 import org.opendaylight.controller.md.sal.dom.api.DOMMountPoint;
30 import org.opendaylight.controller.md.sal.dom.api.DOMRpcException;
31 import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult;
32 import org.opendaylight.controller.md.sal.dom.api.DOMRpcService;
33 import org.opendaylight.controller.sal.core.api.Broker.ConsumerSession;
34 import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorTag;
35 import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorType;
36 import org.opendaylight.controller.sal.streams.listeners.ListenerAdapter;
37 import org.opendaylight.yangtools.concepts.ListenerRegistration;
38 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
39 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
40 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
41 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
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 path, 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         if(payload instanceof MapNode) {
208             final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path);
209             rWTransaction.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
210             ensureParentsByMerge(datastore, path, rWTransaction, schemaContext);
211             for(final MapEntryNode child : ((MapNode) payload).getValue()) {
212                 final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
213                 checkItemDoesNotExists(rWTransaction, datastore, childPath);
214                 rWTransaction.put(datastore, childPath, child);
215             }
216         } else {
217             checkItemDoesNotExists(rWTransaction,datastore, path);
218             ensureParentsByMerge(datastore, path, rWTransaction, schemaContext);
219             rWTransaction.put(datastore, path, payload);
220         }
221         return rWTransaction.submit();
222     }
223
224     private void checkItemDoesNotExists(final DOMDataReadWriteTransaction rWTransaction,final LogicalDatastoreType store, final YangInstanceIdentifier path) {
225         final ListenableFuture<Boolean> futureDatastoreData = rWTransaction.exists(store, path);
226         try {
227             if (futureDatastoreData.get()) {
228                 final String errMsg = "Post Configuration via Restconf was not executed because data already exists";
229                 LOG.debug(errMsg + ":{}", path);
230                 rWTransaction.cancel();
231                 throw new RestconfDocumentedException("Data already exists for path: " + path, ErrorType.PROTOCOL,
232                         ErrorTag.DATA_EXISTS);
233             }
234         } catch (InterruptedException | ExecutionException e) {
235             LOG.trace("It wasn't possible to get data loaded from datastore at path " + path);
236         }
237
238     }
239
240     private CheckedFuture<Void, TransactionCommitFailedException> putDataViaTransaction(
241             final DOMDataReadWriteTransaction writeTransaction, final LogicalDatastoreType datastore,
242             final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload, final SchemaContext schemaContext) {
243         LOG.trace("Put " + datastore.name() + " via Restconf: {}", path);
244         ensureParentsByMerge(datastore, path, writeTransaction, schemaContext);
245         writeTransaction.put(datastore, path, payload);
246         return writeTransaction.submit();
247     }
248
249     private CheckedFuture<Void, TransactionCommitFailedException> deleteDataViaTransaction(
250             final DOMDataWriteTransaction writeTransaction, final LogicalDatastoreType datastore,
251             final YangInstanceIdentifier path) {
252         LOG.trace("Delete " + datastore.name() + " via Restconf: {}", path);
253         writeTransaction.delete(datastore, path);
254         return writeTransaction.submit();
255     }
256
257     public void setDomDataBroker(final DOMDataBroker domDataBroker) {
258         this.domDataBroker = domDataBroker;
259     }
260
261     private void ensureParentsByMerge(final LogicalDatastoreType store,
262                                       final YangInstanceIdentifier normalizedPath, final DOMDataReadWriteTransaction rwTx, final SchemaContext schemaContext) {
263         final List<PathArgument> normalizedPathWithoutChildArgs = new ArrayList<>();
264         YangInstanceIdentifier rootNormalizedPath = null;
265
266         final Iterator<PathArgument> it = normalizedPath.getPathArguments().iterator();
267
268         while(it.hasNext()) {
269             final PathArgument pathArgument = it.next();
270             if(rootNormalizedPath == null) {
271                 rootNormalizedPath = YangInstanceIdentifier.create(pathArgument);
272             }
273
274             // Skip last element, its not a parent
275             if(it.hasNext()) {
276                 normalizedPathWithoutChildArgs.add(pathArgument);
277             }
278         }
279
280         // No parent structure involved, no need to ensure parents
281         if(normalizedPathWithoutChildArgs.isEmpty()) {
282             return;
283         }
284
285         Preconditions.checkArgument(rootNormalizedPath != null, "Empty path received");
286
287         final NormalizedNode<?, ?> parentStructure =
288                 ImmutableNodes.fromInstanceId(schemaContext, YangInstanceIdentifier.create(normalizedPathWithoutChildArgs));
289         rwTx.merge(store, rootNormalizedPath, parentStructure);
290     }
291 }