[ONOS-4691] Refactoring OpticalPortOperator (2/3)

- Define ConfigOperator for a Port
- Refactor OpticalPortOperator as PortConfigOperator
- Add plug-in mechanism for PortConfigOperator on DeviceManager
- Move OpticalPortConfig, OpticalPortOperator to optical-model bundle

Change-Id: I5d416305b0c1b0e31e0ad64baa92d126303548bc
diff --git a/core/api/src/main/java/org/onosproject/net/config/PortConfigOperator.java b/core/api/src/main/java/org/onosproject/net/config/PortConfigOperator.java
new file mode 100644
index 0000000..47886b2
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/config/PortConfigOperator.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.net.config;
+
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.device.PortDescription;
+
+import com.google.common.annotations.Beta;
+
+/**
+ * {@link ConfigOperator} for Port.
+ * <p>
+ * Note: We currently assumes {@link PortConfigOperator}s are commutative.
+ */
+@Beta
+public interface PortConfigOperator extends ConfigOperator {
+
+    /**
+     * Binds {@link NetworkConfigService} to use for retrieving configuration.
+     *
+     * @param networkConfigService the service to use
+     */
+    void bindService(NetworkConfigService networkConfigService);
+
+
+    /**
+     * Generates a PortDescription containing fields from a PortDescription and
+     * configuration.
+     *
+     * @param cp {@link ConnectPoint} representing the port.
+     * @param descr input {@link PortDescription}
+     * @return Combined {@link PortDescription}
+     */
+    PortDescription combine(ConnectPoint cp, PortDescription descr);
+
+    /**
+     * Generates a PortDescription containing fields from a PortDescription and
+     * configuration.
+     *
+     * @param did DeviceId which the port described by {@code descr} resides.
+     * @param descr input {@link PortDescription}
+     * @return Combined {@link PortDescription}
+     */
+    default PortDescription combine(DeviceId did, PortDescription descr) {
+        return combine(new ConnectPoint(did, descr.portNumber()), descr);
+    }
+
+}
diff --git a/core/api/src/main/java/org/onosproject/net/config/PortConfigOperatorRegistry.java b/core/api/src/main/java/org/onosproject/net/config/PortConfigOperatorRegistry.java
new file mode 100644
index 0000000..dce315c
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/config/PortConfigOperatorRegistry.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.onosproject.net.config;
+
+import org.onosproject.net.ConnectPoint;
+
+import com.google.common.annotations.Beta;
+
+/**
+ * Abstraction of a port operator registry.
+ */
+@Beta
+public interface PortConfigOperatorRegistry {
+
+    /**
+     * Registers {@link PortConfigOperator} instance.
+     *
+     * @param portOp {@link PortConfigOperator} instance.
+     * @param configs {@link Config} class for a Port referred by {@code portOp}
+     */
+    void registerPortConfigOperator(PortConfigOperator portOp,
+                                    Class<? extends Config<ConnectPoint>>... configs);
+
+    /**
+     * Unregisters {@link PortConfigOperator} instance.
+     *
+     * @param portOp {@link PortConfigOperator} instance.
+     */
+    void unregisterPortConfigOperator(PortConfigOperator portOp);
+
+}
diff --git a/core/api/src/main/java/org/onosproject/net/config/basics/OpticalPortConfig.java b/core/api/src/main/java/org/onosproject/net/config/basics/OpticalPortConfig.java
deleted file mode 100644
index 0509dfb..0000000
--- a/core/api/src/main/java/org/onosproject/net/config/basics/OpticalPortConfig.java
+++ /dev/null
@@ -1,198 +0,0 @@
-/*
- * Copyright 2015-present Open Networking Laboratory
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.onosproject.net.config.basics;
-
-import java.util.Optional;
-
-import org.onosproject.net.config.Config;
-import org.onosproject.net.ConnectPoint;
-import org.onosproject.net.Port;
-
-import com.fasterxml.jackson.databind.JsonNode;
-
-import static org.onosproject.net.config.Config.FieldPresence.OPTIONAL;
-
-
-/**
- * Configurations for an optical port on a device.
- */
-public final class OpticalPortConfig extends Config<ConnectPoint> {
-    // optical type {OMS, OCH, ODUClt, fiber}
-    public static final String TYPE = "type";
-
-    // port name. "name" is the alphanumeric name of the port, but "port" refers
-    // to the port number used as a name string (i.e., for ports without
-    // alphanumeric names).
-    public static final String NAME = "name";
-    public static final String PORT = "port";
-    public static final String STATIC_PORT = "staticPort";
-    public static final String STATIC_LAMBDA = "staticLambda";
-
-    // **Linc-OE : remove if it's not needed after all.**
-    public static final String SPEED = "speed";
-
-    @Override
-    public boolean isValid() {
-        return hasOnlyFields(TYPE, NAME, PORT, STATIC_PORT, STATIC_LAMBDA, SPEED) &&
-                isNumber(STATIC_LAMBDA, OPTIONAL) && isNumber(SPEED, OPTIONAL);
-    }
-
-    /**
-     * Returns the Enum value representing the type of port.
-     *
-     * @return the port type, or null if invalid or unset
-     */
-    public Port.Type type() {
-        JsonNode type = object.path(TYPE);
-        if (type.isMissingNode()) {
-            return null;
-        }
-        return Port.Type.valueOf(type.asText());
-    }
-
-    /**
-     * Returns the port name associated with this port configuration. The Name
-     * is an alphanumeric string.
-     *
-     * @return the name of this port, else, an empty string
-     */
-    public String name() {
-        return getStringValue(NAME);
-    }
-
-    /**
-     * Returns a stringified representation of the port number, configured in
-     * some port types without an alphanumeric name as the port name.
-     *
-     * @return A string representation of the port number
-     */
-    public String numberName() {
-        return getStringValue(PORT);
-    }
-
-    /**
-     * Returns the string-representation of name of the output port. This is
-     * usually an OMS port for an OCH input ports, or an OCH port for ODU input
-     * ports.
-     *
-     * @return the name of this port, else, an empty string
-     */
-    public String staticPort() {
-        return getStringValue(STATIC_PORT);
-    }
-
-    private String getStringValue(String field) {
-        JsonNode name = object.path(field);
-        return name.isMissingNode() ? "" : name.asText();
-    }
-
-    /**
-     * Returns the output lambda configured for this port. The lambda value is
-     * expressed as a frequency value. If the port type doesn't have a notion of
-     * lambdas, this returns an empty Optional.
-     *
-     * @return an Optional that may contain a frequency value.
-     */
-    public Optional<Long> staticLambda() {
-        JsonNode sl = object.path(STATIC_LAMBDA);
-        if (sl.isMissingNode()) {
-            return Optional.empty();
-        }
-        return Optional.of(sl.asLong());
-    }
-
-    /**
-     * Returns the port speed configured for this port. If the port doesn't have
-     * a notion of speed, this returns an empty Optional.
-     *
-     * @return a port speed value whose default is 0.
-     */
-    public Optional<Integer> speed() {
-        JsonNode s = object.path(SPEED);
-        if (s.isMissingNode()) {
-            return Optional.empty();
-        }
-        return Optional.of(s.asInt());
-    }
-
-    /**
-     * Sets the port type, or updates it if it's already set. A null argument removes
-     * this field.
-     *
-     * @param type the port type
-     * @return this OpticalPortConfig instance
-     */
-    public OpticalPortConfig portType(Port.Type type) {
-        // if unspecified, ideally fall back on FIBER or PACKET.
-        String pt = (type == null) ? null : type.toString();
-        return (OpticalPortConfig) setOrClear(TYPE, pt);
-    }
-
-    /**
-     * Sets the port name, or updates it if already set. A null argument removes
-     * this field.
-     *
-     * @param name the port's name
-     * @return this OpticalPortConfig instance
-     */
-    public OpticalPortConfig portName(String name) {
-        return (OpticalPortConfig) setOrClear(NAME, name);
-    }
-
-    /**
-     * Sets the port name from port number, or updates it if already set. A null
-     * argument removes this field.
-     *
-     * @param name the port number, to be used as name
-     * @return this OpticalPortConfig instance
-     */
-    public OpticalPortConfig portNumberName(Long name) {
-        return (OpticalPortConfig) setOrClear(PORT, name);
-    }
-
-    /**
-     * Sets the output port name, or updates it if already set. A null argument
-     * removes this field.
-     *
-     * @param name the output port's name
-     * @return this OpticalPortConfig instance
-     */
-    public OpticalPortConfig staticPort(String name) {
-        return (OpticalPortConfig) setOrClear(STATIC_PORT, name);
-    }
-
-    /**
-     * Sets the output lambda index, or updates it if already set. A null argument
-     * removes this field.
-     *
-     * @param index the output lambda
-     * @return this OpticalPortConfig instance
-     */
-    public OpticalPortConfig staticLambda(Long index) {
-        return (OpticalPortConfig) setOrClear(STATIC_LAMBDA, index);
-    }
-
-    /**
-     * Sets the port speed, or updates it if already set. A null argument
-     * removes this field.
-     *
-     * @param bw the port bandwidth
-     * @return this OpticalPortConfig instance
-     */
-    public OpticalPortConfig speed(Integer bw) {
-        return (OpticalPortConfig) setOrClear(SPEED, bw);
-    }
-}
diff --git a/core/net/pom.xml b/core/net/pom.xml
index 428ec4d..32b4ab5 100644
--- a/core/net/pom.xml
+++ b/core/net/pom.xml
@@ -105,9 +105,7 @@
         </dependency>
 
         <!-- TODO Remove after decoupling optical -->
-        <!-- - DeviceManager.InternalDeviceProviderService#ensureGeneric -->
         <!-- - OpticalCompilers x4 -->
-        <!-- - OpticalPortOperator -->
         <dependency>
             <groupId>org.onosproject</groupId>
             <artifactId>onos-optical-model</artifactId>
diff --git a/core/net/src/main/java/org/onosproject/net/config/impl/BasicNetworkConfigs.java b/core/net/src/main/java/org/onosproject/net/config/impl/BasicNetworkConfigs.java
index a40f86a..d02b4cb 100644
--- a/core/net/src/main/java/org/onosproject/net/config/impl/BasicNetworkConfigs.java
+++ b/core/net/src/main/java/org/onosproject/net/config/impl/BasicNetworkConfigs.java
@@ -34,7 +34,6 @@
 import org.onosproject.net.config.basics.BasicDeviceConfig;
 import org.onosproject.net.config.basics.BasicHostConfig;
 import org.onosproject.net.config.basics.BasicLinkConfig;
-import org.onosproject.net.config.basics.OpticalPortConfig;
 import org.onosproject.net.config.basics.SubjectFactories;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -85,15 +84,6 @@
                 public BasicLinkConfig createConfig() {
                     return new BasicLinkConfig();
                 }
-            },
-            // TODO move this optical specific configuration out to optical app
-            new ConfigFactory<ConnectPoint, OpticalPortConfig>(CONNECT_POINT_SUBJECT_FACTORY,
-                                                               OpticalPortConfig.class,
-                                                               "optical") {
-                @Override
-                public OpticalPortConfig createConfig() {
-                    return new OpticalPortConfig();
-                }
             }
     );
 
diff --git a/core/net/src/main/java/org/onosproject/net/device/impl/DeviceManager.java b/core/net/src/main/java/org/onosproject/net/device/impl/DeviceManager.java
index 23aba6a..cd3c543 100644
--- a/core/net/src/main/java/org/onosproject/net/device/impl/DeviceManager.java
+++ b/core/net/src/main/java/org/onosproject/net/device/impl/DeviceManager.java
@@ -19,19 +19,22 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
-
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Deactivate;
 import org.apache.felix.scr.annotations.Reference;
 import org.apache.felix.scr.annotations.ReferenceCardinality;
 import org.apache.felix.scr.annotations.Service;
+import org.onlab.util.Tools;
 import org.onosproject.cluster.ClusterService;
 import org.onosproject.cluster.NodeId;
 import org.onosproject.mastership.MastershipEvent;
@@ -46,11 +49,13 @@
 import org.onosproject.net.MastershipRole;
 import org.onosproject.net.Port;
 import org.onosproject.net.PortNumber;
+import org.onosproject.net.config.Config;
 import org.onosproject.net.config.NetworkConfigEvent;
 import org.onosproject.net.config.NetworkConfigListener;
 import org.onosproject.net.config.NetworkConfigService;
+import org.onosproject.net.config.PortConfigOperator;
+import org.onosproject.net.config.PortConfigOperatorRegistry;
 import org.onosproject.net.config.basics.BasicDeviceConfig;
-import org.onosproject.net.config.basics.OpticalPortConfig;
 import org.onosproject.net.device.DefaultPortDescription;
 import org.onosproject.net.device.DeviceAdminService;
 import org.onosproject.net.device.DeviceDescription;
@@ -62,29 +67,23 @@
 import org.onosproject.net.device.DeviceService;
 import org.onosproject.net.device.DeviceStore;
 import org.onosproject.net.device.DeviceStoreDelegate;
-import org.onosproject.net.device.OchPortDescription;
-import org.onosproject.net.device.OduCltPortDescription;
-import org.onosproject.net.device.OmsPortDescription;
-import org.onosproject.net.device.OtuPortDescription;
 import org.onosproject.net.device.PortDescription;
 import org.onosproject.net.device.PortStatistics;
-import org.onosproject.net.optical.device.OmsPortHelper;
-import org.onosproject.net.optical.device.OtuPortHelper;
 import org.onosproject.net.provider.AbstractListenerProviderRegistry;
 import org.onosproject.net.provider.AbstractProviderService;
-import org.onosproject.net.provider.Provider;
 import org.slf4j.Logger;
 
+import com.google.common.collect.Multimap;
 import com.google.common.util.concurrent.Futures;
 
 import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.collect.Multimaps.newListMultimap;
+import static com.google.common.collect.Multimaps.synchronizedListMultimap;
 import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
 import static org.onlab.util.Tools.groupedThreads;
 import static org.onosproject.net.MastershipRole.MASTER;
 import static org.onosproject.net.MastershipRole.NONE;
 import static org.onosproject.net.MastershipRole.STANDBY;
-import static org.onosproject.net.optical.device.OchPortHelper.ochPortDescription;
-import static org.onosproject.net.optical.device.OduCltPortHelper.oduCltPortDescription;
 import static org.onosproject.security.AppGuard.checkPermission;
 import static org.onosproject.security.AppPermission.Type.DEVICE_READ;
 import static org.slf4j.LoggerFactory.getLogger;
@@ -96,7 +95,7 @@
 @Service
 public class DeviceManager
         extends AbstractListenerProviderRegistry<DeviceEvent, DeviceListener, DeviceProvider, DeviceProviderService>
-        implements DeviceService, DeviceAdminService, DeviceProviderRegistry {
+        implements DeviceService, DeviceAdminService, DeviceProviderRegistry, PortConfigOperatorRegistry {
 
     private static final String DEVICE_ID_NULL = "Device ID cannot be null";
     private static final String PORT_NUMBER_NULL = "Port number cannot be null";
@@ -130,6 +129,20 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected NetworkConfigService networkConfigService;
 
+
+    /**
+     * List of all registered PortConfigOperator.
+     */
+    private final List<PortConfigOperator> portOps = new CopyOnWriteArrayList<>();
+
+    /**
+     * Index to look up PortConfigOperator from Config each PortConfigOperator uses.
+     */
+    private final Multimap<Class<? extends Config<ConnectPoint>>, PortConfigOperator> portOpsIndex
+        = synchronizedListMultimap(
+           newListMultimap(new ConcurrentHashMap<>(), CopyOnWriteArrayList::new));
+
+
     @Activate
     public void activate() {
         backgroundService = newSingleThreadScheduledExecutor(
@@ -445,60 +458,6 @@
             }
         }
 
-        /**
-         * Transforms optical specific PortDescription to generic PortDescription.
-         *
-         * @param descr PortDescription
-         * @return generic PortDescription
-         * @deprecated in Goldeneye (1.6.0)
-         */
-        @Deprecated
-        private PortDescription ensureGeneric(PortDescription descr) {
-            switch (descr.type()) {
-            case OCH:
-                if (descr instanceof OchPortDescription) {
-                    OchPortDescription och = (OchPortDescription) descr;
-                    return ochPortDescription(och,
-                                              och.signalType(),
-                                              och.isTunable(),
-                                              och.lambda(),
-                                              och.annotations());
-                }
-                break;
-            case ODUCLT:
-                if (descr instanceof OduCltPortDescription) {
-                    OduCltPortDescription clt = (OduCltPortDescription) descr;
-                    return oduCltPortDescription(clt,
-                                                 clt.signalType(),
-                                                 clt.annotations());
-                }
-                break;
-            case OMS:
-                if (descr instanceof OmsPortDescription) {
-                    OmsPortDescription oms = (OmsPortDescription) descr;
-                    return OmsPortHelper.omsPortDescription(oms,
-                                                            oms.minFrequency(),
-                                                            oms.maxFrequency(),
-                                                            oms.grid(),
-                                                            oms.annotations());
-                }
-                break;
-            case OTU:
-                if (descr instanceof OtuPortDescription) {
-                    OtuPortDescription otu = (OtuPortDescription) descr;
-                    return OtuPortHelper.otuPortDescription(otu,
-                                                            otu.signalType(),
-                                                            otu.annotations());
-                }
-                break;
-
-            default:
-                // no-op
-                break;
-            }
-            return descr;
-        }
-
         @Override
         public void updatePorts(DeviceId deviceId,
                                 List<PortDescription> portDescriptions) {
@@ -512,8 +471,7 @@
                 return;
             }
             portDescriptions = portDescriptions.stream()
-                    .map(e -> consolidate(deviceId, e))
-                    .map(this::ensureGeneric)
+                    .map(e -> applyAllPortOps(deviceId, e))
                     .collect(Collectors.toList());
             List<DeviceEvent> events = store.updatePorts(this.provider().id(),
                                                          deviceId, portDescriptions);
@@ -552,30 +510,16 @@
                                                          portDescription.isEnabled());
             }
 
-            portDescription = consolidate(deviceId, portDescription);
+            portDescription = applyAllPortOps(deviceId, portDescription);
             final DeviceEvent event = store.updatePortStatus(this.provider().id(),
                                                              deviceId,
-                                                             ensureGeneric(portDescription));
+                                                             portDescription);
             if (event != null) {
                 log.info("Device {} port {} status changed", deviceId, event.port().number());
                 post(event);
             }
         }
 
-        // merges the appropriate PortConfig with the description.
-        private PortDescription consolidate(DeviceId did, PortDescription desc) {
-            switch (desc.type()) {
-                case COPPER:
-                case VIRTUAL:
-                    return desc;
-                default:
-                    // TODO: add plugin mechanism in order to decouple this
-                    OpticalPortConfig opc = networkConfigService.getConfig(
-                            new ConnectPoint(did, desc.portNumber()), OpticalPortConfig.class);
-                    return OpticalPortOperator.combine(opc, desc);
-            }
-        }
-
         @Override
         public void receivedRoleReply(DeviceId deviceId, MastershipRole requested,
                                       MastershipRole response) {
@@ -835,7 +779,7 @@
             return (event.type() == NetworkConfigEvent.Type.CONFIG_ADDED
                     || event.type() == NetworkConfigEvent.Type.CONFIG_UPDATED)
                     && (event.configClass().equals(BasicDeviceConfig.class)
-                        || event.configClass().equals(OpticalPortConfig.class));
+                        || portOpsIndex.containsKey(event.configClass()));
         }
 
         @Override
@@ -857,20 +801,17 @@
                     }
                 }
             }
-            if (event.configClass().equals(OpticalPortConfig.class)) {
+            if (portOpsIndex.containsKey(event.configClass())) {
                 ConnectPoint cpt = (ConnectPoint) event.subject();
                 DeviceId did = cpt.deviceId();
-                Provider provider = getProvider(did);
-                Port dpt = getPort(did, cpt.port());
 
-                if (dpt != null && provider != null) {
-                    OpticalPortConfig opc = networkConfigService.getConfig(cpt, OpticalPortConfig.class);
-                    PortDescription desc = store.getPortDescription(provider.id(), did, cpt.port());
-                    desc = OpticalPortOperator.combine(opc, desc);
-                    if (desc != null) {
-                        de = store.updatePortStatus(getProvider(did).id(), did, desc);
-                    }
-                }
+                // Note: assuming PortOperator can modify existing port,
+                //       but cannot add new port purely from Config.
+                de = Optional.ofNullable(getProvider(did))
+                        .map(provider -> store.getPortDescription(provider.id(), did, cpt.port()))
+                        .map(desc -> applyAllPortOps(cpt, desc))
+                        .map(desc -> store.updatePortStatus(getProvider(did).id(), did, desc))
+                        .orElse(null);
             }
 
             if (de != null) {
@@ -888,4 +829,78 @@
         }
     }
 
+    @Override
+    @SafeVarargs
+    public final void registerPortConfigOperator(PortConfigOperator portOp,
+                                                              Class<? extends Config<ConnectPoint>>...configs) {
+        checkNotNull(portOp);
+
+        portOp.bindService(networkConfigService);
+
+        // update both portOpsIndex and portOps
+        synchronized (portOpsIndex) {
+            for (Class<? extends Config<ConnectPoint>> config : configs) {
+                portOpsIndex.put(config, portOp);
+            }
+
+            portOps.add(portOp);
+        }
+
+        // TODO: Should we be applying to all existing Ports?
+        Tools.stream(store.getAvailableDevices())
+            .map(Device::id)
+            .filter(mastershipService::isLocalMaster)
+            // for each locally managed Device, update all port descriptions
+            .map(did -> {
+                List<PortDescription> pds
+                    = store.getPortDescriptions(getProvider(did).id(), did)
+                        .map(pdesc -> applyAllPortOps(did, pdesc))
+                        .collect(Collectors.toList());
+                return store.updatePorts(getProvider(did).id(), did, pds);
+                })
+            // ..and port port update event if necessary
+            .forEach(evts -> evts.forEach(this::post));
+    }
+
+    @Override
+    public void unregisterPortConfigOperator(PortConfigOperator portOp) {
+        checkNotNull(portOp);
+
+
+        // remove all portOp
+        synchronized (portOpsIndex) {
+            portOps.remove(portOp);
+
+            // had to do this since COWArrayList iterator doesn't support remove
+            portOpsIndex.keySet().forEach(key -> portOpsIndex.remove(key, portOp));
+        }
+
+    }
+
+    /**
+     * Merges the appropriate PortConfig with the description.
+     *
+     * @param did  ID of the Device where the port is attached
+     * @param desc {@link PortDescription}
+     * @return merged {@link PortDescription}
+     */
+    private PortDescription applyAllPortOps(DeviceId did, PortDescription desc) {
+        return applyAllPortOps(new ConnectPoint(did, desc.portNumber()), desc);
+    }
+
+    /**
+     * Merges the appropriate PortConfig with the description.
+     *
+     * @param cpt   ConnectPoint where the port is attached
+     * @param desc  {@link PortDescription}
+     * @return merged {@link PortDescription}
+     */
+    private PortDescription applyAllPortOps(ConnectPoint cpt, PortDescription desc) {
+        PortDescription work = desc;
+        for (PortConfigOperator portOp : portOps) {
+            work = portOp.combine(cpt, work);
+        }
+        return work;
+    }
+
 }
diff --git a/core/net/src/main/java/org/onosproject/net/device/impl/OpticalPortOperator.java b/core/net/src/main/java/org/onosproject/net/device/impl/OpticalPortOperator.java
deleted file mode 100644
index b07780b..0000000
--- a/core/net/src/main/java/org/onosproject/net/device/impl/OpticalPortOperator.java
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * Copyright 2015-present Open Networking Laboratory
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.onosproject.net.device.impl;
-
-import static org.onosproject.net.optical.device.OchPortHelper.ochPortDescription;
-import static org.onosproject.net.optical.device.OduCltPortHelper.oduCltPortDescription;
-import static org.onosproject.net.optical.device.OmsPortHelper.omsPortDescription;
-import static org.onosproject.net.optical.device.OtuPortHelper.otuPortDescription;
-import static org.slf4j.LoggerFactory.getLogger;
-import org.onosproject.net.config.ConfigOperator;
-import org.onosproject.net.config.basics.OpticalPortConfig;
-import org.onosproject.net.AnnotationKeys;
-import org.onosproject.net.DefaultAnnotations;
-import org.onosproject.net.PortNumber;
-import org.onosproject.net.SparseAnnotations;
-import org.onosproject.net.device.DefaultPortDescription;
-import org.onosproject.net.device.OchPortDescription;
-import org.onosproject.net.device.OduCltPortDescription;
-import org.onosproject.net.device.OmsPortDescription;
-import org.onosproject.net.device.OtuPortDescription;
-import org.onosproject.net.device.PortDescription;
-import org.slf4j.Logger;
-
-/**
- * Implementations of merge policies for various sources of optical port
- * configuration information. This includes applications, provides, and network
- * configurations.
- */
-public final class OpticalPortOperator implements ConfigOperator {
-
-    private static final Logger log = getLogger(OpticalPortOperator.class);
-
-    private OpticalPortOperator() {
-    }
-
-    /**
-     * Generates a PortDescription containing fields from a PortDescription and
-     * an OpticalPortConfig.
-     *
-     * @param opc the port config entity from network config
-     * @param descr a PortDescription
-     * @return PortDescription based on both sources
-     */
-    public static PortDescription combine(OpticalPortConfig opc, PortDescription descr) {
-        if (opc == null) {
-            return descr;
-        }
-
-        PortNumber port = descr.portNumber();
-        final String name = opc.name();
-        final String numName = opc.numberName();
-        // if the description is null, or the current description port name != config name,
-        // create a new PortNumber.
-        PortNumber newPort = null;
-        if (port == null) {
-            // try to get the portNumber from the numName.
-            if (!numName.isEmpty()) {
-                final long pn = Long.parseLong(numName);
-                newPort = (!name.isEmpty()) ? PortNumber.portNumber(pn, name) : PortNumber.portNumber(pn);
-            } else {
-                // we don't have defining info (a port number value)
-                throw new RuntimeException("Possible misconfig, bailing on handling for: \n\t" + descr);
-            }
-        } else if ((!name.isEmpty()) && !name.equals(port.name())) {
-            final long pn = (numName.isEmpty()) ? port.toLong() : Long.parseLong(numName);
-            newPort = PortNumber.portNumber(pn, name);
-        }
-
-        // Port type won't change unless we're overwriting a port completely.
-        // Watch out for overwrites to avoid class cast craziness.
-        boolean noOwrite = opc.type() == descr.type();
-
-        SparseAnnotations sa = combine(opc, descr.annotations());
-        if (noOwrite) {
-            return updateDescription((newPort == null) ? port : newPort, sa, descr);
-        } else {
-            // TODO: must reconstruct a different type of PortDescription.
-            log.info("Type rewrite from {} to {} required", descr.type(), opc.type());
-        }
-        return descr;
-    }
-
-    // updates a port description whose port type has not changed.
-    /**
-     * Updates {@link PortDescription} using specified number and annotations.
-     *
-     * @param port {@link PortNumber} to use in updated description
-     * @param sa   annotations to use in updated description
-     * @param descr base {@link PortDescription}
-     * @return updated {@link PortDescription}
-     */
-    private static PortDescription updateDescription(PortNumber port,
-                                                     SparseAnnotations sa,
-                                                     PortDescription descr) {
-
-        // TODO This switch can go away once deprecation is complete.
-        switch (descr.type()) {
-            case OMS:
-                if (descr instanceof OmsPortDescription) {
-                    OmsPortDescription oms = (OmsPortDescription) descr;
-                    return omsPortDescription(port, oms.isEnabled(), oms.minFrequency(),
-                                                  oms.maxFrequency(), oms.grid(), sa);
-                }
-                break;
-            case OCH:
-                // We might need to update lambda below with STATIC_LAMBDA.
-                if (descr instanceof OchPortDescription) {
-                    OchPortDescription och = (OchPortDescription) descr;
-                    return ochPortDescription(port, och.isEnabled(), och.signalType(),
-                            och.isTunable(), och.lambda(), sa);
-                }
-                break;
-            case ODUCLT:
-                if (descr instanceof OduCltPortDescription) {
-                    OduCltPortDescription odu = (OduCltPortDescription) descr;
-                    return oduCltPortDescription(port, odu.isEnabled(), odu.signalType(), sa);
-                }
-                break;
-            case PACKET:
-            case FIBER:
-            case COPPER:
-                break;
-            case OTU:
-                if (descr instanceof OtuPortDescription) {
-                    OtuPortDescription otu = (OtuPortDescription) descr;
-                    return otuPortDescription(port, otu.isEnabled(), otu.signalType(), sa);
-                }
-                break;
-            default:
-                log.warn("Unsupported optical port type {} - can't update", descr.type());
-                return descr;
-        }
-        if (port.exactlyEquals(descr.portNumber()) && sa.equals(descr.annotations())) {
-            // result is no-op
-            return descr;
-        }
-        return new DefaultPortDescription(port,
-                                          descr.isEnabled(),
-                                          descr.type(),
-                                          descr.portSpeed(),
-                                          sa);
-    }
-
-    /**
-     * Generates an annotation from an existing annotation and OptcalPortConfig.
-     *
-     * @param opc the port config entity from network config
-     * @param an the annotation
-     * @return annotation combining both sources
-     */
-    public static SparseAnnotations combine(OpticalPortConfig opc, SparseAnnotations an) {
-        DefaultAnnotations.Builder b = DefaultAnnotations.builder();
-        if (!opc.staticPort().isEmpty()) {
-            b.set(AnnotationKeys.STATIC_PORT, opc.staticPort());
-        }
-        if (opc.staticLambda().isPresent()) {
-            b.set(AnnotationKeys.STATIC_LAMBDA, String.valueOf(opc.staticLambda().get()));
-        }
-        // The following may not need to be carried.
-        if (!opc.name().isEmpty()) {
-            b.set(AnnotationKeys.PORT_NAME, opc.name());
-        }
-        return DefaultAnnotations.union(an, b.build());
-    }
-
-}
diff --git a/core/net/src/test/java/org/onosproject/net/device/impl/OpticalPortOperatorTest.java b/core/net/src/test/java/org/onosproject/net/device/impl/OpticalPortOperatorTest.java
deleted file mode 100644
index 6d257e4..0000000
--- a/core/net/src/test/java/org/onosproject/net/device/impl/OpticalPortOperatorTest.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright 2015-present Open Networking Laboratory
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.onosproject.net.device.impl;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.onosproject.net.CltSignalType;
-import org.onosproject.net.config.Config;
-import org.onosproject.net.config.ConfigApplyDelegate;
-import org.onosproject.net.config.basics.OpticalPortConfig;
-import org.onosproject.net.AnnotationKeys;
-import org.onosproject.net.ConnectPoint;
-import org.onosproject.net.DefaultAnnotations;
-import org.onosproject.net.DeviceId;
-import org.onosproject.net.Port;
-import org.onosproject.net.PortNumber;
-import org.onosproject.net.SparseAnnotations;
-import org.onosproject.net.device.PortDescription;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.node.JsonNodeFactory;
-
-import static org.junit.Assert.assertEquals;
-import static org.onosproject.net.optical.device.OduCltPortHelper.oduCltPortDescription;
-
-public class OpticalPortOperatorTest {
-    private static final DeviceId DID = DeviceId.deviceId("op-test");
-    private static final long PORT_NUMBER = 100;
-    private static final String CFG_KEY = "optical";
-
-    private static final String CFG_PORT_NAME = "cfg-name";
-    private static final long CFG_STATIC_LAMBDA = 300L;
-
-    private static final String DESC_PORT_NAME = "test-port-100";
-    private static final PortNumber NAMED = PortNumber.portNumber(PORT_NUMBER, DESC_PORT_NAME);
-    private static final PortNumber UNNAMED = PortNumber.portNumber(PORT_NUMBER);
-
-    private static final String DESC_STATIC_PORT = "out-port-200";
-    private static final SparseAnnotations SA = DefaultAnnotations.builder()
-                                                    .set(AnnotationKeys.STATIC_PORT, DESC_STATIC_PORT)
-                                                    .build();
-
-    private static final PortDescription N_DESC = oduCltPortDescription(
-            NAMED, true, CltSignalType.CLT_100GBE, SA);
-    private static final PortDescription U_DESC = oduCltPortDescription(
-            UNNAMED, true, CltSignalType.CLT_100GBE, SA);
-
-    private final ConfigApplyDelegate delegate = new MockCfgDelegate();
-    private final ObjectMapper mapper = new ObjectMapper();
-
-    private static final ConnectPoint CP = new ConnectPoint(DID, UNNAMED);
-
-    private static final OpticalPortConfig OPC = new OpticalPortConfig();
-
-    @Before
-    public void setUp() {
-        OPC.init(CP, CFG_KEY, JsonNodeFactory.instance.objectNode(), mapper, delegate);
-    }
-
-    @Test
-    public void testConfigPortName() {
-        OPC.portType(Port.Type.ODUCLT)
-            .portNumberName(PORT_NUMBER)
-            .portName(CFG_PORT_NAME);
-
-        PortDescription res;
-        // full desc + opc with name
-        res = OpticalPortOperator.combine(OPC, N_DESC);
-        assertEquals("Configured port name expected",
-                     CFG_PORT_NAME, res.portNumber().name());
-        assertEquals(DESC_STATIC_PORT, res.annotations().value(AnnotationKeys.STATIC_PORT));
-
-        res = OpticalPortOperator.combine(OPC, U_DESC);
-        assertEquals("Configured port name expected",
-                     CFG_PORT_NAME, res.portNumber().name());
-        assertEquals(DESC_STATIC_PORT, res.annotations().value(AnnotationKeys.STATIC_PORT));
-    }
-
-    @Test
-    public void testConfigAddStaticLambda() {
-        OPC.portType(Port.Type.ODUCLT)
-            .portNumberName(PORT_NUMBER)
-            .staticLambda(CFG_STATIC_LAMBDA);
-
-        PortDescription res;
-        res = OpticalPortOperator.combine(OPC, N_DESC);
-        assertEquals("Original port name expected",
-                     DESC_PORT_NAME, res.portNumber().name());
-        assertEquals(DESC_STATIC_PORT, res.annotations().value(AnnotationKeys.STATIC_PORT));
-        long sl = Long.valueOf(res.annotations().value(AnnotationKeys.STATIC_LAMBDA));
-        assertEquals(CFG_STATIC_LAMBDA, sl);
-    }
-
-    @Test
-    public void testEmptyConfig() {
-        OPC.portType(Port.Type.ODUCLT)
-            .portNumberName(PORT_NUMBER);
-
-        PortDescription res;
-        res = OpticalPortOperator.combine(OPC, N_DESC);
-        assertEquals("Configured port name expected",
-                     DESC_PORT_NAME, res.portNumber().name());
-        assertEquals(DESC_STATIC_PORT, res.annotations().value(AnnotationKeys.STATIC_PORT));
-    }
-
-
-    private class MockCfgDelegate implements ConfigApplyDelegate {
-
-        @Override
-        public void onApply(@SuppressWarnings("rawtypes") Config config) {
-            config.apply();
-        }
-
-    }
-}