[ONOS-4513] decouple optical-model from CLI

- core/cli is now optical-model clean
- Add command for pretty printing optical-ports

Change-Id: I2926ef6d52971d1a7f6642dfe4098d7ed2422363
diff --git a/apps/optical-model/BUCK b/apps/optical-model/BUCK
index e311eb3..e78c286 100644
--- a/apps/optical-model/BUCK
+++ b/apps/optical-model/BUCK
@@ -1,6 +1,8 @@
 COMPILE_DEPS = [
     '//lib:CORE_DEPS',
     '//incubator/api:onos-incubator-api',
+    '//cli:onos-cli',
+    '//lib:org.apache.karaf.shell.console',
 ]
 
 TEST_DEPS = [
diff --git a/apps/optical-model/pom.xml b/apps/optical-model/pom.xml
index c5bb627..d8d50d8 100644
--- a/apps/optical-model/pom.xml
+++ b/apps/optical-model/pom.xml
@@ -59,6 +59,17 @@
 
         <dependency>
             <groupId>org.onosproject</groupId>
+            <artifactId>onos-cli</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.karaf.shell</groupId>
+            <artifactId>org.apache.karaf.shell.console</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.onosproject</groupId>
             <artifactId>onlab-junit</artifactId>
             <scope>test</scope>
         </dependency>
diff --git a/apps/optical-model/src/main/java/org/onosproject/net/optical/OchPort.java b/apps/optical-model/src/main/java/org/onosproject/net/optical/OchPort.java
index 8d19889..051bbed 100644
--- a/apps/optical-model/src/main/java/org/onosproject/net/optical/OchPort.java
+++ b/apps/optical-model/src/main/java/org/onosproject/net/optical/OchPort.java
@@ -17,8 +17,6 @@
 
 import org.onosproject.net.OchSignal;
 import org.onosproject.net.OduSignalType;
-import org.onosproject.net.Port;
-
 import com.google.common.annotations.Beta;
 
 /**
@@ -27,7 +25,7 @@
  * See ITU G.709 "Interfaces for the Optical Transport Network (OTN)"
  */
 @Beta
-public interface OchPort extends Port {
+public interface OchPort extends ProjectedPort {
 
     /**
      * Returns ODU signal type.
diff --git a/apps/optical-model/src/main/java/org/onosproject/net/optical/OduCltPort.java b/apps/optical-model/src/main/java/org/onosproject/net/optical/OduCltPort.java
index b4342f4..5dc6dfc 100644
--- a/apps/optical-model/src/main/java/org/onosproject/net/optical/OduCltPort.java
+++ b/apps/optical-model/src/main/java/org/onosproject/net/optical/OduCltPort.java
@@ -16,8 +16,6 @@
 package org.onosproject.net.optical;
 
 import org.onosproject.net.CltSignalType;
-import org.onosproject.net.Port;
-
 import com.google.common.annotations.Beta;
 
 /**
@@ -26,7 +24,7 @@
  * See ITU G.709 "Interfaces for the Optical Transport Network (OTN)"
  */
 @Beta
-public interface OduCltPort extends Port {
+public interface OduCltPort extends ProjectedPort {
 
     /**
      * Returns ODU client signal type.
diff --git a/apps/optical-model/src/main/java/org/onosproject/net/optical/OmsPort.java b/apps/optical-model/src/main/java/org/onosproject/net/optical/OmsPort.java
index 898fce9..f553c71 100644
--- a/apps/optical-model/src/main/java/org/onosproject/net/optical/OmsPort.java
+++ b/apps/optical-model/src/main/java/org/onosproject/net/optical/OmsPort.java
@@ -16,8 +16,6 @@
 package org.onosproject.net.optical;
 
 import org.onlab.util.Frequency;
-import org.onosproject.net.Port;
-
 import com.google.common.annotations.Beta;
 
 /**
@@ -28,7 +26,7 @@
  * Assumes we only support fixed grid for now.
  */
 @Beta
-public interface OmsPort extends Port {
+public interface OmsPort extends ProjectedPort {
 
     /**
      * Returns the total number of channels on the port.
diff --git a/apps/optical-model/src/main/java/org/onosproject/net/optical/OtuPort.java b/apps/optical-model/src/main/java/org/onosproject/net/optical/OtuPort.java
index 15f284d..bf37acf 100644
--- a/apps/optical-model/src/main/java/org/onosproject/net/optical/OtuPort.java
+++ b/apps/optical-model/src/main/java/org/onosproject/net/optical/OtuPort.java
@@ -16,8 +16,6 @@
 package org.onosproject.net.optical;
 
 import org.onosproject.net.OtuSignalType;
-import org.onosproject.net.Port;
-
 import com.google.common.annotations.Beta;
 
 /**
@@ -27,7 +25,7 @@
  * Open Networking Foundation "Optical Transport Protocol Extensions Version 1.0".
  */
 @Beta
-public interface OtuPort extends Port {
+public interface OtuPort extends ProjectedPort {
 
     /**
      * Returns OTU signal type.
diff --git a/apps/optical-model/src/main/java/org/onosproject/net/optical/ProjectedPort.java b/apps/optical-model/src/main/java/org/onosproject/net/optical/ProjectedPort.java
new file mode 100644
index 0000000..a366229
--- /dev/null
+++ b/apps/optical-model/src/main/java/org/onosproject/net/optical/ProjectedPort.java
@@ -0,0 +1,36 @@
+/*
+ * 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.optical;
+
+import org.onosproject.net.Annotations;
+import org.onosproject.net.Port;
+
+import com.google.common.annotations.Beta;
+
+/**
+ * Abstraction of a network port.
+ */
+@Beta
+public interface ProjectedPort extends Port {
+
+    /**
+     * Returns the key/value annotations, not used by projection.
+     *
+     * @return key/value annotations
+     */
+    Annotations unhandledAnnotations();
+
+}
diff --git a/cli/src/main/java/org/onosproject/cli/net/AddOpticalIntentCommand.java b/apps/optical-model/src/main/java/org/onosproject/net/optical/cli/AddOpticalIntentCommand.java
similarity index 93%
rename from cli/src/main/java/org/onosproject/cli/net/AddOpticalIntentCommand.java
rename to apps/optical-model/src/main/java/org/onosproject/net/optical/cli/AddOpticalIntentCommand.java
index 105c53e..382c6e6 100644
--- a/cli/src/main/java/org/onosproject/cli/net/AddOpticalIntentCommand.java
+++ b/apps/optical-model/src/main/java/org/onosproject/net/optical/cli/AddOpticalIntentCommand.java
@@ -13,11 +13,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.onosproject.cli.net;
+package org.onosproject.net.optical.cli;
 
 import org.apache.karaf.shell.commands.Argument;
 import org.apache.karaf.shell.commands.Command;
 import org.apache.karaf.shell.commands.Option;
+import org.onosproject.cli.app.AllApplicationNamesCompleter;
+import org.onosproject.cli.net.ConnectPointCompleter;
+import org.onosproject.cli.net.ConnectivityIntentCommand;
 import org.onosproject.net.CltSignalType;
 import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.Device;
@@ -45,6 +48,14 @@
          description = "Installs optical connectivity intent")
 public class AddOpticalIntentCommand extends ConnectivityIntentCommand {
 
+    // OSGi workaround
+    @SuppressWarnings("unused")
+    private ConnectPointCompleter cpCompleter;
+
+    // OSGi workaround
+    @SuppressWarnings("unused")
+    private AllApplicationNamesCompleter appCompleter;
+
     @Argument(index = 0, name = "ingressDevice",
               description = "Ingress Device/Port Description",
               required = true, multiValued = false)
diff --git a/apps/optical-model/src/main/java/org/onosproject/net/optical/cli/OpticalPortsListCommand.java b/apps/optical-model/src/main/java/org/onosproject/net/optical/cli/OpticalPortsListCommand.java
new file mode 100644
index 0000000..c6aca6d
--- /dev/null
+++ b/apps/optical-model/src/main/java/org/onosproject/net/optical/cli/OpticalPortsListCommand.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2014-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.optical.cli;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.karaf.shell.commands.Command;
+import org.onlab.util.Frequency;
+import org.onosproject.cli.net.DevicePortsListCommand;
+import org.onosproject.net.Device;
+import org.onosproject.net.Port;
+import org.onosproject.net.Port.Type;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.optical.OchPort;
+import org.onosproject.net.optical.OduCltPort;
+import org.onosproject.net.optical.OmsPort;
+import org.onosproject.net.optical.OtuPort;
+
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.List;
+
+import static org.onosproject.net.DeviceId.deviceId;
+import static org.onosproject.net.optical.device.OpticalDeviceServiceView.opticalView;
+
+/**
+ * Lists all ports or all ports of a device.
+ */
+@Command(scope = "onos", name = "optical-ports",
+         description = "Lists all optical ports or all optical ports of a device")
+public class OpticalPortsListCommand extends DevicePortsListCommand {
+
+    private static final String FMT = "  port=%s, state=%s, type=%s, speed=%s %s";
+    private static final String FMT_OCH = "  port=%s, state=%s, type=%s, signalType=%s, isTunable=%s %s";
+    private static final String FMT_ODUCLT_OTU = "  port=%s, state=%s, type=%s, signalType=%s %s";
+    private static final String FMT_OMS = "  port=%s, state=%s, type=%s, freqs=%s / %s / %s GHz, totalChannels=%s %s";
+
+    private static final EnumSet<Port.Type> OPTICAL = EnumSet.of(Type.OCH, Type.ODUCLT, Type.OMS, Type.OTU);
+
+    @Override
+    protected void execute() {
+        DeviceService service = opticalView(get(DeviceService.class));
+        if (uri == null) {
+            if (outputJson()) {
+                print("%s", jsonPorts(service, getSortedDevices(service)));
+            } else {
+                for (Device device : getSortedDevices(service)) {
+                    printDevice(service, device);
+                    printPorts(service, device);
+                }
+            }
+
+        } else {
+            Device device = service.getDevice(deviceId(uri));
+            if (device == null) {
+                error("No such device %s", uri);
+            } else if (outputJson()) {
+                print("%s", jsonPorts(service, new ObjectMapper(), device));
+            } else {
+                printDevice(service, device);
+                printPorts(service, device);
+            }
+        }
+    }
+
+    // Determines if a port should be included in output.
+    @Override
+    protected boolean isIncluded(Port port) {
+        return OPTICAL.contains(port.type()) &&
+               super.isIncluded(port);
+    }
+
+    @Override
+    protected void printPorts(DeviceService service, Device device) {
+        List<Port> ports = new ArrayList<>(service.getPorts(device.id()));
+        ports.sort((p1, p2) ->
+             Long.signum(p1.number().toLong() - p2.number().toLong())
+        );
+        for (Port port : ports) {
+            if (!isIncluded(port)) {
+                continue;
+            }
+            String portName = port.number().toString();
+            String portIsEnabled = port.isEnabled() ? "enabled" : "disabled";
+            String portType = port.type().toString().toLowerCase();
+            switch (port.type()) {
+                case OCH:
+                    if (port instanceof OchPort) {
+                        OchPort och = (OchPort) port;
+                        print(FMT_OCH, portName, portIsEnabled, portType,
+                              och.signalType().toString(),
+                              och.isTunable() ? "yes" : "no",
+                              annotations(och.unhandledAnnotations()));
+                       break;
+                    }
+                    print("WARN: OchPort but not on OpticalDevice or ill-formed");
+                    print(FMT, portName, portIsEnabled, portType, port.portSpeed(), annotations(port.annotations()));
+                    break;
+                case ODUCLT:
+                    if (port instanceof OduCltPort) {
+                        OduCltPort oduCltPort = (OduCltPort) port;
+                        print(FMT_ODUCLT_OTU, portName, portIsEnabled, portType,
+                              oduCltPort.signalType().toString(),
+                              annotations(oduCltPort.unhandledAnnotations()));
+                        break;
+                    }
+                    print("WARN: OduCltPort but not on OpticalDevice or ill-formed");
+                    print(FMT, portName, portIsEnabled, portType, port.portSpeed(), annotations(port.annotations()));
+                    break;
+                case OMS:
+                    if (port instanceof OmsPort) {
+                        OmsPort oms = (OmsPort) port;
+                        print(FMT_OMS, portName, portIsEnabled, portType,
+                              oms.minFrequency().asHz() / Frequency.ofGHz(1).asHz(),
+                              oms.maxFrequency().asHz() / Frequency.ofGHz(1).asHz(),
+                              oms.grid().asHz() / Frequency.ofGHz(1).asHz(),
+                              oms.totalChannels(),
+                              annotations(oms.unhandledAnnotations()));
+                        break;
+                    }
+                    print("WARN: OmsPort but not on OpticalDevice or ill-formed");
+                    print(FMT, portName, portIsEnabled, portType, port.portSpeed(), annotations(port.annotations()));
+                    break;
+                case OTU:
+                    if (port instanceof OtuPort) {
+                        OtuPort otuPort = (OtuPort) port;
+                        print(FMT_ODUCLT_OTU, portName, portIsEnabled, portType,
+                              otuPort.signalType().toString(),
+                              annotations(otuPort.unhandledAnnotations()));
+                        break;
+                    }
+                    print("WARN: OtuPort but not on OpticalDevice or ill-formed");
+                    print(FMT, portName, portIsEnabled, portType, port.portSpeed(), annotations(port.annotations()));
+                    break;
+                default:
+                    // do not print non-optical ports
+                    break;
+            }
+        }
+    }
+}
diff --git a/apps/optical-model/src/main/java/org/onosproject/net/optical/cli/package-info.java b/apps/optical-model/src/main/java/org/onosproject/net/optical/cli/package-info.java
new file mode 100644
index 0000000..36e60f0
--- /dev/null
+++ b/apps/optical-model/src/main/java/org/onosproject/net/optical/cli/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+
+/**
+ * Console command-line for interacting with the
+ * optical network model &amp; services.
+ */
+package org.onosproject.net.optical.cli;
diff --git a/apps/optical-model/src/main/java/org/onosproject/net/optical/device/FilteredAnnotation.java b/apps/optical-model/src/main/java/org/onosproject/net/optical/device/FilteredAnnotation.java
new file mode 100644
index 0000000..915e441
--- /dev/null
+++ b/apps/optical-model/src/main/java/org/onosproject/net/optical/device/FilteredAnnotation.java
@@ -0,0 +1,69 @@
+/*
+ * 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.optical.device;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import org.onosproject.net.Annotations;
+
+import com.google.common.annotations.Beta;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+
+/**
+ * Filtered {@link Annotations} view.
+ */
+@Beta
+public class FilteredAnnotation implements Annotations {
+
+    private final Annotations delegate;
+    private final Set<String> filtered;
+
+    /**
+     * Creates filtered {@link Annotations} view based on {@code delegate}.
+     *
+     * @param delegate input {@link Annotations}
+     * @param keys to filter-out
+     */
+    public FilteredAnnotation(Annotations delegate, Set<String> keys) {
+        this.delegate = checkNotNull(delegate);
+        this.filtered = ImmutableSet.copyOf(keys);
+    }
+
+    @Override
+    public String value(String key) {
+        if (filtered.contains(key)) {
+            return null;
+        }
+        return delegate.value(key);
+    }
+
+    @Override
+    public Set<String> keys() {
+        return Sets.difference(delegate.keys(), filtered);
+    }
+
+    @Override
+    public String toString() {
+        Map<String, String> mapView = new HashMap<>();
+        keys().forEach(key -> mapView.put(key, delegate.value(key)));
+        return mapView.toString();
+    }
+}
diff --git a/apps/optical-model/src/main/java/org/onosproject/net/optical/device/OchPortHelper.java b/apps/optical-model/src/main/java/org/onosproject/net/optical/device/OchPortHelper.java
index 50dc98c..20bcb25 100644
--- a/apps/optical-model/src/main/java/org/onosproject/net/optical/device/OchPortHelper.java
+++ b/apps/optical-model/src/main/java/org/onosproject/net/optical/device/OchPortHelper.java
@@ -38,6 +38,7 @@
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import com.google.common.annotations.Beta;
+import com.google.common.collect.ImmutableSet;
 
 /**
  * OCh port related helpers.
@@ -151,6 +152,16 @@
         }
     }
 
+    /**
+     * Returns {@link Annotations} not used by the port type projection.
+     *
+     * @param input {@link Annotations}
+     * @return filtered view of given {@link Annotations}
+     */
+    public static Annotations stripHandledAnnotations(Annotations input) {
+        return new FilteredAnnotation(input, ImmutableSet.of(SIGNAL_TYPE, TUNABLE, LAMBDA));
+    }
+
     // not meant to be instantiated
     private OchPortHelper() {}
 }
diff --git a/apps/optical-model/src/main/java/org/onosproject/net/optical/device/OduCltPortHelper.java b/apps/optical-model/src/main/java/org/onosproject/net/optical/device/OduCltPortHelper.java
index efda401..380b8fe 100644
--- a/apps/optical-model/src/main/java/org/onosproject/net/optical/device/OduCltPortHelper.java
+++ b/apps/optical-model/src/main/java/org/onosproject/net/optical/device/OduCltPortHelper.java
@@ -33,6 +33,7 @@
 import org.slf4j.Logger;
 
 import com.google.common.annotations.Beta;
+import com.google.common.collect.ImmutableSet;
 
 /**
  * ODU client port related helpers.
@@ -122,6 +123,16 @@
         }
     }
 
+    /**
+     * Returns {@link Annotations} not used by the port type projection.
+     *
+     * @param input {@link Annotations}
+     * @return filtered view of given {@link Annotations}
+     */
+    public static Annotations stripHandledAnnotations(Annotations input) {
+        return new FilteredAnnotation(input, ImmutableSet.of(SIGNAL_TYPE));
+    }
+
     // not meant to be instantiated
     private OduCltPortHelper() {}
 }
diff --git a/apps/optical-model/src/main/java/org/onosproject/net/optical/device/OmsPortHelper.java b/apps/optical-model/src/main/java/org/onosproject/net/optical/device/OmsPortHelper.java
index 2c99c53..f0d878d 100644
--- a/apps/optical-model/src/main/java/org/onosproject/net/optical/device/OmsPortHelper.java
+++ b/apps/optical-model/src/main/java/org/onosproject/net/optical/device/OmsPortHelper.java
@@ -33,6 +33,7 @@
 import org.slf4j.Logger;
 
 import com.google.common.annotations.Beta;
+import com.google.common.collect.ImmutableSet;
 
 /**
  * OMS port related helpers.
@@ -142,6 +143,16 @@
         }
     }
 
+    /**
+     * Returns {@link Annotations} not used by the port type projection.
+     *
+     * @param input {@link Annotations}
+     * @return filtered view of given {@link Annotations}
+     */
+    public static Annotations stripHandledAnnotations(Annotations input) {
+        return new FilteredAnnotation(input, ImmutableSet.of(MIN_FREQ_HZ, MAX_FREQ_HZ, GRID_HZ));
+    }
+
     // not meant to be instantiated
     private OmsPortHelper() {}
 }
diff --git a/apps/optical-model/src/main/java/org/onosproject/net/optical/device/OtuPortHelper.java b/apps/optical-model/src/main/java/org/onosproject/net/optical/device/OtuPortHelper.java
index aeac4f9..968dd6b 100644
--- a/apps/optical-model/src/main/java/org/onosproject/net/optical/device/OtuPortHelper.java
+++ b/apps/optical-model/src/main/java/org/onosproject/net/optical/device/OtuPortHelper.java
@@ -33,6 +33,7 @@
 import org.slf4j.Logger;
 
 import com.google.common.annotations.Beta;
+import com.google.common.collect.ImmutableSet;
 
 /**
  * OTU port related helpers.
@@ -122,6 +123,15 @@
         }
     }
 
+    /**
+     * Returns {@link Annotations} not used by the port type projection.
+     *
+     * @param input {@link Annotations}
+     * @return filtered view of given {@link Annotations}
+     */
+    public static Annotations stripHandledAnnotations(Annotations input) {
+        return new FilteredAnnotation(input, ImmutableSet.of(SIGNAL_TYPE));
+    }
 
     // not meant to be instantiated
     private OtuPortHelper() {}
diff --git a/apps/optical-model/src/main/java/org/onosproject/net/optical/impl/DefaultOchPort.java b/apps/optical-model/src/main/java/org/onosproject/net/optical/impl/DefaultOchPort.java
index 593b021..eec754a 100644
--- a/apps/optical-model/src/main/java/org/onosproject/net/optical/impl/DefaultOchPort.java
+++ b/apps/optical-model/src/main/java/org/onosproject/net/optical/impl/DefaultOchPort.java
@@ -25,6 +25,7 @@
 import com.google.common.annotations.Beta;
 
 import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.net.optical.device.OchPortHelper.stripHandledAnnotations;
 
 import java.util.Objects;
 
@@ -71,12 +72,9 @@
         return signalType.bitRate();
     }
 
-
     @Override
-    public Annotations annotations() {
-        // FIXME Filter OCh annotations, after confirming that
-        // it'll not result in information-loss
-        return super.annotations();
+    public Annotations unhandledAnnotations() {
+        return stripHandledAnnotations(super.annotations());
     }
 
     /**
@@ -140,6 +138,7 @@
                 .add("signalType", signalType())
                 .add("isTunable", isTunable())
                 .add("lambda", lambda())
+                .add("annotations", unhandledAnnotations())
                 .toString();
     }
 }
diff --git a/apps/optical-model/src/main/java/org/onosproject/net/optical/impl/DefaultOduCltPort.java b/apps/optical-model/src/main/java/org/onosproject/net/optical/impl/DefaultOduCltPort.java
index 78a4266..adb6b27 100644
--- a/apps/optical-model/src/main/java/org/onosproject/net/optical/impl/DefaultOduCltPort.java
+++ b/apps/optical-model/src/main/java/org/onosproject/net/optical/impl/DefaultOduCltPort.java
@@ -16,9 +16,11 @@
 package org.onosproject.net.optical.impl;
 
 import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.net.optical.device.OduCltPortHelper.stripHandledAnnotations;
 
 import java.util.Objects;
 
+import org.onosproject.net.Annotations;
 import org.onosproject.net.CltSignalType;
 import org.onosproject.net.Port;
 import org.onosproject.net.optical.OduCltPort;
@@ -58,6 +60,11 @@
     }
 
     @Override
+    public Annotations unhandledAnnotations() {
+        return stripHandledAnnotations(super.annotations());
+    }
+
+    @Override
     public CltSignalType signalType() {
         return signalType;
     }
@@ -87,6 +94,7 @@
     public String toString() {
         return super.toStringHelper()
                 .add("signalType", signalType())
+                .add("annotations", unhandledAnnotations())
                 .toString();
     }
 
diff --git a/apps/optical-model/src/main/java/org/onosproject/net/optical/impl/DefaultOmsPort.java b/apps/optical-model/src/main/java/org/onosproject/net/optical/impl/DefaultOmsPort.java
index eb2c426..980b63e 100644
--- a/apps/optical-model/src/main/java/org/onosproject/net/optical/impl/DefaultOmsPort.java
+++ b/apps/optical-model/src/main/java/org/onosproject/net/optical/impl/DefaultOmsPort.java
@@ -16,10 +16,12 @@
 package org.onosproject.net.optical.impl;
 
 import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.net.optical.device.OmsPortHelper.stripHandledAnnotations;
 
 import java.util.Objects;
 
 import org.onlab.util.Frequency;
+import org.onosproject.net.Annotations;
 import org.onosproject.net.Port;
 import org.onosproject.net.optical.OmsPort;
 import org.onosproject.net.utils.ForwardingPort;
@@ -67,6 +69,12 @@
     }
 
     @Override
+    public Annotations unhandledAnnotations() {
+        return stripHandledAnnotations(super.annotations());
+    }
+
+
+    @Override
     public Frequency minFrequency() {
         return minFrequency;
     }
@@ -112,6 +120,7 @@
                 .add("minFrequency", minFrequency())
                 .add("maxFrequency", maxFrequency())
                 .add("grid", grid())
+                .add("annotations", unhandledAnnotations())
                 .toString();
     }
 
diff --git a/apps/optical-model/src/main/java/org/onosproject/net/optical/impl/DefaultOtuPort.java b/apps/optical-model/src/main/java/org/onosproject/net/optical/impl/DefaultOtuPort.java
index 2c038c8..7db003d 100644
--- a/apps/optical-model/src/main/java/org/onosproject/net/optical/impl/DefaultOtuPort.java
+++ b/apps/optical-model/src/main/java/org/onosproject/net/optical/impl/DefaultOtuPort.java
@@ -16,9 +16,11 @@
 package org.onosproject.net.optical.impl;
 
 import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.net.optical.device.OtuPortHelper.stripHandledAnnotations;
 
 import java.util.Objects;
 
+import org.onosproject.net.Annotations;
 import org.onosproject.net.OtuSignalType;
 import org.onosproject.net.Port;
 import org.onosproject.net.optical.OtuPort;
@@ -57,6 +59,11 @@
 //    }
 
     @Override
+    public Annotations unhandledAnnotations() {
+        return stripHandledAnnotations(super.annotations());
+    }
+
+    @Override
     public OtuSignalType signalType() {
         return signalType;
     }
@@ -86,6 +93,7 @@
     public String toString() {
         return super.toStringHelper()
                 .add("signalType", signalType())
+                .add("annotations", unhandledAnnotations())
                 .toString();
     }
 
diff --git a/apps/optical-model/src/main/resources/OSGI-INF/blueprint/shell-config.xml b/apps/optical-model/src/main/resources/OSGI-INF/blueprint/shell-config.xml
new file mode 100644
index 0000000..280cd33
--- /dev/null
+++ b/apps/optical-model/src/main/resources/OSGI-INF/blueprint/shell-config.xml
@@ -0,0 +1,41 @@
+<!--
+  ~ 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.
+  -->
+<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">
+
+    <command-bundle xmlns="http://karaf.apache.org/xmlns/shell/v1.1.0">
+        <command>
+            <action class="org.onosproject.net.optical.cli.AddOpticalIntentCommand"/>
+            <completers>
+                <ref component-id="connectPointCompleter"/>
+                <ref component-id="connectPointCompleter"/>
+            </completers>
+            <optional-completers>
+                <entry key="-a" value-ref="allAppNameCompleter"/>
+            </optional-completers>
+        </command>
+        <command>
+            <action class="org.onosproject.net.optical.cli.OpticalPortsListCommand"/>
+            <completers>
+                <ref component-id="deviceIdCompleter"/>
+            </completers>
+        </command>
+    </command-bundle>
+
+    <bean id="connectPointCompleter" class="org.onosproject.cli.net.ConnectPointCompleter"/>
+    <bean id="allAppNameCompleter" class="org.onosproject.cli.app.AllApplicationNamesCompleter"/>
+    <bean id="deviceIdCompleter" class="org.onosproject.cli.net.DeviceIdCompleter"/>
+
+</blueprint>
\ No newline at end of file
diff --git a/cli/BUCK b/cli/BUCK
index d9dc9e6..d00ee98 100644
--- a/cli/BUCK
+++ b/cli/BUCK
@@ -5,8 +5,6 @@
     '//incubator/net:onos-incubator-net',
     '//utils/rest:onlab-rest',
     '//core/common:onos-core-common',
-    # TODO Remove after decoupling optical
-    '//apps/optical-model:onos-apps-optical-model',
 ]
 
 osgi_jar (
diff --git a/cli/pom.xml b/cli/pom.xml
index 28579c3..76d69fa 100644
--- a/cli/pom.xml
+++ b/cli/pom.xml
@@ -49,13 +49,6 @@
             <artifactId>onos-core-common</artifactId>
         </dependency>
 
-        <!-- TODO Remove after decoupling optical -->
-        <dependency>
-            <groupId>org.onosproject</groupId>
-            <artifactId>onos-optical-model</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-
         <dependency>
             <groupId>com.fasterxml.jackson.core</groupId>
             <artifactId>jackson-databind</artifactId>
diff --git a/cli/src/main/java/org/onosproject/cli/net/DevicePortsListCommand.java b/cli/src/main/java/org/onosproject/cli/net/DevicePortsListCommand.java
index 5c994d1..312be78 100644
--- a/cli/src/main/java/org/onosproject/cli/net/DevicePortsListCommand.java
+++ b/cli/src/main/java/org/onosproject/cli/net/DevicePortsListCommand.java
@@ -22,24 +22,15 @@
 import org.apache.karaf.shell.commands.Argument;
 import org.apache.karaf.shell.commands.Command;
 import org.apache.karaf.shell.commands.Option;
-import org.onlab.util.Frequency;
 import org.onosproject.utils.Comparators;
 import org.onosproject.net.Device;
 import org.onosproject.net.Port;
-import org.onosproject.net.PortNumber;
 import org.onosproject.net.device.DeviceService;
-import org.onosproject.net.optical.OchPort;
-import org.onosproject.net.optical.OduCltPort;
-import org.onosproject.net.optical.OmsPort;
-import org.onosproject.net.optical.OpticalDevice;
-import org.onosproject.net.optical.OtuPort;
-
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
 import static org.onosproject.net.DeviceId.deviceId;
-import static org.onosproject.net.optical.device.OpticalDeviceServiceView.opticalView;
 
 /**
  * Lists all ports or all ports of a device.
@@ -49,9 +40,6 @@
 public class DevicePortsListCommand extends DevicesListCommand {
 
     private static final String FMT = "  port=%s, state=%s, type=%s, speed=%s %s";
-    private static final String FMT_OCH = "  port=%s, state=%s, type=%s, signalType=%s, isTunable=%s %s";
-    private static final String FMT_ODUCLT_OTU = "  port=%s, state=%s, type=%s, signalType=%s %s";
-    private static final String FMT_OMS = "  port=%s, state=%s, type=%s, Freqs= %s / %s / %s GHz, totalChannels=%s %s";
 
     @Option(name = "-e", aliases = "--enabled", description = "Show only enabled ports",
             required = false, multiValued = false)
@@ -63,17 +51,18 @@
 
     @Argument(index = 0, name = "uri", description = "Device ID",
               required = false, multiValued = false)
-    String uri = null;
+    protected String uri = null;
 
     @Override
     protected void execute() {
-        DeviceService service = opticalView(get(DeviceService.class));
+        DeviceService service = get(DeviceService.class);
         if (uri == null) {
             if (outputJson()) {
                 print("%s", jsonPorts(service, getSortedDevices(service)));
             } else {
                 for (Device device : getSortedDevices(service)) {
                     printDevice(service, device);
+                    printPorts(service, device);
                 }
             }
 
@@ -85,6 +74,7 @@
                 print("%s", jsonPorts(service, new ObjectMapper(), device));
             } else {
                 printDevice(service, device);
+                printPorts(service, device);
             }
         }
     }
@@ -119,7 +109,7 @@
         for (Port port : service.getPorts(device.id())) {
             if (isIncluded(port)) {
                 ports.add(mapper.createObjectNode()
-                                  .put("port", portName(port.number()))
+                                  .put("port", port.number().toString())
                                   .put("isEnabled", port.isEnabled())
                                   .put("type", port.type().toString().toLowerCase())
                                   .put("portSpeed", port.portSpeed())
@@ -131,122 +121,24 @@
         return result;
     }
 
-    private String portName(PortNumber port) {
-        return port.equals(PortNumber.LOCAL) ? "local" : port.toString();
-    }
-
     // Determines if a port should be included in output.
-    private boolean isIncluded(Port port) {
+    protected boolean isIncluded(Port port) {
         return enabled && port.isEnabled() || disabled && !port.isEnabled() ||
                 !enabled && !disabled;
     }
 
-    @Override
-    protected void printDevice(DeviceService service, Device device) {
-        super.printDevice(service, device);
+    protected void printPorts(DeviceService service, Device device) {
         List<Port> ports = new ArrayList<>(service.getPorts(device.id()));
         Collections.sort(ports, Comparators.PORT_COMPARATOR);
         for (Port port : ports) {
             if (!isIncluded(port)) {
                 continue;
             }
-            String portName = portName(port.number());
+            String portName = port.number().toString();
             Object portIsEnabled = port.isEnabled() ? "enabled" : "disabled";
             String portType = port.type().toString().toLowerCase();
             String annotations = annotations(port.annotations());
-            switch (port.type()) {
-                case OCH:
-                    if (port instanceof org.onosproject.net.OchPort) {
-                        // old OchPort model
-                        org.onosproject.net.OchPort oPort = (org.onosproject.net.OchPort) port;
-                        print("WARN: OchPort in old model");
-                        print(FMT_OCH, portName, portIsEnabled, portType,
-                              oPort.signalType().toString(),
-                              oPort.isTunable() ? "yes" : "no", annotations);
-                        break;
-                    }
-                    if (port instanceof OchPort) {
-                        OchPort och = (OchPort) port;
-                        print(FMT_OCH, portName, portIsEnabled, portType,
-                              och.signalType().toString(),
-                              och.isTunable() ? "yes" : "no", annotations);
-                       break;
-                    } else if (port.element().is(OpticalDevice.class)) {
-                        // Note: should never reach here, but
-                        // leaving it here as an example to
-                        // manually translate to specific port.
-                        OpticalDevice optDevice = port.element().as(OpticalDevice.class);
-                        if (optDevice.portIs(port, OchPort.class)) {
-                            OchPort och = optDevice.portAs(port, OchPort.class).get();
-                            print(FMT_OCH, portName, portIsEnabled, portType,
-                                  och.signalType().toString(),
-                                  och.isTunable() ? "yes" : "no", annotations);
-                            break;
-                        }
-                    }
-                    print("WARN: OchPort but not on OpticalDevice or ill-formed");
-                    print(FMT, portName, portIsEnabled, portType, port.portSpeed(), annotations);
-                    break;
-                case ODUCLT:
-                    if (port instanceof org.onosproject.net.OduCltPort) {
-                        // old OduCltPort model
-                        org.onosproject.net.OduCltPort oPort = (org.onosproject.net.OduCltPort) port;
-                        print("WARN: OduCltPort in old model");
-                        print(FMT_ODUCLT_OTU, portName, portIsEnabled, portType,
-                              oPort.signalType().toString(), annotations);
-                        break;
-                    }
-                    if (port instanceof OduCltPort) {
-                        print(FMT_ODUCLT_OTU, portName, portIsEnabled, portType,
-                              ((OduCltPort) port).signalType().toString(), annotations);
-                        break;
-                    }
-                    print("WARN: OduCltPort but not on OpticalDevice or ill-formed");
-                    print(FMT, portName, portIsEnabled, portType, port.portSpeed(), annotations);
-                    break;
-                case OMS:
-                    if (port instanceof org.onosproject.net.OmsPort) {
-                        org.onosproject.net.OmsPort oms = (org.onosproject.net.OmsPort) port;
-                        print("WARN: OmsPort in old model");
-                        print(FMT_OMS, portName, portIsEnabled, portType,
-                              oms.minFrequency().asHz() / Frequency.ofGHz(1).asHz(),
-                              oms.maxFrequency().asHz() / Frequency.ofGHz(1).asHz(),
-                              oms.grid().asHz() / Frequency.ofGHz(1).asHz(),
-                              oms.totalChannels(), annotations);
-                        break;
-                    }
-                    if (port instanceof OmsPort) {
-                        OmsPort oms = (OmsPort) port;
-                        print(FMT_OMS, portName, portIsEnabled, portType,
-                              oms.minFrequency().asHz() / Frequency.ofGHz(1).asHz(),
-                              oms.maxFrequency().asHz() / Frequency.ofGHz(1).asHz(),
-                              oms.grid().asHz() / Frequency.ofGHz(1).asHz(),
-                              oms.totalChannels(), annotations);
-                        break;
-                    }
-                    print("WARN: OmsPort but not on OpticalDevice or ill-formed");
-                    print(FMT, portName, portIsEnabled, portType, port.portSpeed(), annotations);
-                    break;
-                case OTU:
-                    if (port instanceof org.onosproject.net.OtuPort) {
-                        org.onosproject.net.OtuPort otu = (org.onosproject.net.OtuPort) port;
-                        print("WARN: OtuPort in old model");
-                        print(FMT_ODUCLT_OTU, portName, portIsEnabled, portType,
-                              otu.signalType().toString(), annotations);
-                        break;
-                    }
-                    if (port instanceof OtuPort) {
-                        print(FMT_ODUCLT_OTU, portName, portIsEnabled, portType,
-                              ((OtuPort) port).signalType().toString(), annotations);
-                        break;
-                    }
-                    print("WARN: OtuPort but not on OpticalDevice or ill-formed");
-                    print(FMT, portName, portIsEnabled, portType, port.portSpeed(), annotations);
-                    break;
-                default:
-                     print(FMT, portName, portIsEnabled, portType, port.portSpeed(), annotations);
-                    break;
-            }
+            print(FMT, portName, portIsEnabled, portType, port.portSpeed(), annotations);
         }
     }
 }
diff --git a/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml b/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml
index 5e3752c..b55aa9a 100644
--- a/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml
+++ b/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml
@@ -292,16 +292,6 @@
             </optional-completers>
         </command>
         <command>
-            <action class="org.onosproject.cli.net.AddOpticalIntentCommand"/>
-            <completers>
-                <ref component-id="connectPointCompleter"/>
-                <ref component-id="connectPointCompleter"/>
-            </completers>
-            <optional-completers>
-                <entry key="-a" value-ref="allAppNameCompleter"/>
-            </optional-completers>
-        </command>
-        <command>
             <action class="org.onosproject.cli.net.GetStatisticsCommand"/>
             <completers>
                 <ref component-id="connectPointCompleter"/>
diff --git a/features/BUCK b/features/BUCK
index ec2fd10..d249509 100644
--- a/features/BUCK
+++ b/features/BUCK
@@ -128,8 +128,6 @@
   required_features = ['onos-api'],
   included_bundles = [
     '//cli:onos-cli',
-    # TODO Remove after decoupling optical
-    '//apps/optical-model:onos-apps-optical-model',
   ]
 )
 
diff --git a/features/features.xml b/features/features.xml
index 19cb7db..807b85a 100644
--- a/features/features.xml
+++ b/features/features.xml
@@ -142,8 +142,6 @@
              description="ONOS admin command console components">
         <feature>onos-api</feature>
         <bundle>mvn:org.onosproject/onos-cli/@ONOS-VERSION</bundle>
-        <!-- TODO Remove after decoupling optical -->
-        <bundle>mvn:org.onosproject/onos-optical-model/@ONOS-VERSION</bundle>
     </feature>
 
     <feature name="onos-security" version="@FEATURE-VERSION"
diff --git a/providers/openflow/app/BUCK b/providers/openflow/app/BUCK
index 3e082c6..2cad2e4 100644
--- a/providers/openflow/app/BUCK
+++ b/providers/openflow/app/BUCK
@@ -1,4 +1,5 @@
 APPS = [
+    'org.onosproject.optical-model',
     'org.onosproject.openflow-base',
     'org.onosproject.hostprovider',
     'org.onosproject.lldpprovider',
diff --git a/providers/openflow/app/pom.xml b/providers/openflow/app/pom.xml
index 70bf396..989be48 100644
--- a/providers/openflow/app/pom.xml
+++ b/providers/openflow/app/pom.xml
@@ -33,6 +33,7 @@
         <onos.app.title>OpenFlow Meta App</onos.app.title>
         <onos.app.category>Provider</onos.app.category>
         <onos.app.requires>
+            org.onosproject.optical-model,
             org.onosproject.openflow-base,
             org.onosproject.hostprovider,
             org.onosproject.lldpprovider