Skip to main content

HS.SDA3.AbstractQuickXMLLogger

abstract class HS.SDA3.AbstractQuickXMLLogger extends %Library.RegisteredObject

Abstract class for logging functionality within HS.SDA3.QuickXML::XMLImportSDAString(). Such a logger is necessary because XMLImportSDAString will silently drop SDA data during inbound ingestion. In particular, XMLImportSDAString drops the data when the raw XML Character Stream is deserialized into its corresponding HS.SDA3.* objects.

To use this logger:
  1. Add a new class which extends this class (HS.SDA3.AbstractQuickXMLLogger)
  2. In your new class, implement all of the methods that are marked as Abstract in HS.SDA3.AbstractQuickXMLLogger.

Background

HealthShare SDA Pipeline Overview

The process by which a piece of foreign healthcare data gets stored in HealthShare is as follows:
  1. Foreign Healthcare Data -> XML Character Stream (serialized representation of HS.SDA3.Container).
    This happens via a tranformation method (for example, for HL7v2 data, the transformation method is HS.Gateway.HL7.HL7ToSDA3::GetSDA()).

  2. XML Character Stream -> empty HS.SDA3.Container.
    This happens by %New-ing an empty HS.SDA3.Container object, and then calling HS.SDA3.Container::InitializeXMLParse(), which sets the container objects StreamOref property to the XML Character Stream.

    IMPORTANT: At this point the XML Character Stream has still not been serialied into its appropriate HS.SDA3.*. objects. That happens in step 3. Here, we have simply created an empty HS.SDA3.Container object to hold the XML Character Stream in order for it to be deserialized in step 3 (via HS.SDA3.Container::GetNextSDA()).

  3. HS.SDA3.Container -> HS.SDA3.* objects.
    This happens via HS.SDA3.Container::GetNextSDA().

  4. HS.SDA3.* -> HS.SDA3.Streamlet.* objects via %New-ing the appropriate HS.SDA3.Streamlet.* object setting the object's SDA property to its corresponding HS.SDA3.* object, and then calling SaveStreamlet() on the streamlet object.

Example of SDA Pipeline - HL7v.2

The section "SDA Pipeline Overview" above applies to all foreign data for HealthShare, but we will give a more detailed overview of the Pipeline for HL7v2 for concreteness.
  1. HL7v2 -> XML CharacterStream via HS.Gateway.HL7.HL7ToSDA3::GetSDA
  2. XML CharacterStream --> Empty HS.SDA3.Container object
  3. HS.SDA3.Container -> HS.SDA3.* objects via HS.SDA3.Container::GetNextSDA()
  4. HS.SDA3.* -> HS.SDA3.Streamlet.* objects

Where Data is Lost

Steps 1. and 2. of "HealthShare SDA Pipeline Overview" are where the most SDA data is droppped. In particular during the deserailization of the XML character stream to the corresponding SDA objects in HS.SDA3.Container::GetNextSDA(), the data for each XML property is dropped in HS.SD3.QuickXML::XMLImportSDAString() when this method invokes the deserialized objects property's XSDToLogical methods.

References

  1. To see this pipeline for yourself and to understand why this logger is necessary run
    ##class(HS.Util.Installer).InstallDemo()
  2. Then in an EDGE namespace, go to the production. Notice how the operation for HL7v2 messages is HS.Gateway.HL7.InboundProcess.
    Notice then how this class converts an HL7v2 message into its XML Character Stream via HS.Gateway.HL7.HL7ToSDA3::GetSDA.
      // Convert the HL7 message to SDAXML
      Set tSC=$ClassMethod(..HL7ToSDA3Class,"GetSDA",pRequest,.tSDAXML ,..LogHL7Alerts,..ObservationCompatibilityMode,..MultiLineOBXCompatibilityMode, ..KeepDuplicateOBXIdentifiers)
      $$$HSTRACE("SDA3","tSDAXML",tSDAXML)
      Quit:$$$ISERR(tSC)
      
  3. Then, notice then how all operations go to HS.Gateway.ECR.Manager.
    Notice that the ingestion method for this class is HS.Gateway.ECR.Manager::UpdateStreamletECR(). Then, read UpdateStreamletECR and see how it:
    1. Creates an empty HS.SDA3.Container
    2. Calls HS.SDA3.Container::InitializeXMLParse() on the HS.SDA3.Container object, which sets the object's StreamOref property to the XML Character Stream.
    3. Deserializes the XML CharacterStream into its set of HS.SDA3.* objects by calling HS.SDA3.Container::GetNextSDA().
    4. Inspect GetNextSDA() and understand how it invokes the HS.SDA3.QuickXML::XMLImportSDAString().
    5. Notice how during deserialization HS.SDA3.QuickXML::XMLImportSDAString() calls its property's XSDToLogical methods.
    6. Notice how in some of these XSDToLogical methods, the property is set to null if the data is invalid. For an example, view HS.Types.TimeStamp::XSDToLogical() and see how it sets the data to null if the format is not met.

Instrutions

Simply follow these steps to use this logging functionality.
  1. Create a new class that extends this class (HS.SDA3.Debuggers.AbstractQuickXMLLogger).
  2. Implement the Abstract methods.
  3. Get an instance of your logger.
  4. In the same OS process, run data through HS.Gateway.ECR.Manager.
  5. Your logger will now be getting invoked. From there use your logger how ever you want to.

Example

The following is an example to show you would use this logger.

HS.Local.FeedOnboarding.Edge.cls

Class HS.Local.FeedOnboarding.Edge Extends HS.SDA3.Debuggers.AbstractQuickXMLLogger 
{
Property CurrentLocation As %String;
Property Errors;

/// Records opening tags to keep track of where we are in SDA deserializer
/// <var> pSDAClass </var> - current SDA API Object type being deserialized
/// <var> pTag </var> - Current open tag being opened.
Method OnOpenTag(pSDAClass As %Dictionary.Classname, pTag As %String)
{
   // Push tag to keep track of where we are, close tag will be where \
   // we track lost values
   Set ..CurrentLocation = ..CurrentLocation _"/"_pTag
}

/// Hook that is invoked when an particular XML value is being set on an SDA object 
/// during deserialiation. 
/// This hooks tells us the original XML value and the new corresponding SDA object property value. 
/// Usually these values are the same, but we want to flag where they are droppped.
/// <var> pSDAClass </var> - The Current SDA API Object type being deserialized
/// <var> pTag </var> - The current tag being deserialized. 
/// Not necessary as you should use <method>OnOpenTag</method>, and <method>OnCloseTag</method>
/// to keep track of where you are in the XPath.
/// <var> pXMLValue </var> - The value that <var>pTag</var> surrounds in in the XML Character stream.
/// <var> pSDAValue </var> - The value that the current SDA API Object property took on.
/// Usually pSDAValue is equal to pXML value, but not always.
Method ONValue(pSDAClass As %String, pTag As %String, pXMLValue As %String, pSDAValue As %String)
{
  // We say there is an error when the original value is transformed/normalized, to the SDA property value.
  if (pXMLValue '= pSDAValue) {
    Set ^Errors($INCREMENT(^ERRORS))  = $LISTBUILD(..CurrentLocation, pXMLValue, pSDAValue)
    Return
  }
}

/// Records closing tags to keep track of where we are in SDA deserialization 
/// <var> pSDAClass </var> - The Current SDA API Object type being deserialized
/// <var> pTag </var> - The current tag being closed.
Method OnCloseTag(pSDAClass As %String, pTag As %String)
{
  // Pop the tag
  set ..CurrentLocation = $PIECE(..CurrentLocation, "/", 1, *-1)
}
}

HS.Local.FeedOnBoarding.Test.cls

Class HS.Local.FeedOnBoarding.Test
{
ClassMethod TestAnHL7File() {
  // get an instance of our logger object
  set logger = ##class(HS.Local.FeedOnboarding.Edge).%New()
  // load an hl7 message from disk
  set path = "C:\Temp\hl7-messages\message-1.hl7"
  set message = ##class(EnsLib.HL7.Message).ImportFromFile(path)
  // Run message through HealthShare
  // ...
  // ...
  // Inspect logger
  Set errorIdx = $ORDER(logger.Errors("")) 
  While errorIdx '= "" {
    Write logger.Errors(errorIdx), !
    set errorIdx = $ORDER(logger.Errors(errorIdx)) 
  }
} 
}
Then on terminal do
USER > Do ##class(HS.Local.FeedOnboarding.Test).TestAnHL7File()

Method Inventory

Methods

final method %OnNew() as %Status
If the OS process current has an XMLImportSDAString logger, a new logger cannot be created before calling TerminateLogging() This way makes it clearer exactly whose object is doing the logging if there are multiple implementations of this class.
abstract method OnCloseTag(pSDAClass As %String, pTag As %String)
Records closing tags to keep track of where we are in SDA deserialization pSDAClass - The Current SDA API Object type being deserialized pTag - The current tag being closed.
abstract method OnOpenTag(pSDAClass As %Dictionary.Classname, pTag As %String)
Records opening tags to keep track of where we are in SDA deserializer.
pSDAClass - current SDA API Object type being deserialized.
pTag - Current open tag being opened.
abstract method OnValue(pSDAClass As %String, pTag As %String, pXMLValue As %String, pSDAValue As %String)
Hook that is invoked when an particular XML value is being set on an SDA object during deserialiation. This hooks tells us the original XML value and the new corresponding SDA object property value. Usually these values are the same, but we want to flag where they are droppped.
pSDAClass - The Current SDA API Object type being deserialized.
pTag - The current tag being deserialized. Not necessary as you should use OnOpenTag(), and OnCloseTag() to keep track of where you are in the XPath.
pXMLValue - The value that pTag surrounds in in the XML Character stream.
pSDAValue - The value that the current SDA API Object property took on. Usually pSDAValue is equal to pXML value, but not always.
final classmethod TerminateLogging() as %Status
Terminate Logging By killing the %-variable

Inherited Members

Inherited Methods

FeedbackOpens in a new tab