Bug 3959 - support netconf notification
[netconf.git] / restconf / sal-rest-connector / src / main / java / org / opendaylight / netconf / sal / restconf / impl / BrokerFacade.java
index fcf82e5623c142bd41ab261e48ee44eda79e2514..37c90515abab3c33cff7f38ecb2f06a3e881b17b 100644 (file)
@@ -9,8 +9,10 @@ package org.opendaylight.netconf.sal.restconf.impl;
 
 import static org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType.CONFIGURATION;
 import static org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType.OPERATIONAL;
+
 import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
 import com.google.common.util.concurrent.CheckedFuture;
 import com.google.common.util.concurrent.ListenableFuture;
 import java.util.ArrayList;
@@ -27,6 +29,8 @@ import org.opendaylight.controller.md.sal.dom.api.DOMDataReadTransaction;
 import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
 import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
 import org.opendaylight.controller.md.sal.dom.api.DOMMountPoint;
+import org.opendaylight.controller.md.sal.dom.api.DOMNotificationListener;
+import org.opendaylight.controller.md.sal.dom.api.DOMNotificationService;
 import org.opendaylight.controller.md.sal.dom.api.DOMRpcException;
 import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult;
 import org.opendaylight.controller.md.sal.dom.api.DOMRpcService;
@@ -34,6 +38,7 @@ import org.opendaylight.controller.sal.core.api.Broker.ConsumerSession;
 import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorTag;
 import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorType;
 import org.opendaylight.netconf.sal.streams.listeners.ListenerAdapter;
+import org.opendaylight.netconf.sal.streams.listeners.NotificationListenerAdapter;
 import org.opendaylight.yangtools.concepts.ListenerRegistration;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
@@ -53,12 +58,17 @@ public class BrokerFacade {
     private volatile DOMRpcService rpcService;
     private volatile ConsumerSession context;
     private DOMDataBroker domDataBroker;
+    private DOMNotificationService domNotification;
 
     private BrokerFacade() {
     }
 
     public void setRpcService(final DOMRpcService router) {
-        rpcService = router;
+        this.rpcService = router;
+    }
+
+    public void setDomNotificationService(final DOMNotificationService domNotification) {
+        this.domNotification = domNotification;
     }
 
     public void setContext(final ConsumerSession context) {
@@ -70,7 +80,7 @@ public class BrokerFacade {
     }
 
     private void checkPreconditions() {
-        if (context == null || domDataBroker == null) {
+        if ((this.context == null) || (this.domDataBroker == null)) {
             throw new RestconfDocumentedException(Status.SERVICE_UNAVAILABLE);
         }
     }
@@ -78,7 +88,7 @@ public class BrokerFacade {
     // READ configuration
     public NormalizedNode<?, ?> readConfigurationData(final YangInstanceIdentifier path) {
         checkPreconditions();
-        return readDataViaTransaction(domDataBroker.newReadOnlyTransaction(), CONFIGURATION, path);
+        return readDataViaTransaction(this.domDataBroker.newReadOnlyTransaction(), CONFIGURATION, path);
     }
 
     public NormalizedNode<?, ?> readConfigurationData(final DOMMountPoint mountPoint, final YangInstanceIdentifier path) {
@@ -94,7 +104,7 @@ public class BrokerFacade {
     // READ operational
     public NormalizedNode<?, ?> readOperationalData(final YangInstanceIdentifier path) {
         checkPreconditions();
-        return readDataViaTransaction(domDataBroker.newReadOnlyTransaction(), OPERATIONAL, path);
+        return readDataViaTransaction(this.domDataBroker.newReadOnlyTransaction(), OPERATIONAL, path);
     }
 
     public NormalizedNode<?, ?> readOperationalData(final DOMMountPoint mountPoint, final YangInstanceIdentifier path) {
@@ -111,7 +121,7 @@ public class BrokerFacade {
     public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataPut(
             final SchemaContext globalSchema, final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload) {
         checkPreconditions();
-        return putDataViaTransaction(domDataBroker.newReadWriteTransaction(), CONFIGURATION, path, payload, globalSchema);
+        return putDataViaTransaction(this.domDataBroker.newReadWriteTransaction(), CONFIGURATION, path, payload, globalSchema);
     }
 
     public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataPut(
@@ -126,11 +136,93 @@ public class BrokerFacade {
         throw new RestconfDocumentedException(errMsg);
     }
 
+    public PATCHStatusContext patchConfigurationDataWithinTransaction(final PATCHContext context,
+                                                                      final SchemaContext globalSchema) {
+        final DOMDataReadWriteTransaction patchTransaction = this.domDataBroker.newReadWriteTransaction();
+        final List<PATCHStatusEntity> editCollection = new ArrayList<>();
+        List<RestconfError> editErrors;
+        final List<RestconfError> globalErrors = null;
+        int errorCounter = 0;
+
+        for (final PATCHEntity patchEntity : context.getData()) {
+            final PATCHEditOperation operation = PATCHEditOperation.valueOf(patchEntity.getOperation().toUpperCase());
+
+            switch (operation) {
+                case CREATE:
+                    if (errorCounter == 0) {
+                        try {
+                            postDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity.getTargetNode(),
+                                    patchEntity.getNode(), globalSchema);
+                            editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null));
+                        } catch (final RestconfDocumentedException e) {
+                            editErrors = new ArrayList<>();
+                            editErrors.addAll(e.getErrors());
+                            editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), false, editErrors));
+                            errorCounter++;
+                        }
+                    }
+                    break;
+                case REPLACE:
+                    if (errorCounter == 0) {
+                        try {
+                            putDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity
+                                    .getTargetNode(), patchEntity.getNode(), globalSchema);
+                            editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null));
+                        } catch (final RestconfDocumentedException e) {
+                            editErrors = new ArrayList<>();
+                            editErrors.addAll(e.getErrors());
+                            editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), false, editErrors));
+                            errorCounter++;
+                        }
+                    }
+                    break;
+                case DELETE:
+                    if (errorCounter == 0) {
+                        try {
+                            deleteDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity
+                                    .getTargetNode());
+                            editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null));
+                        } catch (final RestconfDocumentedException e) {
+                            editErrors = new ArrayList<>();
+                            editErrors.addAll(e.getErrors());
+                            editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), false, editErrors));
+                            errorCounter++;
+                        }
+                    }
+                    break;
+                case REMOVE:
+                    if (errorCounter == 0) {
+                        try {
+                            deleteDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity
+                                    .getTargetNode());
+                            editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null));
+                        } catch (final RestconfDocumentedException e) {
+                            LOG.error("Error removing {} by {} operation", patchEntity.getTargetNode().toString(),
+                                    patchEntity.getEditId(), e);
+                        }
+                    }
+                    break;
+            }
+        }
+
+        //TODO: make sure possible global errors are filled up correctly and decide transaction submission based on that
+        //globalErrors = new ArrayList<>();
+        if (errorCounter == 0) {
+            final CheckedFuture<Void, TransactionCommitFailedException> submit = patchTransaction.submit();
+            return new PATCHStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection), true,
+                    globalErrors);
+        } else {
+            patchTransaction.cancel();
+            return new PATCHStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection), false,
+                    globalErrors);
+        }
+    }
+
     // POST configuration
     public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataPost(
             final SchemaContext globalSchema, final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload) {
         checkPreconditions();
-        return postDataViaTransaction(domDataBroker.newReadWriteTransaction(), CONFIGURATION, path, payload, globalSchema);
+        return postDataViaTransaction(this.domDataBroker.newReadWriteTransaction(), CONFIGURATION, path, payload, globalSchema);
     }
 
     public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataPost(
@@ -149,7 +241,7 @@ public class BrokerFacade {
     public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataDelete(
             final YangInstanceIdentifier path) {
         checkPreconditions();
-        return deleteDataViaTransaction(domDataBroker.newWriteOnlyTransaction(), CONFIGURATION, path);
+        return deleteDataViaTransaction(this.domDataBroker.newWriteOnlyTransaction(), CONFIGURATION, path);
     }
 
     public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataDelete(
@@ -166,11 +258,11 @@ public class BrokerFacade {
     // RPC
     public CheckedFuture<DOMRpcResult, DOMRpcException> invokeRpc(final SchemaPath type, final NormalizedNode<?, ?> input) {
         checkPreconditions();
-        if (rpcService == null) {
+        if (this.rpcService == null) {
             throw new RestconfDocumentedException(Status.SERVICE_UNAVAILABLE);
         }
         LOG.trace("Invoke RPC {} with input: {}", type, input);
-        return rpcService.invokeRpc(type, input);
+        return this.rpcService.invokeRpc(type, input);
     }
 
     public void registerToListenDataChanges(final LogicalDatastoreType datastore, final DataChangeScope scope,
@@ -182,7 +274,7 @@ public class BrokerFacade {
         }
 
         final YangInstanceIdentifier path = listener.getPath();
-        final ListenerRegistration<DOMDataChangeListener> registration = domDataBroker.registerDataChangeListener(
+        final ListenerRegistration<DOMDataChangeListener> registration = this.domDataBroker.registerDataChangeListener(
                 datastore, path, listener, scope);
 
         listener.setRegistration(registration);
@@ -190,7 +282,7 @@ public class BrokerFacade {
 
     private NormalizedNode<?, ?> readDataViaTransaction(final DOMDataReadTransaction transaction,
             final LogicalDatastoreType datastore, final YangInstanceIdentifier path) {
-        LOG.trace("Read " + datastore.name() + " via Restconf: {}", path);
+        LOG.trace("Read {} via Restconf: {}", datastore.name(), path);
         final ListenableFuture<Optional<NormalizedNode<?, ?>>> listenableFuture = transaction.read(datastore, path);
         if (listenableFuture != null) {
             Optional<NormalizedNode<?, ?>> optional;
@@ -198,7 +290,7 @@ public class BrokerFacade {
                 LOG.debug("Reading result data from transaction.");
                 optional = listenableFuture.get();
             } catch (InterruptedException | ExecutionException e) {
-                LOG.warn("Exception by reading " + datastore.name() + " via Restconf: {}", path, e);
+                LOG.warn("Exception by reading {} via Restconf: {}", datastore.name(), path, e);
                 throw new RestconfDocumentedException("Problem to get data from transaction.", e.getCause());
 
             }
@@ -217,7 +309,7 @@ public class BrokerFacade {
         // FIXME: This is doing correct post for container and list children
         //        not sure if this will work for choice case
         if(payload instanceof MapNode) {
-            LOG.trace("POST " + datastore.name() + " via Restconf: {} with payload {}", path, payload);
+            LOG.trace("POST {} via Restconf: {} with payload {}", datastore.name(), path, payload);
             final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path);
             rWTransaction.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
             ensureParentsByMerge(datastore, path, rWTransaction, schemaContext);
@@ -234,18 +326,40 @@ public class BrokerFacade {
         return rWTransaction.submit();
     }
 
+    private void postDataWithinTransaction(
+            final DOMDataReadWriteTransaction rWTransaction, final LogicalDatastoreType datastore,
+            final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload, final SchemaContext schemaContext) {
+        // FIXME: This is doing correct post for container and list children
+        //        not sure if this will work for choice case
+        if(payload instanceof MapNode) {
+            LOG.trace("POST {} within Restconf PATCH: {} with payload {}", datastore.name(), path, payload);
+            final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path);
+            rWTransaction.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
+            ensureParentsByMerge(datastore, path, rWTransaction, schemaContext);
+            for(final MapEntryNode child : ((MapNode) payload).getValue()) {
+                final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
+                checkItemDoesNotExists(rWTransaction, datastore, childPath);
+                rWTransaction.put(datastore, childPath, child);
+            }
+        } else {
+            checkItemDoesNotExists(rWTransaction,datastore, path);
+            ensureParentsByMerge(datastore, path, rWTransaction, schemaContext);
+            rWTransaction.put(datastore, path, payload);
+        }
+    }
+
     private void checkItemDoesNotExists(final DOMDataReadWriteTransaction rWTransaction,final LogicalDatastoreType store, final YangInstanceIdentifier path) {
         final ListenableFuture<Boolean> futureDatastoreData = rWTransaction.exists(store, path);
         try {
             if (futureDatastoreData.get()) {
                 final String errMsg = "Post Configuration via Restconf was not executed because data already exists";
-                LOG.trace(errMsg + ":{}", path);
+                LOG.trace("{}:{}", errMsg, path);
                 rWTransaction.cancel();
                 throw new RestconfDocumentedException("Data already exists for path: " + path, ErrorType.PROTOCOL,
                         ErrorTag.DATA_EXISTS);
             }
         } catch (InterruptedException | ExecutionException e) {
-            LOG.warn("It wasn't possible to get data loaded from datastore at path " + path, e);
+            LOG.warn("It wasn't possible to get data loaded from datastore at path {}", path, e);
         }
 
     }
@@ -253,20 +367,35 @@ public class BrokerFacade {
     private CheckedFuture<Void, TransactionCommitFailedException> putDataViaTransaction(
             final DOMDataReadWriteTransaction writeTransaction, final LogicalDatastoreType datastore,
             final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload, final SchemaContext schemaContext) {
-        LOG.trace("Put " + datastore.name() + " via Restconf: {} with payload {}", path, payload);
+        LOG.trace("Put {} via Restconf: {} with payload {}", datastore.name(), path, payload);
         ensureParentsByMerge(datastore, path, writeTransaction, schemaContext);
         writeTransaction.put(datastore, path, payload);
         return writeTransaction.submit();
     }
 
+    private void putDataWithinTransaction(
+            final DOMDataReadWriteTransaction writeTransaction, final LogicalDatastoreType datastore,
+            final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload, final SchemaContext schemaContext) {
+        LOG.trace("Put {} within Restconf PATCH: {} with payload {}", datastore.name(), path, payload);
+        ensureParentsByMerge(datastore, path, writeTransaction, schemaContext);
+        writeTransaction.put(datastore, path, payload);
+    }
+
     private CheckedFuture<Void, TransactionCommitFailedException> deleteDataViaTransaction(
             final DOMDataWriteTransaction writeTransaction, final LogicalDatastoreType datastore,
             final YangInstanceIdentifier path) {
-        LOG.trace("Delete " + datastore.name() + " via Restconf: {}", path);
+        LOG.trace("Delete {} via Restconf: {}", datastore.name(), path);
         writeTransaction.delete(datastore, path);
         return writeTransaction.submit();
     }
 
+    private void deleteDataWithinTransaction(
+            final DOMDataWriteTransaction writeTransaction, final LogicalDatastoreType datastore,
+            final YangInstanceIdentifier path) {
+        LOG.trace("Delete {} within Restconf PATCH: {}", datastore.name(), path);
+        writeTransaction.delete(datastore, path);
+    }
+
     public void setDomDataBroker(final DOMDataBroker domDataBroker) {
         this.domDataBroker = domDataBroker;
     }
@@ -301,4 +430,18 @@ public class BrokerFacade {
                 ImmutableNodes.fromInstanceId(schemaContext, YangInstanceIdentifier.create(normalizedPathWithoutChildArgs));
         rwTx.merge(store, rootNormalizedPath, parentStructure);
     }
+
+    public void registerToListenNotification(final NotificationListenerAdapter listener) {
+        checkPreconditions();
+
+        if (listener.isListening()) {
+            return;
+        }
+
+        final SchemaPath path = listener.getSchemaPath();
+        final ListenerRegistration<DOMNotificationListener> registration = this.domNotification
+                .registerNotificationListener(listener, path);
+
+        listener.setRegistration(registration);
+    }
 }