Monday, October 29, 2012

Everest for Windows Phone 7–WCF Connector

Well, as I mentioned in my previous post, Everest is here for WP7. Currently we’re still sorting out some bugs but the first bit of code is available in the 1.1 branch on our public SVN.

So, what can you do with Everest for WP7? Well, pretty much anything you’d like to do with HL7v3. I made a sample application that will hit the MARC-HI client registry and shared health record to demonstrate how the WCF Connector works in the mobile version of Everest (it is slightly different than the full fledged desktop version).

We’re going to be making this app:

image

Create a simple Windows Phone 7 application in Visual Studio 2010 (like you normally would) and add these references to your project:

  • MARC.Everest.Phone.dll
  • MARC.Everest.Phone.Connectors.WCF.dll
  • MARC.Everest.Phone.Formatters.XML.ITS1.dll
  • MARC.Everest.Phone.Formatters.Datatypes.R1.dll
  • MARC.Everest.RMIM.CA.R020402.Phone.dll

First, we’ll need to create a ServiceReferences.ClientConfig file and set its build action to Content. In that file, we’ll configure the WCF client configuration (similar to the Everest desktop version):

<configuration>
    <system.serviceModel>
        <bindings>
            <basicHttpBinding>
                <binding name="hl7v3Binding" maxBufferSize="2147483647"
                    maxReceivedMessageSize="2147483647">
                    <security mode="None" />
                </binding>
            </basicHttpBinding>
        </bindings>
        <client>
            <endpoint address="http://cr.marc-hi.ca:8080/cr" binding="basicHttpBinding"
                bindingConfiguration="hl7v3Binding"
                contract="MARC.Everest.Connectors.WCF.Core.IConnectorContract" name="ClientRegistry" />
        <endpoint address="http://shr.marc-hi.ca:8080/shr" binding="basicHttpBinding"
            bindingConfiguration="hl7v3Binding"
            contract="MARC.Everest.Connectors.WCF.Core.IConnectorContract" name="SHR" />
      </client>
    </system.serviceModel>
</configuration>

Note that WCF for the phone platform only supports basicHttpBinding and customBinding (no wsHttpBinding so SOAP 1.2 will need adjustments).


Next, we’ll initialize two connectors: one for the client registry and one for the shared health record. Because of the way that the WCF connectors work on Silverlight mobile (async only) you’re going to be using BeginSend and EndSend methods so these methods are fields for the Main.xaml.cs class:

private WcfClientConnector m_clientRegistryConnector;
private WcfClientConnector m_sharedHealthRecordConnector;

Next, we’re going to need a shared reference to the resolved patient identity received from the client registry, this is done via another field:

private MARC.Everest.RMIM.CA.R020402.PRPA_MT101104CA.IdentifiedEntity m_patientIdentification;

Since the WCF client connector will be sending the message asynchronously, meaning the actual sending is done on a separate thread. When the result callback is executed, it will be in the context of the thread that sent the message and not the thread that the UI is running on, we need to use the dispatcher to update the UI. Let’s declare a delegate to perform the UI update:

delegate void MessageSentDelegate(IGraphable connector);

Now, we can initialize the WCF connectors on the main page. I’ve done this in the constructor after InitializeComponent() is executed (which is hackish, but this is a tutorial after all).

// Constructor
public MainPage()
{
     InitializeComponent();
    m_clientRegistryConnector = new WcfClientConnector("endpointname=ClientRegistry");
    m_clientRegistryConnector.Formatter = new XmlIts1Formatter() { ValidateConformance = false };
    m_clientRegistryConnector.Formatter.GraphAides.Add(new DatatypeFormatter());
    m_clientRegistryConnector.Open();
    m_sharedHealthRecordConnector = new WcfClientConnector("endpointname=SHR");
    m_sharedHealthRecordConnector.Formatter = new XmlIts1Formatter() { ValidateConformance = false };
    m_sharedHealthRecordConnector.Formatter.GraphAides.Add(new DatatypeFormatter());
    m_sharedHealthRecordConnector.Open();
    ResolveClientIdentifiers();
}

Our resolve client identifiers code will execute a client registry lookup to translate our local identifier (I’m using 000-123-456) and get additional information about the client (their name, address, etc.). Here is the code:

private void ResolveClientIdentifiers()
{
    PRPA_IN101103CA instance = new PRPA_IN101103CA(
        Guid.NewGuid(),
        DateTime.Now,
        ResponseMode.Immediate,
        PRPA_IN101103CA.GetInteractionId(),
        PRPA_IN101103CA.GetProfileId(),
        ProcessingID.Production,
        AcknowledgementCondition.Always,
        new MARC.Everest.RMIM.CA.R020402.MCCI_MT002200CA.Receiver(
            null,
            new MARC.Everest.RMIM.CA.R020402.MCCI_MT002200CA.Device2(
                new II("1.3.6.1.4.1.33349.3.1.1.2", "CR")
            )
        ),
        new MARC.Everest.RMIM.CA.R020402.MCCI_MT002200CA.Sender(
            new MARC.Everest.RMIM.CA.R020402.MCCI_MT002200CA.Device1(
                new II("1.3.6.1.4.1.33349.3.1.1.20.4", "MARC-W1-1")
            )
        ),
        new MARC.Everest.RMIM.CA.R020402.MFMI_MT700751CA.ControlActEvent<MARC.Everest.RMIM.CA.R020402.PRPA_MT101103CA.ParameterList>(
            Guid.NewGuid(),
            PRPA_IN101103CA.GetTriggerEvent(),
            new MARC.Everest.RMIM.CA.R020402.MFMI_MT700711CA.Author(
                DateTime.Now
            ),
            new MARC.Everest.RMIM.CA.R020402.QUQI_MT120008CA.QueryByParameter<MARC.Everest.RMIM.CA.R020402.PRPA_MT101103CA.ParameterList>(
                Guid.NewGuid(),
                new MARC.Everest.RMIM.CA.R020402.PRPA_MT101103CA.ParameterList()
                {
                    ClientId = new List<MARC.Everest.RMIM.CA.R020402.PRPA_MT101103CA.ClientId>() {
                        new MARC.Everest.RMIM.CA.R020402.PRPA_MT101103CA.ClientId(
                            new II("1.3.6.1.4.1.33349.3.1.3.12", "000-123-456")
                        )
                    }
                }
            )
        )
        {
            EffectiveTime = new IVL<TS>(DateTime.Now)
        }
    );
    instance.ProfileId[0].Extension = "R02.04.02";


    instance.controlActEvent.Author.SetAuthorPerson(new MARC.Everest.RMIM.CA.R020402.COCT_MT090502CA.AssignedEntity(
        new MARC.Everest.RMIM.CA.R020402.COCT_MT090102CA.Organization(
            new II("1.2.840.114350.1.13.99998.8734"),
            "Good Health Hospital"
        )
    ));
     var retVal = m_clientRegistryConnector.BeginSend(instance, this.CrConnectorCallback, null);
           

}


You will notice that the callback for the BeginSend method is a callback delegate CrConnectorCallback. This method will be executed on the thread that the message was processed on, and will use the Dispatcher object to update the UI:

private void CrConnectorCallback(IAsyncResult result)
{
           
    var sendResult = this.m_clientRegistryConnector.EndSend(result);
    var res = this.m_clientRegistryConnector.Receive(sendResult);
    this.Dispatcher.BeginInvoke(
            new MessageSentDelegate(this.MessageSentResult),
            new object[] { res.Structure });
           
}

This callback executes the MessageSendResult delegate (of type MessageSentDelegate that we created earlier). This updates the UI to include the patient’s name in the Application Text area.

private void MessageSentResult(IGraphable message)
{
     if(message is PRPA_IN101104CA)
    {
        var msg = message as PRPA_IN101104CA;
        if(msg.Acknowledgement.TypeCode != AcknowledgementType.ApplicationAcknowledgementAccept)
            return;
        var rreg = msg.controlActEvent.Subject[0].RegistrationEvent.Subject.registeredRole;
        this.m_patientIdentification = rreg;
        this.ApplicationTitle.Text = rreg.IdentifiedPerson.Name[0].ToString("{FAM}, {GIV}");
        ExecuteGetSummary();
    }
}

The result of this will be an app that, a few seconds after loading, will display the name of patient 000-123-456 in the application text bar.


image


In my next post, I’ll show you how to get the clinical summary using COMT_IN100000CA messages with query continuation (a very useful construct on the phone).

Saturday, October 27, 2012

Everest for Windows Phone 7.1

Here at the Everest team we’ve been so busy building the Java version of Everest (jEverest) that I haven’t had a chance to discuss another exciting Everest feature, Windows Phone 7 Silverlight libraries.

Basically we’ve done some cross-compiling in Visual Studio to remove the features of Everest that don’t work on the mobile platform, affected features are:

  1. Code generator formatter is disabled since the mobile platform doesn’t support code-dom or runtime code generation.
  2. The only connector supported is the WCF client connector
  3. XML ITS1 and Data Types R1 is currently only supported in the phone assemblies.

The current 1.1 unstable branch of Everest contains the Everest Phone code in the api.phone.sln code. As with all code in the unstable branch it is untested and your mileage may vary.

Ciao

Sunday, October 21, 2012

jEverest Bubbling Away

Well, it has been a busy week and the Everest team (mostly yours truly) has been busy churning the jEverest code. I’m pleased to report that there has been lots of progress made as far as core functionality goes.

We have a basic core library! That’s right there is a working copy of ca.marc.everest.jar containing about 85% of the HL7v3 data-types to Java. Not all the methods are fully fleshed out but structurally everything is there (no jUnit tests yet, volunteers?). The core library is at a point where it can be used with the latest unstable GPMR to generate Java classes for RMIMs.

Wait, did you say RMIMS? Why yes I did! Right now the latest unstable version of GPMR will generate RMIM jarchives for Canadian release 2.04.01, 2.04.02 and 2.04.03 (just trying out the NE2008 one right now). You can’t really do much with these until the other infrastructure components come online, namely the formatters and formatter utilities.

There is still a long way to go but for those wishing to see what Everest RMIM JARs will look like can download the latest unstable at: https://fisheye.marc-hi.ca/svn/Everest/branches/1.1/ (un: Guest). The copy of GPMR will render Java (after being compiled) and executing:

gpmr -c -r RIMBA_JA -o C:\java\r02.04.01  --quirks --rimbapi-gen-vocab=true --rimbapi-compile=true --optimize=true --combine=true --rimbapi-profileid=R02.04.01 --rimbapi-target-ns=ca.marc.everest.rmim.ca.r020401 --rimbapi-license=apache --rimbapi-org="Health Level Seven" --rimbapi-jdk="C:\Program Files (x86)\Java\jdk1.6.0_26" --rimbapi-jdoc=true  .\CA\R02.04.01\*.*mif

After a few minutes GPMR will spit out a complete Eclipse project (it will also attempt to compile the resulting files).


Source code for the core jEverest file can be found in the jEverest folder.

Wednesday, October 10, 2012

Java Everest (jEverest) Update

I was looking through some old Everest code on the trunk and 1.0 branch about a month ago and realized that the jEverest project had stalled. I was trying to think of a killer new feature for Everest 1.2 and I hadn’t realized the answer was staring me right in the face this whole time.

jEverest is a Java version of Everest which was intended to be completed by January of this year, however because of more pressing issues it was dropped in favour of other work (such as the Client Registry reference implementation and Shared Health Record implementations). But not anymore. We’ve made some really good progress on the datatypes and supporting classes and with our public SVN server you can take a look!

jEverest is in the 1.1 branch (1.2 development) of the Everest project on the server. So far we have completed a port of the majority of data types and some enhancements to the GPMR java renderer. Hopefully we’ll have something to demonstrate by HL7 WGM in January but I wouldn’t expect a release until later in 2013.

Anyone who has talked to me (in person) knows that I’m not the biggest fan of the Java platform (IMO C# and .NET is much better) however it is not nearly as terrible as I had remembered it. Java 1.7 does have some nice features (like strings in switch cases, finally!) however I do have some legitimate complaints about Java development (just observations, don’t want to start a war)

  1. Eclipse : Nice IDE, lots of features and bells and whistles however I’d much rather prefer an IDE which has less features but does them properly. For example:
    • Subclipse isn’t the best when you’re in the “Java perspective”, it simply wraps the SVN command line and places conflict markers directly in your code which I then have to resolve by “Right Click -> Open in Conflict Editor”. Why? Maybe I’m just used to VisualSVN.
    • Why are there two places to get Eclipse extensions? Some extensions I have to visit “Help -> Install New Software” and others “Help -> Eclipse Marketplace”. There should be one place to get extensions.
    • On the topic of extensions, my major complaint with Eclipse is the plethora of options. Everywhere I look, options options options! The context menus are huge and cluttered with options.
  2. Type Erasure : By far, this is my number one pet-peeve with Java. The fact that LIST<REAL> at runtime is indistinguishable from LIST<INT>. This causes lots of problems especially since the majority of data types use generics. Every time I implement a conversion method or validation method that really does need to know the type of a generic I can’t (or I have to use some hack)
  3. Closures : Believe it or not, this actually does matter in jEverest, especially when searching a collection. In Everest all collections have a Find() method (for example, to find all numbers in a list larger than a given number : for(int i = 0; i < 10; i++) numbers.FindAll(o=>o > i);). This can be a problem jEverest and to get around it I have to use a custom IPredicate / Predicate implementation. (contrast the previous Everest example with jEverest : for(int i=0; i<10; i++) numbers.findAll(new Predicate<INT>(i) { public bool match(INT c) { return c > this.getScopeValue(); } }); )

Overall these aren’t showstoppers, just require some clever trickery to make compatible solutions. The idea behind jEverest isn’t to be 100% code compatible with the .NET version, but to be a meaningful port of the functionality.

I’ll end on a positive note: Java enumerations are really cool and much better than C#/.NET. Being a class means that enumerations in Java can implement interfaces and I’ve taken full advantage of this for representing enumerated vocabularies (via IEnumeratedVocabulary) and hierarchical vocabularies (via IHierarchicEnumeratedVocabulary).

Keep your eyes open, jEverest is coming!