--- /dev/null
+/*\r
+ * ============LICENSE_START========================================================================\r
+ * ONAP : tr-069-adapter\r
+ * =================================================================================================\r
+ * Copyright (C) 2020 CommScope Inc Intellectual Property.\r
+ * =================================================================================================\r
+ * This tr-069-adapter software file is distributed by CommScope Inc under the Apache License,\r
+ * Version 2.0 (the "License"); you may not use this file except in compliance with the License. You\r
+ * may obtain a copy of the License at\r
+ *\r
+ * http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,\r
+ * either express or implied. See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ * ===============LICENSE_END=======================================================================\r
+ */\r
+\r
+package org.commscope.tr069adapter.netconf.rpc;\r
+\r
+import com.google.common.base.Preconditions;\r
+import com.google.common.collect.Maps;\r
+\r
+import java.io.File;\r
+import java.io.IOException;\r
+import java.text.SimpleDateFormat;\r
+import java.util.Collection;\r
+import java.util.Collections;\r
+import java.util.Date;\r
+import java.util.HashMap;\r
+import java.util.List;\r
+import java.util.Map;\r
+import java.util.Optional;\r
+import java.util.concurrent.Executors;\r
+import java.util.concurrent.ScheduledExecutorService;\r
+import java.util.concurrent.TimeUnit;\r
+\r
+import javax.xml.bind.JAXBContext;\r
+import javax.xml.bind.JAXBException;\r
+import javax.xml.bind.Unmarshaller;\r
+import javax.xml.bind.annotation.XmlRootElement;\r
+\r
+import org.opendaylight.netconf.api.NetconfMessage;\r
+import org.opendaylight.netconf.api.xml.XmlElement;\r
+import org.opendaylight.netconf.api.xml.XmlNetconfConstants;\r
+import org.opendaylight.netconf.api.xml.XmlUtil;\r
+import org.opendaylight.netconf.impl.NetconfServerSession;\r
+import org.opendaylight.netconf.impl.mapping.operations.DefaultNetconfOperation;\r
+import org.opendaylight.netconf.util.mapping.AbstractLastNetconfOperation;\r
+import org.slf4j.Logger;\r
+import org.slf4j.LoggerFactory;\r
+import org.w3c.dom.Document;\r
+import org.w3c.dom.Element;\r
+import org.xml.sax.SAXException;\r
+\r
+public class CreateSubscription extends AbstractLastNetconfOperation\r
+ implements DefaultNetconfOperation {\r
+\r
+ private static final Logger logger = LoggerFactory.getLogger(CreateSubscription.class);\r
+ private final Map<Notification, NetconfMessage> notifications;\r
+ private NetconfServerSession session;\r
+ private ScheduledExecutorService scheduledExecutorService;\r
+ private static Map<String, NetconfServerSession> netconfServerSessionMap = new HashMap<>();\r
+ private String deviceID;\r
+\r
+ public CreateSubscription(final String id, final Optional<File> notificationsFile,\r
+ String deviceID) {\r
+\r
+ super(id);\r
+\r
+ logger.debug("CreateSubscription RPC is created with macID {}", deviceID);\r
+ this.deviceID = deviceID;\r
+\r
+ final Optional<Notifications> notifs;\r
+\r
+ if (notificationsFile.isPresent()) {\r
+ notifs = Optional.of(loadNotifications(notificationsFile.get()));\r
+ scheduledExecutorService = Executors.newScheduledThreadPool(1);\r
+ } else {\r
+ notifs = Optional.empty();\r
+ }\r
+\r
+ if (notifs.isPresent()) {\r
+ final Collection<Notification> toCopy = notifs.get().getNotificationList();\r
+ final Map<Notification, NetconfMessage> preparedMessages =\r
+ Maps.newHashMapWithExpectedSize(toCopy.size());\r
+ for (final Notification notification : toCopy) {\r
+ final NetconfMessage parsedNotification =\r
+ parseNetconfNotification(notification.getContent());\r
+ preparedMessages.put(notification, parsedNotification);\r
+ }\r
+ this.notifications = preparedMessages;\r
+ } else {\r
+ this.notifications = Collections.emptyMap();\r
+ }\r
+ }\r
+\r
+ private static Notifications loadNotifications(final File file) {\r
+ try {\r
+ final JAXBContext jaxbContext = JAXBContext.newInstance(Notifications.class);\r
+ final Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();\r
+ return (Notifications) jaxbUnmarshaller.unmarshal(file);\r
+ } catch (final JAXBException e) {\r
+ throw new IllegalArgumentException("can not parse file " + file + " as a notifications file",\r
+ e);\r
+ }\r
+ }\r
+\r
+ @Override\r
+ protected String getOperationName() {\r
+ return "create-subscription";\r
+ }\r
+\r
+ @Override\r
+ protected String getOperationNamespace() {\r
+ return "urn:ietf:params:xml:ns:netconf:notification:1.0";\r
+ }\r
+\r
+ @Override\r
+ protected Element handleWithNoSubsequentOperations(final Document document,\r
+ final XmlElement operationElement) {\r
+ long delayAggregator = 0;\r
+ for (final Map.Entry<Notification, NetconfMessage> notification : notifications.entrySet()) {\r
+ for (int i = 0; i <= notification.getKey().getTimes(); i++) {\r
+\r
+ delayAggregator += notification.getKey().getDelayInSeconds();\r
+\r
+ scheduledExecutorService.schedule(() -> {\r
+ Preconditions.checkState(session != null,\r
+ "Session is not set, cannot process notifications");\r
+ session.sendMessage(notification.getValue());\r
+ }, delayAggregator, TimeUnit.SECONDS);\r
+ }\r
+ }\r
+ return document.createElement(XmlNetconfConstants.OK);\r
+ }\r
+\r
+ private static NetconfMessage parseNetconfNotification(String content) {\r
+ final int startEventTime = content.indexOf("<eventTime>") + "<eventTime>".length();\r
+ final int endEventTime = content.indexOf("</eventTime>");\r
+ final String eventTime = content.substring(startEventTime, endEventTime);\r
+ if (eventTime.equals("XXXX")) {\r
+ content = content.replace(eventTime,\r
+ new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX").format(new Date()));\r
+ }\r
+\r
+ try {\r
+ return new NetconfMessage(XmlUtil.readXmlToDocument(content));\r
+ } catch (SAXException | IOException e) {\r
+ throw new IllegalArgumentException("Cannot parse notifications", e);\r
+ }\r
+ }\r
+\r
+ @Override\r
+ public void setNetconfSession(final NetconfServerSession newSession) {\r
+ logger.debug("Adding netconf session to notification server sessions map : {}", newSession);\r
+ logger.debug("This CreateSubscription is setup to support macID= {}", deviceID);\r
+ this.session = newSession;\r
+ netconfServerSessionMap.put(deviceID, newSession);\r
+ }\r
+\r
+ public static void sendNotification(NetconfMessage netconfMessage, String deviceID) {\r
+ logger.debug("Request to send notification. NetConfMessage : {}", netconfMessage);\r
+ NetconfServerSession session = netconfServerSessionMap.get(deviceID);\r
+ if (session != null && session.isUp()) {\r
+ session.sendMessage(netconfMessage);\r
+ logger.debug("Successfully send notification for deviceID: {}", deviceID);\r
+ } else {\r
+ logger.debug("Failed to send notification. None of valid netconf session is available.");\r
+ }\r
+ logger.debug("Available netconf sessions : {}", netconfServerSessionMap);\r
+\r
+ }\r
+\r
+ @XmlRootElement(name = "notifications")\r
+ public static final class Notifications {\r
+\r
+ @javax.xml.bind.annotation.XmlElement(nillable = false, name = "notification", required = true)\r
+ private List<Notification> notificationList;\r
+\r
+ public List<Notification> getNotificationList() {\r
+ return notificationList;\r
+ }\r
+\r
+ @Override\r
+ public String toString() {\r
+ final StringBuilder sb = new StringBuilder("Notifications{");\r
+ sb.append("notificationList=").append(notificationList);\r
+ sb.append('}');\r
+ return sb.toString();\r
+ }\r
+ }\r
+\r
+ public static final class Notification {\r
+\r
+ @javax.xml.bind.annotation.XmlElement(nillable = false, name = "delay")\r
+ private long delayInSeconds;\r
+\r
+ @javax.xml.bind.annotation.XmlElement(nillable = false, name = "times")\r
+ private long times;\r
+\r
+ @javax.xml.bind.annotation.XmlElement(nillable = false, name = "content", required = true)\r
+ private String content;\r
+\r
+ public long getDelayInSeconds() {\r
+ return delayInSeconds;\r
+ }\r
+\r
+ public long getTimes() {\r
+ return times;\r
+ }\r
+\r
+ public String getContent() {\r
+ return content;\r
+ }\r
+\r
+ @Override\r
+ public String toString() {\r
+ final StringBuilder sb = new StringBuilder("Notification{");\r
+ sb.append("delayInSeconds=").append(delayInSeconds);\r
+ sb.append(", times=").append(times);\r
+ sb.append(", content='").append(content).append('\'');\r
+ sb.append('}');\r
+ return sb.toString();\r
+ }\r
+ }\r
+}\r