Simple tutorial for saving and loading data

Introduction

This tutorial makes use of a single Java class which saves some data using one of the observation archetypes released with Opereffa.

The Java class, ArchetypeSaveLoadExample.java is included in Opereffa source code. Please check out the lates code from SVN, for source release packages are not always up to date with the SVN server.

You can find the source code here if you want to see what it looks like. The class does something very simple. It loads an archetype in ADL form from disk, and inserts data using the Archetype Wrapper, which is main class for managing archetype related operations in Opereffa.

After data is inserted, it is saved into database, and later the saved values are loaded from db, and printed on the screen. The class has a main method, so you can simply run it to see the results.

Setup

This tutorial assumes that you have the Eclipse environment or your favorite development environment at your disposal. If you are not using Opereffa Source, then you'll need references to openEHRRefImpExtensions project, which in turn has references to openEHR Java Reference Implementation jars and Hibernate Layer project. Since there are quite a lot of libraries that depend on each other, you are adviced to get the source code from SVN. Otherwise you'll have to configure dependencies on your own, say for example if you have downloaded only the binary release. The Live DVD environment is connected to SVN server, so all you have to do is to get the latest source code, and you should be ready to go.

For those using the source of binary setup, the postgresql database must be installed before running the example, and necessary scripts to create the table is under dbscripts folder in source code. Again, the Live DVD has this setup prepared for you.

The Example

The following piece of code is pretty self explanatory, which shows how an Archetype wrapper is used. Let's first see the part where data is created and saved:

            ArchetypeSaveLoadExample testInstance = new ArchetypeSaveLoadExample();
            ArchetypeWrapper wrapperToSaveData = testInstance.loadArchetypeFromFile("C:\\archetypeRepository\\openEHR-EHR-OBSERVATION.SOAP_Clerking8.v8.adl");
            testInstance.setValuesOfClerkingArchetype(wrapperToSaveData);
            testInstance.saveClerkingArchetypeToDB(wrapperToSaveData);

First a wrapper is created via reading an ADL file from the disk. Then actual values of the RM class instances are set with

testInstance.setValuesOfClerkingArchetype(wrapperToSaveData);

Let's see what this method looks like:

        public ArchetypeWrapper setValuesOfClerkingArchetype(ArchetypeWrapper pArchetypeWrapper) throws Exception{
        //responsibleConsultant       
        String respConsNodePath = "/data[at0001]/events[at0002]/data[at0003]/items[at0007]/items[at0004]/value";
        pArchetypeWrapper.getElementAtNode(respConsNodePath).setText("this is responsible consultant");
        //clerking doctor
        String clerkingDoctorNodePath = "/data[at0001]/events[at0002]/data[at0003]/items[at0007]/items[at0005]/value";
        pArchetypeWrapper.getElementAtNode(clerkingDoctorNodePath).setText("clerking doctor is...");
        //source of referral
        String sourceOfReferralNodePath = "/data[at0001]/events[at0002]/data[at0003]/items[at0007]/items[at0042]/value";
        pArchetypeWrapper.getElementAtNode(sourceOfReferralNodePath).setText("source of referral value here");
...

As you can see, this method simply assigns values using the paths of archetype nodes. These are simple text values, so single path, value assignments is all it takes. In case of a quantity or an element with multiple selection of codes, this would be a different code, though basics would stay the same.

You may say that this is not such a practical method of writing code, especialy when you have to get the full path of the node you want to use. You are right. In its web interface for example, Opereffa uses these nodes with a special markup and those artifacts are not generated by hand (XHTML files of JSF Facelets).

This is why Opereffa has a tooling branch, which generated these boring bits of necessary coding. At the moment, a class named ParserTestForm is used for tooling. If you run this class, you'll see that it lets you select adl files and it generates necessary files to embed into web interface. Current work in Eclipse tooling provides functionality like this, so that you won't loose time during actual development.

After this method is executed, we now have an archetype wrapper with RM instances created, and linked to their relevant archetype nodes which are in the form of AOM objects. The wrappers link AOM instances and RM instances together. When setText method is called on the DataValueWrapper, (a TextValueWrapper in this case) , createRMInstance method is called. This is where a value is attached to a newly created RM instance, and the instance is kept in the wrapper. Here is the code from TextValueWrapper:

protected void createRMInstance(String pValue) throws RMObjectBuildingException{
        String type = "DvText";
        Map<String, Object> values = new HashMap<String, Object>();
        String value = pValue;
        values.put("value", value);
        RMObject obj = builder.construct(type, values);
        dataValue = (DvText) obj;   
}

(the assignment from pValue to value is completely unnecessary by the way..fixed it)

As you can see, this method creates an RM instance. After all the values are set, the wrapper is called so that it can save its value to DB.

saveClerkingArchetypeToDB method is self explanatory, but these calls can benefit from some explanation:

        pArchetypeWrapper.setDelayedDBPersistence(true);
        pArchetypeWrapper.saveDataValues();
        pArchetypeWrapper.persistPendingData();

The setDelayedPersistenceMethod tells the wrapper to not to save values when saveDataValues are called. If delayedpersistence is true, the wrapper simply puts the values of RM instances into hibernate classes, (POJOs) and stores them, to benefit from batch inserts of jdbc. The call the db server is performed with persistPendingData() call. Otherwise, each node would open and close a connection (though it is possible that hibernate may be handling this efficiently at the background)

There are some non-archetype data fields like persistence session id, context id and creation date. They are explained in the comments, but to give a brief explanation:

Persistence Session Id: This is a unique identifier which is the same for all data values of all nodes in an archetype wrapper. You can say that this is the instance id of an archetype wrapper. Each new wrapper would be saved with a new unique id, and you can use this id to load all nodes from DB that belongs to a single commit of an archetype wrapper (which in turn uses an ADL file) Opereffa uses a persistence model with high granularity, where each and every node ends up as one or more (usually more) rows in db, and this corresponds to representing a dynamic tree like structure (a graph actually) in a table structure (a database table). Therefore we need a key value to group together these rows into the proper structure again.

One can see how these values are turned into RM instances and how a wrapper is loaded from DB by tracking the execution in the following calls:

                ArrayList archetypeDatas = new ArrayList(testInstance.getArchetypeDataList(sessionId, contextId));
...
                wrapperToLoadData = testInstance.loadArchetypeFromFile("C:\\archetypeRepository\\" + archetypeFileName);
                wrapperToLoadData.loadFromPOJO(archetypeDatas);

All rows corresponding to a given session Id are loaded into POJO objects, and they are given to wrapper, which uses them to create RM instances and their values. setDataValueWithPersistedValue method in TextValueWrapper is called to do this.

Finally, a summary method is called on the wrapper's root wrapper to print the values from DB on the screen. Since this archetype wrapper uses only TextValueWrapper, or in other words the archetype has only text fields, this code should be fairly easy to debug for the curious, to see the steps of the execution.

This class performs the simplified task performed by many classes in a multi layer, multi process space setup. In the demo application, these values are posted from the browser via AJAX, then the values are passed to java server faces, which uses a custom resolver to send the values to a layer of adapters, which in turn sends them to wrappers. So this example simplifies what is done in the browser and web application.