##
# File:  MessagingDepict.py
# Date:  02-Feb-2012
# Updates:
#    2012-04-30    RPS    Created based on EditorDepict
#    2013-09-03    RPS    Updated to use cif-based backend storage.
#    2013-10-30    RPS    Support for "Notes" UI and for UI feature for classifying messages (e.g. "action required" or "unread")
#                            Also, now supporting canned starter template for composing messages.
#    2013-11-19    RPS    Added support for obtaining content from "correspondence-to-depositor" files to integrate within starter 
#                            template for message body. Also added PDB ID field to starter message template.
#    2013-12-03    RPS    Introduced support for different message templates.
#    2013-12-16    RPS    Introduced support for rendering view as embedded view (iframe)
#    2014-01-14    RPS    Message templates updated.
#    2014-02-05    RPS    Value of "wwpdb annotator" value enforced as sender when login account is "dep" user
#    2014-03-04    RPS    Introduced support for message templates related to validation.
#    2014-06-09    RPS    Updated handling for citation related info so that these data points are rendered in related message tempalte
#                            only when the given data items are valid/populated.
#    2014-08-12    RPS    Moved message templates into separate MessagingTemplates module
#    2014-09-23    RPS    Integrated use of PdbxPersist/datastore to minimize reliance on parsing model file which will improve
#                            response times on front-end when sending messages in cases of very large model files (e.g. ribosomes)
#    2014-10-10    RPS    msgTmplt_vldtnMinorIssues and msgTmplt_vldtnMajorIssues abbreviated to eliminate boilerplate that is already contained in correspondence content.
#    2014-12-04    RPS    msgTmplt_correspondence, msgTmplt_vldtnMinorIssues, and msgTmplt_vldtnMajorIssues removed.
#                            Updates to support new "Complete Correspondence History" view and new msgTmplt_systemUnlocked template.
#    2014-12-05    RPS    getDataTableTemplate updated to provide inventory of Notes message IDs
#    2014-12-09    RPS    Updates to support new strategy of rendering message content in separate browser window
#    2015-03-02    RPS    Updates per introduction of sanity check safeguards on writes to messaging cif data files.
#    2015-05-08    RPS    Needed to correct getDataTableTemplate() to create proper target HTML table as per upgrade to use of DataTables 1.10.5
#    2015-07-28    RPS    Updates to support handling for cases of EM experimental method. Introduced support for "Unlock Dep UI" button.
#    2015-10-27    RPS    Introduction support for EM specific message templates.
#    2015-10-28    RPS    More updates to support EM specific message templates.
#    2015-12-02    RPS    Updates to optimize response time to user by running template processing in background.
#                            Removed obsolete validation letter content (validation content no longer generated by Msgmodule)
#    2016-01-24    RPS    Updated for new template for EM map-only, post-annotation letter
#    2016-02-29    RPS    Removing use of "COLUMN_DISPLAY_NAMES" as this hadn't found a use on front-end
#    2016-05-06    RPS    Setting annotator to "wwPDB annotator" if received as "unknown" from calling code.
#    2016-08-09    RPS    Changes to support site/annotator specific footers in message templates.
#                            Introducing support for standalone correspondence viewer.
#    2016-09-14    ZF     In doRender() function, introducing support for group deposition
##
##
"""
Base class for HTML depictions containing common HTML constructs.

"""
__docformat__ = "restructuredtext en"
__author__    = "Raul Sala"
__email__     = "rsala@rcsb.rutgers.edu"
__license__   = "Creative Commons Attribution 3.0 Unported"
__version__   = "V0.02"

import os
import sys
import time
from wwpdb.apps.msgmodule.io.MessagingIo import MessagingIo
from wwpdb.apps.msgmodule.io.MessagingDataImport import MessagingDataImport
from wwpdb.apps.msgmodule.depict.MessagingTemplates import MessagingTemplates
from wwpdb.utils.config.ConfigInfo import ConfigInfo


class MessagingDepict(object):
    """Base class for HTML depictions contain definitions of common constructs.

    """    
    
    def __init__(self,verbose=False,log=sys.stderr):
        """

         :param `verbose`:  boolean flag to activate verbose logging.
         :param `log`:      stream for logging.
          
        """
        self.__verbose=verbose
        self.__lfh=log
        self.__debug=False
        self.__debugLvl2=False
        #
        self.absltSessionPath=None
        self.absltEdtrSessionPath=None
        self.rltvSessionPath=None
        self.rltvEdtrSessionPath=None
        #
        self.__expMethodList = []

    def doRender(self,p_reqObj,p_bIsWorkflow):
        ''' Render HTML used as starter page/container for the wwPDB Messaging interface
            Once this content is in the browser, AJAX calls are made to populate the page
            with content when needed.
            
            :Params:
            
                + ``p_reqObj``: Web Request object
                + ``p_bIsWorkflow``: boolean indicating whether or not operating in Workflow Manager environment
                    
            :Returns:
                ``oL``: output list consisting of HTML markup
        '''        
        oL=[]
        #
        sessionId   =p_reqObj.getSessionId()
        wfInstId    =str(p_reqObj.getValue("instance")).upper()
        depId       =str(p_reqObj.getValue("identifier"))
        classId     =str(p_reqObj.getValue("classID")).lower()
        fileSource  =str(p_reqObj.getValue("filesource")).lower()
        contentType =str(p_reqObj.getValue("content_type")).lower()
        annotator   =str(p_reqObj.getValue("annotator"))
        expMethodList = (p_reqObj.getValue("expmethod").replace('"','')).split(",") if( len(p_reqObj.getValue("expmethod").replace('"','')) > 1 ) else []
        initContentType =str(p_reqObj.getValue("init_content_type")).lower()
        tmpltPath   =p_reqObj.getValue("TemplatePath")
        embeddedVw =str(p_reqObj.getValue("embed"))
        autoLaunchCompose =str(p_reqObj.getValue("auto_launch_compose"))
        allowUnlockDepUI = str(p_reqObj.getValue("allowunlock"))
        siteId  = str(p_reqObj.getValue("WWPDB_SITE_ID"))
        cI=ConfigInfo(siteId)
        fileFormatExtDict = cI.get('FILE_FORMAT_EXTENSION_DICTIONARY')
        #
        contentType = initContentType if( contentType is None or len(contentType) < 1 ) else contentType #if content type not specified then fall back on initial content type requested
        #
        if contentType is None or len(contentType) < 1 : #if we don't have a requested content type at this point, use "msgs" as default
           contentType = 'msgs' 
        #
        #if depId is not None:
        #    depId = self.__formatDepositionDataId(depId, p_bIsWorkflow) 
        #
        self.__expMethodList = (p_reqObj.getValue("expmethod").replace('"','')).split(",") if( len(p_reqObj.getValue("expmethod").replace('"','')) > 1 ) else []
        bEmDeposition = True if( "ELECTRON MICROSCOPY" in self.__expMethodList or "ELECTRON CRYSTALLOGRAPHY" in self.__expMethodList ) else False
        #
        if (self.__verbose):
            self.__lfh.write("--------------------------------------------\n")
            className = self.__class__.__name__
            methodName = sys._getframe().f_code.co_name
            self.__lfh.write("Starting %s %s at %s\n" % (className,
                                                       methodName,
                                                       time.strftime("%Y %m %d %H:%M:%S", time.localtime())))
            self.__lfh.write("+%s.%s() identifier   %s\n" % ( className, methodName, depId ) )
            self.__lfh.write("+%s.%s() instance     %s\n" % ( className, methodName, wfInstId ) )
            self.__lfh.write("+%s.%s() file source  %s\n" % ( className, methodName, fileSource ) )
            self.__lfh.write("+%s.%s() sessionId  %s\n" % ( className, methodName, sessionId ) )      
            self.__lfh.flush()
        #

        # Determine if submitted

        deposited = False
        msgDI = MessagingDataImport(p_reqObj, verbose=self.__verbose, log=self.__lfh)
        modelFilePath = msgDI.getFilePath(contentType='model', format='pdbx')
        # parse info from model file
        if (modelFilePath is not None and os.access(modelFilePath, os.R_OK)):
            deposited = True
        
        annotator = "wwPDB annotator" if( annotator.lower() in ["dep","unknown"] ) else annotator
        
        ############################################################################
        # create dictionary of content that will be used to populate HTML template
        ############################################################################
        myD={}
        myD['sessionid'] = sessionId
        myD['instance']   = wfInstId        
        myD['classid']    = classId
        myD['filesource'] = fileSource
        myD['content_type'] = contentType
        myD['annotator'] = annotator if( annotator is not None and len(annotator) > 1 ) else "wwPDB annotator" 
        myD['content_type_hdr'] = "Annotator Processing Notes" if contentType == "notes" else "Communications with Depositor"
        myD['cntnt_type_btnlbl'] = "Notes" if contentType == "notes" else "Messages"
        myD['new_cntnt_type_btnlbl'] = "Note" if contentType == "notes" else "Message"
        myD['auto_launch_compose'] = autoLaunchCompose if( autoLaunchCompose is not None and len(autoLaunchCompose) > 1 and autoLaunchCompose.lower() == 'false') else 'true'
        myD['expmethod'] = p_reqObj.getValue("expmethod")
        myD['display_unlock'] = ""  if( allowUnlockDepUI and allowUnlockDepUI == "yes" ) else "hidden"
        myD['display_rel_status'] = ""  if deposited else "hidden"
        #
        listKnownFileExtensions = list( set( fileFormatExtDict.values() ) )
        listKnownFileExtensions.sort()
        myD['known_file_extensions'] = ", ".join(listKnownFileExtensions)
        
        # following params only for rcsb stand-alone version
        myD['caller'] = p_reqObj.getValue("caller")
        myD['filepath'] = p_reqObj.getValue('filePath')
        myD['filetype'] = p_reqObj.getValue('fileType')
        #
        if( p_bIsWorkflow ):
            myD['identifier'] = depId
        else:
            if not depId or len(depId) < 1:
                myD['identifier'] = 'D_000000'
            else:
                myD['identifier'] = depId
        #
        myD['session_url_prefix'] = self.rltvEdtrSessionPath
        #
        # Added by ZF to handle group deposition
        #
        myD['display_title'] = 'DEPOSITION ID'
        myD['display_identifier'] = myD['identifier']
        groupId = str(p_reqObj.getValue("groupid"))
        if groupId:
            if groupId == depId:
                myD['display_title'] = 'GROUP ID'
                myD['display_identifier'] = groupId
            else:
                myD['display_title'] = 'GROUP/DEPOSITION IDS'
                myD['display_identifier'] = groupId + '/' + myD['identifier']
            #
        #
        tmpltFile = "msging_launch_embed_tmplt.html" if embeddedVw.lower() == "true" else "msging_launch_tmplt.html"
        #tmpltFile = "msging_launch_embed_tmplt.previewMsg.html" if embeddedVw.lower() == "true" else "msging_launch_tmplt.previewMsg.20150622.html"
            
        oL.append( self.processTemplate(tmpltPth=tmpltPath,fn=tmpltFile, parameterDict=myD) )
        #
        return oL
    
    def getMsgTmplts(self,p_reqObj,p_bIsWorkflow):
        """ Get  
            
            :Helpers:
                
            :Returns:
                 
        """
        className = self.__class__.__name__
        methodName = sys._getframe().f_code.co_name
        #
        oL=[]
        #
        strParamDict = {}
        #
        if (self.__verbose):
            self.__lfh.write("+%s.%s() -- Starting.\n" % (className, methodName) )        
        #
        depId = str(p_reqObj.getValue("identifier"))
        tmpltPath = p_reqObj.getValue("TemplatePath")
        self.__expMethodList = (p_reqObj.getValue("expmethod").replace('"','')).split(",") if( len(p_reqObj.getValue("expmethod").replace('"','')) > 1 ) else []
        bEmDeposition = True if( "ELECTRON MICROSCOPY" in self.__expMethodList or "ELECTRON CRYSTALLOGRAPHY" in self.__expMethodList ) else False
        #
        if( p_bIsWorkflow ):
            strParamDict['identifier'] = depId
        else:
            if not depId or len(depId) < 1:
                strParamDict['identifier'] = 'D_000000'
            else:
                strParamDict['identifier'] = depId
        #
        if (self.__verbose):
            self.__lfh.write("\n%s.%s() -- dep_id is:%s\n" % (self.__class__.__name__,
                                                       sys._getframe().f_code.co_name,
                                                       depId) )
        #
        msgingIo = MessagingIo(p_reqObj,self.__verbose,self.__lfh)
        msgingIo.initializeDataStore() # THIS CALL MUST BE MADE HERE TO SUPPORT ALL DOWNSTREAM PROCESSING IN NEED OF DATA PARSED FROM THE COORDINATE FILE 
        msgingIo.getMsgTmpltDataItems(strParamDict)
        starterMsgBody = msgingIo.getStarterMsgBody()
        #  
        strParamDict['starter_msg_body'] =  starterMsgBody if starterMsgBody is not None else ""
        strParamDict['msg_tmplt_default'] = (MessagingTemplates.msgTmplt_default_em % strParamDict) if bEmDeposition else (MessagingTemplates.msgTmplt_default % strParamDict)
        strParamDict['msg_tmplt_approval-expl'] = (MessagingTemplates.msgTmplt_approvalExplicit_em % strParamDict) if bEmDeposition else (MessagingTemplates.msgTmplt_approvalExplicit % strParamDict)
        strParamDict['msg_tmplt_approval-impl'] = (MessagingTemplates.msgTmplt_approvalImplicit_em % strParamDict) if bEmDeposition else (MessagingTemplates.msgTmplt_approvalImplicit % strParamDict)
        strParamDict['msg_tmplt_reminder'] = (MessagingTemplates.msgTmplt_reminder_em % strParamDict) if bEmDeposition else (MessagingTemplates.msgTmplt_reminder % strParamDict)
        strParamDict['msg_tmplt_release-publ'] = (MessagingTemplates.msgTmplt_releaseWthPblctn_em % strParamDict) if bEmDeposition else (MessagingTemplates.msgTmplt_releaseWthPblctn % strParamDict)
        strParamDict['msg_tmplt_release-nopubl'] = (MessagingTemplates.msgTmplt_releaseWthOutPblctn_em % strParamDict) if bEmDeposition else (MessagingTemplates.msgTmplt_releaseWthOutPblctn % strParamDict)
        strParamDict['msg_tmplt_withdrawn'] =  (MessagingTemplates.msgTmplt_withdrawn_em % strParamDict) if bEmDeposition else (MessagingTemplates.msgTmplt_withdrawn % strParamDict)
        strParamDict['msg_tmplt_vldtn'] =  (MessagingTemplates.msgTmplt_vldtn % strParamDict)

        statusCode = strParamDict.get('status_code', '???')
        if statusCode == 'REL' and strParamDict.get('pdb_id', '') != '[PDBID NOT AVAIL]':
            # Post Release Unlock
            strParamDict['msg_tmplt_system-unlocked'] =  MessagingTemplates.msgTmplt_systemUnlockedPostRel % strParamDict
        else:
            strParamDict['msg_tmplt_system-unlocked'] =  (MessagingTemplates.msgTmplt_systemUnlocked_em % strParamDict) if bEmDeposition else (MessagingTemplates.msgTmplt_systemUnlocked % strParamDict)
        strParamDict['msg_tmplt_maponly-authstatus-em'] =  (MessagingTemplates.msgTmplt_mapOnly_authStatus_em% strParamDict) if bEmDeposition else ""
            
        oL.append( self.processTemplate(tmpltPth=tmpltPath,fn="msg_tmplts.html", parameterDict=strParamDict) )
        #
        return oL
    
    def doRenderAllCorrespondence(self,p_reqObj):
        ''' Render HTML used as starter page/container for the wwPDB Messaging interface
            Once this content is in the browser, AJAX calls are made to populate the page
            with content when needed.
            
            :Params:
            
                + ``p_reqObj``: Web Request object
                + ``p_bIsWorkflow``: boolean indicating whether or not operating in Workflow Manager environment
                    
            :Returns:
                ``oL``: output list consisting of HTML markup
        '''        
        oL=[]
        #
        sessionId   =p_reqObj.getSessionId()
        depId       =str(p_reqObj.getValue("identifier")).upper()
        fileSource  =str(p_reqObj.getValue("filesource")).lower()
        contentType = "commhstry"
        
        initContentType =str(p_reqObj.getValue("init_content_type")).lower()
        tmpltPath   =p_reqObj.getValue("TemplatePath")
        siteId  = str(p_reqObj.getValue("WWPDB_SITE_ID"))
        cI=ConfigInfo(siteId)
        #
        if (self.__verbose):
            self.__lfh.write("--------------------------------------------\n")
            className = self.__class__.__name__
            methodName = sys._getframe().f_code.co_name
            self.__lfh.write("Starting %s %s at %s\n" % (className,
                                                       methodName,
                                                       time.strftime("%Y %m %d %H:%M:%S", time.localtime())))
            self.__lfh.write("+%s.%s() identifier   %s\n" % ( className, methodName, depId ) )
            self.__lfh.write("+%s.%s() file source  %s\n" % ( className, methodName, fileSource ) )
            self.__lfh.write("+%s.%s() sessionId  %s\n" % ( className, methodName, sessionId ) )      
            self.__lfh.flush()
        #
        
        ############################################################################
        # create dictionary of content that will be used to populate HTML template
        ############################################################################
        myD={}
        myD['sessionid'] = sessionId
        myD['filesource'] = fileSource
        myD['content_type'] = contentType
        #
        
        myD['identifier'] = depId
        myD['session_url_prefix'] = self.rltvEdtrSessionPath
        
        tmpltFile = "msging_launch_commhstry_tmplt.html"
        #tmpltFile = "msging_launch_embed_tmplt.previewMsg.html" if embeddedVw.lower() == "true" else "msging_launch_tmplt.previewMsg.20150622.html"
            
        oL.append( self.processTemplate(tmpltPth=tmpltPath,fn=tmpltFile, parameterDict=myD) )
        #
        return oL
    
    def doRenderDisplayMsg( self, p_reqObj, p_bIsWorkflow, p_msgDict):
        ''' Render HTML used to display individual message
            
            :Params:
            
                + ``p_reqObj``: Web Request object
                + ``p_bIsWorkflow``: boolean indicating whether or not operating in Workflow Manager environment
                + ``p_msgDict``: dictionary representing individual message and associated data
                    
            :Returns:
                ``oL``: output list consisting of HTML markup
        '''        
        oL=[]
        #
        sessionId   =p_reqObj.getSessionId()
        wfInstId    =str(p_reqObj.getValue("instance")).upper()
        depId       =str(p_reqObj.getValue("identifier"))
        classId     =str(p_reqObj.getValue("classID")).lower()
        fileSource  =str(p_reqObj.getValue("filesource")).lower()
        contentType =str(p_reqObj.getValue("content_type")).lower()
        annotator   =str(p_reqObj.getValue("annotator"))
        initContentType =str(p_reqObj.getValue("init_content_type")).lower()
        tmpltPath   =p_reqObj.getValue("TemplatePath")
        embeddedVw =str(p_reqObj.getValue("embed"))
        msgsHighWatermark = str( p_reqObj.getValue("msgs_high_watermark") )
        notesHighWatermark = str( p_reqObj.getValue("notes_high_watermark") )
        #
        contentType = initContentType if( contentType is None or len(contentType) < 1 ) else contentType #if content type not specified then fall back on initial content type requested
        #
        if contentType is None or len(contentType) < 1 : #if we don't have a requested content type at this point, use "msgs" as default
           contentType = 'msgs' 
        #
        #if depId is not None:
        #    depId = self.__formatDepositionDataId(depId, p_bIsWorkflow) 
        #
        if (self.__verbose):
            self.__lfh.write("--------------------------------------------\n")
            className = self.__class__.__name__
            methodName = sys._getframe().f_code.co_name
            self.__lfh.write("Starting %s %s at %s\n" % (className,
                                                       methodName,
                                                       time.strftime("%Y %m %d %H:%M:%S", time.localtime())))
            self.__lfh.write("+%s.%s() identifier   %s\n" % ( className, methodName, depId ) )
            self.__lfh.write("+%s.%s() instance     %s\n" % ( className, methodName, wfInstId ) )
            self.__lfh.write("+%s.%s() file source  %s\n" % ( className, methodName, fileSource ) )
            self.__lfh.write("+%s.%s() sessionId  %s\n" % ( className, methodName, sessionId ) )      
            self.__lfh.flush()
        #
        
        
        annotator = "wwPDB annotator" if annotator.lower() == "dep" else annotator
        
        ############################################################################
        # create dictionary of content that will be used to populate HTML template
        ############################################################################
        myD={}
        myD['sessionid'] = sessionId
        myD['instance']   = wfInstId        
        myD['classid']    = classId
        myD['filesource'] = fileSource
        myD['content_type'] = contentType
        myD['annotator'] = annotator if( annotator is not None and len(annotator) > 1 ) else "wwPDB annotator"
        myD['cntnt_type_btnlbl'] = "Notes" if contentType == "notes" else "Messages"
        myD['new_cntnt_type_btnlbl'] = "Note" if contentType == "notes" else "Message"
        myD['msgs_high_watermark'] = msgsHighWatermark
        myD['notes_high_watermark'] = notesHighWatermark
        
        if contentType == "notes":
            myD['content_type_hdr'] = "Annotator Processing Notes"
        elif contentType == "msgs":
            myD['content_type_hdr'] = "Communications with Depositor"
        else:
            myD['content_type_hdr'] = "Complete Correspondence History"
        
        
        ############################################################################
        # get message template data from MessagingIo 
        ############################################################################
        msgingIo = MessagingIo(p_reqObj,self.__verbose,self.__lfh)
        msgingIo.getMsgTmpltDataItems(myD)
        
        
        if( len(p_msgDict['files_referenced']) > 0 ):
            htmlStr = ""
            for fileRef in p_msgDict['files_referenced']:
                filePathSplitArr = fileRef['relative_file_url'].split("/")
                fileName = filePathSplitArr[len(filePathSplitArr)-1]
                displayUploadFlName = " ("+fileRef['upload_file_name']+")" if len( fileRef['upload_file_name'] ) > 1 else ""; 
                htmlStr += '<p><span class=""><a href="'+fileRef['relative_file_url']+'" target="_blank">'+fileName+displayUploadFlName+'</a></span></p>';
        
            myD['files_rfrncd_dsply'] = ""
            myD['files_rfrncd'] = htmlStr
        else:
            myD['files_rfrncd_dsply'] = "displaynone"
            myD['files_rfrncd'] = ""
        
        if( len(myD['pdb_id']) > 1 ):
            myD['display_pdbid'] = ""
        else:
            myD['display_pdbid'] = "displaynone"
        
        # following params only for rcsb stand-alone version
        myD['caller'] = p_reqObj.getValue("caller")
        myD['filepath'] = p_reqObj.getValue('filePath')
        myD['filetype'] = p_reqObj.getValue('fileType')
        #
        if( p_bIsWorkflow ):
            myD['identifier'] = depId
        else:
            if not depId or len(depId) < 1:
                myD['identifier'] = 'D_000000'
            else:
                myD['identifier'] = depId
        #
        myD['session_url_prefix'] = self.rltvEdtrSessionPath
        myD.update(p_msgDict)
        
        self.__lfh.write("+%s.%s() p_msgDict is:  %r\n" % ( className, methodName, p_msgDict ) )
        self.__lfh.write("+%s.%s() myD is:  %r\n" % ( className, methodName, myD ) )
        
        tmpltFile = "display_msg_tmplt.html"
        oL.append( self.processTemplate(tmpltPth=tmpltPath,fn=tmpltFile, parameterDict=myD) )
        #
        return oL    
    
    ######## BEGIN -- Specific to DataTable Implementation ##################
    'NOTE: consider encapsulating DataTable functionality as separate class'
    
    def getJsonDataTable( self, p_msgRcrdList, p_msgColList, p_iDisplayStart=0 ):
        ''' Generate contents of json object expected by DataTables for populating display
            table with data.
            
            :Params:
            
                + ``p_msgColList``: list of column names, sequence of which corresponds to 
                                    sequence of fields in each record in p_recordList 
                + ``p_msgRcrdList``: list of records corresponding to messages obtained from database for given deposition dataset
                + ``p_iDisplayStart``: DataTable parameter used by the plugin as index of first record in set actually being displayed on screen
                
                    
            :Returns:
                ``rtrnDict``: dictionary of records for display on screen as complies with DataTables requirements for JSON object it expects from server
        '''                        
        rtrnDict = {}
        
        sColumns = ""
        sComma = ""
        for idx,colName in enumerate(p_msgColList):
            if idx > 0 : sComma = ","
            sColumns += sComma + colName
        #
        rtrnDict['sColumns'] = sColumns
        
        aaDataList = self.__createDataTableAaDataList( p_msgColList, p_msgRcrdList, p_iDisplayStart )
        rtrnDict['aaData'] = aaDataList
        
        return rtrnDict

    def getDataTableTemplate( self, p_reqObj ):
        '''
            For given deposition data set ID, obtain "staging" components to be used in 
            preparation for loading webpage with DataTable for display of text messages:
                    +  html skeleton template which will be populated by jQuery DataTables plugin
            
            :Params:
            
                + ``p_reqObj``: Web Request object
                    
            :Returns:
                ``mrkpList``: output list consisting of HTML markup serving as skeleton starter template for DataTable
                ``dtblConfigDict``: dictionary of display, config settings for DataTable
                
        '''
        className = self.__class__.__name__
        methodName = sys._getframe().f_code.co_name
        dtblConfigDict = {}
        depId = p_reqObj.getValue("identifier")
        sContentType = p_reqObj.getValue("content_type")
        bCommHstryRqstd = True if sContentType == "commhstry" else False
        #
        msgingIo = MessagingIo(p_reqObj,self.__verbose,self.__lfh)
        bOk, msgColList = msgingIo.getMsgColList(bCommHstryRqstd)
        #
        if( bOk ):           
            ###############################################################################################
            # FIRST: obtain any config info needed by DataTables plugin 
            ###############################################################################################
            # provide DataTables "aoColumns" property to be used in initializing the DataTable 
            # with set of known column names so that it can use for keeping track of column ordering  
            sNameList = []
            for colName in msgColList:
                newSnameDict = {}
                newSnameDict["mDataProp"] = colName 
                sNameList.append(newSnameDict)
            #
            dtblConfigDict['DTBL_AOCOLUMNS'] =  sNameList
                       
            ###############################################################################################
            # SECOND: generating HTML that serves as markup starter skeleton to be populated by DataTables 
            ###############################################################################################
            mrkpList = []
            srchHdrList = [] #separate list of markup representing <th> elements accommodating individual column search filtering
            
            # Create dictionary of user-friendly labels for columns where desired.
            # The actual cif attribute name serves as the key and corresponding value is the friendlier label 
            dictColDisplNames={}
            dictColDisplNames['message_subject']="Subject"
            dictColDisplNames['sender']="Sender"
            dictColDisplNames['timestamp']="Date/Time"
            dictColDisplNames['orig_timestamp']="Orig Date/Time"
            dictColDisplNames['orig_sender']="Orig Sender"
            dictColDisplNames['orig_recipient']="Orig Recipient"
            dictColDisplNames['orig_message_subject']="Orig Subject"
            dictColDisplNames['orig_attachments']="Orig Attachments"
            
            #mrkpList.append('<table id="'+depId+'_tbl" width="100%"><thead><tr>')  #needed for targeted update to Bootstrap based UI?
            mrkpList.append('<table id="'+depId+'_tbl"><thead><tr>')
                        
            for idx,cifAttribNm in enumerate(msgColList):
                # here we impose more user-friendly labels for column headers
                # as we iterate through each cif attrib name in the list of msging columns
                # we check dictionary--> if cif attrib name is a key that has corresponding value for user friendly label,
                # then that user-friendly label is used as the colName, otherwise the cifAttribName itself is used as the colName by default 
                colName = dictColDisplNames.get( cifAttribNm, cifAttribNm )
                
                mrkpList.append('<th class="cifitemhdr">'+colName+'</th>')
                
                srchHdrList.append('<th><input type="text" name="search_'+str(idx)+'" placeholder="Search '+colName+'" class="search_init" /></th>')
                
            mrkpList.append('</tr><tr class="srch_hdrs displaynone">')
            mrkpList.append( ''.join(srchHdrList) )
            
            
            mrkpList.append('</tr></thead><tbody></tbody></table>') # 2015-04-16, RPS removed <tr></tr> as this was causing DataTables 1.10.5 to choke
            
            dtblConfigDict['ORDERED_COLUMN_LIST'] = msgColList
            dtblConfigDict['MSGS_ALREADY_READ'] = msgingIo.getMsgReadList(depId)
            dtblConfigDict['MSGS_NO_ACTION_REQD'] = msgingIo.getMsgNoActionReqdList(depId)
            dtblConfigDict['MSGS_FOR_RELEASE'] = msgingIo.getMsgForReleaseList(depId)
            dtblConfigDict['NOTES_MSG_IDS'] = msgingIo.getNotesList()
                
        else:
            mrkpList = ["<h3>Problem getting messages for this deposition dataset.</h3>" ]
        # RETURNing both markup and config dictionary
        return mrkpList, dtblConfigDict
    

    
    ######## END -- Specific to DataTable Implementation ##################
    
                
    ######################################   HELPER FUNCTIONS   #################################################
    
    ######## BEGIN -- Specific to DataTable Implementation ##################
    def __createDataTableAaDataList( self, p_colList, p_recordList, p_iDisplayStart  ):
        ''' Generate contents of "aaData" json object expected by DataTables for populating display
            table with data.
            
            :Params:
            
                + ``p_colList``: list of column names corresponding sequence of which corresponds to 
                                    sequence of fields in each record in p_recordList 
                + ``p_recordList``: list of records corresponding to messages obtained from database for given deposition dataset
                + ``p_iDisplayStart``: DataTable parameter used by the plugin as index of first record in set actually being displayed on screen
                
                    
            :Returns:
                ``rtrnLst``: list of records for display on screen as complies with DataTables requirements for "aaData" object
        '''                
        className = self.__class__.__name__
        methodName = sys._getframe().f_code.co_name
        if (self.__verbose):
            self.__lfh.write("--------------------------------------------\n")
            self.__lfh.write("Starting %s.%s() at %s\n" % (className,
                                                       methodName,
                                                       time.strftime("%Y %m %d %H:%M:%S", time.localtime())))
        rtrnLst = []
        
        for indx,row in enumerate( p_recordList ):
            if (self.__verbose and self.__debug ): 
                self.__lfh.write("+%s.%s() -- row[%s] is: %s\n" %(className,
                                                                            methodName,
                                                                            indx,
                                                                            row) )
            #
            newRecordJsonObj = {}
            #
            if type(row) is list or type(row) is tuple:
                newRecordJsonObj['DT_RowId'] = "row_"+str(indx+p_iDisplayStart)
                newRecordJsonObj['DT_RowClass'] = "dt_row"
            elif type(row) is dict:
                # if the row is itself a dictionary, indicates that search filtering was done
                # and so dict here will consist of single entry where the "key" represents true  
                # row index of the cif record as it sits in persistent store and "value" is the cif record itself
                key,value = row.popitem()
                newRecordJsonObj['DT_RowId'] = "row_"+str(key+p_iDisplayStart)
                newRecordJsonObj['DT_RowClass'] = "dt_row"
                row = value
            #
            for colName, rcrdValue in ( zip(p_colList,row) ):
                newRecordJsonObj[colName] = str(rcrdValue)
            #
            rtrnLst.append(newRecordJsonObj)
        
        return rtrnLst    
            
    ######## END -- Specific to DataTable Implementation ##################       
        
    def setSessionPaths(self,p_reqObj):
        ''' Establish absolute/relative paths to be used for storing/accessing session-related data
        
            :Params:
                ``p_reqObj``: Web Request object
        '''
        sessionMgr = p_reqObj.getSessionObj()
        
        #### absolute paths ####
        absSessPth = sessionMgr.getPath()
        # absolute path used for referencing session directory content from front end
        self.absltSessionPath = absSessPth
        
        
        #### relative paths #####
        rltvSessPth = sessionMgr.getRelativePath()
        # relative path used for referencing session directory content from front end
        self.rltvSessionPath = rltvSessPth
        
        
    def isWorkflow(self,p_reqObj):
        """ Determine if currently operating in Workflow Managed environment
        """
        #
        fileSource  = str(p_reqObj.getValue("filesource")).lower()
        #
        if fileSource in ['archive','wf-archive','wf_archive','wf-instance','wf_instance']:
            #if the file source is any of the above then we are in the workflow manager environment
            return True
        else:
            #else we are in the standalone dev environment
            return False

    def processTemplate(self,tmpltPth,fn,parameterDict={}):
        """ Read the input HTML template data file and perform the key/value substitutions in the
            input parameter dictionary.
        """
        fPath=os.path.join(tmpltPth,fn)
        ifh=open(fPath,'r')
        sIn=ifh.read()
        ifh.close()
        return (  sIn % parameterDict )
    
    def truncateForDisplay(self,content,maxlength=20,suffix='...'):
        """ Obtain truncated version of long identifiers for display purposes (e.g. in comparison panel)
        """
        if not ( content is None ):
            if len(content) <= maxlength:
                return content
            else:
                return content[:maxlength+1] + suffix
        else:
            return ''
        
    def __formatDepositionDataId(self,p_depid,p_bIsWorkflow):
        if( p_bIsWorkflow or ( p_depid.upper() == 'TMP_ID' ) ):
            depId = p_depid.upper()
        else:
            depId = p_depid.lower()
        return depId
