Improve the implementation of NTF pyosaf utils
---
 python/Makefile.am                    |   6 +-
 python/pyosaf/saNtf.py                |   4 +-
 python/pyosaf/utils/ntf/__init__.py   | 855 ++++++++++---------------------
 python/pyosaf/utils/ntf/agent.py      | 500 ++++++++++++++++++
 python/pyosaf/utils/ntf/producer.py   | 711 ++++++++++++++++++++++++++
 python/pyosaf/utils/ntf/reader.py     | 187 +++++++
 python/pyosaf/utils/ntf/subscriber.py | 923 ++++++++++++++++++++++++++++++++++
 7 files changed, 2599 insertions(+), 587 deletions(-)
 create mode 100644 python/pyosaf/utils/ntf/agent.py
 create mode 100644 python/pyosaf/utils/ntf/producer.py
 create mode 100644 python/pyosaf/utils/ntf/reader.py
 create mode 100644 python/pyosaf/utils/ntf/subscriber.py

diff --git a/python/Makefile.am b/python/Makefile.am
index 390366a..0b79067 100644
--- a/python/Makefile.am
+++ b/python/Makefile.am
@@ -62,6 +62,10 @@ pkgpyosafutilsimmoi_PYTHON = \
        python/pyosaf/utils/immoi/implementer.py
 
 pkgpyosafutilsntf_PYTHON = \
-       python/pyosaf/utils/ntf/__init__.py
+       python/pyosaf/utils/ntf/__init__.py \
+       python/pyosaf/utils/ntf/agent.py \
+       python/pyosaf/utils/ntf/producer.py \
+       python/pyosaf/utils/ntf/subscriber.py \
+       python/pyosaf/utils/ntf/reader.py
 
 endif
diff --git a/python/pyosaf/saNtf.py b/python/pyosaf/saNtf.py
index 7438cdc..9ee096e 100644
--- a/python/pyosaf/saNtf.py
+++ b/python/pyosaf/saNtf.py
@@ -15,7 +15,7 @@
 #
 ############################################################################
 
-from ctypes import POINTER, CFUNCTYPE, Structure, Union, CDLL, c_char
+from ctypes import POINTER, CFUNCTYPE, Structure, Union, CDLL, c_char, cast
 from pyosaf.saAis import SaUint64T, SaEnumT, Enumeration, Const, BYREF, \
         SaUint32T, SaUint16T, SaBoolT, SaStringT, SaNameT, SaTimeT, SaDoubleT, 
\
         SaInt64T, SaUint8T, SaInt8T, SaInt16T, SaInt32T, SaFloatT, SaAnyT
@@ -655,7 +655,7 @@ class SaNtfNotificationTypeFilterHandlesT_3(Structure):
                ('securityAlarmFilterHandle', SaNtfNotificationFilterHandleT),
                ('miscellaneousFilterHandle', SaNtfNotificationFilterHandleT)]
 
-saNtf.SA_NTF_FILTER_HANDLE_NULL = None
+saNtf.SA_NTF_FILTER_HANDLE_NULL = 0
 
 #if defined(SA_NTF_A01) || defined(SA_NTF_A02)
 class _notification(Union):
diff --git a/python/pyosaf/utils/ntf/__init__.py 
b/python/pyosaf/utils/ntf/__init__.py
index a76867e..0687517 100644
--- a/python/pyosaf/utils/ntf/__init__.py
+++ b/python/pyosaf/utils/ntf/__init__.py
@@ -15,259 +15,49 @@
 # Author(s): Ericsson
 #
 ############################################################################
-# pylint: disable=unused-argument,too-many-arguments
+# pylint: disable=too-many-arguments
 """ NTF common utilities """
-import ctypes
-
-from pyosaf.saAis import saAis, SaVersionT, SaSelectionObjectT, eSaBoolT, \
-    eSaDispatchFlagsT
+from pyosaf.saAis import saAis, eSaAisErrorT, SaVersionT, eSaDispatchFlagsT
 from pyosaf import saNtf
-from pyosaf.utils import decorate, initialize_decorate
-
-
-# Decorate pure saNtf* API's with error-handling retry and exception raising
-saNtfInitialize = initialize_decorate(saNtf.saNtfInitialize)
-saNtfLocalizedMessageFree = decorate(saNtf.saNtfLocalizedMessageFree)
-saNtfStateChangeNotificationFilterAllocate = \
-    decorate(saNtf.saNtfStateChangeNotificationFilterAllocate)
-saNtfNotificationUnsubscribe = decorate(saNtf.saNtfNotificationUnsubscribe)
-saNtfNotificationReadInitialize = \
-    decorate(saNtf.saNtfNotificationReadInitialize)
-saNtfInitialize_2 = initialize_decorate(saNtf.saNtfInitialize_2)
-saNtfNotificationReadInitialize_2 = \
-    decorate(saNtf.saNtfNotificationReadInitialize_2)
-saNtfNotificationSubscribe = decorate(saNtf.saNtfNotificationSubscribe)
-saNtfInitialize_3 = initialize_decorate(saNtf.saNtfInitialize_3)
-saNtfSelectionObjectGet = decorate(saNtf.saNtfSelectionObjectGet)
-saNtfDispatch = decorate(saNtf.saNtfDispatch)
-saNtfFinalize = decorate(saNtf.saNtfFinalize)
-saNtfObjectCreateDeleteNotificationAllocate = \
-    decorate(saNtf.saNtfObjectCreateDeleteNotificationAllocate)
-saNtfAttributeChangeNotificationAllocate = \
-    decorate(saNtf.saNtfAttributeChangeNotificationAllocate)
-saNtfStateChangeNotificationAllocate = \
-    decorate(saNtf.saNtfStateChangeNotificationAllocate)
-saNtfStateChangeNotificationAllocate_3 = \
-    decorate(saNtf.saNtfStateChangeNotificationAllocate_3)
-saNtfAlarmNotificationAllocate = decorate(saNtf.saNtfAlarmNotificationAllocate)
-saNtfSecurityAlarmNotificationAllocate = \
-    decorate(saNtf.saNtfSecurityAlarmNotificationAllocate)
-saNtfMiscellaneousNotificationAllocate = \
-    decorate(saNtf.saNtfMiscellaneousNotificationAllocate)
-saNtfPtrValAllocate = decorate(saNtf.saNtfPtrValAllocate)
-saNtfArrayValAllocate = decorate(saNtf.saNtfArrayValAllocate)
-saNtfIdentifierAllocate = decorate(saNtf.saNtfIdentifierAllocate)
-saNtfNotificationSend = decorate(saNtf.saNtfNotificationSend)
-saNtfNotificationSendWithId = decorate(saNtf.saNtfNotificationSendWithId)
-saNtfNotificationFree = decorate(saNtf.saNtfNotificationFree)
-saNtfVariableDataSizeGet = decorate(saNtf.saNtfVariableDataSizeGet)
-saNtfLocalizedMessageGet = decorate(saNtf.saNtfLocalizedMessageGet)
-saNtfLocalizedMessageFree_2 = decorate(saNtf.saNtfLocalizedMessageFree_2)
-saNtfPtrValGet = decorate(saNtf.saNtfPtrValGet)
-saNtfArrayValGet = decorate(saNtf.saNtfArrayValGet)
-saNtfObjectCreateDeleteNotificationFilterAllocate = \
-    decorate(saNtf.saNtfObjectCreateDeleteNotificationFilterAllocate)
-saNtfAttributeChangeNotificationFilterAllocate = \
-    decorate(saNtf.saNtfAttributeChangeNotificationFilterAllocate)
-saNtfStateChangeNotificationFilterAllocate_2 = \
-    decorate(saNtf.saNtfStateChangeNotificationFilterAllocate_2)
-saNtfAlarmNotificationFilterAllocate = \
-    decorate(saNtf.saNtfAlarmNotificationFilterAllocate)
-saNtfSecurityAlarmNotificationFilterAllocate = \
-    decorate(saNtf.saNtfSecurityAlarmNotificationFilterAllocate)
-saNtfNotificationFilterFree = decorate(saNtf.saNtfNotificationFilterFree)
-saNtfNotificationSubscribe_3 = decorate(saNtf.saNtfNotificationSubscribe_3)
-saNtfNotificationReadInitialize_3 = \
-    decorate(saNtf.saNtfNotificationReadInitialize_3)
-saNtfNotificationUnsubscribe_2 = decorate(saNtf.saNtfNotificationUnsubscribe_2)
-saNtfNotificationReadNext = decorate(saNtf.saNtfNotificationReadNext)
-saNtfNotificationReadNext_3 = decorate(saNtf.saNtfNotificationReadNext_3)
-saNtfNotificationReadFinalize = decorate(saNtf.saNtfNotificationReadFinalize)
-
-
-handle = saNtf.SaNtfHandleT()
-selection_object = SaSelectionObjectT()
-callbacks = saNtf.SaNtfCallbacksT()
-
-
-class AdditionalInfo(object):
-    """ Represent a piece of additional info to be included in a notification
-    """
-    def __init__(self, info_id, info_type, info_value):
-        self.info_id = info_id
-        self.info_type = info_type
-        self.info_value = info_value
-
-
-class StateChange(object):
-    """ Contain information about a state change event """
-    def __init__(self):
-        pass
-
-
-class AttributeChange(object):
-    """ Contain information about a change in an attribute """
-    def __init__(self):
-        pass
-
-
-class Attribute(object):
-    """ Contain information about the value and value type of an attribute """
-    def __init__(self):
-        pass
-
-
-class SecurityAlarmDetector(object):
-    """ Represent an instance of a security alarm detector """
-    def __init__(self, value=None, value_type=None):
-        self.value = value
-        self.value_type = value_type
+from pyosaf.utils import deprecate, SafException
+from pyosaf.utils.ntf import agent as ntf
+from pyosaf.utils.ntf.producer import NtfProducer
+from pyosaf.utils.ntf.subscriber import NtfSubscriber
 
-
-class ServiceUser(object):
-    """ Represent a service user """
-    def __init__(self, value=None, value_type=None):
-        self.value = value
-        self.value_type = value_type
-
-
-class ServiceProvider(object):
-    """ Represent a service provider """
-    def __init__(self, value=None, value_type=None):
-        self.value = value
-        self.value_type = value_type
-
-
-def dummy_func(*args):
-    """ Dummy function used as a callback when no proper callbacks are set """
-    pass
+_ntf_producer = None
+_ntf_subscriber = None
 
 
+@deprecate
 def initialize(notification_callback=None):
     """ Initialize the NTF library
 
     Args:
         notification_callback (SaNtfNotificationCallbackT): Callback to be
             invoked by NTF server to deliver a notification to the subscriber
-    """
-    # Assign default values for callbacks
-    callbacks.saNtfNotificationCallback = \
-        saNtf.SaNtfNotificationCallbackT(dummy_func)
-    callbacks.saNtfNotificationDiscardedCallback = \
-        saNtf.SaNtfNotificationDiscardedCallbackT(dummy_func)
-
-    # Override the default notification subscribe callback if one is provided
-    if notification_callback:
-        callbacks.saNtfNotificationCallback = \
-                    saNtf.SaNtfNotificationCallbackT(notification_callback)
-
-    # Define which version of the NTF API to use
-    version = SaVersionT('A', 1, 1)
-
-    # Initialize the NTF interface
-    saNtfInitialize(handle, callbacks, version)
-
-    # Get the selection object
-    saNtfSelectionObjectGet(handle, selection_object)
-
-
-def assign_ntf_value_to_attribute(attr_value_field, value, value_type):
-    """ Assign the correct sub-field in the given attribute
-
-    Args:
-        attr_value_field (SaNtfValueT): Object attribute value in an object
-            creation or deletion notification
-        value (variable-size C data type): Actual value of the object attribute
-        value_type (SaNtfValueTypeT): Type of the object attribute value
 
+    Raises:
+        SafException: If any NTF API call did not return SA_AIS_OK
     """
-    if value_type == saNtf.eSaNtfValueTypeT.SA_NTF_VALUE_UINT8:
-        attr_value_field.uint8Val = value
-
-    elif value_type == saNtf.eSaNtfValueTypeT.SA_NTF_VALUE_INT8:
-        attr_value_field.int8Val = value
-
-    elif value_type == saNtf.eSaNtfValueTypeT.SA_NTF_VALUE_UINT16:
-        attr_value_field.uint16Val = value
-
-    elif value_type == saNtf.eSaNtfValueTypeT.SA_NTF_VALUE_INT16:
-        attr_value_field.int16Val = value
-
-    elif value_type == saNtf.eSaNtfValueTypeT.SA_NTF_VALUE_UINT32:
-        attr_value_field.uint32Val = value
-
-    elif value_type == saNtf.eSaNtfValueTypeT.SA_NTF_VALUE_INT32:
-        attr_value_field.int32Val = value
-
-    elif value_type == saNtf.eSaNtfValueTypeT.SA_NTF_VALUE_FLOAT:
-        attr_value_field.floatVal = value
+    # Set the NTF API version to initialize
+    version = SaVersionT('A', 1, 1)
 
-    elif value_type == saNtf.eSaNtfValueTypeT.SA_NTF_VALUE_UINT64:
-        attr_value_field.uint64Val = value
+    global _ntf_producer
+    _ntf_producer = NtfProducer(version)
 
-    elif value_type == saNtf.eSaNtfValueTypeT.SA_NTF_VALUE_INT64:
-        attr_value_field.int64Val = value
+    global _ntf_subscriber
+    _ntf_subscriber = NtfSubscriber(version)
 
-    elif value_type == saNtf.eSaNtfValueTypeT.SA_NTF_VALUE_DOUBLE:
-        attr_value_field.doubleVal = value
+    rc = _ntf_producer.init()
+    if rc != eSaAisErrorT.SA_AIS_OK:
+        raise SafException(rc)
 
-
-def fill_in_header(notification_handle,
-                   header, notification_object, notifying_object, vendor_id,
-                   major_id, minor_id, additional_text, event_type, event_time,
-                   additional_info):
-    """ Fill in the given notification header with the provided information
-
-    Args:
-        notification_handle (SaNtfNotificationHandleT): Notification handle
-        header (SaNtfNotificationHeaderT): Notification header
-        notification_object (str): Notification object's dn
-        notifying_object (str): Notifying object's dn
-        vendor_id (SaUint32T): Vendor id
-        major_id (SaUint16T): Major id
-        minor_id (SaUint16T): Minor id
-        additional_text (str): Additional text
-        event_type (SaNtfEventTypeT): Event type
-        event_time (SaTimeT): Event time
-        additional_info (list): List of AdditionalInfo class instances
-    """
-    header.eventType.contents.value = event_type
-    header.notificationObject.contents.value = notification_object
-    header.notificationObject.contents.length = len(notification_object)
-    header.notifyingObject.contents.value = notifying_object
-    header.notifyingObject.contents.length = len(notifying_object)
-    header.notificationClassId.contents.vendorId = vendor_id
-    header.notificationClassId.contents.majorId = major_id
-    header.notificationClassId.contents.minorId = minor_id
-    header.eventTime.contents.value = event_time
-    header.numCorrelatedNotifications = 0
-    header.lengthAdditionalText = len(additional_text)
-
-    ctypes.memmove(header.additionalText,
-                   additional_text,
-                   len(additional_text))
-
-    header.numAdditionalInfo = len(additional_info)
-    header.thresholdInformation = None
-
-    # Fill in additional info
-    if additional_info:
-        for i, add_info in enumerate(additional_info):
-            header.additionalInfo[i].infoId = add_info.info_id
-            header.additionalInfo[i].infoType = add_info.info_type
-
-            dest_ptr = (ctypes.c_char * len(add_info.info_value))()
-
-            saNtf.saNtfPtrValAllocate(notification_handle,
-                                      len(add_info.info_value) + 1,
-                                      dest_ptr,
-                                      header.additionalInfo[i].infoValue)
-            ctypes.memmove(ctypes.addressof(dest_ptr), add_info.info_value,
-                           len(add_info.info_value) + 1)
-    else:
-        header.additionalInfo = None
+    rc = _ntf_subscriber.init(notification_callback)
+    if rc != eSaAisErrorT.SA_AIS_OK:
+        raise SafException(rc)
 
 
+@deprecate
 def send_object_create_notification(vendor_id, major_id, minor_id,
                                     additional_text="",
                                     notification_object="",
@@ -284,27 +74,42 @@ def send_object_create_notification(vendor_id, major_id, 
minor_id,
         additional_text (str): Additional text
         notification_object (str): Notification object's dn
         notifying_object (str): Notifying object's dn
-        attributes (list): List of Attribute class instances
+        attributes (list(Attribute)): List of Attribute structures
         event_time (SaTimeT): Event time
-        additional_info (list): List of AdditionalInfo class instances
+        additional_info (list(AdditionalInfo)): List of AdditionalInfo
+            structures
+
+    Returns:
+        SaAisErrorT: Return code of the corresponding NTF API call(s)
+
+    Raises:
+        SafException: If any NTF API call did not return SA_AIS_OK
     """
-    if attributes is None:
-        attributes = []
+    ntf_class_id = saNtf.SaNtfClassIdT(vendor_id, major_id, minor_id)
+    if _ntf_producer is None:
+        # SA_AIS_ERR_INIT is returned if user calls this function without first
+        # calling initialize()
+        return eSaAisErrorT.SA_AIS_ERR_INIT
+
+    _ntf_producer.set_event_type(saNtf.eSaNtfEventTypeT.SA_NTF_OBJECT_CREATION)
+    _ntf_producer.set_class_id(ntf_class_id)
+    _ntf_producer.set_additional_text(additional_text)
+    _ntf_producer.set_notification_object(notification_object)
+    _ntf_producer.set_notifying_object(notifying_object)
+    if attributes:
+        _ntf_producer.set_object_attributes(attributes)
+    _ntf_producer.set_event_time(event_time)
+    if additional_info:
+        _ntf_producer.set_additional_info(additional_info)
 
-    if additional_info is None:
-        additional_info = []
+    rc = _ntf_producer.send_object_create_delete_notification()
+    if rc != eSaAisErrorT.SA_AIS_OK:
+        raise SafException(rc)
 
-    _send_object_create_delete_notification(
-        saNtf.eSaNtfEventTypeT.SA_NTF_OBJECT_CREATION,
-        vendor_id, major_id, minor_id,
-        additional_text=additional_text,
-        notification_object=notification_object,
-        notifying_object=notifying_object,
-        attributes=attributes,
-        event_time=event_time,
-        additional_info=additional_info)
+    return rc
 
 
+@deprecate
 def send_object_delete_notification(vendor_id, major_id, minor_id,
                                     additional_text="",
                                     notification_object="",
@@ -321,88 +126,95 @@ def send_object_delete_notification(vendor_id, major_id, 
minor_id,
         additional_text (str): Additional text
         notification_object (str): Notification object's dn
         notifying_object (str): Notifying object's dn
-        attributes (list): List of Attribute class instances
+        attributes (list(Attribute)): List of Attribute structures
         event_time (SaTimeT): Event time
-        additional_info (list): List of AdditionalInfo class instances
+        additional_info (list(AdditionalInfo)): List of AdditionalInfo
+            structures
+
+    Returns:
+        SaAisErrorT: Return code of the corresponding NTF API call(s)
+
+    Raises:
+        SafException: If any NTF API call did not return SA_AIS_OK
     """
-    if attributes is None:
-        attributes = []
-
-    if additional_info is None:
-        additional_info = []
-
-    _send_object_create_delete_notification(
-        saNtf.eSaNtfEventTypeT.SA_NTF_OBJECT_DELETION,
-        vendor_id, major_id, minor_id,
-        additional_text=additional_text,
-        notification_object=notification_object,
-        notifying_object=notifying_object,
-        attributes=attributes,
-        event_time=event_time,
-        additional_info=additional_info)
-
-
-def _send_object_create_delete_notification(event_type, vendor_id, major_id,
-                                            minor_id, additional_text="",
-                                            notification_object="",
-                                            notifying_object="",
-                                            attributes=None,
-                                            event_time=saAis.SA_TIME_UNKNOWN,
-                                            additional_info=None):
-    """ Common function to send notification about an object creation/deletion
+    ntf_class_id = saNtf.SaNtfClassIdT(vendor_id, major_id, minor_id)
+    if _ntf_producer is None:
+        # SA_AIS_ERR_INIT is returned if user calls this function without first
+        # calling initialize()
+        return eSaAisErrorT.SA_AIS_ERR_INIT
+
+    _ntf_producer.set_event_type(saNtf.eSaNtfEventTypeT.SA_NTF_OBJECT_DELETION)
+    _ntf_producer.set_class_id(ntf_class_id)
+    _ntf_producer.set_additional_text(additional_text)
+    _ntf_producer.set_notification_object(notification_object)
+    _ntf_producer.set_notifying_object(notifying_object)
+    if attributes:
+        _ntf_producer.set_object_attributes(attributes)
+    _ntf_producer.set_event_time(event_time)
+    if additional_info:
+        _ntf_producer.set_additional_info(additional_info)
+
+    rc = _ntf_producer.send_object_create_delete_notification()
+    if rc != eSaAisErrorT.SA_AIS_OK:
+        raise SafException(rc)
+
+    return rc
+
+
+@deprecate
+def send_attribute_change_notification(
+        vendor_id, major_id, minor_id, additional_text="",
+        notification_object="", notifying_object="",
+        event_type=saNtf.eSaNtfEventTypeT.SA_NTF_ATTRIBUTE_ADDED,
+        event_time=saAis.SA_TIME_UNKNOWN, additional_info=None,
+        changed_attributes=None):
+    """ Send notification about an attribute change event
 
     Args:
-        event_type (SaNtfEventTypeT): Event type
         vendor_id (SaUint32T): Vendor id
         major_id (SaUint16T): Major id
         minor_id (SaUint16T): Minor id
         additional_text (str): Additional text
         notification_object (str): Notification object's dn
         notifying_object (str): Notifying object's dn
-        attributes (list): List of Attribute class instances
+        event_type (SaNtfEventTypeT): Event type
         event_time (SaTimeT): Event time
-        additional_info (list): List of AdditionalInfo class instances
-    """
-
-    if attributes is None:
-        attributes = []
-
-    if additional_info is None:
-        additional_info = []
-
-    # Create the notification
-    notification = saNtf.SaNtfObjectCreateDeleteNotificationT()
+        additional_info (list(AdditionalInfo)): List of AdditionalInfo
+            structures
+        changed_attributes (list(AttributeChange)): List of AttributeChange
+            structures
 
-    saNtfObjectCreateDeleteNotificationAllocate(handle, notification, 0,
-                                                len(additional_text) + 1,
-                                                len(additional_info),
-                                                len(attributes), 0)
+    Returns:
+        SaAisErrorT: Return code of the corresponding NTF API call(s)
 
-    # Fill in the header
-    fill_in_header(notification.notificationHandle,
-                   notification.notificationHeader,
-                   notification_object, notifying_object, vendor_id,
-                   major_id, minor_id, additional_text,
-                   event_type, event_time, additional_info)
-
-    # Fill in attributes
-    for i in range(notification.numAttributes):
-        ptr = notification.objectAttributes[i]
-
-        ptr.attributeId = attributes[i].attribute_id
-        ptr.attributeType = attributes[i].attribute_type
-
-        assign_ntf_value_to_attribute(ptr.attributeValue,
-                                      attributes[i].attribute_value,
-                                      attributes[i].attribute_type)
+    Raises:
+        SafException: If any NTF API call did not return SA_AIS_OK
+    """
+    ntf_class_id = saNtf.SaNtfClassIdT(vendor_id, major_id, minor_id)
+    if _ntf_producer is None:
+        # SA_AIS_ERR_INIT is returned if user calls this function without first
+        # calling initialize()
+        return eSaAisErrorT.SA_AIS_ERR_INIT
+
+    _ntf_producer.set_event_type(event_type)
+    _ntf_producer.set_class_id(ntf_class_id)
+    _ntf_producer.set_additional_text(additional_text)
+    _ntf_producer.set_notification_object(notification_object)
+    _ntf_producer.set_notifying_object(notifying_object)
+    _ntf_producer.set_event_time(event_time)
+    if additional_info:
+        _ntf_producer.set_additional_info(additional_info)
+    if changed_attributes:
+        _ntf_producer.set_changed_attributes(changed_attributes)
 
-    # Send the notification
-    saNtfNotificationSend(notification.notificationHandle)
+    rc = _ntf_producer.send_attribute_change_notification()
+    if rc != eSaAisErrorT.SA_AIS_OK:
+        raise SafException(rc)
 
-    # Free the notification
-    saNtfNotificationFree(notification.notificationHandle)
+    return rc
 
 
+@deprecate
 def send_state_change_notification(vendor_id,
                                    major_id,
                                    minor_id,
@@ -422,119 +234,92 @@ def send_state_change_notification(vendor_id,
         notification_object (str): Notification object's dn
         notifying_object (str): Notifying object's dn
         event_time (SaTimeT): Event time
-        additional_info (list): List of AdditionalInfo class instances
-        state_changes (list): List of StateChange class instances
-    """
-    if additional_info is None:
-        additional_info = []
-
-    if state_changes is None:
-        state_changes = []
-
-    event_type = saNtf.eSaNtfEventTypeT.SA_NTF_OBJECT_STATE_CHANGE
-
-    # Create the notification
-    notification = saNtf.SaNtfStateChangeNotificationT()
+        additional_info (list(AdditionalInfo)): List of AdditionalInfo
+            structures
+        state_changes (list(StateChange)): List of StateChange structures
 
-    saNtfStateChangeNotificationAllocate(handle, notification, 0,
-                                         len(additional_text) + 1,
-                                         len(additional_info),
-                                         len(state_changes), 0)
+    Returns:
+        SaAisErrorT: Return code of the corresponding NTF API call(s)
 
-    # Fill in the header
-    fill_in_header(notification.notificationHandle,
-                   notification.notificationHeader,
-                   notification_object, notifying_object, vendor_id,
-                   major_id, minor_id, additional_text, event_type,
-                   event_time, additional_info)
-
-    # Fill in state changes
-    for i, state_change in enumerate(state_changes):
-        notification.changedStates[i].stateId = state_change.state_id
-        if state_change.old_state_present:
-            notification.changedStates[i].oldStatePresent = eSaBoolT.SA_TRUE
-            notification.changedStates[i].oldState = state_change.old_state
-        else:
-            notification.changedStates[i].oldStatePresent = eSaBoolT.SA_FALSE
-
-        notification.changedStates[i].newState = state_change.new_state
+    Raises:
+        SafException: If any NTF API call did not return SA_AIS_OK
+    """
+    ntf_class_id = saNtf.SaNtfClassIdT(vendor_id, major_id, minor_id)
+    if _ntf_producer is None:
+        # SA_AIS_ERR_INIT is returned if user calls this function without first
+        # calling initialize()
+        return eSaAisErrorT.SA_AIS_ERR_INIT
+
+    _ntf_producer.set_event_type(
+        saNtf.eSaNtfEventTypeT.SA_NTF_OBJECT_STATE_CHANGE)
+    _ntf_producer.set_class_id(ntf_class_id)
+    _ntf_producer.set_additional_text(additional_text)
+    _ntf_producer.set_notification_object(notification_object)
+    _ntf_producer.set_notifying_object(notifying_object)
+    _ntf_producer.set_event_time(event_time)
+    if additional_info:
+        _ntf_producer.set_additional_info(additional_info)
+    if state_changes:
+        _ntf_producer.set_state_changes(state_changes)
 
-    # Send the notification
-    saNtfNotificationSend(notification.notificationHandle)
+    rc = _ntf_producer.send_state_change_notification()
+    if rc != eSaAisErrorT.SA_AIS_OK:
+        raise SafException(rc)
 
-    # Free the notification
-    saNtfNotificationFree(notification.notificationHandle)
+    return rc
 
 
-def send_attribute_change_notification(
-        vendor_id, major_id, minor_id, additional_text="",
+@deprecate
+def send_alarm_notification(
+        vendor_id, major_id, minor_id, perceived_severity, additional_text="",
         notification_object="", notifying_object="",
-        event_type=saNtf.eSaNtfEventTypeT.SA_NTF_ATTRIBUTE_ADDED,
-        event_time=saAis.SA_TIME_UNKNOWN, additional_info=None,
-        changed_attributes=None):
-    """ Send notification about an attribute change event
+        event_type=saNtf.eSaNtfEventTypeT.SA_NTF_ALARM_PROCESSING,
+        event_time=saAis.SA_TIME_UNKNOWN, additional_info=None):
+    """ Send an alarm notification
 
     Args:
         vendor_id (SaUint32T): Vendor id
         major_id (SaUint16T): Major id
         minor_id (SaUint16T): Minor id
+        perceived_severity (SaNtfSeverityT): Perceived severity
         additional_text (str): Additional text
         notification_object (str): Notification object's dn
         notifying_object (str): Notifying object's dn
         event_type (SaNtfEventTypeT): Event type
         event_time (SaTimeT): Event time
-        additional_info (list): List of AdditionalInfo class instances
-        changed_attributes (list): List of AttributeChange class instances
-    """
-    if additional_info is None:
-        additional_info = []
-
-    if changed_attributes is None:
-        changed_attributes = []
-
-    # Create the notification
-    notification = saNtf.SaNtfAttributeChangeNotificationT()
-
-    saNtfAttributeChangeNotificationAllocate(handle, notification, 0,
-                                             len(additional_text) + 1,
-                                             len(additional_info),
-                                             len(changed_attributes), 0)
-
-    # Fill in the header
-    fill_in_header(notification.notificationHandle,
-                   notification.notificationHeader,
-                   notification_object, notifying_object, vendor_id,
-                   major_id, minor_id, additional_text, event_type,
-                   event_time, additional_info)
-
-    # Fill in attributes
-    for i, changed_attribute in enumerate(changed_attributes):
-        ptr = notification.changedAttributes[i]
+        additional_info (list(AdditionalInfo)): List of AdditionalInfo
+            structures
 
-        ptr.attributeId = changed_attribute.attribute_id
-        ptr.attributeType = changed_attribute.attribute_type
+    Returns:
+        SaAisErrorT: Return code of the corresponding NTF API call(s)
 
-        if changed_attribute.old_attribute_present:
-            ptr.oldAttributePresent = eSaBoolT.SA_TRUE
-
-            assign_ntf_value_to_attribute(
-                ptr.oldAttributeValue,
-                changed_attribute.old_attribute_value,
-                changed_attribute.attribute_type)
-        else:
-            ptr.oldAttributePresent = eSaBoolT.SA_FALSE
-
-        assign_ntf_value_to_attribute(
-            ptr.newAttributeValue, changed_attribute.new_attribute_value,
-            changed_attribute.attribute_type)
+    Raises:
+        SafException: If any NTF API call did not return SA_AIS_OK
+    """
+    ntf_class_id = saNtf.SaNtfClassIdT(vendor_id, major_id, minor_id)
+    if _ntf_producer is None:
+        # SA_AIS_ERR_INIT is returned if user calls this function without first
+        # calling initialize()
+        return eSaAisErrorT.SA_AIS_ERR_INIT
+
+    _ntf_producer.set_event_type(event_type)
+    _ntf_producer.set_class_id(ntf_class_id)
+    _ntf_producer.set_additional_text(additional_text)
+    _ntf_producer.set_notification_object(notification_object)
+    _ntf_producer.set_notifying_object(notifying_object)
+    _ntf_producer.set_event_time(event_time)
+    _ntf_producer.set_perceived_severity(perceived_severity)
+    if additional_info:
+        _ntf_producer.set_additional_info(additional_info)
 
-    # Send the notification
-    saNtfNotificationSend(notification.notificationHandle)
+    rc = _ntf_producer.send_alarm_notification()
+    if rc != eSaAisErrorT.SA_AIS_OK:
+        raise SafException(rc)
 
-    # Free the notification
-    saNtfNotificationFree(notification.notificationHandle)
+    return rc
 
 
+@deprecate
 def send_security_alarm_notification(
         vendor_id, major_id, minor_id, severity, alarm_detector, user,
         provider, additional_text="", notification_object="",
@@ -550,220 +335,122 @@ def send_security_alarm_notification(
         major_id (SaUint16T): Major id
         minor_id (SaUint16T): Minor id
         severity (SaNtfSeverityT): Severity
-        alarm_detector (SecurityAlarmDetector): Alarm detector information
-        user (ServiceUser): Service user information
-        provider (ServiceProvider): Service provider information
+        alarm_detector (SecurityAlarmDetector): SecurityAlarmDetector
+            information structure
+        user (ServiceUser): ServiceUser information structure
+        provider (ServiceProvider): ServiceProvider information structure
         additional_text (str): Additional text
         notification_object (str): Notification object's dn
         notifying_object (str): Notifying object's dn
         event_type (SaNtfEventTypeT): Event type
         event_time (SaTimeT): Event time
-        additional_info (list): List of AdditionalInfo class instances
+        additional_info (list(AdditionalInfo)): List of AdditionalInfo
+            structures
         probable_cause (SaNtfProbableCauseT): Probable cause
-    """
-    if additional_info is None:
-        additional_info = []
-
-    # Create the notification
-    notification = saNtf.SaNtfSecurityAlarmNotificationT()
-
-    saNtfSecurityAlarmNotificationAllocate(handle, notification, 0,
-                                           len(additional_text) + 1,
-                                           len(additional_info), 0)
-
-    # Fill in the header
-    fill_in_header(notification.notificationHandle,
-                   notification.notificationHeader,
-                   notification_object, notifying_object, vendor_id,
-                   major_id, minor_id, additional_text, event_type,
-                   event_time, additional_info)
-
-    # Fill in security alarm-specific fields
-    notification.probableCause.contents.value = probable_cause
-    notification.severity.contents.value = severity
-
-    assign_ntf_value_to_attribute(notification.securityAlarmDetector,
-                                  alarm_detector.value,
-                                  alarm_detector.value_type)
 
-    assign_ntf_value_to_attribute(notification.serviceUser,
-                                  user.value,
-                                  user.value_type)
+    Returns:
+        SaAisErrorT: Return code of the corresponding NTF API call(s)
 
-    assign_ntf_value_to_attribute(notification.serviceProvider,
-                                  provider.value,
-                                  provider.value_type)
-
-    # Send the notification
-    saNtfNotificationSend(notification.notificationHandle)
-
-    # Free the notification
-    saNtfNotificationFree(notification.notificationHandle)
-
-
-def send_alarm_notification(
-        vendor_id, major_id, minor_id, severity, additional_text="",
-        notification_object="", notifying_object="",
-        event_type=saNtf.eSaNtfEventTypeT.SA_NTF_ALARM_PROCESSING,
-        event_time=saAis.SA_TIME_UNKNOWN, additional_info=None):
-    """ Send an alarm notification
-
-    Args:
-        vendor_id (SaUint32T): Vendor id
-        major_id (SaUint16T): Major id
-        minor_id (SaUint16T): Minor id
-        severity (SaNtfSeverityT): Severity
-        additional_text (str): Additional text
-        notification_object (str): Notification object's dn
-        notifying_object (str): Notifying object's dn
-        event_type (SaNtfEventTypeT): Event type
-        event_time (SaTimeT): Event time
-        additional_info (list): List of AdditionalInfo class instances
+    Raises:
+        SafException: If any NTF API call did not return SA_AIS_OK
     """
-    if additional_info is None:
-        additional_info = []
-
-    # Create the notification
-    notification = saNtf.SaNtfAlarmNotificationT()
-
-    saNtfAlarmNotificationAllocate(handle, notification, 0,
-                                   len(additional_text) + 1,
-                                   len(additional_info), 0, 0, 0, 0)
-
-    # Fill in the header
-    fill_in_header(notification.notificationHandle,
-                   notification.notificationHeader,
-                   notification_object, notifying_object, vendor_id,
-                   major_id, minor_id, additional_text, event_type,
-                   event_time, additional_info)
-
-    notification.numMonitoredAttributes = 0
-    notification.numSpecificProblems = 0
-    notification.numProposedRepairActions = 0
-    notification.perceivedSeverity.contents.value = severity
-    notification.probableCause.contents.value = \
-        saNtf.eSaNtfProbableCauseT.SA_NTF_DEGRADED_SIGNAL
+    ntf_class_id = saNtf.SaNtfClassIdT(vendor_id, major_id, minor_id)
+    if _ntf_producer is None:
+        # SA_AIS_ERR_INIT is returned if user calls this function without first
+        # calling initialize()
+        return eSaAisErrorT.SA_AIS_ERR_INIT
+
+    _ntf_producer.set_event_type(event_type)
+    _ntf_producer.set_class_id(ntf_class_id)
+    _ntf_producer.set_additional_text(additional_text)
+    _ntf_producer.set_notification_object(notification_object)
+    _ntf_producer.set_notifying_object(notifying_object)
+    _ntf_producer.set_event_time(event_time)
+    if additional_info:
+        _ntf_producer.set_additional_info(additional_info)
+    _ntf_producer.set_severity(severity)
+    _ntf_producer.set_probable_cause(probable_cause)
+    _ntf_producer.set_security_alarm_detector(alarm_detector)
+    _ntf_producer.set_service_user(user)
+    _ntf_producer.set_service_provider(provider)
 
-    # Send the notification
-    saNtfNotificationSend(notification.notificationHandle)
+    rc = _ntf_producer.send_security_alarm_notification()
+    if rc != eSaAisErrorT.SA_AIS_OK:
+        raise SafException(rc)
 
-    # Free the notification
-    saNtfNotificationFree(notification.notificationHandle)
+    return rc
 
 
+@deprecate
 def subscribe_for_notifications(notification_types=None):
-    """ Subscribe for notifications from NTF. The types of notifications to
-    subscribe to are passed in the notification_types list and subscriptions
-    are set up for all types if the list is empty.
+    """ Subscribe for notifications from NTF with the types specified in the
+    notification_types list
+    If the list is not provided, all types of notification are subscribed to
+    by default.
 
     Args:
-        notification_types (list): List of notification types
-    """
-    filters = []
-
-    filter_handles = saNtf.SaNtfNotificationTypeFilterHandlesT()
-    filter_handles.objectCreateDeleteFilterHandle = \
-        saNtf.SaNtfNotificationFilterHandleT(0)
-    filter_handles.attributeChangeFilterHandle = \
-        saNtf.SaNtfNotificationFilterHandleT(0)
-    filter_handles.stateChangeFilterHandle = \
-        saNtf.SaNtfNotificationFilterHandleT(0)
-    filter_handles.alarmFilterHandle = saNtf.SaNtfNotificationFilterHandleT(0)
-    filter_handles.securityFilterHandle = \
-        saNtf.SaNtfNotificationFilterHandleT(0)
-
-    # Create and allocate the alarm filter
-    if not notification_types or \
-       saNtf.eSaNtfNotificationTypeT.SA_NTF_TYPE_ALARM in notification_types:
-        notification_filter = saNtf.SaNtfAlarmNotificationFilterT()
+        notification_types (list(SaNtfNotificationTypeT)): List of
+            notification types
 
-        saNtfAlarmNotificationFilterAllocate(handle, notification_filter,
-                                             0, 0, 0, 0, 0, 0, 0)
+    Returns:
+        SaAisErrorT: Return code of the corresponding NTF API call(s)
 
-        filter_handles.alarmFilterHandle = \
-            notification_filter.notificationFilterHandle
-
-        filters.append(notification_filter.notificationFilterHandle)
-
-    # Create and allocate the object create delete filter
-    if not notification_types or \
-       saNtf.eSaNtfNotificationTypeT.SA_NTF_TYPE_OBJECT_CREATE_DELETE in \
-       notification_types:
-        notification_filter = \
-            saNtf.SaNtfObjectCreateDeleteNotificationFilterT()
-
-        saNtfObjectCreateDeleteNotificationFilterAllocate(handle,
-                                                          notification_filter,
-                                                          0, 0, 0, 0, 0)
-
-        filter_handles.objectCreateDeleteFilterHandle = \
-            notification_filter.notificationFilterHandle
-        filters.append(notification_filter.notificationFilterHandle)
-
-    # Create and allocate the attribute change filter
-    if not notification_types or \
-       saNtf.eSaNtfNotificationTypeT.SA_NTF_TYPE_ATTRIBUTE_CHANGE in \
-       notification_types:
-
-        notification_filter = saNtf.SaNtfAttributeChangeNotificationFilterT()
-
-        saNtfAttributeChangeNotificationFilterAllocate(handle,
-                                                       notification_filter,
-                                                       0, 0, 0, 0, 0)
-
-        filter_handles.attributeChangeFilterHandle = \
-            notification_filter.notificationFilterHandle
-
-        filters.append(notification_filter.notificationFilterHandle)
-
-    # Create and allocate the state change filter
-    if not notification_types or \
-       saNtf.eSaNtfNotificationTypeT.SA_NTF_TYPE_STATE_CHANGE in \
-       notification_types:
-
-        notification_filter = saNtf.SaNtfStateChangeNotificationFilterT()
-
-        saNtfStateChangeNotificationFilterAllocate(handle,
-                                                   notification_filter,
-                                                   0, 0, 0, 0, 0, 0)
+    Raises:
+        SafException: If any NTF API call did not return SA_AIS_OK
+    """
+    rc = _ntf_subscriber.subscribe(1, notification_types)
+    if rc != eSaAisErrorT.SA_AIS_OK:
+        raise SafException(rc)
 
-        filter_handles.stateChangeFilterHandle = \
-            notification_filter.notificationFilterHandle
+    return rc
 
-        filters.append(notification_filter.notificationFilterHandle)
 
-    # Create and allocate the security alarm filter
-    if not notification_types or \
-       saNtf.eSaNtfNotificationTypeT.SA_NTF_TYPE_SECURITY_ALARM in \
-       notification_types:
+@deprecate
+def get_producer_handle():
+    """ Get the NTF producer agent handle
 
-        notification_filter = saNtf.SaNtfSecurityAlarmNotificationFilterT()
+    Returns:
+        SaNtfHandleT: NTF producer agent handle
+    """
+    return _ntf_producer.get_handle()
 
-        saNtfSecurityAlarmNotificationFilterAllocate(handle,
-                                                     notification_filter,
-                                                     0, 0, 0, 0, 0, 0, 0, 0, 0)
 
-        filter_handles.securityAlarmFilterHandle = \
-            notification_filter.notificationFilterHandle
+@deprecate
+def get_subscriber_handle():
+    """ Get the NTF subscriber agent handle
 
-        filters.append(notification_filter.notificationFilterHandle)
+    Returns:
+        SaNtfHandleT: NTF subscriber agent handle
+    """
+    return _ntf_subscriber.get_handle()
 
-    # Create a unique subscription id
-    sub_id = saNtf.SaNtfSubscriptionIdT(1)
 
-    # Start subscription
-    saNtfNotificationSubscribe(filter_handles, sub_id)
+@deprecate
+def get_subscriber_selection_object():
+    """ Get the selection object associated with the subscriber agent handle
 
-    # Free up the filters
-    for filter_handle in filters:
-        saNtfNotificationFilterFree(filter_handle)
+    Returns:
+        SaSelectionObjectT: The NTF subscriber selection object
+    """
+    return _ntf_subscriber.get_selection_object()
 
 
+@deprecate
 def dispatch(flags=eSaDispatchFlagsT.SA_DISPATCH_ALL):
     """ Invoke NTF callbacks for queued events. The default is to dispatch all
     available events.
 
     Args:
         flags (eSaDispatchFlagsT): Flags specifying dispatch mode
+
+    Returns:
+        SaAisErrorT: Return code of the corresponding NTF API call(s)
+
+    Raises:
+        SafException: If any NTF API call did not return SA_AIS_OK
     """
-    saNtfDispatch(handle, flags)
+    rc = _ntf_subscriber.dispatch(flags)
+    if rc != eSaAisErrorT.SA_AIS_OK:
+        raise SafException(rc)
+
+    return rc
diff --git a/python/pyosaf/utils/ntf/agent.py b/python/pyosaf/utils/ntf/agent.py
new file mode 100644
index 0000000..dd2d218
--- /dev/null
+++ b/python/pyosaf/utils/ntf/agent.py
@@ -0,0 +1,500 @@
+############################################################################
+#
+# (C) Copyright 2017 Ericsson AB. All rights reserved.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. This file and program are licensed
+# under the GNU Lesser General Public License Version 2.1, February 1999.
+# The complete license can be accessed from the following location:
+# http://opensource.org/licenses/lgpl-license.php
+# See the Copying file included with the OpenSAF distribution for full
+# licensing terms.
+#
+# Author(s): Ericsson
+#
+############################################################################
+# pylint: disable=unused-argument,too-few-public-methods
+""" NTF utils common data structures and functions """
+from copy import deepcopy
+
+from pyosaf.saAis import saAis, SaVersionT, SaSelectionObjectT, eSaAisErrorT
+from pyosaf import saNtf
+from pyosaf.utils import decorate, initialize_decorate, log_err
+
+# Decorate pure saNtf* API's with error-handling retry and exception raising
+# Library Life Cycle API's
+saNtfInitialize = initialize_decorate(saNtf.saNtfInitialize)
+saNtfInitialize_2 = initialize_decorate(saNtf.saNtfInitialize_2)
+saNtfInitialize_3 = initialize_decorate(saNtf.saNtfInitialize_3)
+saNtfSelectionObjectGet = decorate(saNtf.saNtfSelectionObjectGet)
+saNtfDispatch = decorate(saNtf.saNtfDispatch)
+saNtfFinalize = decorate(saNtf.saNtfFinalize)
+# Producer API's
+saNtfObjectCreateDeleteNotificationAllocate = \
+    decorate(saNtf.saNtfObjectCreateDeleteNotificationAllocate)
+saNtfAttributeChangeNotificationAllocate = \
+    decorate(saNtf.saNtfAttributeChangeNotificationAllocate)
+saNtfStateChangeNotificationAllocate = \
+    decorate(saNtf.saNtfStateChangeNotificationAllocate)
+saNtfStateChangeNotificationAllocate_3 = \
+    decorate(saNtf.saNtfStateChangeNotificationAllocate_3)
+saNtfAlarmNotificationAllocate = decorate(saNtf.saNtfAlarmNotificationAllocate)
+saNtfSecurityAlarmNotificationAllocate = \
+    decorate(saNtf.saNtfSecurityAlarmNotificationAllocate)
+saNtfMiscellaneousNotificationAllocate = \
+    decorate(saNtf.saNtfMiscellaneousNotificationAllocate)
+saNtfPtrValAllocate = decorate(saNtf.saNtfPtrValAllocate)
+saNtfArrayValAllocate = decorate(saNtf.saNtfArrayValAllocate)
+saNtfIdentifierAllocate = decorate(saNtf.saNtfIdentifierAllocate)
+saNtfNotificationSend = decorate(saNtf.saNtfNotificationSend)
+saNtfNotificationSendWithId = decorate(saNtf.saNtfNotificationSendWithId)
+saNtfNotificationFree = decorate(saNtf.saNtfNotificationFree)
+saNtfVariableDataSizeGet = decorate(saNtf.saNtfVariableDataSizeGet)
+# Consumer API's
+saNtfLocalizedMessageGet = decorate(saNtf.saNtfLocalizedMessageGet)
+saNtfLocalizedMessageFree = decorate(saNtf.saNtfLocalizedMessageFree)
+saNtfLocalizedMessageFree_2 = decorate(saNtf.saNtfLocalizedMessageFree_2)
+saNtfPtrValGet = decorate(saNtf.saNtfPtrValGet)
+saNtfArrayValGet = decorate(saNtf.saNtfArrayValGet)
+saNtfObjectCreateDeleteNotificationFilterAllocate = \
+    decorate(saNtf.saNtfObjectCreateDeleteNotificationFilterAllocate)
+saNtfAttributeChangeNotificationFilterAllocate = \
+    decorate(saNtf.saNtfAttributeChangeNotificationFilterAllocate)
+saNtfStateChangeNotificationFilterAllocate = \
+    decorate(saNtf.saNtfStateChangeNotificationFilterAllocate)
+saNtfStateChangeNotificationFilterAllocate_2 = \
+    decorate(saNtf.saNtfStateChangeNotificationFilterAllocate_2)
+saNtfAlarmNotificationFilterAllocate = \
+    decorate(saNtf.saNtfAlarmNotificationFilterAllocate)
+saNtfSecurityAlarmNotificationFilterAllocate = \
+    decorate(saNtf.saNtfSecurityAlarmNotificationFilterAllocate)
+saNtfNotificationFilterFree = decorate(saNtf.saNtfNotificationFilterFree)
+# Subscriber API's
+saNtfNotificationSubscribe = decorate(saNtf.saNtfNotificationSubscribe)
+saNtfNotificationSubscribe_3 = decorate(saNtf.saNtfNotificationSubscribe_3)
+saNtfNotificationUnsubscribe = decorate(saNtf.saNtfNotificationUnsubscribe)
+saNtfNotificationUnsubscribe_2 = decorate(saNtf.saNtfNotificationUnsubscribe_2)
+# Reader API's
+saNtfNotificationReadInitialize = \
+    decorate(saNtf.saNtfNotificationReadInitialize)
+saNtfNotificationReadInitialize_2 = \
+    decorate(saNtf.saNtfNotificationReadInitialize_2)
+saNtfNotificationReadInitialize_3 = \
+    decorate(saNtf.saNtfNotificationReadInitialize_3)
+saNtfNotificationReadNext = decorate(saNtf.saNtfNotificationReadNext)
+saNtfNotificationReadNext_3 = decorate(saNtf.saNtfNotificationReadNext_3)
+saNtfNotificationReadFinalize = decorate(saNtf.saNtfNotificationReadFinalize)
+
+
+class NotificationInfo(object):
+    """ This class encapsulates data structures for use by each specific
+    notification type """
+    def __init__(self):
+        # Header info
+        self.event_type = 0
+        self.notification_object = ""
+        self.notifying_object = ""
+        self.ntf_class_id = saNtf.SaNtfClassIdT(0, 0, 0)
+        self.event_time = saAis.SA_TIME_UNKNOWN
+        self.notification_id = None
+        self.additional_text = ""
+        self.additional_info = []
+        # Object create/delete notification info
+        self.object_attributes = []
+        self.source_indicator = \
+            saNtf.eSaNtfSourceIndicatorT.SA_NTF_UNKNOWN_OPERATION
+        # Attribute change notification info
+        self.changed_attributes = []
+        # State change notification info
+        self.state_changes = []
+        # Alarm info
+        self.probable_cause = \
+            saNtf.eSaNtfProbableCauseT.SA_NTF_UNSPECIFIED_REASON
+        self.specific_problems = []
+        self.perceived_severity = saNtf.eSaNtfSeverityT.SA_NTF_SEVERITY_MINOR
+        self.trend = None
+        self.threshold_information = None
+        self.monitored_attrs = []
+        self.proposed_repair_actions = []
+        # Security alarm info
+        self.severity = saNtf.eSaNtfSeverityT.SA_NTF_SEVERITY_MINOR
+        self.security_alarm_detector = None
+        self.service_user = None
+        self.service_provider = None
+
+
+class NotificationFilterInfo(object):
+    """ This class encapsulates the notification filter data structure for use
+    by each specific notification type """
+    def __init__(self):
+        # Header info
+        self.obj_create_del_evt_list = []
+        self.attr_change_evt_list = []
+        self.state_change_evt_list = []
+        self.alarm_evt_list = []
+        self.sec_alarm_evt_list = []
+        self.notification_object_list = []
+        self.notifying_objects_list = []
+        self.ntf_class_id_list = []
+        # Filter info
+        self.source_indicator_list = []
+        self.changed_state_list = []
+        self.probable_cause_list = []
+        self.perceived_severity_list = []
+        self.trend_list = []
+        self.severity_list = []
+
+
+class AdditionalInfo(object):
+    """ This class contains a piece of additional information to be included
+    in a notification """
+    def __init__(self, info_id=None, info_type=None, info_value=None):
+        """ Constructor for AdditionalInfo class
+
+        Args:
+            info_id (SaNtfElementIdT): infoId field of SaNtfAdditionalInfoT
+            info_type (SaNtfValueTypeT): infoType field of SaNtfAdditionalInfoT
+            info_value (Any type of eSaNtfValueTypeT): infoValue field of
+                SaNtfAdditionalInfoT
+        """
+        self.info_id = info_id
+        self.info_type = info_type
+        self.info_value = info_value
+
+
+class StateChange(object):
+    """ This class contains information about a state change event """
+    def __init__(self, state_id=None, new_state=None, old_state_present=False,
+                 old_state=None):
+        """ Constructor for StateChange class
+
+        Args:
+            state_id (SaNtfElementIdT): stateId field of SaNtfStateChangeT
+            new_state (int): newState field of SaNtfStateChangeT
+            old_state_present (bool): oldStatePresent field of
+                SaNtfStateChangeT
+            old_state (int): oldState field of SaNtfStateChangeT
+        """
+        self.state_id = state_id
+        self.new_state = new_state
+        self.old_state_present = old_state_present
+        self.old_state = old_state
+
+
+class AttributeChange(object):
+    """ This class contains information about an object's attribute change """
+    def __init__(self, attribute_id=None, attribute_type=None,
+                 new_attribute_value=None, old_attribute_present=False,
+                 old_attribute_value=None):
+        """ Constructor for AttributeChange class
+
+        Args:
+            attribute_id (SaNtfElementIdT): attributeId field of
+                SaNtfAttributeChangeT
+            attribute_type (SaNtfValueTypeT): attributeType field of
+                SaNtfAttributeChangeT
+            new_attribute_value (Any type of eSaNtfValueTypeT):
+                newAttributeValue field of SaNtfAttributeChangeT
+            old_attribute_present (bool): oldAttributePresent field of
+                SaNtfAttributeChangeT
+            old_attribute_value (Any type of eSaNtfValueTypeT):
+                oldAttributeValue field of SaNtfAttributeChangeT
+        """
+        self.attribute_id = attribute_id
+        self.attribute_type = attribute_type
+        self.new_attribute_value = new_attribute_value
+        self.old_attribute_present = old_attribute_present
+        self.old_attribute_value = old_attribute_value
+
+
+class Attribute(object):
+    """ This class contains information about an object's attribute """
+    def __init__(self, attribute_id=None, attribute_type=None,
+                 attribute_value=None):
+        """ Constructor for Attribute class
+
+        Args:
+            attribute_id (SaNtfElementIdT): attributeId field of
+                SaNtfAttributeT
+            attribute_type (SaNtfValueTypeT): attributeType field of
+                SaNtfAttributeT
+            attribute_value (Any type of eSaNtfValueTypeT): attributeValue
+                field of SaNtfAttributeT
+        """
+        self.attribute_id = attribute_id
+        self.attribute_type = attribute_type
+        self.attribute_value = attribute_value
+
+
+class SecurityAlarmDetector(object):
+    """ This class contains information about a security alarm detector """
+    def __init__(self, value=None, value_type=None):
+        """ Constructor for SecurityAlarmDetector class
+
+        Args:
+            value (Any type of eSaNtfValueTypeT): valueType field of
+                SaNtfSecurityAlarmDetectorT
+            value_type (SaNtfValueTypeT): value field of
+                SaNtfSecurityAlarmDetectorT
+        """
+        self.value = value
+        self.value_type = value_type
+
+
+class ServiceUser(object):
+    """ This class contains information about a service user """
+    def __init__(self, value=None, value_type=None):
+        """ Constructor for ServiceUser class
+
+        Args:
+            value (Any type of eSaNtfValueTypeT): valueType field of
+                SaNtfServiceUserT
+            value_type (SaNtfValueTypeT): value field of SaNtfServiceUserT
+        """
+        self.value = value
+        self.value_type = value_type
+
+
+class ServiceProvider(ServiceUser):
+    """ This class contains information about a service provider """
+    pass
+
+
+class ThresholdInformation(object):
+    """ This class contains information about a threshold """
+    def __init__(self, threshold_id=None, threshold_value_type=None,
+                 threshold_value=None, threshold_hysteresis=None,
+                 observed_value=None, arm_time=None):
+        """ Constructor for ThresholdInformation class
+
+        Args:
+            threshold_id (SaNtfElementIdT): thresholdId field of
+                SaNtfThresholdInformationT
+            threshold_value_type (SaNtfValueTypeT): thresholdValueType field of
+                SaNtfThresholdInformationT
+            threshold_value (Any type of eSaNtfValueTypeT): thresholdValue
+                 field of SaNtfThresholdInformationT
+            threshold_hysteresis (SaNtfValueTypeT): thresholdHysteresis
+                field of SaNtfThresholdInformationT
+            observed_value (Any type of eSaNtfValueTypeT): observedValue field
+                of SaNtfThresholdInformationT
+            arm_time (SaTimeT): armTime field of SaNtfThresholdInformationT
+        """
+        self.threshold_id = threshold_id
+        self.threshold_value_type = threshold_value_type
+        self.threshold_value = threshold_value
+        self.threshold_hysteresis = threshold_hysteresis
+        self.observed_value = observed_value
+        self.arm_time = arm_time
+
+
+class SpecificProblem(object):
+    """ This class contains information about a specific problem """
+    def __init__(self, problem_id=None, problem_class_id=None,
+                 problem_type=None, problem_value=None):
+        """ Constructor for SpecificProblem class
+
+        Args:
+            problem_id (SaNtfElementIdT): problemId field of
+                SaNtfSpecificProblemT
+            problem_class_id (SaNtfClassIdT): problemClassId field of
+                SaNtfSpecificProblemT
+            problem_type (SaNtfValueTypeT): problemType field of
+                SaNtfSpecificProblemT
+            problem_value (Any type of eSaNtfValueTypeT): problemValue field of
+                SaNtfSpecificProblemT
+        """
+        self.problem_id = problem_id
+        self.problem_class_id = problem_class_id
+        self.problem_type = problem_type
+        self.problem_value = problem_value
+
+
+class ProposedRepairAction(object):
+    """ This class contains information about a proposed repair action """
+    def __init__(self, action_id=None, action_value_type=None,
+                 action_value=None):
+        """ Constructor for ProposedRepairAction class
+
+        Args:
+            action_id (SaNtfElementIdT): actionId field of
+                SaNtfProposedRepairActionT
+            action_value_type (SaNtfValueTypeT): actionValueType field of
+                SaNtfProposedRepairActionT
+            action_value (Any type of eSaNtfValueTypeT): actionValue field of
+                SaNtfProposedRepairActionT
+        """
+        self.action_id = action_id
+        self.action_value_type = action_value_type
+        self.action_value = action_value
+
+
+class NtfAgent(object):
+    """ This class manages the life cycle of an NTF agent """
+
+    def __init__(self, version=None):
+        """ Constructor for NtfAgent class
+
+        Args:
+            version (SaVersionT): NTF API version
+        """
+        self.init_version = version if version is not None \
+            else SaVersionT('A', 1, 1)
+        self.version = None
+        self.handle = None
+        self.callbacks = None
+        self.sel_obj = SaSelectionObjectT()
+        self.ntf_notif_function = None
+        self.ntf_notif_discarded_function = None
+
+    def __enter__(self):
+        """ Enter method for NtfAgent class """
+        return self
+
+    def __exit__(self, exception_type, exception_value, traceback):
+        """ Exit method for NtfAgent class
+
+        Finalize the NTF agent handle
+        """
+        if self.handle is not None:
+            saNtfFinalize(self.handle)
+            self.handle = None
+
+    def __del__(self):
+        """ Destructor for NtfAgent class
+
+        Finalize the NTF agent handle
+        """
+        if self.handle is not None:
+            saNtf.saNtfFinalize(self.handle)
+            self.handle = None
+
+    def _ntf_notif_callback(self, c_subscription_id, c_notif):
+        """ This callback is invoked by NTF to deliver a notification to a
+        subscriber of that notification type.
+
+        Args:
+            c_subscription_id (SaNtfSubscriptionIdT): The subscription id
+                previously provided by the subscriber when subscribing for this
+                type of notification
+            c_notif (SaNtfNotificationsT): The notification delivered by this
+                callback
+        """
+        pass
+
+    def _ntf_notif_discarded_callback(self, c_subscription_id,
+                                      c_notification_type, c_number_discarded,
+                                      c_discarded_notification_identifiers):
+        """ This callback is invoked by NTF to notify a subscriber of a
+        particular notification type that one or more notifications of that
+        type have been discarded.
+
+        Args:
+            c_subscription_id (SaNtfSubscriptionIdT): The subscription id
+                previously provided by the subscriber when subscribing for
+                discarded notifications
+            c_notification_type (SaNtfNotificationTypeT): The notification type
+                of the discarded notifications
+            c_number_discarded (SaUint32T): The number of discarded
+                notifications
+            c_discarded_notification_identifiers (SaNtfIdentifierT): The list
+                of notification identifiers of the discarded notifications
+        """
+        pass
+
+    def initialize(self, ntf_notif_func=None, ntf_notif_discarded_func=None):
+        """ Initialize the NTF agent library
+
+        Args:
+            ntf_notif_func (callback): Callback function for subscribed
+                notifications
+            ntf_notif_discarded_func (callback): Callback function for
+                discarded notifications
+
+        Returns:
+            SaAisErrorT: Return code of the saNtfInitialize() API call
+        """
+        self.callbacks = None
+        # Set up callbacks if any
+        if ntf_notif_func is not None or ntf_notif_discarded_func is not None:
+            self.ntf_notif_function = ntf_notif_func
+            self.ntf_notif_discarded_function = ntf_notif_discarded_func
+
+            self.callbacks = saNtf.SaNtfCallbacksT()
+            self.callbacks.saNtfNotificationCallback = \
+                saNtf.SaNtfNotificationCallbackT(self._ntf_notif_callback)
+            self.callbacks.saNtfNotificationDiscardedCallback = \
+                saNtf.SaNtfNotificationDiscardedCallbackT(
+                    self._ntf_notif_discarded_callback)
+
+        self.handle = saNtf.SaNtfHandleT()
+        self.version = deepcopy(self.init_version)
+        rc = saNtfInitialize(self.handle, self.callbacks, self.version)
+        if rc != eSaAisErrorT.SA_AIS_OK:
+            log_err("saNtfInitialize FAILED - %s" % eSaAisErrorT.whatis(rc))
+
+        return rc
+
+    def get_handle(self):
+        """ Return the NTF agent handle successfully initialized
+
+        Returns:
+            SaNtfHandleT: NTF agent handle
+        """
+        return self.handle
+
+    def _fetch_sel_obj(self):
+        """ Obtain a selection object (OS file descriptor)
+
+        Returns:
+            SaAisErrorT: Return code of the saNtfSelectionObjectGet() API call
+        """
+        rc = saNtfSelectionObjectGet(self.handle, self.sel_obj)
+        if rc != eSaAisErrorT.SA_AIS_OK:
+            log_err("saNtfSelectionObjectGet FAILED - %s" %
+                    eSaAisErrorT.whatis(rc))
+
+        return rc
+
+    def get_selection_object(self):
+        """ Return the selection object associated with the NTF handle
+
+        Returns:
+            SaSelectionObjectT: Selection object associated with the NTF handle
+        """
+        return self.sel_obj
+
+    def dispatch(self, flags):
+        """ Invoke NTF callbacks for queued events
+
+        Args:
+            flags (eSaDispatchFlagsT): Flags specifying dispatch mode
+
+        Returns:
+            SaAisErrorT: Return code of the saNtfDispatch() API call
+        """
+        rc = saNtfDispatch(self.handle, flags)
+        if rc != eSaAisErrorT.SA_AIS_OK:
+            log_err("saNtfDispatch FAILED - %s" % eSaAisErrorT.whatis(rc))
+
+        return rc
+
+    def finalize(self):
+        """ Finalize the NTF agent handle
+
+        Returns:
+            SaAisErrorT: Return code of the saNtfFinalize() API call
+        """
+        rc = eSaAisErrorT.SA_AIS_OK
+        if self.handle:
+            rc = saNtfFinalize(self.handle)
+            if rc != eSaAisErrorT.SA_AIS_OK:
+                log_err("saNtfFinalize FAILED - %s" % eSaAisErrorT.whatis(rc))
+            elif rc == eSaAisErrorT.SA_AIS_OK \
+                    or rc == eSaAisErrorT.SA_AIS_ERR_BAD_HANDLE:
+                # If the Finalize() call returned BAD_HANDLE, the handle should
+                # already become stale and invalid, so we reset it anyway
+                self.handle = None
+
+        return rc
diff --git a/python/pyosaf/utils/ntf/producer.py 
b/python/pyosaf/utils/ntf/producer.py
new file mode 100644
index 0000000..cea5585
--- /dev/null
+++ b/python/pyosaf/utils/ntf/producer.py
@@ -0,0 +1,711 @@
+############################################################################
+#
+# (C) Copyright 2017 Ericsson AB. All rights reserved.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. This file and program are licensed
+# under the GNU Lesser General Public License Version 2.1, February 1999.
+# The complete license can be accessed from the following location:
+# http://opensource.org/licenses/lgpl-license.php
+# See the Copying file included with the OpenSAF distribution for full
+# licensing terms.
+#
+# Author(s): Ericsson
+#
+############################################################################
+# pylint: disable=too-many-public-methods
+""" NTF producer utilities """
+import ctypes
+
+from pyosaf import saNtf
+from pyosaf.saAis import eSaBoolT, eSaAisErrorT, SaVoidPtr
+from pyosaf.utils import log_warn, log_err, bad_handle_retry
+from pyosaf.utils.ntf import agent as ntf
+
+
+class NtfProducer(ntf.NtfAgent):
+    """ This class provides functions of the NTF Producer interface """
+
+    def __init__(self, version=None):
+        """ Constructor for NtfProducer class """
+        super(NtfProducer, self).__init__(version)
+        self.ntf_info = ntf.NotificationInfo()
+
+    def init(self):
+        """ Initialize the NTF agent
+
+        Returns:
+            SaAisErrorT: Return code of the corresponding NTF API call(s)
+        """
+        # This finalize() is needed for re-init case
+        self.finalize()
+        return self.initialize()
+
+    @staticmethod
+    def _assign_ntf_value(ntf_handle, attr_value, value, value_type):
+        """ Assign the correct sub-field in the given attribute
+
+        Args:
+            ntf_handle (SaNtfNotificationHandleT): Notification handle
+            attr_value (SaNtfValueT): Object attribute value in an object
+                creation or deletion notification
+            value (Any type of eSaNtfValueTypeT): Actual value of the object
+                attribute
+            value_type (SaNtfValueTypeT): Type of the object attribute value
+        """
+        if value_type == saNtf.eSaNtfValueTypeT.SA_NTF_VALUE_UINT8:
+            attr_value.uint8Val = int(value)
+
+        elif value_type == saNtf.eSaNtfValueTypeT.SA_NTF_VALUE_INT8:
+            attr_value.int8Val = int(value)
+
+        elif value_type == saNtf.eSaNtfValueTypeT.SA_NTF_VALUE_UINT16:
+            attr_value.uint16Val = int(value)
+
+        elif value_type == saNtf.eSaNtfValueTypeT.SA_NTF_VALUE_INT16:
+            attr_value.int16Val = int(value)
+
+        elif value_type == saNtf.eSaNtfValueTypeT.SA_NTF_VALUE_UINT32:
+            attr_value.uint32Val = int(value)
+
+        elif value_type == saNtf.eSaNtfValueTypeT.SA_NTF_VALUE_INT32:
+            attr_value.int32Val = int(value)
+
+        elif value_type == saNtf.eSaNtfValueTypeT.SA_NTF_VALUE_FLOAT:
+            attr_value.floatVal = int(value)
+
+        elif value_type == saNtf.eSaNtfValueTypeT.SA_NTF_VALUE_UINT64:
+            attr_value.uint64Val = int(value)
+
+        elif value_type == saNtf.eSaNtfValueTypeT.SA_NTF_VALUE_INT64:
+            attr_value.int64Val = int(value)
+
+        elif value_type == saNtf.eSaNtfValueTypeT.SA_NTF_VALUE_DOUBLE:
+            attr_value.doubleVal = int(value)
+
+        elif value_type == saNtf.eSaNtfValueTypeT.SA_NTF_VALUE_LDAP_NAME or \
+            value_type == saNtf.eSaNtfValueTypeT.SA_NTF_VALUE_STRING or \
+                value_type == saNtf.eSaNtfValueTypeT.SA_NTF_VALUE_IPADDRESS:
+            len_value = len(value)
+            dest_ptr = SaVoidPtr()
+            rc = ntf.saNtfPtrValAllocate(ntf_handle, len_value + 1, dest_ptr,
+                                         attr_value)
+            if rc == eSaAisErrorT.SA_AIS_OK:
+                ctypes.memmove(dest_ptr, value, len_value + 1)
+            else:
+                log_warn("saNtfPtrValAllocate FAILED, rc = %s" %
+                         eSaAisErrorT.whatis(rc))
+
+    def set_event_type(self, event_type):
+        """ Fill in the eventType field in the notification header
+
+        Args:
+            event_type (SaNtfEventTypeT): Event type
+        """
+        self.ntf_info.event_type = event_type
+
+    def set_notification_object(self, notification_object):
+        """ Fill in the notificationObject field in the notification header
+
+        Args:
+            notification_object (str): Notification object's dn
+        """
+        self.ntf_info.notification_object = notification_object
+
+    def set_notifying_object(self, notifying_object):
+        """ Fill in the notifyingObject field in the notification header
+
+        Args:
+            notifying_object (str): Notifying object's dn
+        """
+        self.ntf_info.notifying_object = notifying_object
+
+    def set_class_id(self, notif_class_id):
+        """ Fill in the notificationClassId field in the notification header
+
+        Args:
+            notif_class_id (SaNtfClassIdT): Notification class id
+        """
+        self.ntf_info.ntf_class_id = notif_class_id
+
+    def set_event_time(self, event_time):
+        """ Fill in the eventTime field in the notification header
+
+        Args:
+            event_time (SaTimeT): Event time
+        """
+        self.ntf_info.event_time = event_time
+
+    def set_additional_text(self, additional_text):
+        """ Fill in the additionalText field in the notification header
+
+        Args:
+            additional_text (str): Additional text
+        """
+        self.ntf_info.additional_text = additional_text
+
+    def set_additional_info(self, additional_info):
+        """ Fill in the additionalInfo field in the notification header
+
+        Args:
+            additional_info (list(AdditionalInfo)): List of AdditionalInfo
+                structures
+        """
+        self.ntf_info.additional_info = additional_info
+
+    def set_source_indicator(self, source_indicator):
+        """ Fill in the sourceIndicator field in either object-create/delete
+        notification, attribute-change notification, or state-change
+        notification
+
+        Args:
+            source_indicator (SaNtfSourceIndicatorT): Source indicator of the
+                notification
+        """
+        self.ntf_info.source_indicator = source_indicator
+
+    def set_object_attributes(self, attributes):
+        """ Fill in the objectAttributes field in object-create/delete
+        notification
+
+        Args:
+            attributes (list(Attribute)): List of Attribute structures
+        """
+        self.ntf_info.object_attributes = attributes
+
+    def set_changed_attributes(self, changed_attributes):
+        """ Fill in the changedAttributes field in attribute-change
+        notification
+
+        Args:
+            changed_attributes(list(AttributeChange)): List of AttributeChange
+                structures
+        """
+        self.ntf_info.changed_attributes = changed_attributes
+
+    def set_state_changes(self, state_changes):
+        """ Fill in the changedStates field in state-change notification
+
+        Args:
+            state_changes (list(StateChange)): List of StateChange structures
+        """
+        self.ntf_info.state_changes = state_changes
+
+    def set_probable_cause(self, probable_cause):
+        """ Fill in the probableCause field in either alarm notification or
+        security-alarm notification
+
+        Args:
+            probable_cause (SaNtfProbableCauseT): Probable cause of the alarm
+        """
+        self.ntf_info.probable_cause = probable_cause
+
+    def set_specific_problems(self, specific_problems):
+        """ Fill in the specificProblems field in alarm notification
+
+        Args:
+            specific_problems (list(SpecificProblem)): List of SpecificProblem
+                structures
+        """
+        self.ntf_info.specific_problems = specific_problems
+
+    def set_perceived_severity(self, perceived_severity):
+        """ Fill in the perceivedSeverity field in alarm notification
+
+        Args:
+            perceived_severity (SaNtfSeverityT): Severity of the alarm
+        """
+        self.ntf_info.perceived_severity = perceived_severity
+
+    def set_trend(self, trend):
+        """ Fill in the trend field in alarm notification
+
+        Args:
+            trend (SaNtfSeverityTrendT): Trend of alarm severity
+        """
+        self.ntf_info.trend = trend
+
+    def set_threshold_information(self, threshold_information):
+        """ Fill in the thresholdInformation field in alarm notification
+
+        Args:
+            threshold_information (list(ThresholdInformation)): List of
+                ThresholdInformation structures
+        """
+        self.ntf_info.threshold_information = threshold_information
+
+    def set_monitored_attributes(self, monitored_attributes):
+        """ Fill in the monitoredAttributes field in alarm notification
+
+        Args:
+            monitored_attributes (list(Attribute)): List of monitored
+                Attribute structures
+        """
+        self.ntf_info.monitored_attrs = monitored_attributes
+
+    def set_proposed_repair_actions(self, proposed_repair_action):
+        """ Fill in the proposedRepairActions in alarm notification
+
+        Args:
+            proposed_repair_action (list(ProposedRepairAction)): List of
+                ProposedRepairAction structures
+        """
+        self.ntf_info.proposed_repair_actions = proposed_repair_action
+
+    def set_severity(self, severity):
+        """ Fill in the severity field in security alarm notification
+
+        Args:
+            severity (SaNtfSeverityT): Severity of the security alarm
+        """
+        self.ntf_info.severity = severity
+
+    def set_security_alarm_detector(self, security_alarm_detector):
+        """ Fill in the securityAlarmDetector field in security alarm
+        notification
+
+        Args:
+            security_alarm_detector (SecurityAlarmDetector):
+                A SecurityAlarmDetector structure
+        """
+        self.ntf_info.security_alarm_detector = security_alarm_detector
+
+    def set_service_user(self, service_user):
+        """ Fill in the serviceUser field in security alarm notification
+
+        Args:
+            service_user (ServiceUser): A ServiceUser structure
+        """
+        self.ntf_info.service_user = service_user
+
+    def set_service_provider(self, service_provider):
+        """ Fill in the serviceProvider field in security alarm notification
+
+        Args:
+            service_provider (ServiceProvider): A ServiceProvider structure
+        """
+        self.ntf_info.service_provider = service_provider
+
+    def clear_info(self):
+        """ Reset the NotificationInfo field values """
+        self.ntf_info = ntf.NotificationInfo()
+
+    def _fill_in_header(self, ntf_handle, header):
+        """ Fill in the given notification header with the provided information
+
+        Args:
+            ntf_handle (SaNtfNotificationHandleT): Notification handle
+            header (SaNtfNotificationHeaderT): Notification header
+        """
+        header.eventType.contents.value = self.ntf_info.event_type
+
+        header.notificationObject.contents.value = \
+            self.ntf_info.notification_object
+        header.notificationObject.contents.length = \
+            len(self.ntf_info.notification_object)
+
+        header.notifyingObject.contents.value = self.ntf_info.notifying_object
+        header.notifyingObject.contents.length = \
+            len(self.ntf_info.notifying_object)
+
+        header.notificationClassId.contents.vendorId = \
+            self.ntf_info.ntf_class_id.vendorId
+        header.notificationClassId.contents.majorId = \
+            self.ntf_info.ntf_class_id.majorId
+        header.notificationClassId.contents.minorId = \
+            self.ntf_info.ntf_class_id.minorId
+
+        header.eventTime.contents.value = self.ntf_info.event_time
+
+        header.lengthAdditionalText = len(self.ntf_info.additional_text)
+        ctypes.memmove(header.additionalText,
+                       self.ntf_info.additional_text,
+                       len(self.ntf_info.additional_text) + 1)
+
+        if self.ntf_info.additional_info:
+            for i, add_info in enumerate(self.ntf_info.additional_info):
+                header.additionalInfo[i].infoId = add_info.info_id
+                header.additionalInfo[i].infoType = add_info.info_type
+
+                self._assign_ntf_value(ntf_handle,
+                                       header.additionalInfo[i].infoValue,
+                                       add_info.info_value, add_info.info_type)
+
+    @bad_handle_retry
+    def send_object_create_delete_notification(self):
+        """ Send an SaNtfObjectCreateDeleteNotificationT notification
+
+        Returns:
+            SaAisErrorT: Return code of the corresponding NTF API call(s)
+        """
+        # Create the notification
+        notification = saNtf.SaNtfObjectCreateDeleteNotificationT()
+        rc = ntf.saNtfObjectCreateDeleteNotificationAllocate(
+            self.handle, notification, 0,
+            len(self.ntf_info.additional_text) + 1,
+            len(self.ntf_info.additional_info),
+            len(self.ntf_info.object_attributes),
+            saNtf.saNtf.SA_NTF_ALLOC_SYSTEM_LIMIT)
+        if rc != eSaAisErrorT.SA_AIS_OK:
+            log_err("saNtfObjectCreateDeleteNotificationAllocate FAILED, "
+                    "rc = %s" % eSaAisErrorT.whatis(rc))
+        else:
+            # Fill in the header
+            self._fill_in_header(notification.notificationHandle,
+                                 notification.notificationHeader)
+
+            # Fill in the notification
+            notification.sourceIndicator.contents.value = \
+                self.ntf_info.source_indicator
+
+            for i, attribute in enumerate(self.ntf_info.object_attributes):
+                ptr = notification.objectAttributes[i]
+
+                ptr.attributeId = attribute.attribute_id
+                ptr.attributeType = attribute.attribute_type
+
+                self._assign_ntf_value(notification.notificationHandle,
+                                       ptr.attributeValue,
+                                       attribute.attribute_value,
+                                       attribute.attribute_type)
+
+            # Send the notification
+            rc = ntf.saNtfNotificationSend(notification.notificationHandle)
+            if rc != eSaAisErrorT.SA_AIS_OK:
+                log_err("saNtfNotificationSend FAILED, rc = %s" %
+                        eSaAisErrorT.whatis(rc))
+            else:
+                self.clear_info()
+
+            # Free the notification
+            ntf.saNtfNotificationFree(notification.notificationHandle)
+
+        if rc == eSaAisErrorT.SA_AIS_ERR_BAD_HANDLE:
+            init_rc = self.init()
+            # If the re-initialization of agent handle succeeds, we still need
+            # to return BAD_HANDLE to the function decorator, so that it would
+            # re-try the failed operation. Otherwise, the true error code is
+            # returned to the user to decide further actions.
+            if init_rc != eSaAisErrorT.SA_AIS_OK:
+                rc = init_rc
+
+        return rc
+
+    @bad_handle_retry
+    def send_attribute_change_notification(self):
+        """ Send an SaNtfAttributeChangeNotificationT notification
+
+        Returns:
+            SaAisErrorT: Return code of the corresponding NTF API call(s)
+        """
+        # Create the notification
+        notification = saNtf.SaNtfAttributeChangeNotificationT()
+
+        rc = ntf.saNtfAttributeChangeNotificationAllocate(
+            self.handle, notification, 0,
+            len(self.ntf_info.additional_text) + 1,
+            len(self.ntf_info.additional_info),
+            len(self.ntf_info.changed_attributes),
+            saNtf.saNtf.SA_NTF_ALLOC_SYSTEM_LIMIT)
+        if rc != eSaAisErrorT.SA_AIS_OK:
+            log_err("saNtfAttributeChangeNotificationAllocate FAILED, "
+                    "rc = %s" % eSaAisErrorT.whatis(rc))
+        else:
+            # Fill in the header
+            self._fill_in_header(notification.notificationHandle,
+                                 notification.notificationHeader)
+
+            # Fill in the notification
+            notification.sourceIndicator.contents.value = \
+                self.ntf_info.source_indicator
+
+            for i, changed_attr in enumerate(self.ntf_info.changed_attributes):
+                ptr = notification.changedAttributes[i]
+
+                ptr.attributeId = changed_attr.attribute_id
+                ptr.attributeType = changed_attr.attribute_type
+                self._assign_ntf_value(
+                    notification.notificationHandle, ptr.newAttributeValue,
+                    changed_attr.new_attribute_value,
+                    changed_attr.attribute_type)
+
+                if changed_attr.old_attribute_present:
+                    ptr.oldAttributePresent = eSaBoolT.SA_TRUE
+
+                    self._assign_ntf_value(notification.notificationHandle,
+                                           ptr.oldAttributeValue,
+                                           changed_attr.old_attribute_value,
+                                           changed_attr.attribute_type)
+                else:
+                    ptr.oldAttributePresent = eSaBoolT.SA_FALSE
+
+            # Send the notification
+            rc = ntf.saNtfNotificationSend(notification.notificationHandle)
+            if rc != eSaAisErrorT.SA_AIS_OK:
+                log_err("saNtfNotificationSend FAILED, rc = %s" %
+                        eSaAisErrorT.whatis(rc))
+
+            else:
+                self.clear_info()
+
+            # Free the notification
+            ntf.saNtfNotificationFree(notification.notificationHandle)
+
+        if rc == eSaAisErrorT.SA_AIS_ERR_BAD_HANDLE:
+            init_rc = self.init()
+            # If the re-initialization of agent handle succeeds, we still need
+            # to return BAD_HANDLE to the function decorator, so that it would
+            # re-try the failed operation. Otherwise, the true error code is
+            # returned to the user to decide further actions.
+            if init_rc != eSaAisErrorT.SA_AIS_OK:
+                rc = init_rc
+
+        return rc
+
+    @bad_handle_retry
+    def send_state_change_notification(self):
+        """ Send an SaNtfStateChangeNotificationT notification
+
+        Returns:
+            SaAisErrorT: Return code of the corresponding NTF API call(s)
+        """
+        # Create the notification
+        notification = saNtf.SaNtfStateChangeNotificationT()
+
+        rc = ntf.saNtfStateChangeNotificationAllocate(
+            self.handle, notification, 0,
+            len(self.ntf_info.additional_text) + 1,
+            len(self.ntf_info.additional_info),
+            len(self.ntf_info.state_changes),
+            saNtf.saNtf.SA_NTF_ALLOC_SYSTEM_LIMIT)
+        if rc != eSaAisErrorT.SA_AIS_OK:
+            log_err("saNtfStateChangeNotificationAllocate FAILED, "
+                    "rc = %s" % eSaAisErrorT.whatis(rc))
+        else:
+            # Fill in the header
+            self._fill_in_header(notification.notificationHandle,
+                                 notification.notificationHeader)
+
+            # Fill in the notification
+            notification.sourceIndicator.contents.value = \
+                self.ntf_info.source_indicator
+
+            for i, state_change in enumerate(self.ntf_info.state_changes):
+                ptr = notification.changedStates[i]
+
+                ptr.stateId = state_change.state_id
+                ptr.newState = state_change.new_state
+
+                if state_change.old_state_present:
+                    ptr.oldStatePresent = eSaBoolT.SA_TRUE
+                    ptr.oldState = state_change.old_state
+                else:
+                    ptr.oldStatePresent = eSaBoolT.SA_FALSE
+
+            # Send the notification
+            rc = ntf.saNtfNotificationSend(notification.notificationHandle)
+            if rc != eSaAisErrorT.SA_AIS_OK:
+                log_err("saNtfNotificationSend FAILED, rc = %s" %
+                        eSaAisErrorT.whatis(rc))
+            else:
+                self.clear_info()
+
+            # Free the notification
+            ntf.saNtfNotificationFree(notification.notificationHandle)
+
+        if rc == eSaAisErrorT.SA_AIS_ERR_BAD_HANDLE:
+            init_rc = self.init()
+            # If the re-initialization of agent handle succeeds, we still need
+            # to return BAD_HANDLE to the function decorator, so that it would
+            # re-try the failed operation. Otherwise, the true error code is
+            # returned to the user to decide further actions.
+            if init_rc != eSaAisErrorT.SA_AIS_OK:
+                rc = init_rc
+
+        return rc
+
+    @bad_handle_retry
+    def send_alarm_notification(self):
+        """ Send an SaNtfAlarmNotificationT notification
+
+        Returns:
+            SaAisErrorT: Return code of the corresponding NTF API call(s)
+        """
+        # Create the notification
+        notification = saNtf.SaNtfAlarmNotificationT()
+        rc = ntf.saNtfAlarmNotificationAllocate(
+            self.handle, notification, 0,
+            len(self.ntf_info.additional_text) + 1,
+            len(self.ntf_info.additional_info),
+            len(self.ntf_info.specific_problems),
+            len(self.ntf_info.monitored_attrs),
+            len(self.ntf_info.proposed_repair_actions),
+            saNtf.saNtf.SA_NTF_ALLOC_SYSTEM_LIMIT)
+        if rc != eSaAisErrorT.SA_AIS_OK:
+            log_err("saNtfAlarmNotificationAllocate FAILED, "
+                    "rc = %s" % eSaAisErrorT.whatis(rc))
+        else:
+            # Fill in the header
+            self._fill_in_header(notification.notificationHandle,
+                                 notification.notificationHeader)
+
+            # Fill in the notification
+            notification.probableCause.contents.value = \
+                self.ntf_info.probable_cause
+
+            for i, problem in enumerate(self.ntf_info.specific_problems):
+                ptr = notification.specificProblems[i]
+                ptr.problemId = problem.problem_id
+                ptr.problemClassId.vendorId = problem.problem_class_id.vendorId
+                ptr.problemClassId.majorId = problem.problem_class_id.majorId
+                ptr.problemClassId.minorId = problem.problem_class_id.minorId
+                ptr.problemType = problem.problem_type
+                self._assign_ntf_value(
+                    notification.notificationHandle, ptr.problemValue,
+                    problem.problem_value, problem.problem_type)
+
+            notification.perceivedSeverity.contents.value = \
+                self.ntf_info.perceived_severity
+
+            if self.ntf_info.trend:
+                notification.trend.contents.value = self.ntf_info.trend
+
+            if self.ntf_info.threshold_information:
+                ptr = notification.thresholdInformation.contents
+                ptr.thresholdId = \
+                    self.ntf_info.threshold_information.threshold_id
+                ptr.thresholdValueType = \
+                    self.ntf_info.threshold_information.threshold_value_type
+
+                self._assign_ntf_value(
+                    notification.notificationHandle, ptr.thresholdValue,
+                    self.ntf_info.threshold_information.threshold_value,
+                    ptr.thresholdValueType)
+                self._assign_ntf_value(
+                    notification.notificationHandle, ptr.thresholdHysteresis,
+                    self.ntf_info.threshold_information.threshold_hysteresis,
+                    ptr.thresholdValueType)
+                self._assign_ntf_value(
+                    notification.notificationHandle, ptr.observedValue,
+                    self.ntf_info.threshold_information.observed_value,
+                    ptr.thresholdValueType)
+                ptr.armTime = self.ntf_info.threshold_information.arm_time
+
+            notification.perceivedSeverity.contents.value = \
+                self.ntf_info.perceived_severity
+
+            for i, attribute in enumerate(self.ntf_info.monitored_attrs):
+                ptr = notification.monitoredAttributes[i]
+
+                ptr.attributeId = attribute.attribute_id
+                ptr.attributeType = attribute.attribute_type
+
+                self._assign_ntf_value(notification.notificationHandle,
+                                       ptr.attributeValue,
+                                       attribute.attribute_value,
+                                       attribute.attribute_type)
+
+            for i, action in enumerate(self.ntf_info.proposed_repair_actions):
+                ptr = notification.proposedRepairActions[i]
+                ptr.actionId = action.action_id
+                ptr.actionValueType = action.action_value_type
+                self._assign_ntf_value(notification.notificationHandle,
+                                       ptr.actionValue,
+                                       action.action_value,
+                                       action.action_value_type)
+
+            # Send the notification
+            rc = ntf.saNtfNotificationSend(notification.notificationHandle)
+            if rc != eSaAisErrorT.SA_AIS_OK:
+                log_err("saNtfNotificationSend FAILED, rc = %s" %
+                        eSaAisErrorT.whatis(rc))
+            else:
+                self.clear_info()
+
+            # Free the notification
+            ntf.saNtfNotificationFree(notification.notificationHandle)
+
+        if rc == eSaAisErrorT.SA_AIS_ERR_BAD_HANDLE:
+            init_rc = self.init()
+            # If the re-initialization of agent handle succeeds, we still need
+            # to return BAD_HANDLE to the function decorator, so that it would
+            # re-try the failed operation. Otherwise, the true error code is
+            # returned to the user to decide further actions.
+            if init_rc != eSaAisErrorT.SA_AIS_OK:
+                rc = init_rc
+
+        return rc
+
+    @bad_handle_retry
+    def send_security_alarm_notification(self):
+        """ Send an SaNtfSecurityAlarmNotificationT notification
+
+        Returns:
+            SaAisErrorT: Return code of the corresponding NTF API call(s)
+        """
+        notification = saNtf.SaNtfSecurityAlarmNotificationT()
+        rc = ntf.saNtfSecurityAlarmNotificationAllocate(
+            self.handle, notification, 0,
+            len(self.ntf_info.additional_text) + 1,
+            len(self.ntf_info.additional_info),
+            saNtf.saNtf.SA_NTF_ALLOC_SYSTEM_LIMIT)
+        if rc != eSaAisErrorT.SA_AIS_OK:
+            log_err("saNtfSecurityAlarmNotificationAllocate FAILED, "
+                    "rc = %s" % eSaAisErrorT.whatis(rc))
+        else:
+            # Fill in the header
+            self._fill_in_header(notification.notificationHandle,
+                                 notification.notificationHeader)
+
+            # Fill in security alarm-specific fields
+            notification.probableCause.contents.value = \
+                self.ntf_info.probable_cause
+            notification.severity.contents.value = self.ntf_info.severity
+
+            if self.ntf_info.security_alarm_detector:
+                notification.securityAlarmDetector.contents.valueType = \
+                    self.ntf_info.security_alarm_detector.value_type
+                self._assign_ntf_value(
+                    notification.notificationHandle,
+                    notification.securityAlarmDetector.contents.value,
+                    self.ntf_info.security_alarm_detector.value,
+                    self.ntf_info.security_alarm_detector.value_type)
+
+            if self.ntf_info.service_user:
+                notification.serviceUser.contents.valueType = \
+                    self.ntf_info.service_user.value_type
+                self._assign_ntf_value(notification.notificationHandle,
+                                       notification.serviceUser.contents.value,
+                                       self.ntf_info.service_user.value,
+                                       self.ntf_info.service_user.value_type)
+            if self.ntf_info.service_provider:
+                notification.serviceProvider.contents.valueType = \
+                    self.ntf_info.service_provider.value_type
+                self._assign_ntf_value(
+                    notification.notificationHandle,
+                    notification.serviceProvider.contents.value,
+                    self.ntf_info.service_provider.value,
+                    self.ntf_info.service_provider.value_type)
+
+            # Send the notification
+            rc = ntf.saNtfNotificationSend(notification.notificationHandle)
+            if rc != eSaAisErrorT.SA_AIS_OK:
+                log_err("saNtfNotificationSend FAILED, rc = %s" %
+                        eSaAisErrorT.whatis(rc))
+            else:
+                self.clear_info()
+
+            # Free the notification
+            ntf.saNtfNotificationFree(notification.notificationHandle)
+
+        if rc == eSaAisErrorT.SA_AIS_ERR_BAD_HANDLE:
+            init_rc = self.init()
+            # If the re-initialization of agent handle succeeds, we still need
+            # to return BAD_HANDLE to the function decorator, so that it would
+            # re-try the failed operation. Otherwise, the true error code is
+            # returned to the user to decide further actions.
+            if init_rc != eSaAisErrorT.SA_AIS_OK:
+                rc = init_rc
+
+        return rc
diff --git a/python/pyosaf/utils/ntf/reader.py 
b/python/pyosaf/utils/ntf/reader.py
new file mode 100644
index 0000000..560ecd8
--- /dev/null
+++ b/python/pyosaf/utils/ntf/reader.py
@@ -0,0 +1,187 @@
+############################################################################
+#
+# (C) Copyright 2017 Ericsson AB. All rights reserved.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. This file and program are licensed
+# under the GNU Lesser General Public License Version 2.1, February 1999.
+# The complete license can be accessed from the following location:
+# http://opensource.org/licenses/lgpl-license.php
+# See the Copying file included with the OpenSAF distribution for full
+# licensing terms.
+#
+# Author(s): Ericsson
+#
+############################################################################
+""" NTF reader utilities """
+from collections import Iterator
+from pyosaf import saNtf
+from pyosaf.saAis import eSaAisErrorT
+from pyosaf.utils import log_err, bad_handle_retry
+from pyosaf.utils.ntf import agent as ntf
+from pyosaf.utils.ntf.subscriber import NtfConsumer
+
+
+class NtfReader(NtfConsumer, Iterator):
+    """ This class provides functions of the NTF Reader interface """
+
+    def __init__(self, version=None):
+        """ Constructor for NtfReader class """
+        super(NtfReader, self).__init__(version)
+        self.read_handle = None
+        self.filter_handles = \
+            saNtf.SaNtfNotificationTypeFilterHandlesT(0, 0, 0, 0, 0)
+        self.search_direction = \
+            saNtf.eSaNtfSearchDirectionT.SA_NTF_SEARCH_OLDER
+        self.search_criteria = saNtf.SaNtfSearchCriteriaT(
+            saNtf.eSaNtfSearchModeT.SA_NTF_SEARCH_ONLY_FILTER, 0, 0)
+
+    def __enter__(self):
+        """ Enter method for NtfReader class """
+        return self
+
+    def __exit__(self, exception_type, exception_value, traceback):
+        """ Exit method for NtfReader class
+
+        Finalize the NTF agent handle and clean up any allocated resources
+        """
+        if self.read_handle is not None:
+            ntf.saNtfNotificationReadFinalize(self.read_handle)
+            self.read_handle = None
+        if self.handle is not None:
+            ntf.saNtfFinalize(self.handle)
+            self.handle = None
+
+    def __del__(self):
+        """ Destructor for NtfReader class
+
+        Finalize the NTF agent handle and clean up any allocated resources
+        """
+        if self.read_handle is not None:
+            saNtf.saNtfNotificationReadFinalize(self.read_handle)
+            self.read_handle = None
+        if self.handle is not None:
+            saNtf.saNtfFinalize(self.handle)
+            self.handle = None
+
+    def __iter__(self):
+        """ Iterator for NtfReader class """
+        return self
+
+    def next(self):
+        """ Override next() method of Iterator class """
+        return self.__next__()
+
+    def __next__(self):
+        """ Return the next element of the class iterator """
+        notification = saNtf.SaNtfNotificationsT()
+        rc = ntf.saNtfNotificationReadNext(self.read_handle,
+                                           self.search_direction,
+                                           notification)
+        if rc != eSaAisErrorT.SA_AIS_OK:
+            if rc != eSaAisErrorT.SA_AIS_ERR_NOT_EXIST:
+                log_err("saNtfNotificationReadNext FAILED - %s" %
+                        eSaAisErrorT.whatis(rc))
+            raise StopIteration
+
+        return self._parse_notification(notification)
+
+    def init(self):
+        """ Initialize the NTF agent
+
+        Returns:
+            SaAisErrorT: Return code of the corresponding NTF API call(s)
+        """
+        self.finalize()
+        return self.initialize()
+
+    def _parse_notification(self, notification):
+        """ Parse the read-out notification to retrieve its information
+
+        Args:
+            notification (SaNtfNotificationsT): Notification to parse
+
+        Returns:
+            SaNtfNotificationTypeT: Notification type
+            NotificationInfo: A NotificationInfo structure containing
+                information of the parsed notification
+        """
+        _ntf_type = notification.notificationType
+        if _ntf_type == saNtf.eSaNtfNotificationTypeT.SA_NTF_TYPE_ALARM:
+            notification = notification.notification.alarmNotification
+            _ntf_info = self._parse_notification_header(
+                notification.notificationHandle,
+                notification.notificationHeader)
+            self._parse_alarm_ntf(notification, _ntf_info)
+        else:
+            notification = notification.notification.securityAlarmNotification
+            _ntf_info = self._parse_notification_header(
+                notification.notificationHandle,
+                notification.notificationHeader)
+            self._parse_security_alarm_ntf(notification, _ntf_info)
+
+        return _ntf_type, _ntf_info
+
+    def set_search_criteria(self, search_criteria):
+        """ Set the notification search criteria
+
+        Args:
+            search_criteria (SaNtfSearchCriteriaT): Search criteria
+        """
+        self.search_criteria = search_criteria
+
+    @bad_handle_retry
+    def read(self, notification_types=None):
+        """ Start reading NTF notifications with the types specified in the
+        notification_types list. If the list is not provided, all types of
+        notification are read by default.
+
+        Current NTF implementation of this function only supports read filters
+        for alarm and security alarm notification types.
+
+        NOTE: Users have to set the wanted filters criteria via the set of
+        set_filter_*() methods before calling this method. Otherwise, the
+        filters criteria will be set with default values.
+
+        Args:
+            notification_types (list(SaNtfNotificationTypeT)): List of
+                notification types
+
+        Returns:
+            SaAisErrorT: Return code of the corresponding NTF API call(s)
+        """
+        alarm_type = saNtf.eSaNtfNotificationTypeT.SA_NTF_TYPE_ALARM
+        security_alarm_type = \
+            saNtf.eSaNtfNotificationTypeT.SA_NTF_TYPE_SECURITY_ALARM
+
+        # Filters for notification types other than SA_NTF_TYPE_ALARM and
+        # SA_NTF_TYPE_SECURITY_ALARM are not supported
+        if notification_types is not None:
+            if alarm_type not in notification_types \
+                    and security_alarm_type not in notification_types:
+                return eSaAisErrorT.SA_AIS_ERR_NOT_SUPPORTED
+
+        # Generate the alarm notification filter
+        if notification_types is None or alarm_type in notification_types:
+            self._generate_alarm_filter()
+
+        # Generate the security alarm notification filter
+        if notification_types is None \
+                or security_alarm_type in notification_types:
+            self._generate_security_alarm_filter()
+
+        self.read_handle = saNtf.SaNtfReadHandleT()
+        rc = ntf.saNtfNotificationReadInitialize(self.search_criteria,
+                                                 self.filter_handles,
+                                                 self.read_handle)
+        if rc == eSaAisErrorT.SA_AIS_ERR_BAD_HANDLE:
+            init_rc = self.init()
+            # If the re-initialization of agent handle succeeds, we still need
+            # to return BAD_HANDLE to the function decorator, so that it would
+            # re-try the failed operation. Otherwise, the true error code is
+            # returned to the user to decide further actions.
+            if init_rc != eSaAisErrorT.SA_AIS_OK:
+                rc = init_rc
+
+        return rc
diff --git a/python/pyosaf/utils/ntf/subscriber.py 
b/python/pyosaf/utils/ntf/subscriber.py
new file mode 100644
index 0000000..5eea9da
--- /dev/null
+++ b/python/pyosaf/utils/ntf/subscriber.py
@@ -0,0 +1,923 @@
+############################################################################
+#
+# (C) Copyright 2015 The OpenSAF Foundation
+# (C) Copyright 2017 Ericsson AB. All rights reserved.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. This file and program are licensed
+# under the GNU Lesser General Public License Version 2.1, February 1999.
+# The complete license can be accessed from the following location:
+# http://opensource.org/licenses/lgpl-license.php
+# See the Copying file included with the OpenSAF distribution for full
+# licensing terms.
+#
+# Author(s): Ericsson
+#
+############################################################################
+# pylint: disable=unused-argument
+""" NTF subscriber utilities """
+from __future__ import print_function
+import ctypes
+from copy import deepcopy
+
+from pyosaf import saNtf
+from pyosaf.saAis import BYREF, eSaAisErrorT, SaVoidPtr, SaNameT, SaUint16T, \
+    SaStringT
+from pyosaf.utils import bad_handle_retry, log_err, log_warn
+from pyosaf.utils.ntf import agent as ntf
+
+STRING_TYPES = [saNtf.eSaNtfValueTypeT.SA_NTF_VALUE_STRING,
+                saNtf.eSaNtfValueTypeT.SA_NTF_VALUE_LDAP_NAME,
+                saNtf.eSaNtfValueTypeT.SA_NTF_VALUE_IPADDRESS]
+INT_TYPES = [saNtf.eSaNtfValueTypeT.SA_NTF_VALUE_UINT8,
+             saNtf.eSaNtfValueTypeT.SA_NTF_VALUE_INT8,
+             saNtf.eSaNtfValueTypeT.SA_NTF_VALUE_UINT16,
+             saNtf.eSaNtfValueTypeT.SA_NTF_VALUE_INT16,
+             saNtf.eSaNtfValueTypeT.SA_NTF_VALUE_UINT32,
+             saNtf.eSaNtfValueTypeT.SA_NTF_VALUE_INT32,
+             saNtf.eSaNtfValueTypeT.SA_NTF_VALUE_FLOAT,
+             saNtf.eSaNtfValueTypeT.SA_NTF_VALUE_UINT64,
+             saNtf.eSaNtfValueTypeT.SA_NTF_VALUE_INT64,
+             saNtf.eSaNtfValueTypeT.SA_NTF_VALUE_DOUBLE]
+
+
+class NtfConsumer(ntf.NtfAgent):
+    """ This class provides functions of the NTF Consumer interface """
+    def __init__(self, version=None):
+        """ Constructor for NtfConsumer class """
+        super(NtfConsumer, self).__init__(version)
+        self.filter_info = ntf.NotificationFilterInfo()
+        self.filter_handles = None
+
+    def __enter__(self):
+        """ Enter method for NtfConsumer class """
+        return self
+
+    def __exit__(self, exception_type, exception_value, traceback):
+        """ Exit method for NtfConsumer class
+
+        Finalize the NTF agent handle
+        """
+        if self.handle is not None:
+            ntf.saNtfFinalize(self.handle)
+            self.handle = None
+
+    def __del__(self):
+        """ Destructor for NtfConsumer class
+
+        Finalize the NTF agent handle
+        """
+        if self.handle is not None:
+            saNtf.saNtfFinalize(self.handle)
+            self.handle = None
+
+    def set_filter_event_types(self, event_type_list):
+        """ Set data for the eventTypes field in the notification filter header
+        of all notification types
+
+        Args:
+            event_type_list (list(SaNtfEventTypeT)): List of notification event
+                types
+        """
+        for event_type in event_type_list:
+            if event_type <= saNtf.eSaNtfEventTypeT.SA_NTF_OBJECT_DELETION:
+                self.filter_info.obj_create_del_evt_list.append(event_type)
+            elif event_type <= saNtf.eSaNtfEventTypeT.SA_NTF_ATTRIBUTE_RESET:
+                self.filter_info.attr_change_evt_list.append(event_type)
+            elif event_type <= \
+                    saNtf.eSaNtfEventTypeT.SA_NTF_OBJECT_STATE_CHANGE:
+                self.filter_info.state_change_evt_list.append(event_type)
+            elif event_type <= saNtf.eSaNtfEventTypeT.SA_NTF_ALARM_ENVIRONMENT:
+                self.filter_info.alarm_evt_list.append(event_type)
+            elif event_type <= saNtf.eSaNtfEventTypeT.SA_NTF_TIME_VIOLATION:
+                self.filter_info.sec_alarm_evt_list.append(event_type)
+
+    def set_filter_notification_objects(self, notification_object_list):
+        """ Set data for the notificationObjects field in the notification
+        filter header of all notification types
+
+        Args:
+            notification_object_list (list(str)): List of notification
+                objects' dns
+        """
+        self.filter_info.notification_object_list = notification_object_list
+
+    def set_filter_notifying_objects(self, notifying_objects_list):
+        """ Set data for the notifyingObjects field in the notification filter
+        header of all notification types
+
+        Args:
+            notifying_objects_list (list(str)): List of notifying objects' dns
+        """
+        self.filter_info.notifying_objects_list = notifying_objects_list
+
+    def set_filter_ntf_class_ids(self, ntf_class_id_list):
+        """ Set data for the notificationClassIds field in the notification
+        filter header of all notification types
+
+        Args:
+            ntf_class_id_list (list(SaNtfClassIdT)): List of notification
+                class ids
+        """
+        self.filter_info.ntf_class_id_list = ntf_class_id_list
+
+    def set_filter_source_indicators(self, source_indicators):
+        """ Set data for the sourceIndicators field in the notification filter
+        header of either one of object-create/delete, attribute-change,
+        state-change notification types
+
+        Args:
+            source_indicators (list(SaNtfSourceIndicatorT)): List of
+                notification source indicators
+        """
+        self.filter_info.source_indicator_list = source_indicators
+
+    def set_filter_changed_states(self, changed_states):
+        """ Set data for the changedStates field in the notification filter
+        header of state-change notification type
+
+        Args:
+            changed_states (list(StateChange)): List of StateChange structures
+        """
+        self.filter_info.changed_state_list = changed_states
+
+    def set_filter_probable_causes(self, probable_causes):
+        """ Set data for the probableCauses field in the notification filter
+        header of either of alarm or security alarm notification types
+
+        Args:
+            probable_causes (list(SaNtfProbableCauseT)): List of alarm probable
+                causes
+        """
+        self.filter_info.probable_cause_list = probable_causes
+
+    def set_filter_perceived_severities(self, perceived_severities):
+        """ Set data for the perceivedSeverities field in the notification
+        filter header of alarm notification type
+
+        Args:
+            perceived_severities (list(SaNtfSeverityT)): List of alarm
+                severities
+        """
+        self.filter_info.probable_cause_list = perceived_severities
+
+    def set_filter_trends(self, trends):
+        """ Set data for the trends field in the notification filter header of
+        alarm notification type
+
+        Args:
+            trends (list(SaNtfSeverityTrendT)): List of alarm severity trends
+        """
+        self.filter_info.trend_list = trends
+
+    def set_filter_severities(self, severities):
+        """ Set data for the severities field in the notification filter header
+        of security alarm notification type
+
+        Args:
+            severities (list(SaNtfSeverityT)): List of security alarm
+                severities
+        """
+        self.filter_info.severity_list = severities
+
+    def clear_filter_info(self):
+        """ Reset the NotificationInfo filter field values """
+        self.filter_info = ntf.NotificationFilterInfo()
+
+    def _fill_in_filter_header(self, filter_header):
+        """ Fill in the given notification filter header with user-provided
+        data """
+        for i, info in enumerate(self.filter_info.notification_object_list):
+            filter_header.notificationObjects[i] = SaNameT(info)
+        for i, info in enumerate(self.filter_info.notifying_objects_list):
+            filter_header.notifyingObjects[i] = SaNameT(info)
+        for i, info in enumerate(self.filter_info.ntf_class_id_list):
+            filter_header.notificationClassIds[i] = info
+
+    def _generate_object_create_delete_filter(self):
+        """ Allocate memory for the object-create/delete notification filter
+        and fill in the corresponding user-provided data
+
+        Returns:
+            SaAisErrorT: Return code of the
+                saNtfObjectCreateDeleteNotificationFilterAllocate() API call
+        """
+        notification_filter = \
+            saNtf.SaNtfObjectCreateDeleteNotificationFilterT()
+
+        rc = ntf.saNtfObjectCreateDeleteNotificationFilterAllocate(
+            self.handle, notification_filter,
+            len(self.filter_info.obj_create_del_evt_list),
+            len(self.filter_info.notification_object_list),
+            len(self.filter_info.notifying_objects_list),
+            len(self.filter_info.ntf_class_id_list),
+            len(self.filter_info.source_indicator_list))
+        if rc != eSaAisErrorT.SA_AIS_OK:
+            log_err("saNtfObjectCreateDeleteNotificationFilterAllocate"
+                    " FAILED, rc = %s" % eSaAisErrorT.whatis(rc))
+        else:
+            self.filter_handles.objectCreateDeleteFilterHandle = \
+                notification_filter.notificationFilterHandle
+
+            self._fill_in_filter_header(
+                notification_filter.notificationFilterHeader)
+
+            # Fill in the eventTypes array with user-provided data
+            for i, evt in enumerate(self.filter_info.obj_create_del_evt_list):
+                notification_filter.notificationFilterHeader.eventTypes[i] = \
+                    evt
+
+            # Fill in the sourceIndicators array with user-provided data
+            for i, source_indicator in \
+                    enumerate(self.filter_info.source_indicator_list):
+                notification_filter.sourceIndicators[i] = source_indicator
+
+        return rc
+
+    def _generate_attribute_change_filter(self):
+        """ Allocate memory for the attribute change notification filter and
+        fill in the corresponding user-provided data
+
+        Returns:
+            SaAisErrorT: Return code of the
+                saNtfAttributeChangeNotificationFilterAllocate() API call
+        """
+        notification_filter = saNtf.SaNtfAttributeChangeNotificationFilterT()
+
+        rc = ntf.saNtfAttributeChangeNotificationFilterAllocate(
+            self.handle, notification_filter,
+            len(self.filter_info.attr_change_evt_list),
+            len(self.filter_info.notification_object_list),
+            len(self.filter_info.notifying_objects_list),
+            len(self.filter_info.ntf_class_id_list),
+            len(self.filter_info.source_indicator_list))
+        if rc != eSaAisErrorT.SA_AIS_OK:
+            log_err("saNtfAttributeChangeNotificationFilterAllocate FAILED, "
+                    "rc = %s" % eSaAisErrorT.whatis(rc))
+        else:
+            self.filter_handles.attributeChangeFilterHandle = \
+                notification_filter.notificationFilterHandle
+
+            self._fill_in_filter_header(
+                notification_filter.notificationFilterHeader)
+
+            # Fill in the eventTypes array with user-provided data
+            for i, evt in enumerate(self.filter_info.attr_change_evt_list):
+                notification_filter.notificationFilterHeader.eventTypes[i] = \
+                    evt
+
+            # Fill in the sourceIndicators array with user-provided data
+            for i, source_indicator in \
+                    enumerate(self.filter_info.source_indicator_list):
+                notification_filter.sourceIndicators[i] = source_indicator
+
+        return rc
+
+    def _generate_state_change_filter(self):
+        """ Allocate memory for the state change notification filter and fill
+        in the corresponding user-provided data
+
+        Returns:
+            SaAisErrorT: Return code of the
+                saNtfStateChangeNotificationFilterAllocate() API call
+        """
+        notification_filter = saNtf.SaNtfStateChangeNotificationFilterT()
+
+        rc = ntf.saNtfStateChangeNotificationFilterAllocate(
+            self.handle, notification_filter,
+            len(self.filter_info.state_change_evt_list),
+            len(self.filter_info.notification_object_list),
+            len(self.filter_info.notifying_objects_list),
+            len(self.filter_info.ntf_class_id_list),
+            len(self.filter_info.source_indicator_list),
+            len(self.filter_info.changed_state_list))
+        if rc != eSaAisErrorT.SA_AIS_OK:
+            log_err("saNtfStateChangeNotificationFilterAllocate FAILED, "
+                    "rc = %s" % eSaAisErrorT.whatis(rc))
+        else:
+            self.filter_handles.stateChangeFilterHandle = \
+                notification_filter.notificationFilterHandle
+
+            self._fill_in_filter_header(
+                notification_filter.notificationFilterHeader)
+
+            # Fill in the eventTypes array with user-provided data
+            for i, evt in enumerate(self.filter_info.state_change_evt_list):
+                notification_filter.notificationFilterHeader.eventTypes[i] = \
+                    evt
+
+            # Fill in the sourceIndicators array with user-provided data
+            for i, source_indicator in \
+                    enumerate(self.filter_info.source_indicator_list):
+                notification_filter.sourceIndicators[i] = source_indicator
+
+            # Fill in the changedStates array with user-provided data
+            for i, state_change in \
+                    enumerate(self.filter_info.changed_state_list):
+                notification_filter.changedStates[i].stateId = \
+                    state_change.state_id
+                notification_filter.changedStates[i].newState = \
+                    state_change.new_state
+                notification_filter.changedStates[i].oldStatePresent = \
+                    state_change.old_state_present
+                if notification_filter.changedStates[i].oldStatePresent:
+                    notification_filter.changedStates[i].oldState = \
+                        state_change.old_state
+
+        return rc
+
+    def _generate_alarm_filter(self):
+        """ Allocate memory for the alarm notification filter and fill in the
+        corresponding user-provided data
+
+        Returns:
+            SaAisErrorT: Return code of the
+                saNtfAlarmNotificationFilterAllocate() API call
+        """
+        notification_filter = saNtf.SaNtfAlarmNotificationFilterT()
+
+        rc = ntf.saNtfAlarmNotificationFilterAllocate(
+            self.handle, notification_filter,
+            len(self.filter_info.alarm_evt_list),
+            len(self.filter_info.notification_object_list),
+            len(self.filter_info.notifying_objects_list),
+            len(self.filter_info.ntf_class_id_list),
+            len(self.filter_info.probable_cause_list),
+            len(self.filter_info.perceived_severity_list),
+            len(self.filter_info.trend_list))
+        if rc != eSaAisErrorT.SA_AIS_OK:
+            log_err("saNtfAlarmNotificationFilterAllocate FAILED, "
+                    "rc = %s" % eSaAisErrorT.whatis(rc))
+        else:
+            self.filter_handles.alarmFilterHandle = \
+                notification_filter.notificationFilterHandle
+
+            self._fill_in_filter_header(
+                notification_filter.notificationFilterHeader)
+
+            # Fill in the eventTypes array with user-provided data
+            for i, evt in enumerate(self.filter_info.alarm_evt_list):
+                notification_filter.notificationFilterHeader.eventTypes[i] = \
+                    evt
+
+            # Fill in the probableCauses array with user-provided data
+            for i, probable_cause in \
+                    enumerate(self.filter_info.probable_cause_list):
+                notification_filter.probableCauses[i] = probable_cause
+
+            # Fill in the perceivedSeverities array with user-provided data
+            for i, perceived_severity in \
+                    enumerate(self.filter_info.perceived_severity_list):
+                notification_filter.perceivedSeverities[i] = perceived_severity
+
+            # Fill in the trends array with user-provided data
+            for i, trend in enumerate(self.filter_info.trend_list):
+                notification_filter.trends[i] = trend
+
+        return rc
+
+    def _generate_security_alarm_filter(self):
+        """ Allocate memory for the security alarm notification filter and fill
+        in the corresponding user-provided data
+
+        Returns:
+            SaAisErrorT: Return code of the
+                saNtfSecurityAlarmNotificationFilterAllocate() API call
+        """
+        notification_filter = saNtf.SaNtfSecurityAlarmNotificationFilterT()
+
+        rc = ntf.saNtfSecurityAlarmNotificationFilterAllocate(
+            self.handle, notification_filter,
+            len(self.filter_info.sec_alarm_evt_list),
+            len(self.filter_info.notification_object_list),
+            len(self.filter_info.notifying_objects_list),
+            len(self.filter_info.ntf_class_id_list),
+            len(self.filter_info.probable_cause_list),
+            len(self.filter_info.severity_list), 0, 0, 0)
+        if rc != eSaAisErrorT.SA_AIS_OK:
+            log_err("saNtfAlarmNotificationFilterAllocate FAILED, "
+                    "rc = %s" % eSaAisErrorT.whatis(rc))
+        else:
+            self.filter_handles.securityAlarmFilterHandle = \
+                notification_filter.notificationFilterHandle
+
+            self._fill_in_filter_header(
+                notification_filter.notificationFilterHeader)
+
+            # Fill in the eventTypes array with user-provided data
+            for i, evt in enumerate(self.filter_info.sec_alarm_evt_list):
+                notification_filter.notificationFilterHeader.eventTypes[i] = \
+                    evt
+
+            # Fill in the probableCauses array with user-provided data
+            for i, probable_cause in \
+                    enumerate(self.filter_info.probable_cause_list):
+                notification_filter.probableCauses[i] = probable_cause
+
+            # Fill in the severities array with user-provided data
+            for i, severity in enumerate(self.filter_info.severity_list):
+                notification_filter.severities[i] = severity
+
+        return rc
+
+    def _free_notification_filters(self):
+        """ Free the memory previously allocated for a notification filter """
+        if self.filter_handles.objectCreateDeleteFilterHandle:
+            ntf.saNtfNotificationFilterFree(
+                self.filter_handles.objectCreateDeleteFilterHandle)
+        if self.filter_handles.attributeChangeFilterHandle:
+            ntf.saNtfNotificationFilterFree(
+                self.filter_handles.attributeChangeFilterHandle)
+        if self.filter_handles.stateChangeFilterHandle:
+            ntf.saNtfNotificationFilterFree(
+                self.filter_handles.stateChangeFilterHandle)
+        if self.filter_handles.alarmFilterHandle:
+            ntf.saNtfNotificationFilterFree(
+                self.filter_handles.alarmFilterHandle)
+        if self.filter_handles.securityAlarmFilterHandle:
+            ntf.saNtfNotificationFilterFree(
+                self.filter_handles.securityAlarmFilterHandle)
+
+    @staticmethod
+    def _get_ptr_value(ntf_handle, value):
+        """ Get the correct string value from the given 'value' field in the
+        received notification
+
+        Args:
+            ntf_handle (SaNtfNotificationHandleT): A handle to the internal
+                notification structure
+            value (SaNtfValueT): A 'value' field in the notification structure
+
+        Returns:
+            SaStringT: The value in string format
+        """
+        data_ptr = SaVoidPtr()
+        data_size = SaUint16T()
+        rc = ntf.saNtfPtrValGet(ntf_handle, value, data_ptr, data_size)
+        if rc != eSaAisErrorT.SA_AIS_OK:
+            log_warn("saNtfPtrValGet FAILED - %s" % (eSaAisErrorT.whatis(rc)))
+        return ctypes.cast(data_ptr, SaStringT).value
+
+    def _get_ntf_value(self, ntf_handle, value, value_type):
+        """ Get the correct typed value from a specific 'value' field in the
+        received notification
+
+        Args:
+            ntf_handle (SaNtfNotificationHandleT): A handle to the internal
+                notification structure
+            value (SaNtfValueT): A 'value' field in the notification structure
+            value_type (SaNtfValueTypeT): Value type
+
+        Returns:
+            typed_value: Value of a specific type
+        """
+        if value_type in STRING_TYPES:
+            return self._get_ptr_value(ntf_handle, value)
+        elif value_type in INT_TYPES:
+            return saNtf.unmarshalSaNtfValue(BYREF(value), value_type)
+
+        return "This value type is currently not supported"
+
+    def _parse_notification_header(self, ntf_handle, ntf_header):
+        """ Parse the notification header in the received notification to
+        retrieve information
+
+        Args:
+            ntf_handle (SaNtfNotificationHandleT): A handle to the internal
+                notification structure
+            ntf_header (SaNtfNotificationHeaderT): Notification header
+                structure in the received notification
+
+        Returns:
+            NotificationInfo: A NotificationInfo structure containing
+                information from the notification header
+        """
+        ntf_info = ntf.NotificationInfo()
+        ntf_info.event_type = ntf_header.eventType.contents.value
+        ntf_info.notification_object = \
+            ntf_header.notificationObject.contents.value
+        ntf_info.notifying_object = \
+            ntf_header.notifyingObject.contents.value
+        ntf_info.ntf_class_id = \
+            ntf_header.notificationClassId.contents
+        ntf_info.event_time = ntf_header.eventTime.contents.value
+        ntf_info.notification_id = \
+            ntf_header.notificationId.contents.value
+        ntf_info.additional_text = \
+            ntf_header.additionalText[0:ntf_header.lengthAdditionalText]
+
+        for i in range(ntf_header.numAdditionalInfo):
+            c_add_info = ntf_header.additionalInfo[i]
+            add_info = ntf.AdditionalInfo()
+            add_info.info_id = c_add_info.infoId
+            add_info.info_type = c_add_info.infoType
+            add_info.info_value = self._get_ntf_value(ntf_handle,
+                                                      c_add_info.infoValue,
+                                                      c_add_info.infoType)
+            ntf_info.additional_info.append(add_info)
+
+        return ntf_info
+
+    def _parse_object_create_delete_ntf(self, c_ntf, ntf_info):
+        """ Parse the received object-create/delete notification to retrieve
+        its information
+
+        Args:
+            c_ntf (SaNtfObjectCreateDeleteNotificationT): Object-create/delete
+                notification structure
+            ntf_info (NotificationInfo): NotificationInfo structure with
+                information from the notification
+        """
+        ntf_handle = c_ntf.notificationHandle
+        ntf_info.source_indicator = \
+            c_ntf.sourceIndicator.contents.value
+        for i in range(c_ntf.numAttributes):
+            c_attr = c_ntf.objectAttributes[i]
+            attr = ntf.Attribute()
+            attr.attribute_id = c_attr.attributeId
+            attr.attribute_type = c_attr.attributeType
+            attr.attribute_value = \
+                self._get_ntf_value(ntf_handle, c_attr.attributeValue,
+                                    c_attr.attributeType)
+            ntf_info.object_attributes.append(attr)
+
+    def _parse_attribute_change_ntf(self, c_ntf, ntf_info):
+        """ Parse the received attribute-change notification to retrieve its
+        information
+
+        Args:
+            c_ntf (SaNtfAttributeChangeNotificationT): Attribute-change
+                notification structure
+            ntf_info (NotificationInfo): NotificationInfo structure with
+                information from the notification
+        """
+        ntf_handle = c_ntf.notificationHandle
+        ntf_info.source_indicator = \
+            c_ntf.sourceIndicator.contents.value
+        for i in range(c_ntf.numAttributes):
+            c_attr = c_ntf.changedAttributes[i]
+            attr = ntf.AttributeChange()
+            attr.attribute_id = c_attr.attributeId
+            attr.attribute_type = c_attr.attributeType
+            attr.new_attribute_value = \
+                self._get_ntf_value(ntf_handle, c_attr.newAttributeValue,
+                                    c_attr.attributeType)
+            attr.old_attribute_present = c_attr.oldAttributePresent
+            if c_attr.oldAttributePresent:
+                attr.old_attribute_value = \
+                    self._get_ntf_value(ntf_handle, c_attr.oldAttributeValue,
+                                        c_attr.attributeType)
+            ntf_info.changed_attributes.append(attr)
+
+    @staticmethod
+    def _parse_state_change_ntf(c_ntf, ntf_info):
+        """ Parse the received state-change notification to retrieve its
+        information
+
+        Args:
+            c_ntf (SaNtfStateChangeNotificationT): State-change notification
+                structure
+            ntf_info (NotificationInfo): NotificationInfo structure with
+                information from the notification
+        """
+        ntf_info.source_indicator = \
+            c_ntf.sourceIndicator.contents.value
+        for i in range(c_ntf.numStateChanges):
+            c_attr = c_ntf.changedStates[i]
+            attr = ntf.StateChange()
+            attr.state_id = c_attr.stateId
+            attr.new_state = c_attr.newState
+            attr.old_state_present = c_attr.oldStatePresent
+            if c_attr.oldStatePresent:
+                attr.old_state = c_attr.oldState
+            ntf_info.state_changes.append(attr)
+
+    def _parse_alarm_ntf(self, c_ntf, ntf_info):
+        """ Parse the received alarm notification to retrieve its information
+
+        Args:
+            c_ntf (SaNtfAlarmNotificationT): Alarm notification structure
+            ntf_info (NotificationInfo): NotificationInfo structure with
+                information from the notification
+        """
+        ntf_handle = c_ntf.notificationHandle
+        ntf_info.probable_cause = c_ntf.probableCause.contents.value
+
+        for i in range(c_ntf.numSpecificProblems):
+            ntf_info.specific_problems.append(
+                c_ntf.specificProblems[i])
+        ntf_info.perceived_severity = c_ntf.perceivedSeverity.contents.value
+        ntf_info.trend = c_ntf.trend.contents.value
+
+        c_threshold_info = c_ntf.thresholdInformation.contents
+        if c_threshold_info:
+            threshold_info = ntf.ThresholdInformation()
+            c_threshold_info = c_ntf.thresholdInformation.contents
+
+            threshold_info.threshold_id = c_threshold_info.thresholdId
+            threshold_info.threshold_value_type = \
+                c_threshold_info.thresholdValueType
+            threshold_info.threshold_value = \
+                self._get_ntf_value(ntf_handle,
+                                    c_threshold_info.thresholdValue,
+                                    c_threshold_info.thresholdValueType)
+            threshold_info.threshold_hysteresis = \
+                self._get_ntf_value(ntf_handle,
+                                    c_threshold_info.thresholdHysteresis,
+                                    c_threshold_info.thresholdValueType)
+            threshold_info.observed_value = \
+                self._get_ntf_value(ntf_handle,
+                                    c_threshold_info.observedValue,
+                                    c_threshold_info.thresholdValueType)
+            threshold_info.arm_time = c_threshold_info.armTime
+
+            ntf_info.threshold_information = threshold_info
+
+        for i in range(c_ntf.numMonitoredAttributes):
+            c_attr = c_ntf.monitoredAttributes[i]
+            attr = ntf.Attribute()
+            attr.attribute_id = c_attr.attributeId
+            attr.attribute_type = c_attr.attributeType
+            attr.attribute_value = \
+                self._get_ntf_value(ntf_handle, c_attr.attributeValue,
+                                    c_attr.attributeType)
+            ntf_info.monitored_attrs.append(attr)
+
+        for i in range(c_ntf.numProposedRepairActions):
+            c_action = c_ntf.proposedRepairActions[i]
+            action = ntf.ProposedRepairAction()
+            action.action_id = c_action.actionId
+            action.action_value_type = c_action.actionValueType
+            action.action_value = \
+                self._get_ntf_value(ntf_handle, c_action.actionValue,
+                                    c_action.actionValueType)
+            ntf_info.proposed_repair_actions.append(action)
+
+    def _parse_security_alarm_ntf(self, c_ntf, ntf_info):
+        """ Parse the received security alarm notification to retrieve its
+        information
+
+        Args:
+            c_ntf (SaNtfSecurityAlarmNotificationT): Security alarm
+                notification structure
+            ntf_info (NotificationInfo): NotificationInfo structure with
+                information from the notification
+        """
+        ntf_handle = c_ntf.notificationHandle
+        ntf_info.probable_cause = c_ntf.probableCause.contents.value
+        ntf_info.severity = c_ntf.severity.contents.value
+        if c_ntf.securityAlarmDetector.contents:
+            ntf_info.security_alarm_detector = ntf.SecurityAlarmDetector()
+            ntf_info.security_alarm_detector.value_type = \
+                c_ntf.securityAlarmDetector.contents.valueType
+            ntf_info.security_alarm_detector.value = self._get_ntf_value(
+                ntf_handle,
+                c_ntf.securityAlarmDetector.contents.value,
+                c_ntf.securityAlarmDetector.contents.valueType)
+        if c_ntf.serviceUser.contents:
+            ntf_info.service_user = ntf.SecurityAlarmDetector()
+            ntf_info.service_user.value_type = \
+                c_ntf.serviceUser.contents.valueType
+            ntf_info.service_user.value = self._get_ntf_value(
+                ntf_handle,
+                c_ntf.serviceUser.contents.value,
+                c_ntf.serviceUser.contents.valueType)
+        if c_ntf.serviceProvider.contents:
+            ntf_info.service_provider = ntf.SecurityAlarmDetector()
+            ntf_info.service_provider.value_type = \
+                c_ntf.serviceProvider.contents.valueType
+            ntf_info.service_provider.value = self._get_ntf_value(
+                ntf_handle,
+                c_ntf.serviceProvider.contents.value,
+                c_ntf.serviceProvider.contents.valueType)
+
+
+class NtfSubscriber(NtfConsumer):
+    """ This class provides functions of the NTF Subscriber interface """
+
+    def _ntf_notif_callback(self, c_subscription_id, c_notif):
+        """ This callback is invoked by NTF to deliver a notification to a
+        subscriber of that notification type.
+
+        Args:
+            c_subscription_id (SaNtfSubscriptionIdT): The subscription id
+                previously provided by the subscriber when subscribing for this
+                type of notification
+            c_notif (SaNtfNotificationsT): The notification delivered by this
+                callback
+        """
+        if not self.ntf_notif_function:
+            return
+
+        subscription_id = c_subscription_id
+        notification_type = c_notif.contents.notificationType
+        if notification_type == \
+                saNtf.eSaNtfNotificationTypeT.SA_NTF_TYPE_OBJECT_CREATE_DELETE:
+            notification = \
+                c_notif.contents.notification.objectCreateDeleteNotification
+            ntf_handle = notification.notificationHandle
+            ntf_header = notification.notificationHeader
+            ntf_info = self._parse_notification_header(ntf_handle, ntf_header)
+            self._parse_object_create_delete_ntf(notification, ntf_info)
+        elif notification_type == \
+                saNtf.eSaNtfNotificationTypeT.SA_NTF_TYPE_ATTRIBUTE_CHANGE:
+            notification = \
+                c_notif.contents.notification.attributeChangeNotification
+            ntf_handle = notification.notificationHandle
+            ntf_header = notification.notificationHeader
+            ntf_info = self._parse_notification_header(ntf_handle, ntf_header)
+            self._parse_attribute_change_ntf(notification, ntf_info)
+
+        elif notification_type == \
+                saNtf.eSaNtfNotificationTypeT.SA_NTF_TYPE_STATE_CHANGE:
+            notification = \
+                c_notif.contents.notification.stateChangeNotification
+            ntf_handle = notification.notificationHandle
+            ntf_header = notification.notificationHeader
+            ntf_info = self._parse_notification_header(ntf_handle, ntf_header)
+            self._parse_state_change_ntf(notification, ntf_info)
+
+        elif notification_type == \
+                saNtf.eSaNtfNotificationTypeT.SA_NTF_TYPE_ALARM:
+            notification = c_notif.contents.notification.alarmNotification
+            ntf_handle = notification.notificationHandle
+            ntf_header = notification.notificationHeader
+            ntf_info = self._parse_notification_header(ntf_handle, ntf_header)
+            self._parse_alarm_ntf(notification, ntf_info)
+
+        elif notification_type == \
+                saNtf.eSaNtfNotificationTypeT.SA_NTF_TYPE_SECURITY_ALARM:
+            notification = \
+                c_notif.contents.notification.securityAlarmNotification
+            ntf_handle = notification.notificationHandle
+            ntf_header = notification.notificationHeader
+            ntf_info = self._parse_notification_header(ntf_handle, ntf_header)
+            self._parse_security_alarm_ntf(notification, ntf_info)
+
+        else:
+            return
+
+        # Send the ntf info to user's callback function
+        self.ntf_notif_function(subscription_id, notification_type, ntf_info)
+
+    def _ntf_notif_discarded_callback(self, c_subscription_id,
+                                      c_notification_type, c_number_discarded,
+                                      c_discarded_notification_identifiers):
+        """ This callback is invoked by NTF to notify a subscriber of a
+        particular notification type that one or more notifications of that
+        type have been discarded.
+
+        Args:
+            c_subscription_id (SaNtfSubscriptionIdT): The subscription id
+                previously provided by the subscriber when subscribing for
+                discarded notifications
+            c_notification_type (SaNtfNotificationTypeT): The notification type
+                of the discarded notifications
+            c_number_discarded (SaUint32T): The number of discarded
+                notifications
+            c_discarded_notification_identifiers (SaNtfIdentifierT): The list
+                of notification identifiers of the discarded notifications
+        """
+        if not self.ntf_notif_discarded_function:
+            return
+
+        # Send all info to user callback
+        self.ntf_notif_discarded_function(c_subscription_id,
+                                          c_notification_type,
+                                          c_number_discarded,
+                                          c_discarded_notification_identifiers)
+
+    @bad_handle_retry
+    def _re_init(self):
+        """ Internal function to re-initialize the NTF agent and fetch a new
+        selection object in case of getting BAD_HANDLE during an operation
+
+        Returns:
+            SaAisErrorT: Return code of the corresponding NTF API call(s)
+        """
+        self.finalize()
+        self.version = deepcopy(self.init_version)
+        self.handle = saNtf.SaNtfHandleT()
+        rc = ntf.saNtfInitialize(self.handle, self.callbacks, self.version)
+        if rc != eSaAisErrorT.SA_AIS_OK:
+            log_err("saNtfInitialize FAILED - %s" % (eSaAisErrorT.whatis(rc)))
+        else:
+            rc = self._fetch_sel_obj()
+
+        return rc
+
+    def init(self, ntf_notif_func=None, ntf_notif_discarded_func=None):
+        """ Initialize the NTF agent and fetch the selection object
+
+        Args:
+            ntf_notif_func (callback): Callback function for subscribed
+                notifications
+            ntf_notif_discarded_func (callback): Callback function for
+                discarded notifications
+
+        Returns:
+            SaAisErrorT: Return code of the corresponding NTF API call(s)
+        """
+        rc = self.initialize(ntf_notif_func, ntf_notif_discarded_func)
+        if rc == eSaAisErrorT.SA_AIS_OK:
+            rc = self._fetch_sel_obj()
+            if rc == eSaAisErrorT.SA_AIS_ERR_BAD_HANDLE:
+                self._re_init()
+        return rc
+
+    @bad_handle_retry
+    def subscribe(self, subscription_id, notification_types=None):
+        """ Subscribe for notifications from NTF with the types specified in
+        the notification_types list. If the list is not provided, all types of
+        notification are subscribed to by default.
+
+        NOTE: Users have to set the wanted filters criteria via the set of
+        set_filter_*() methods before calling this method. Otherwise, the
+        filters criteria will be set with default values.
+
+        Args:
+            subscription_id (SaNtfSubscriptionIdT): Subscription id
+            notification_types (list(SaNtfNotificationTypeT)): List of
+                notification types
+
+        Returns:
+            SaAisErrorT: Return code of the corresponding NTF API call(s)
+        """
+        self.filter_handles = \
+            saNtf.SaNtfNotificationTypeFilterHandlesT(0, 0, 0, 0, 0)
+
+        create_delete = \
+            saNtf.eSaNtfNotificationTypeT.SA_NTF_TYPE_OBJECT_CREATE_DELETE
+        attr_change = \
+            saNtf.eSaNtfNotificationTypeT.SA_NTF_TYPE_ATTRIBUTE_CHANGE
+        state_change = \
+            saNtf.eSaNtfNotificationTypeT.SA_NTF_TYPE_STATE_CHANGE
+        alarm = saNtf.eSaNtfNotificationTypeT.SA_NTF_TYPE_ALARM
+        security_alarm = \
+            saNtf.eSaNtfNotificationTypeT.SA_NTF_TYPE_SECURITY_ALARM
+
+        rc = eSaAisErrorT.SA_AIS_OK
+        # Create and allocate the object create delete filter
+        if notification_types is None or create_delete in notification_types:
+            rc = self._generate_object_create_delete_filter()
+
+        if rc == eSaAisErrorT.SA_AIS_OK:
+            # Create and allocate the attribute change filter
+            if notification_types is None or attr_change in notification_types:
+                rc = self._generate_attribute_change_filter()
+
+        if rc == eSaAisErrorT.SA_AIS_OK:
+            # Create and allocate the state change filter
+            if notification_types is None \
+                    or state_change in notification_types:
+                rc = self._generate_state_change_filter()
+
+        if rc == eSaAisErrorT.SA_AIS_OK:
+            # Create and allocate the alarm filter
+            if notification_types is None or alarm in notification_types:
+                rc = self._generate_alarm_filter()
+
+        if rc == eSaAisErrorT.SA_AIS_OK:
+            # Create and allocate the security alarm filter
+            if notification_types is None \
+                    or security_alarm in notification_types:
+                rc = self._generate_security_alarm_filter()
+
+        if rc == eSaAisErrorT.SA_AIS_OK:
+            # Start subscription
+            rc = ntf.saNtfNotificationSubscribe(self.filter_handles,
+                                                subscription_id)
+            if rc != eSaAisErrorT.SA_AIS_OK:
+                log_err("saNtfNotificationSubscribe FAILED, rc = %s" %
+                        eSaAisErrorT.whatis(rc))
+            else:
+                self.clear_filter_info()
+
+            # Free the notification filters
+            self._free_notification_filters()
+
+        if rc == eSaAisErrorT.SA_AIS_ERR_BAD_HANDLE:
+            init_rc = self._re_init()
+            # If the re-initialization of agent handle succeeds, we still need
+            # to return BAD_HANDLE to the function decorator, so that it would
+            # re-try the failed operation. Otherwise, the true error code is
+            # returned to the user to decide further actions.
+            if init_rc != eSaAisErrorT.SA_AIS_OK:
+                rc = init_rc
+
+        return rc
+
+    @staticmethod
+    def unsubscribe(subscription_id):
+        """ Unsubscribe the previous notification subscription
+
+        Args:
+            subscription_id (SaNtfSubscriptionIdT): Subscription id provided in
+                the previous notification subscription
+
+        Returns:
+            SaAisErrorT: Return code of the saNtfNotificationUnsubscribe()
+                API call
+        """
+        return ntf.saNtfNotificationUnsubscribe(subscription_id)
-- 
2.7.4


------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot
_______________________________________________
Opensaf-devel mailing list
Opensaf-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/opensaf-devel

Reply via email to