Added response data to operational datastore, refactored data validation
[packetcable.git] / packetcable-policy-server / src / main / java / org / opendaylight / controller / packetcable / provider / PacketcableProvider.java
index 894eccc0305e42fa5b5f428007f7fd08b7949c53..bbd960349151971d16a22167885ed4ec4a7b0a22 100644 (file)
@@ -1,20 +1,42 @@
+/*
+ * Copyright (c) 2015 CableLabs and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
 package org.opendaylight.controller.packetcable.provider;
 
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.google.common.util.concurrent.CheckedFuture;
 import java.net.InetAddress;
 import java.net.UnknownHostException;
 import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import javax.annotation.Nonnull;
 import javax.annotation.concurrent.ThreadSafe;
 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
 import org.opendaylight.controller.md.sal.binding.api.DataChangeListener;
-import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeEvent;
+import org.opendaylight.controller.md.sal.binding.api.WriteTransaction;
 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
+import org.opendaylight.controller.packetcable.provider.validation.DataValidator;
+import org.opendaylight.controller.packetcable.provider.validation.ValidationException;
+import org.opendaylight.controller.packetcable.provider.validation.Validator;
+import org.opendaylight.controller.packetcable.provider.validation.impl.CcapsValidatorProviderFactory;
+import org.opendaylight.controller.packetcable.provider.validation.impl.QosValidatorProviderFactory;
 import org.opendaylight.controller.sal.binding.api.BindingAwareBroker.ProviderContext;
 import org.opendaylight.controller.sal.binding.api.BindingAwareProvider;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.IpPrefix;
@@ -23,14 +45,22 @@ import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.Ccaps;
 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.Qos;
 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.ServiceClassName;
 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.ServiceFlowDirection;
-import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.ccap.attributes.Connection;
+import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.ccap.attributes.ConnectionBuilder;
 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.ccaps.Ccap;
-import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.ccaps.CcapKey;
+import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.ccaps.CcapBuilder;
+import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.gates.Apps;
 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.gates.apps.App;
+import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.gates.apps.AppBuilder;
 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.gates.apps.AppKey;
+import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.gates.apps.app.Subscribers;
+import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.gates.apps.app.SubscribersBuilder;
 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.gates.apps.app.subscribers.Subscriber;
+import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.gates.apps.app.subscribers.SubscriberBuilder;
 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.gates.apps.app.subscribers.SubscriberKey;
+import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.gates.apps.app.subscribers.subscriber.Gates;
+import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.gates.apps.app.subscribers.subscriber.GatesBuilder;
 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.gates.apps.app.subscribers.subscriber.gates.Gate;
+import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.gates.apps.app.subscribers.subscriber.gates.GateBuilder;
 import org.opendaylight.yang.gen.v1.urn.packetcable.rev151026.pcmm.qos.gates.apps.app.subscribers.subscriber.gates.GateKey;
 import org.opendaylight.yangtools.concepts.ListenerRegistration;
 import org.opendaylight.yangtools.yang.binding.DataObject;
@@ -41,28 +71,18 @@ import org.slf4j.LoggerFactory;
 
 /**
  * Called by ODL framework to start this bundle.
- *
+ * <p>
  * This class is responsible for processing messages received from ODL's restconf interface.
  * TODO - Remove some of these state maps and move some of this into the PCMMService
  */
 @ThreadSafe
-public class PacketcableProvider implements BindingAwareProvider, DataChangeListener, AutoCloseable {
+public class PacketcableProvider implements BindingAwareProvider, AutoCloseable {
 
     private static final Logger logger = LoggerFactory.getLogger(PacketcableProvider.class);
 
-    // keys to the /restconf/config/packetcable:ccap and /restconf/config/packetcable:qos config datastore
-    public static final InstanceIdentifier<Ccaps> ccapIID = InstanceIdentifier.builder(Ccaps.class).build();
-    public static final InstanceIdentifier<Qos> qosIID = InstanceIdentifier.builder(Qos.class).build();
-
-    /**
-     * The ODL object used to broker messages throughout the framework
-     */
-    private DataBroker dataBroker;
-
-    private MdsalUtils mdsalUtils;
-
-    private ListenerRegistration<DataChangeListener> ccapDataChangeListenerRegistration;
-    private ListenerRegistration<DataChangeListener> qosDataChangeListenerRegistration;
+    // keys to the /restconf/config/packetcable:ccaps and /restconf/config/packetcable:qos config datastore
+    private static final InstanceIdentifier<Ccaps> ccapsIID = InstanceIdentifier.builder(Ccaps.class).build();
+    private static final InstanceIdentifier<Qos> qosIID = InstanceIdentifier.builder(Qos.class).build();
 
     // TODO - Revisit these maps and remove the ones no longer necessary
     private final Map<String, Ccap> ccapMap = new ConcurrentHashMap<>();
@@ -72,11 +92,26 @@ public class PacketcableProvider implements BindingAwareProvider, DataChangeList
     private final Map<ServiceClassName, List<Ccap>> downstreamScnMap = new ConcurrentHashMap<>();
     private final Map<ServiceClassName, List<Ccap>> upstreamScnMap = new ConcurrentHashMap<>();
 
+    private final Executor executor = Executors.newSingleThreadExecutor();
+
     /**
      * Holds a PCMMService object for each CCAP being managed.
      */
     private final Map<String, PCMMService> pcmmServiceMap = new ConcurrentHashMap<>();
 
+    /**
+     * The ODL object used to broker messages throughout the framework
+     */
+    private DataBroker dataBroker;
+    private MdsalUtils mdsalUtils;
+
+    // Data change listeners/registrations
+    private final CcapsDataChangeListener ccapsDataChangeListener = new CcapsDataChangeListener();
+    private final QosDataChangeListener qosDataChangeListener = new QosDataChangeListener();
+
+    private ListenerRegistration<DataChangeListener> ccapsDataChangeListenerRegistration;
+    private ListenerRegistration<DataChangeListener> qosDataChangeListenerRegistration;
+
     /**
      * Constructor
      */
@@ -87,26 +122,51 @@ public class PacketcableProvider implements BindingAwareProvider, DataChangeList
     @Override
     public void onSessionInitiated(ProviderContext session) {
         logger.info("Packetcable Session Initiated");
+        logger.info("logging levels: error={}, warn={}, info={}, debug={}, trace={}", logger.isErrorEnabled(),
+                logger.isWarnEnabled(), logger.isInfoEnabled(), logger.isDebugEnabled(), logger.isTraceEnabled());
 
-        dataBroker =  session.getSALService(DataBroker.class);
+        dataBroker = session.getSALService(DataBroker.class);
 
         mdsalUtils = new MdsalUtils(dataBroker);
 
-        ccapDataChangeListenerRegistration =
-                dataBroker.registerDataChangeListener(LogicalDatastoreType.CONFIGURATION,
-                        PacketcableProvider.ccapIID, this, DataBroker.DataChangeScope.SUBTREE );
+        ccapsDataChangeListenerRegistration = dataBroker
+                .registerDataChangeListener(LogicalDatastoreType.CONFIGURATION, ccapsIID.child(Ccap.class),
+                        ccapsDataChangeListener, DataBroker.DataChangeScope.SUBTREE);
+
+        qosDataChangeListenerRegistration = dataBroker
+                .registerDataChangeListener(LogicalDatastoreType.CONFIGURATION, PacketcableProvider.qosIID.child(Apps.class).child(App.class),
+                        qosDataChangeListener, DataBroker.DataChangeScope.SUBTREE);
+
+        // Add empty top level elements
+//        for (LogicalDatastoreType datastoreType : LogicalDatastoreType.values()) {
+//            WriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction();
+//            writeTransaction.put(datastoreType, ccapsIID, new CcapsBuilder().build());
+//            CheckedFuture<Void, TransactionCommitFailedException> future = writeTransaction.submit();
+//            try {
+//                future.checkedGet();
+//            } catch (TransactionCommitFailedException e) {
+//                logger.error("Failed to initialise top level ccaps in datastore {}", datastoreType, e);
+//            }
+//            writeTransaction = dataBroker.newWriteOnlyTransaction();
+//            writeTransaction.put(datastoreType, qosIID, new QosBuilder().build());
+//            future = writeTransaction.submit();
+//            try {
+//                future.checkedGet();
+//            } catch (TransactionCommitFailedException e) {
+//                logger.error("Failed to initialise top level qos in datastore {}", datastoreType, e);
+//            }
+//        }
+
 
-        qosDataChangeListenerRegistration =
-                dataBroker.registerDataChangeListener(LogicalDatastoreType.CONFIGURATION,
-                        PacketcableProvider.qosIID, this, DataBroker.DataChangeScope.SUBTREE );
     }
+
     /**
      * Implemented from the AutoCloseable interface.
      */
     @Override
     public void close() throws ExecutionException, InterruptedException {
-        if (ccapDataChangeListenerRegistration != null) {
-            ccapDataChangeListenerRegistration.close();
+        if (ccapsDataChangeListenerRegistration != null) {
+            ccapsDataChangeListenerRegistration.close();
         }
 
         if (qosDataChangeListenerRegistration != null) {
@@ -114,24 +174,6 @@ public class PacketcableProvider implements BindingAwareProvider, DataChangeList
         }
     }
 
-    public InetAddress getInetAddress(final String subId){
-        try {
-            return InetAddress.getByName(subId);
-        } catch (UnknownHostException e) {
-            logger.error("getInetAddress: {} FAILED: {}", subId, e.getMessage());
-            return null;
-        }
-    }
-
-    private String getIpPrefixStr(final IpPrefix ipPrefix) {
-        final Ipv4Prefix ipv4 = ipPrefix.getIpv4Prefix();
-        if (ipv4 != null) {
-            return ipv4.getValue();
-        } else {
-            return ipPrefix.getIpv6Prefix().getValue();
-        }
-    }
-
     private void updateCcapMaps(final Ccap ccap) {
         // add ccap to the subscriberSubnets map
         for (final IpPrefix ipPrefix : ccap.getSubscriberSubnets()) {
@@ -163,33 +205,22 @@ public class PacketcableProvider implements BindingAwareProvider, DataChangeList
         }
     }
 
-    private void removeCcapFromAllMaps(final Ccap ccap) {
-        // remove the ccap from all maps
-        // subscriberSubnets map
-        for (final Map.Entry<Subnet, Ccap> entry : subscriberSubnetsMap.entrySet()) {
-            if (entry.getValue() == ccap) {
-                subscriberSubnetsMap.remove(entry.getKey());
-            }
-        }
-        // ccap to upstream SCN map
-        for (final Map.Entry<ServiceClassName, List<Ccap>> entry : upstreamScnMap.entrySet()) {
-            final List<Ccap> ccapList = entry.getValue();
-            ccapList.remove(ccap);
-            if (ccapList.isEmpty()) {
-                upstreamScnMap.remove(entry.getKey());
-            }
-        }
-        // ccap to downstream SCN map
-        for (final Map.Entry<ServiceClassName, List<Ccap>> entry : downstreamScnMap.entrySet()) {
-            final List<Ccap> ccapList = entry.getValue();
-            ccapList.remove(ccap);
-            if (ccapList.isEmpty()) {
-                downstreamScnMap.remove(entry.getKey());
-            }
+    private String getIpPrefixStr(final IpPrefix ipPrefix) {
+        final Ipv4Prefix ipv4 = ipPrefix.getIpv4Prefix();
+        if (ipv4 != null) {
+            return ipv4.getValue();
+        } else {
+            return ipPrefix.getIpv6Prefix().getValue();
         }
+    }
 
-        final PCMMService service = pcmmServiceMap.remove(ccap.getCcapId());
-        if (service != null) service.disconect();
+    public InetAddress getInetAddress(final String subId) {
+        try {
+            return InetAddress.getByName(subId);
+        } catch (UnknownHostException e) {
+            logger.error("getInetAddress: {} FAILED: {}", subId, e.getMessage());
+            return null;
+        }
     }
 
     private Ccap findCcapForSubscriberId(final InetAddress inetAddr) {
@@ -209,6 +240,9 @@ public class PacketcableProvider implements BindingAwareProvider, DataChangeList
     }
 
     private ServiceFlowDirection findScnOnCcap(final ServiceClassName scn, final Ccap ccap) {
+        checkNotNull(scn);
+        checkNotNull(ccap);
+
         if (upstreamScnMap.containsKey(scn)) {
             final List<Ccap> ccapList = upstreamScnMap.get(scn);
             if (ccapList.contains(ccap)) {
@@ -223,313 +257,586 @@ public class PacketcableProvider implements BindingAwareProvider, DataChangeList
         return null;
     }
 
+    private void removeCcapFromAllMaps(final Ccap ccap) {
+        // remove the ccap from all maps
+        // subscriberSubnets map
+        for (final Map.Entry<Subnet, Ccap> entry : subscriberSubnetsMap.entrySet()) {
+            if (entry.getValue() == ccap) {
+                subscriberSubnetsMap.remove(entry.getKey());
+            }
+        }
+        // ccap to upstream SCN map
+        for (final Map.Entry<ServiceClassName, List<Ccap>> entry : upstreamScnMap.entrySet()) {
+            final List<Ccap> ccapList = entry.getValue();
+            ccapList.remove(ccap);
+            if (ccapList.isEmpty()) {
+                upstreamScnMap.remove(entry.getKey());
+            }
+        }
+        // ccap to downstream SCN map
+        for (final Map.Entry<ServiceClassName, List<Ccap>> entry : downstreamScnMap.entrySet()) {
+            final List<Ccap> ccapList = entry.getValue();
+            ccapList.remove(ccap);
+            if (ccapList.isEmpty()) {
+                downstreamScnMap.remove(entry.getKey());
+            }
+        }
+
+        final PCMMService service = pcmmServiceMap.remove(ccap.getCcapId());
+        if (service != null) {
+            service.disconect();
+        }
+    }
+
+    // ValidationException does not need to be thrown again
+    @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
+    private <T extends DataObject> void saveErrors(@Nonnull Map<InstanceIdentifier<T>, ValidationException> errorMap,
+            @Nonnull Map<InstanceIdentifier<T>, T> dataMap) {
+
+        final WriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction();
+
+
+        for (InstanceIdentifier<T> iid : errorMap.keySet()) {
+
+            final ValidationException exception = errorMap.get(iid);
+            final T badData = dataMap.get(iid);
+
+            if (!badData.getImplementedInterface().isAssignableFrom(iid.getTargetType())) {
+                // InstanceIdentifier<T> does not have the same type as the DataObject
+                logger.error("Bad InstanceIdentifier to DataObject mapping, {} : {}", iid, badData);
+                continue;
+            }
+
+            if (badData instanceof Ccap) {
+                final Ccap ccap = (Ccap) badData;
+
+                final Ccap opperationalCcap =
+                        new CcapBuilder().setCcapId(ccap.getCcapId()).setError(exception.getErrorMessages()).build();
+
+
+                // type match between iid and badData is done at start of loop
+                @SuppressWarnings("unchecked")
+                final InstanceIdentifier<Ccap> ccapIID = (InstanceIdentifier<Ccap>) iid;
+                writeTransaction.put(LogicalDatastoreType.OPERATIONAL, ccapIID, opperationalCcap);
+            }
+            else if (badData instanceof Gate) {
+                final Gate gate = (Gate) badData;
+
+                final Gate operationalGate =
+                        new GateBuilder()
+                        .setGateId(gate.getGateId())
+                        .setError(exception.getErrorMessages())
+                        .build();
+
+                final Gates operationalGates = new GatesBuilder()
+                        .setGate(Collections.singletonList(operationalGate))
+                        .build();
+
+                final SubscriberKey subscriberKey = InstanceIdentifier.keyOf(iid.firstIdentifierOf(Subscriber.class));
+                final Subscriber operationalSubscriber = new SubscriberBuilder()
+                        .setSubscriberId(subscriberKey.getSubscriberId())
+                        .setGates(operationalGates)
+                        .build();
+
+                final Subscribers operationalSubscribers = new SubscribersBuilder()
+                        .setSubscriber(Collections.singletonList(operationalSubscriber))
+                        .build();
+
+                final InstanceIdentifier<App> appIID = iid.firstIdentifierOf(App.class);
+                final AppKey appKey = InstanceIdentifier.keyOf(appIID);
+                final App operationalApp = new AppBuilder()
+                        .setAppId(appKey.getAppId())
+                        .setSubscribers(operationalSubscribers)
+                        .build();
+
+
+                writeTransaction.put(LogicalDatastoreType.OPERATIONAL, appIID, operationalApp);
+            }
+            else {
+                // If you get here a developer forgot to add a type above
+                logger.error("Unexpected type requested for error saving: {}", badData);
+                throw new IllegalStateException("Unsupported type for error saving");
+            }
+
+        }
+
+
+        CheckedFuture<Void, TransactionCommitFailedException> future = writeTransaction.submit();
+
+        try {
+            future.checkedGet();
+        } catch (TransactionCommitFailedException e) {
+            logger.error("Failed to write errors to operational datastore", e);
+        }
+    }
+
     /**
-     * Implemented from the DataChangeListener interface.
+     * Removes Ccaps if all Ccap instances are removed
      */
+    private class CcapsCleaner extends AbstractCleaner<Ccaps> {
 
-    private class InstanceData {
-        // CCAP Identity
-        public final Map<InstanceIdentifier<Ccap>, Ccap> ccapIidMap = new HashMap<>();
-        // Gate Identity
-        public String subId;
-        public final Map<String, String> gatePathMap = new HashMap<>();
-        public String gatePath;
-        public final Map<InstanceIdentifier<Gate>, Gate> gateIidMap = new HashMap<>();
-        // remove path for either CCAP or Gate
-        public final Set<String> removePathList = new HashSet<>();
-
-        public final Set<InstanceIdentifier<?>> reqCcapIds = new HashSet<>();
-
-        public InstanceData(final Map<InstanceIdentifier<?>, DataObject> thisData) {
-            // only used to parse createdData or updatedData
-            getCcaps(thisData);
-            if (ccapIidMap.isEmpty()) {
-                getGates(thisData);
-                if (! gateIidMap.isEmpty()){
-                    gatePath = gatePathMap.get("appId") + "/" + gatePathMap.get("subId");
-                }
-            }
+        public CcapsCleaner(final InstanceIdentifier<?> removedIID) {
+            super(removedIID, Ccaps.class, LogicalDatastoreType.OPERATIONAL);
         }
 
-        public InstanceData(final Set<InstanceIdentifier<?>> thisData) {
-            // only used to parse the removedData paths
-            for (final InstanceIdentifier<?> removeThis : thisData) {
-                getGatePathMap(removeThis);
-                if (gatePathMap.containsKey("ccapId")) {
-                    gatePath = gatePathMap.get("ccapId");
-                    removePathList.add(gatePath);
-                } else if (gatePathMap.containsKey("gateId")) {
-                    gatePath = gatePathMap.get("appId") + "/" + gatePathMap.get("subId") + "/" + gatePathMap.get("gateId");
-                    removePathList.add(gatePath);
-                }
-            }
+        @Override
+        protected boolean shouldClean(final Ccaps ccaps) {
+            return ccaps.getCcap().isEmpty();
         }
-        private void getGatePathMap(final InstanceIdentifier<?> thisInstance) {
-            logger.info("onDataChanged().getGatePathMap(): " + thisInstance);
-            try {
-                final InstanceIdentifier<Ccap> ccapInstance = thisInstance.firstIdentifierOf(Ccap.class);
-                if (ccapInstance != null) {
-                    final CcapKey ccapKey = InstanceIdentifier.keyOf(ccapInstance);
-                    if (ccapKey != null) {
-                        gatePathMap.put("ccapId", ccapKey.getCcapId());
-                    }
-                } else {
-                    // get the gate path keys from the InstanceIdentifier Map key set if they are there
-                    final InstanceIdentifier<App> appInstance = thisInstance.firstIdentifierOf(App.class);
-                    if (appInstance != null) {
-                        final AppKey appKey = InstanceIdentifier.keyOf(appInstance);
-                        if (appKey != null) {
-                            gatePathMap.put("appId", appKey.getAppId());
-                        }
-                    }
-                    final InstanceIdentifier<Subscriber> subInstance = thisInstance.firstIdentifierOf(Subscriber.class);
-                    if (subInstance != null) {
-                        final SubscriberKey subKey = InstanceIdentifier.keyOf(subInstance);
-                        if (subKey != null) {
-                            subId = subKey.getSubscriberId();
-                            gatePathMap.put("subId", subId);
+    }
+
+    /**
+     * Removes Subscriber if all Gate instances are removed
+     */
+    private class SubscriberCleaner extends AbstractCleaner<Subscriber> {
+
+        public SubscriberCleaner(InstanceIdentifier<Gate> removedGateIID) {
+            super(removedGateIID, Subscriber.class, LogicalDatastoreType.OPERATIONAL);
+        }
+
+        @Override
+        protected boolean shouldClean(final Subscriber subscriber) {
+            return subscriber.getGates().getGate().isEmpty();
+        }
+
+        @Override
+        protected void postRemove(InstanceIdentifier<Subscriber> subscriberIID) {
+            executor.execute(new AppCleaner(subscriberIID));
+        }
+    }
+
+
+    /**
+     * Removes App if all Subscribers are removed.
+     */
+    private class AppCleaner extends AbstractCleaner<App> {
+
+        public AppCleaner(InstanceIdentifier<Subscriber> removedSubscriberIID) {
+            super(removedSubscriberIID, App.class, LogicalDatastoreType.OPERATIONAL);
+        }
+
+        @Override
+        boolean shouldClean(final App app) {
+            return app.getSubscribers().getSubscriber().isEmpty();
+        }
+
+        @Override
+        void postRemove(final InstanceIdentifier<App> appIID) {
+            executor.execute(new AppsCleaner(appIID));
+        }
+    }
+
+
+    /**
+     * Removes Apps if all App instances are removed.
+     */
+    private class AppsCleaner extends  AbstractCleaner<Apps> {
+
+        public AppsCleaner(InstanceIdentifier<App> removedAppIID) {
+            super(removedAppIID, Apps.class, LogicalDatastoreType.OPERATIONAL);
+        }
+
+        @Override
+        protected boolean shouldClean(final Apps apps) {
+            return apps.getApp().isEmpty();
+        }
+    }
+
+
+    /**
+     * Helper class to do the heavy lifting in removing object. Lets subclasses decide with
+     *  {@link #shouldClean(DataObject)}. <br>
+     *
+     * Subclasses can react after an instance is removed by overriding {@link #postRemove(InstanceIdentifier)}
+     * @param <T> The type that will be removed
+     */
+    private abstract class AbstractCleaner <T extends DataObject> implements Runnable {
+        final InstanceIdentifier<?> removedIID;
+        final Class<T> tClass;
+        final LogicalDatastoreType datastoreType;
+
+        public AbstractCleaner(InstanceIdentifier<?> removedIID, Class<T> tClass, LogicalDatastoreType datastoreType) {
+            this.removedIID = checkNotNull(removedIID);
+            this.tClass = checkNotNull(tClass);
+            this.datastoreType = checkNotNull(datastoreType);
+        }
+
+        @Override
+        public void run() {
+            InstanceIdentifier<T> tIID = removedIID.firstIdentifierOf(tClass);
+            if (tIID != null) {
+                Optional<T> optional = mdsalUtils.read(datastoreType, tIID);
+                if (optional.isPresent()) {
+
+                    if (shouldClean(optional.get())) {
+                        if (mdsalUtils.delete(datastoreType, tIID)) {
+                            postRemove(tIID);
                         }
-                    }
-                    final InstanceIdentifier<Gate> gatesInstance = thisInstance.firstIdentifierOf(Gate.class);
-                    if (gatesInstance != null) {
-                        final GateKey gateKey = InstanceIdentifier.keyOf(gatesInstance);
-                        if (gateKey != null) {
-                            gatePathMap.put("gateId", gateKey.getGateId());
+                        else {
+                            removeFailed(tIID);
                         }
                     }
+
                 }
-            } catch (ClassCastException err) {
-                logger.warn("Unexpected exception", err);
+            }
+            else {
+                logger.error("Expected to find InstanceIdentifier<{}> but was not found: {}",
+                        tClass.getSimpleName(), removedIID);
             }
         }
 
-        private void getCcaps(final Map<InstanceIdentifier<?>, DataObject> thisData) {
-            logger.info("onDataChanged().getCcaps(): " + thisData);
-            for (final Map.Entry<InstanceIdentifier<?>, DataObject> entry : thisData.entrySet()) {
+        /**
+         * If returns true the object will be removed from the datastore
+         * @param object The object that might be removed.
+         * @return true if it should be removed.
+         */
+        abstract boolean shouldClean(final T object);
 
-                if (entry.getKey().getTargetType().equals(Ccap.class)) {
-                    Ccap ccaps = ((Ccap) entry.getValue());
-                    InstanceIdentifier<Ccap> ccapsIid = InstanceIdentifier.builder(Ccaps.class).child(Ccap.class, new CcapKey(ccaps.getCcapId())).build();
-                    ccapIidMap.put(ccapsIid, ccaps);
-                }
+        /**
+         * Called after an instance is removed.
+         * @param tIID the InstanceIdentifier of the removed object
+         */
+        void postRemove(InstanceIdentifier<T> tIID) {
 
-                if (entry.getKey().getTargetType().equals(Connection.class) ||
-                        entry.getKey().getTargetType().equals(Ccap.class)) {
-                    reqCcapIds.add(entry.getKey());
-                }
-            }
         }
 
-        private void getGates(final Map<InstanceIdentifier<?>, DataObject> thisData) {
-            logger.info("onDataChanged().getGates(): " + thisData);
-            for (final Map.Entry<InstanceIdentifier<?>, DataObject> entry : thisData.entrySet()) {
-                if (entry.getValue() instanceof Gate) {
-                    final Gate gate = (Gate)entry.getValue();
-
-                    // TODO FIXME - Potential ClassCastException thrown here!!!
-                    final InstanceIdentifier<Gate> gateIID = (InstanceIdentifier<Gate>)entry.getKey();
-                    getGatePathMap(gateIID);
-                    if (!gateIidMap.containsKey(gateIID)){
-                        gateIidMap.put(gateIID, gate);
-                    }
-                }
-                // TODO reconciliate gates
-//                if (entry.getValue() instanceof Qos) {
-//                    final Qos qos = (Qos) entry.getValue();
-//                    if (qos.getApps() != null) {
-//                        for (Apps apps : qos.getApps()) {
-//                            if (apps.getSubs() != null) {
-//                                for (Subs subs : apps.getSubs()) {
-//                                    if (subs.getGates() != null) {
-//                                        for (Gate gates : subs.getGates()) {
-//                                            final InstanceIdentifier<Gate> gateIID = (InstanceIdentifier<Gate>)entry.getKey();
-//                                            getGatePathMap(gateIID);
-//                                            if (!gateIidMap.containsKey(gateIID)){
-//                                                gateIidMap.put(gateIID, gates);
-//                                            }
-//                                        }
-//                                    }
-//                                }
-//                            }
-//                        }
-//                    }
-//                }
-            }
+        void removeFailed(InstanceIdentifier<T> tIID) {
+            logger.error("Failed to remove {}", tIID);
         }
     }
 
-    @Override
-    public void onDataChanged(final AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> change) {
-        logger.info("onDataChanged");
-        // Determine what change action took place by looking at the change object's InstanceIdentifier sets
-        // and validate all instance data
-        if (!change.getCreatedData().isEmpty()) {
-            if (!new ValidateInstanceData(mdsalUtils, change.getCreatedData()).validateYang()) {
-                // leave now -- a bad yang object has been detected and a response object has been inserted
+
+    /**
+     * Listener for the packetcable:ccaps tree
+     */
+    private class CcapsDataChangeListener extends AbstractDataChangeListener<Ccap> {
+
+        private final DataValidator ccapsDataValidator = new DataValidator(new CcapsValidatorProviderFactory().build());
+
+        private final Set<InstanceIdentifier<Ccap>> updateQueue = Sets.newConcurrentHashSet();
+
+        public CcapsDataChangeListener() {
+            super(Ccap.class);
+        }
+
+        @Override
+        protected void handleCreatedData(final Map<InstanceIdentifier<Ccap>, Ccap> createdCcaps) {
+            if (createdCcaps.isEmpty()) {
                 return;
             }
-            onCreate(new InstanceData(change.getCreatedData()));
-        } else if (!change.getRemovedPaths().isEmpty()) {
-            onRemove(new InstanceData(change.getRemovedPaths()));
-        } else if (!change.getUpdatedData().isEmpty()) {
-            onUpdate(new InstanceData(change.getUpdatedData()));
-        } else {
-            // we should not be here -- complain bitterly and return
-            logger.error("onDataChanged(): Unknown change action: " + change);
-        }
-    }
 
-    private void onCreate(final InstanceData thisData) {
-        logger.info("onCreate(): " + thisData);
+            final Map<InstanceIdentifier<Ccap>, ValidationException> errorMap =
+                    ccapsDataValidator.validateOneType(createdCcaps, Validator.Extent.NODE_AND_SUBTREE);
+
+            // validate all new objects an update operational datastore
+            if (!errorMap.isEmpty()) {
+                // bad data write errors to operational datastore
+                saveErrors(errorMap, createdCcaps);
+            }
+
+            if (createdCcaps.size() > errorMap.size()) {
+                final Map<InstanceIdentifier<Ccap>, Ccap> goodData =
+                        Maps.newHashMapWithExpectedSize(createdCcaps.size() - errorMap.size());
+                for (InstanceIdentifier<Ccap> iid : createdCcaps.keySet()) {
+                    if (!errorMap.containsKey(iid)) {
+                        goodData.put(iid, createdCcaps.get(iid));
+                    }
+                }
+                addNewCcaps(goodData);
+            }
+        }
 
-        // get the CCAP parameters
-        String message;
-        if (! thisData.reqCcapIds.isEmpty()) {
-            for (Map.Entry<InstanceIdentifier<Ccap>, Ccap> entry : thisData.ccapIidMap.entrySet()) {
-                final Ccap thisCcap = entry.getValue();
-                // get the CCAP node identity from the Instance Data
-                final String ccapId = thisCcap.getCcapId();
+        private void addNewCcaps(final Map<InstanceIdentifier<Ccap>, Ccap> goodData) {
+            for (InstanceIdentifier<Ccap> iid : goodData.keySet()) {
+                final Ccap ccap = goodData.get(iid);
 
-                if (pcmmServiceMap.get(thisCcap.getCcapId()) == null) {
-                    final PCMMService pcmmService = new PCMMService(IPCMMClient.CLIENT_TYPE, thisCcap);
-                    // TODO - may want to use the AMID but for the client type but probably not???
+                // add service
+                if (pcmmServiceMap.containsKey(ccap.getCcapId())) {
+                    logger.error("Already monitoring CCAP - " + ccap);
+                    continue;
+                }
+                final PCMMService pcmmService = new PCMMService(IPCMMClient.CLIENT_TYPE, ccap);
+                // TODO - may want to use the AMID but for the client type but probably not???
 /*
                             final PCMMService pcmmService = new PCMMService(
                                     thisCcap.getAmId().getAmType().shortValue(), thisCcap);
 */
-                    message = pcmmService.addCcap();
-                    if (message.contains("200 OK")) {
-                        pcmmServiceMap.put(thisCcap.getCcapId(), pcmmService);
-                        ccapMap.put(ccapId, thisCcap);
-                        updateCcapMaps(thisCcap);
-                        logger.info("Created CCAP: {}/{} : {}", thisData.gatePath, thisCcap, message);
-                        logger.info("Created CCAP: {} : {}", thisData.gatePath, message);
-                    } else {
-                        logger.error("Create CCAP Failed: {} : {}", thisData.gatePath, message);
-                        for (final InstanceIdentifier<?> instId : thisData.reqCcapIds) {
-                            mdsalUtils.delete(LogicalDatastoreType.CONFIGURATION, instId);
-                        }
-                        ccapMap.remove(ccapId);
-                    }
+                ConnectionBuilder connectionBuilder = new ConnectionBuilder();
+                String message = pcmmService.addCcap();
+                if (message.contains("200 OK")) {
+                    pcmmServiceMap.put(ccap.getCcapId(), pcmmService);
+                    ccapMap.put(ccap.getCcapId(), ccap);
+                    updateCcapMaps(ccap);
+                    logger.info("Created CCAP: {}/{} : {}", iid, ccap, message);
+                    logger.info("Created CCAP: {} : {}", iid, message);
+                    connectionBuilder.setConnected(true).setError(Collections.<String>emptyList());
                 } else {
-                    logger.error("Already monitoring CCAP - " + thisCcap);
-                    break;
+                    logger.error("Create CCAP Failed: {} : {}", iid, message);
+
+                    connectionBuilder.setConnected(false).setError(Collections.singletonList(message));
                 }
-            }
-        } else {
-            // get the PCMM gate parameters from the ccapId/appId/subId/gateId path in the Maps entry (if new gate)
-            for (final Map.Entry<InstanceIdentifier<Gate>, Gate> entry : thisData.gateIidMap.entrySet()) {
-                message = null;
-                final Gate gate = entry.getValue();
-                final String gateId = gate.getGateId();
-                final String gatePathStr = thisData.gatePath + "/" + gateId ;
-                final InetAddress subId = getInetAddress(thisData.subId);
-                if (subId != null) {
-                    final Ccap thisCcap = findCcapForSubscriberId(subId);
-                    if (thisCcap != null) {
-                        final String ccapId = thisCcap.getCcapId();
-                        // verify SCN exists on CCAP and force gateSpec.Direction to align with SCN direction
-                        final ServiceClassName scn = gate.getTrafficProfile().getServiceClassName();
-                        if (scn != null) {
-                            final ServiceFlowDirection scnDir = findScnOnCcap(scn, thisCcap);
-                            if (scnDir != null) {
-                                if (pcmmServiceMap.get(thisCcap.getCcapId()) != null) {
-                                    message = pcmmServiceMap.get(thisCcap.getCcapId()).sendGateSet(gatePathStr, subId, gate, scnDir);
-                                    gateMap.put(gatePathStr, gate);
-                                    gateCcapMap.put(gatePathStr, thisCcap.getCcapId());
-
-                                    if (message.contains("200 OK")) {
-                                        logger.info("Created QoS gate {} for {}/{}/{} - {}",
-                                                gateId, ccapId, gatePathStr, gate, message);
-                                        logger.info("Created QoS gate {} for {}/{} - {}",
-                                                gateId, ccapId, gatePathStr, message);
-                                    } else {
-                                        logger.info("Unable to create QoS gate {} for {}/{}/{} - {}",
-                                                gateId, ccapId, gatePathStr, gate, message);
-                                        logger.error("Unable to create QoS gate {} for {}/{} - {}",
-                                                gateId, ccapId, gatePathStr, message);
-                                    }
-                                } else {
-                                    logger.error("Unable to locate PCMM Service for CCAP - " + thisCcap);
-                                    break;
-                                }
-                            } else {
-                                logger.error("PCMMService: sendGateSet(): SCN {} not found on CCAP {} for {}/{}",
-                                        scn.getValue(), thisCcap, gatePathStr, gate);
-                                message = String.format("404 Not Found - SCN %s not found on CCAP %s for %s",
-                                        scn.getValue(), thisCcap.getCcapId(), gatePathStr);
-                            }
-                        }
-                    } else {
-                        final String subIdStr = thisData.subId;
-                        message = String.format("404 Not Found - no CCAP found for subscriber %s in %s",
-                                subIdStr, gatePathStr);
-                        logger.info("Create QoS gate {} FAILED: no CCAP found for subscriber {}: @ {}/{}",
-                                gateId, subIdStr, gatePathStr, gate);
-                        logger.error("Create QoS gate {} FAILED: no CCAP found for subscriber {}: @ {}",
-                                gateId, subIdStr, gatePathStr);
-                    }
+
+                Optional<Ccap> optionalCcap = mdsalUtils.read(LogicalDatastoreType.OPERATIONAL, iid);
+
+                final CcapBuilder responseCcapBuilder;
+                if (optionalCcap.isPresent()) {
+                    responseCcapBuilder = new CcapBuilder(optionalCcap.get());
                 } else {
-                    final String subIdStr = thisData.subId;
-                    message = String.format("400 Bad Request - subId must be a valid IP address for subscriber %s in %s",
-                            subIdStr, gatePathStr);
-                    logger.info("Create QoS gate {} FAILED: subId must be a valid IP address for subscriber {}: @ {}/{}",
-                            gateId, subIdStr, gatePathStr, gate);
-                    logger.error("Create QoS gate {} FAILED: subId must be a valid IP address for subscriber {}: @ {}",
-                            gateId, subIdStr, gatePathStr);
+                    responseCcapBuilder = new CcapBuilder();
+                    responseCcapBuilder.setCcapId(ccap.getCcapId());
                 }
-                if (!message.contains("200 OK")) {
-                    mdsalUtils.delete(LogicalDatastoreType.CONFIGURATION, entry.getKey());
+
+                responseCcapBuilder.setConnection(connectionBuilder.build());
+
+                mdsalUtils.put(LogicalDatastoreType.OPERATIONAL, iid, responseCcapBuilder.build());
+            }
+
+        }
+
+        @Override
+        protected void handleUpdatedData(final Map<InstanceIdentifier<Ccap>, Ccap> updatedCcaps,
+                final Map<InstanceIdentifier<Ccap>, Ccap> originalCcaps) {
+
+            // TODO actually support updates
+
+            // update operation not allowed -- restore the original config object and complain
+            for (final Map.Entry<InstanceIdentifier<Ccap>, Ccap> entry : updatedCcaps.entrySet()) {
+                if (!originalCcaps.containsKey(entry.getKey())) {
+                    logger.error("No original data found for supposedly updated data: {}", entry.getValue());
+                    continue;
                 }
+
+                // If this notification is coming from our modification ignore it.
+                if (updateQueue.contains(entry.getKey())) {
+                    updateQueue.remove(entry.getKey());
+                    continue;
+                }
+
+                final Ccap originalCcap = originalCcaps.get(entry.getKey());
+                //final Ccap updatedCcap = entry.getValue();
+
+                // restore the original data
+                updateQueue.add(entry.getKey());
+                mdsalUtils.put(LogicalDatastoreType.CONFIGURATION, entry.getKey(), originalCcap);
+                logger.error("CCAP update not permitted {}", entry.getKey());
             }
         }
+
+        @Override
+        protected void handleRemovedData(final Set<InstanceIdentifier<Ccap>> removedCcapPaths,
+                final Map<InstanceIdentifier<Ccap>, Ccap> originalCcaps) {
+
+            for (InstanceIdentifier<Ccap> iid : removedCcapPaths) {
+                final Ccap nukedCcap = originalCcaps.get(iid);
+                removeCcapFromAllMaps(nukedCcap);
+
+                mdsalUtils.delete(LogicalDatastoreType.OPERATIONAL, iid);
+
+                // clean up ccaps level if it is now empty
+                executor.execute(new CcapsCleaner(iid));
+            }
+
+        }
     }
 
-    private void onRemove(final InstanceData thisData) {
-        logger.info("onRemove(): " + thisData);
-        for (final String gatePathStr: thisData.removePathList) {
-            if (gateMap.containsKey(gatePathStr)) {
-                final Gate thisGate = gateMap.remove(gatePathStr);
-                final String gateId = thisGate.getGateId();
-                final String ccapId = gateCcapMap.remove(gatePathStr);
-                final Ccap thisCcap = ccapMap.get(ccapId);
-                final PCMMService service = pcmmServiceMap.get(thisCcap.getCcapId());
-                if (service != null) {
-                    service.sendGateDelete(gatePathStr);
-                    logger.info("onDataChanged(): removed QoS gate {} for {}/{}/{}: ", gateId, ccapId, gatePathStr, thisGate);
-                    logger.info("onDataChanged(): removed QoS gate {} for {}/{}: ", gateId, ccapId, gatePathStr);
-                } else
-                    logger.warn("Unable to send to locate PCMMService to send gate delete message with CCAP - "
-                            + thisCcap);
+
+    private class QosDataChangeListener extends AbstractDataChangeListener<Gate> {
+
+        private final DataValidator qosDataValidator = new DataValidator(new QosValidatorProviderFactory().build());
+        private final Set<InstanceIdentifier<Gate>> updateQueue = Sets.newConcurrentHashSet();
+
+        public QosDataChangeListener() {
+            super(Gate.class);
+        }
+
+        @Override
+        protected void handleCreatedData(final Map<InstanceIdentifier<Gate>, Gate> createdData) {
+
+            final Map<InstanceIdentifier<Gate>, ValidationException> errorMap =
+                    qosDataValidator.validateOneType(createdData, Validator.Extent.NODE_AND_SUBTREE);
+
+            // validate all new objects an update operational datastore
+            if (!errorMap.isEmpty()) {
+                // bad data write errors to operational datastore
+                saveErrors(errorMap, createdData);
+            }
+
+            if (createdData.size() > errorMap.size()) {
+                final Map<InstanceIdentifier<Gate>, Gate> goodData =
+                        Maps.newHashMapWithExpectedSize(createdData.size() - errorMap.size());
+                for (InstanceIdentifier<Gate> iid : createdData.keySet()) {
+                    if (!errorMap.containsKey(iid)) {
+                        goodData.put(iid, createdData.get(iid));
+                    }
+                }
+                addNewGates(goodData);
             }
+
         }
-        for (final String ccapIdStr: thisData.removePathList) {
-            if (ccapMap.containsKey(ccapIdStr)) {
-                final Ccap thisCcap = ccapMap.remove(ccapIdStr);
-                removeCcapFromAllMaps(thisCcap);
+
+        private void addNewGates(final Map<InstanceIdentifier<Gate>, Gate> createdGates) {
+
+            for (InstanceIdentifier<Gate> gateIID : createdGates.keySet()) {
+                final Gate newGate = createdGates.get(gateIID);
+
+                final String newGatePathStr = makeGatePathString(gateIID);
+
+                final InstanceIdentifier<Subscriber> subscriberIID = gateIID.firstIdentifierOf(Subscriber.class);
+                final SubscriberKey subscriberKey = InstanceIdentifier.keyOf(subscriberIID);
+                final InetAddress subscriberAddr = getInetAddress(subscriberKey.getSubscriberId());
+                if (subscriberAddr == null) {
+                    final String msg = String.format("subscriberId must be a valid ipaddress: %s",
+                            subscriberKey.getSubscriberId());
+                    logger.error(msg);
+                    saveGateError(gateIID, newGatePathStr, msg);
+                    continue;
+                }
+
+                final Ccap ccap = findCcapForSubscriberId(subscriberAddr);
+                if (ccap == null) {
+                    final String msg = String.format("Unable to find Ccap for subscriber %s: @ %s",
+                            subscriberKey.getSubscriberId(), newGatePathStr);
+                    logger.error(msg);
+                    saveGateError(gateIID, newGatePathStr, msg);
+                    continue;
+                }
+
+                final ServiceClassName scn = newGate.getTrafficProfile().getServiceClassName();
+                final ServiceFlowDirection scnDirection = findScnOnCcap(scn, ccap);
+                if (scnDirection == null) {
+                    final String msg = String.format("SCN %s not found on CCAP %s for %s",
+                            scn, ccap.getCcapId(), newGatePathStr);
+                    logger.error(msg);
+                    saveGateError(gateIID, newGatePathStr, msg);
+                    continue;
+                }
+
+                final PCMMService pcmmService = pcmmServiceMap.get(ccap.getCcapId());
+                if (pcmmService == null) {
+                    final String msg = String.format("Unable to locate PCMM Service for CCAP: %s ; with subscriber: %s",
+                            ccap, subscriberKey.getSubscriberId());
+                    logger.error(msg);
+                    saveGateError(gateIID, newGatePathStr, msg);
+                    continue;
+                }
+
+                PCMMService.GateSetStatus status = pcmmService.sendGateSet(newGatePathStr, subscriberAddr, newGate, scnDirection);
+                gateMap.put(newGatePathStr, newGate);
+                gateCcapMap.put(newGatePathStr, ccap.getCcapId());
+
+                final GateBuilder gateBuilder = new GateBuilder();
+                gateBuilder.setGateId(newGate.getGateId())
+                        .setGatePath(newGatePathStr)
+                        .setCcapId(ccap.getCcapId())
+                        .setCopsGateId(status.getCopsGateId())
+                        .setCopsState(status.didSucceed() ? "success" : "failure");
+                if (!status.didSucceed()) {
+                    gateBuilder.setError(Collections.singletonList(status.getMessage()));
+                }
+
+                Gate operationalGate = gateBuilder.build();
+
+                mdsalUtils.put(LogicalDatastoreType.OPERATIONAL, gateIID, operationalGate);
+
             }
+
         }
-    }
 
-    private void onUpdate(final InstanceData oldData) {
-        logger.info("onUpdate(): " + oldData);
-        // update operation not allowed -- restore the original config object and complain
-        if (! oldData.ccapIidMap.isEmpty()) {
-            for (final Map.Entry<InstanceIdentifier<Ccap>, Ccap> entry : oldData.ccapIidMap.entrySet()) {
-                final Ccap ccap = entry.getValue();
-                final String ccapId = ccap.getCcapId();
-                // restores the original data - although I don't think this is what is done here! I think the update data is put into the DS/config
-                mdsalUtils.merge(LogicalDatastoreType.CONFIGURATION, entry.getKey(), ccap);
-                logger.error("onDataChanged(): CCAP update not permitted {}/{}", ccapId, ccap);
+        private void saveGateError(@Nonnull final InstanceIdentifier<Gate> gateIID, @Nonnull final String gatePathStr,
+                @Nonnull final String error) {
+            checkNotNull(gateIID);
+            checkNotNull(error);
+
+            final GateBuilder gateBuilder = new GateBuilder();
+            gateBuilder.setGateId(InstanceIdentifier.keyOf(gateIID).getGateId())
+                    .setGatePath(gatePathStr)
+                    .setCopsGateId("")
+                    .setCopsState("N/A");
+
+                gateBuilder.setError(Collections.singletonList(error));
+
+            Gate operationalGate = gateBuilder.build();
+
+            mdsalUtils.put(LogicalDatastoreType.OPERATIONAL, gateIID, operationalGate);
+        }
+
+        @Override
+        protected void handleUpdatedData(final Map<InstanceIdentifier<Gate>, Gate> updatedData,
+                final Map<InstanceIdentifier<Gate>, Gate> originalData) {
+            // TODO actually support updates
+
+            // update operation not allowed -- restore the original config object and complain
+            for (final Map.Entry<InstanceIdentifier<Gate>, Gate> entry : updatedData.entrySet()) {
+                if (!originalData.containsKey(entry.getKey())) {
+                    logger.error("No original data found for supposedly updated data: {}", entry.getValue());
+                    continue;
+                }
+
+                // If this notification is coming from our modification ignore it.
+                if (updateQueue.contains(entry.getKey())) {
+                    updateQueue.remove(entry.getKey());
+                    continue;
+                }
+
+                final Gate originalGate = originalData.get(entry.getKey());
+
+                // restores the original data
+                updateQueue.add(entry.getKey());
+                mdsalUtils.put(LogicalDatastoreType.CONFIGURATION, entry.getKey(), originalGate);
+                logger.error("Update not permitted {}", entry.getKey());
+
             }
-        } else {
-            for (final Map.Entry<InstanceIdentifier<Gate>, Gate> entry : oldData.gateIidMap.entrySet()) {
-                final Gate gate = entry.getValue();
-                final String gatePathStr = oldData.gatePath + "/" + gate.getGateId() ;
-             // restores the original data - although I don't think this is what is done here! I think the update data is put into the DS/config
-                mdsalUtils.merge(LogicalDatastoreType.CONFIGURATION, entry.getKey(), gate);
-                logger.error("onDataChanged(): QoS Gate update not permitted: {}/{}", gatePathStr, gate);
+        }
+
+
+
+        @Override
+        protected void handleRemovedData(final Set<InstanceIdentifier<Gate>> removedPaths,
+                final Map<InstanceIdentifier<Gate>, Gate> originalData) {
+
+            for (final InstanceIdentifier<Gate> removedGateIID : removedPaths) {
+
+                mdsalUtils.delete(LogicalDatastoreType.OPERATIONAL, removedGateIID);
+                //TODO check if this was the last gate for this app/subscriber and if so delete them
+
+                executor.execute(new SubscriberCleaner(removedGateIID));
+
+                final String gatePathStr = makeGatePathString(removedGateIID);
+
+                    if (gateMap.containsKey(gatePathStr)) {
+                        final Gate thisGate = gateMap.remove(gatePathStr);
+                        final String gateId = thisGate.getGateId();
+                        final String ccapId = gateCcapMap.remove(gatePathStr);
+                        final Ccap thisCcap = ccapMap.get(ccapId);
+                        final PCMMService service = pcmmServiceMap.get(thisCcap.getCcapId());
+                        if (service != null) {
+                            service.sendGateDelete(gatePathStr);
+                            logger.info("onDataChanged(): removed QoS gate {} for {}/{}/{}: ", gateId, ccapId, gatePathStr,
+                                    thisGate);
+                        } else {
+                            logger.warn(
+                                    "Unable to send to locate PCMMService to send gate delete message with CCAP - " + thisCcap);
+                        }
+                    }
+
+
             }
+
+        }
+
+        private String makeGatePathString(InstanceIdentifier<Gate> iid) {
+            final InstanceIdentifier<App> appIID = iid.firstIdentifierOf(App.class);
+            final AppKey appKey = InstanceIdentifier.keyOf(appIID);
+
+            final InstanceIdentifier<Subscriber> subscriberIID = iid.firstIdentifierOf(Subscriber.class);
+            final SubscriberKey subscriberKey = InstanceIdentifier.keyOf(subscriberIID);
+
+            final GateKey gateKey = InstanceIdentifier.keyOf(iid);
+
+            return appKey.getAppId()
+                    + "/" + subscriberKey.getSubscriberId()
+                    + "/" + gateKey.getGateId();
         }
     }
+
 }