Translate Polatis NETCONF notifications

Change-Id: I856b90b984dead5680c2ad757473cd3fdeeacab6
diff --git a/drivers/polatis/netconf/src/main/java/org/onosproject/drivers/polatis/netconf/PolatisAlarmConfig.java b/drivers/polatis/netconf/src/main/java/org/onosproject/drivers/polatis/netconf/PolatisAlarmConfig.java
new file mode 100644
index 0000000..33cb860
--- /dev/null
+++ b/drivers/polatis/netconf/src/main/java/org/onosproject/drivers/polatis/netconf/PolatisAlarmConfig.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2018 Open Networking Foundation
+ *
+ * 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.drivers.polatis.netconf;
+
+import org.onlab.packet.IpAddress;
+
+import org.onosproject.incubator.net.faultmanagement.alarm.Alarm;
+import org.onosproject.incubator.net.faultmanagement.alarm.AlarmId;
+import org.onosproject.incubator.net.faultmanagement.alarm.DefaultAlarm;
+import org.onosproject.incubator.net.faultmanagement.alarm.DeviceAlarmConfig;
+import org.onosproject.incubator.net.faultmanagement.alarm.XmlEventParser;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.driver.AbstractHandlerBehaviour;
+import org.onosproject.netconf.NetconfDeviceOutputEvent;
+
+import org.slf4j.Logger;
+
+import org.xml.sax.SAXException;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+
+import javax.xml.parsers.ParserConfigurationException;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Polatis specific implementation to provide asynchronous alarms via NETCONF.
+ */
+public class PolatisAlarmConfig extends AbstractHandlerBehaviour implements DeviceAlarmConfig {
+    private final Logger log = getLogger(getClass());
+
+    private DeviceId deviceId;
+
+    @Override
+    public boolean configureDevice(IpAddress address, int port, String protocol) {
+        return false;
+    }
+
+    @Override
+    public <T> Set<Alarm> translateAlarms(List<T> unparsedAlarms) {
+        deviceId = handler().data().deviceId();
+        Set<Alarm> alarms = new HashSet<>();
+        for (T alarm : unparsedAlarms) {
+            if (alarm instanceof NetconfDeviceOutputEvent) {
+                NetconfDeviceOutputEvent event = (NetconfDeviceOutputEvent) alarm;
+                if (event.type() == NetconfDeviceOutputEvent.Type.DEVICE_NOTIFICATION) {
+                    String message = event.getMessagePayload();
+                    InputStream in = new ByteArrayInputStream(message.getBytes(StandardCharsets.UTF_8));
+                    try {
+                        Document doc = XmlEventParser.createDocFromMessage(in);
+                        long timeStamp = XmlEventParser.getEventTime(doc);
+                        Node descriptionNode = XmlEventParser.getDescriptionNode(doc);
+                        while (descriptionNode != null) {
+                            if (descriptionNode.getNodeType() == Node.ELEMENT_NODE) {
+                                String nodeName = descriptionNode.getNodeName();
+                                if (nodeName == "port-power-alarm") {
+                                    Node portIdNode = descriptionNode.getChildNodes().item(1);
+                                    String portId = portIdNode.getTextContent();
+                                    String description = "Loss of Service alarm raised for fibre " + portId;
+                                    alarms.add(new DefaultAlarm.Builder(AlarmId.alarmId(deviceId,
+                                                Long.toString(timeStamp)), deviceId, description,
+                                                Alarm.SeverityLevel.MAJOR, timeStamp).build());
+                                    descriptionNode = null;
+                                }
+                            } else {
+                                descriptionNode = descriptionNode.getNextSibling();
+                            }
+                        }
+                    } catch (SAXException | IOException | ParserConfigurationException |
+                            UnsupportedOperationException | IllegalArgumentException e) {
+                        log.error("Exception thrown translating message from {}.", deviceId, e);
+                    }
+                }
+            }
+        }
+        return alarms;
+    }
+}
diff --git a/drivers/polatis/netconf/src/main/resources/polatis-drivers.xml b/drivers/polatis/netconf/src/main/resources/polatis-drivers.xml
index 0c36cbc..f95cbe2 100644
--- a/drivers/polatis/netconf/src/main/resources/polatis-drivers.xml
+++ b/drivers/polatis/netconf/src/main/resources/polatis-drivers.xml
@@ -27,6 +27,8 @@
                    impl="org.onosproject.drivers.polatis.netconf.PolatisFlowRuleProgrammable"/>
         <behaviour api="org.onosproject.incubator.net.faultmanagement.alarm.AlarmConsumer"
                    impl="org.onosproject.drivers.polatis.netconf.PolatisAlarmConsumer"/>
+        <behaviour api="org.onosproject.incubator.net.faultmanagement.alarm.DeviceAlarmConfig"
+                   impl="org.onosproject.drivers.polatis.netconf.PolatisAlarmConfig"/>
         <property name="uiType">policon</property>
         <property name="notificationStream">Polatis</property>
     </driver>
diff --git a/drivers/utilities/src/main/java/org/onosproject/drivers/utilities/XmlConfigParser.java b/drivers/utilities/src/main/java/org/onosproject/drivers/utilities/XmlConfigParser.java
index e804a81..5fdca6d 100644
--- a/drivers/utilities/src/main/java/org/onosproject/drivers/utilities/XmlConfigParser.java
+++ b/drivers/utilities/src/main/java/org/onosproject/drivers/utilities/XmlConfigParser.java
@@ -34,7 +34,6 @@
 import java.util.ArrayList;
 import java.util.List;
 
-
 /**
  * Parser for Netconf XML configurations and replys.
  */
diff --git a/incubator/api/src/main/java/org/onosproject/incubator/net/faultmanagement/alarm/XmlEventParser.java b/incubator/api/src/main/java/org/onosproject/incubator/net/faultmanagement/alarm/XmlEventParser.java
new file mode 100644
index 0000000..6ed2d7a
--- /dev/null
+++ b/incubator/api/src/main/java/org/onosproject/incubator/net/faultmanagement/alarm/XmlEventParser.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.incubator.net.faultmanagement.alarm;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.time.Instant;
+import java.time.format.DateTimeFormatter;
+
+/**
+ * Parser for Netconf notifications.
+ */
+public final class XmlEventParser {
+    public static final Logger log = LoggerFactory
+            .getLogger(XmlEventParser.class);
+
+    private static final String DISALLOW_DTD_FEATURE = "http://apache.org/xml/features/disallow-doctype-decl";
+    private static final String DISALLOW_EXTERNAL_DTD =
+            "http://apache.org/xml/features/nonvalidating/load-external-dtd";
+    private static final String EVENTTIME_TAGNAME = "eventTime";
+
+    private XmlEventParser() {
+    }
+
+    /**
+     * Creates a document from the input stream message and returns the result.
+     *
+     * @param message input stream message
+     * @return the document result
+     * @throws SAXException Throws SAX Exception
+     * @throws IOException Throws IO Exception
+     * @throws ParserConfigurationException Throws ParserConfigurationException
+     */
+    public static Document createDocFromMessage(InputStream message)
+            throws SAXException, IOException, ParserConfigurationException {
+        DocumentBuilderFactory dbfactory = DocumentBuilderFactory.newInstance();
+        //Disabling DTDs in order to avoid XXE xml-based attacks.
+        disableFeature(dbfactory, DISALLOW_DTD_FEATURE);
+        disableFeature(dbfactory, DISALLOW_EXTERNAL_DTD);
+        dbfactory.setXIncludeAware(false);
+        dbfactory.setExpandEntityReferences(false);
+        DocumentBuilder builder = dbfactory.newDocumentBuilder();
+        return builder.parse(new InputSource(message));
+    }
+
+    private static void disableFeature(DocumentBuilderFactory dbfactory, String feature) {
+        try {
+            dbfactory.setFeature(feature, true);
+        } catch (ParserConfigurationException e) {
+            // This should catch a failed setFeature feature
+            log.info("ParserConfigurationException was thrown. The feature '" +
+                    feature + "' is probably not supported by your XML processor.");
+        }
+    }
+
+    public static long getEventTime(Document doc)
+        throws UnsupportedOperationException, IllegalArgumentException {
+        String dateTime = getEventTimeNode(doc).getTextContent();
+        return DateTimeFormatter.ISO_DATE_TIME.parse(dateTime, Instant::from).getEpochSecond();
+    }
+
+    public static Node getDescriptionNode(Document doc) {
+        return getEventTimeNode(doc).getNextSibling();
+    }
+
+    private static Node getEventTimeNode(Document doc) {
+        return doc.getElementsByTagName(EVENTTIME_TAGNAME).item(0);
+    }
+}
diff --git a/providers/netconf/alarm/src/main/java/org/onosproject/provider/netconf/alarm/NetconfAlarmProvider.java b/providers/netconf/alarm/src/main/java/org/onosproject/provider/netconf/alarm/NetconfAlarmProvider.java
index 47790f3..ec4f8ae 100644
--- a/providers/netconf/alarm/src/main/java/org/onosproject/provider/netconf/alarm/NetconfAlarmProvider.java
+++ b/providers/netconf/alarm/src/main/java/org/onosproject/provider/netconf/alarm/NetconfAlarmProvider.java
@@ -16,6 +16,7 @@
 
 package org.onosproject.provider.netconf.alarm;
 
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Maps;
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
@@ -27,7 +28,15 @@
 import org.onosproject.incubator.net.faultmanagement.alarm.AlarmProviderService;
 import org.onosproject.incubator.net.faultmanagement.alarm.AlarmProviderRegistry;
 import org.onosproject.incubator.net.faultmanagement.alarm.AlarmTranslator;
+import org.onosproject.incubator.net.faultmanagement.alarm.DeviceAlarmConfig;
+import org.onosproject.net.Device;
 import org.onosproject.net.DeviceId;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.driver.DefaultDriverData;
+import org.onosproject.net.driver.DefaultDriverHandler;
+import org.onosproject.net.driver.Driver;
+import org.onosproject.net.driver.DriverData;
+import org.onosproject.net.driver.DriverService;
 import org.onosproject.net.provider.AbstractProvider;
 import org.onosproject.net.provider.ProviderId;
 import org.onosproject.netconf.FilteringNetconfDeviceOutputEventListener;
@@ -45,6 +54,7 @@
 import java.nio.charset.StandardCharsets;
 import java.util.Collection;
 import java.util.Map;
+import java.util.Set;
 
 import static org.slf4j.LoggerFactory.getLogger;
 
@@ -64,6 +74,12 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected NetconfController controller;
 
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected DriverService driverService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected DeviceService deviceService;
+
     protected AlarmProviderService providerService;
 
     private Map<DeviceId, InternalNotificationListener> idNotificationListenerMap = Maps.newHashMap();
@@ -123,10 +139,20 @@
         public void event(NetconfDeviceOutputEvent event) {
             if (event.type() == NetconfDeviceOutputEvent.Type.DEVICE_NOTIFICATION) {
                 DeviceId deviceId = event.getDeviceInfo().getDeviceId();
-                String message = event.getMessagePayload();
-                InputStream in = new ByteArrayInputStream(message.getBytes(StandardCharsets.UTF_8));
-                Collection<Alarm> newAlarms = translator.translateToAlarm(deviceId, in);
-                triggerProbe(deviceId, newAlarms);
+                Driver deviceDriver = driverService.getDriver(deviceId);
+                Device device = deviceService.getDevice(deviceId);
+                if (deviceDriver != null && device.is(DeviceAlarmConfig.class)) {
+                    DeviceAlarmConfig alarmTranslator = device.as(DeviceAlarmConfig.class);
+                    DriverData driverData = new DefaultDriverData(deviceDriver, deviceId);
+                    alarmTranslator.setHandler(new DefaultDriverHandler(driverData));
+                    Set<Alarm> alarms = alarmTranslator.translateAlarms(ImmutableList.of(event));
+                    triggerProbe(deviceId, alarms);
+                } else {
+                    String message = event.getMessagePayload();
+                    InputStream in = new ByteArrayInputStream(message.getBytes(StandardCharsets.UTF_8));
+                    Collection<Alarm> newAlarms = translator.translateToAlarm(deviceId, in);
+                    triggerProbe(deviceId, newAlarms);
+                }
             }
         }
     }
diff --git a/providers/netconf/alarm/src/main/java/org/onosproject/provider/netconf/alarm/NetconfAlarmTranslator.java b/providers/netconf/alarm/src/main/java/org/onosproject/provider/netconf/alarm/NetconfAlarmTranslator.java
index a614635..05847e1 100644
--- a/providers/netconf/alarm/src/main/java/org/onosproject/provider/netconf/alarm/NetconfAlarmTranslator.java
+++ b/providers/netconf/alarm/src/main/java/org/onosproject/provider/netconf/alarm/NetconfAlarmTranslator.java
@@ -21,14 +21,12 @@
 import org.onosproject.incubator.net.faultmanagement.alarm.AlarmId;
 import org.onosproject.incubator.net.faultmanagement.alarm.AlarmTranslator;
 import org.onosproject.incubator.net.faultmanagement.alarm.DefaultAlarm;
+import org.onosproject.incubator.net.faultmanagement.alarm.XmlEventParser;
 import org.onosproject.net.DeviceId;
 import org.slf4j.Logger;
 import org.w3c.dom.Document;
 import org.w3c.dom.Node;
-import org.xml.sax.InputSource;
 
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
 import javax.xml.parsers.ParserConfigurationException;
 import javax.xml.transform.OutputKeys;
 import javax.xml.transform.Transformer;
@@ -39,8 +37,6 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.StringWriter;
-import java.time.Instant;
-import java.time.format.DateTimeFormatter;
 import java.util.ArrayList;
 import java.util.Collection;
 
@@ -56,24 +52,13 @@
     private final Logger log = getLogger(getClass());
     private static final String EVENTTIME_TAGNAME = "eventTime";
 
-    private static final String DISALLOW_DTD_FEATURE = "http://apache.org/xml/features/disallow-doctype-decl";
-
-    private static final String DISALLOW_EXTERNAL_DTD =
-            "http://apache.org/xml/features/nonvalidating/load-external-dtd";
-
     @Override
     public Collection<Alarm> translateToAlarm(DeviceId deviceId, InputStream message) {
         try {
             Collection<Alarm> alarms = new ArrayList<>();
-            Document doc = createDocFromMessage(message);
-
-            // parse date element value into long
-            Node eventTime = doc.getElementsByTagName(EVENTTIME_TAGNAME).item(0);
-            String date = eventTime.getTextContent();
-            long timeStamp = parseDate(date);
-
-            // event-specific tag names as alarm descriptions
-            Node descriptionNode = eventTime.getNextSibling();
+            Document doc = XmlEventParser.createDocFromMessage(message);
+            long timeStamp = XmlEventParser.getEventTime(doc);
+            Node descriptionNode = XmlEventParser.getDescriptionNode(doc);
             while (descriptionNode != null) {
                 if (descriptionNode.getNodeType() == Node.ELEMENT_NODE) {
                     String description = nodeToString(descriptionNode);
@@ -95,33 +80,6 @@
         }
     }
 
-    private Document createDocFromMessage(InputStream message)
-            throws SAXException, IOException, ParserConfigurationException {
-        DocumentBuilderFactory dbfactory = DocumentBuilderFactory.newInstance();
-        //Disabling DTDs in order to avoid XXE xml-based attacks.
-        disableFeature(dbfactory, DISALLOW_DTD_FEATURE);
-        disableFeature(dbfactory, DISALLOW_EXTERNAL_DTD);
-        dbfactory.setXIncludeAware(false);
-        dbfactory.setExpandEntityReferences(false);
-        DocumentBuilder builder = dbfactory.newDocumentBuilder();
-        return builder.parse(new InputSource(message));
-    }
-
-    private void disableFeature(DocumentBuilderFactory dbfactory, String feature) {
-        try {
-            dbfactory.setFeature(feature, true);
-        } catch (ParserConfigurationException e) {
-            // This should catch a failed setFeature feature
-            log.info("ParserConfigurationException was thrown. The feature '" +
-                    feature + "' is probably not supported by your XML processor.");
-        }
-    }
-
-    private long parseDate(String timeStr)
-            throws UnsupportedOperationException, IllegalArgumentException {
-        return DateTimeFormatter.ISO_DATE_TIME.parse(timeStr, Instant::from).getEpochSecond();
-    }
-
     private static String nodeToString(Node rootNode) throws TransformerException {
         TransformerFactory tf = TransformerFactory.newInstance();
         Transformer transformer = tf.newTransformer();
@@ -131,4 +89,4 @@
         transformer.transform(source, new StreamResult(writer));
         return writer.getBuffer().toString();
     }
-}
\ No newline at end of file
+}