Thursday, May 29, 2008

Logic Web Service API Details

Below are the details I interpreted from tracing through the various Logic Service methods like getting filters and tags as well as constructing a query to more fine tune a request. Appreciate any feedback or comments on any more details or any misinterpretations that I made.

Also, the project requirements are still a work in progress but are outlined here.

Details on the API

The org.openmrs.logic.LogicService class is an interface. The org.openmrs.logic.impl.LogicServiceImpl class is the default implementation for this interface.

The get and add methods use the org.openmrs.logic.RuleFactory to return or add their data. As a temporary hack, when initialized, the RuleFactory adds the following hard coded rules: [AGE, BIRTHDATE, DEATH DATE, GENDER, HIV POSITIVE, CAUSE OF DEATH, DEAD, BIRTHDATE ESTIMATED].

The following are details on all the get methods:

  • getDefaultDataType(String token)
    • The data type for a token is returned via RuleFactory.
    • The getDefaultDatatype method of the org.openmrs.logic.rule.Rule interface is called on the token. The AgeRule, EnrolledBeforeDateRule, HIVPositiveRule, and ReferenceRule classes all implement this class and return different data types as appropriate.
  • getLogicDataSource(String name)
  • getLogicDataSources(Map<String, LogicDataSource> logicDataSources)
    • Gets all the registered logic data sources via RuleFactory.
  • getParameterList(String token)
    • Returns the expected parameters for a rule under which the passed token is registered via RuleFactory.
    • The getParameterList method of the Rule interface is called on the token (similiar to getDefaultDataType). The AgeRule, EnrolledBeforeDateRule, HIVPositiveRule, and ReferenceRule classes all implement this class. All of these implementer classes return null when queried for the parameter list.
  • getRule(String token)
    • Pass the token under which the rule was registered.
    • Uses the RuleFactory class and returns an org.openmrs.logic.rule.ReferenceRule based on that token if the token starts with "%%".
  • getTagsByToken(String token)
    • Uses the RuleFactory class and returns all of the tags that are attached to a give token.
  • getTokens()
    • returns the Set of all hardcoded rules via the RuleFactory.
  • getTokensByTag(String tag)
    • returns the Set of all hardcoded rules that match the tag argument via the RuleFactory.

The following are the details on all the add methods:

  • addRule(String token, Rule rule)
    • Uses RuleFactory to first make sure that rule doesn't already exist.
    • If rule does not already exist, the rule is registered under the given token.
  • addRule(String token, String[] tags, Rule rule)
    • Same as addRule method described above except for it assigns given tag(s) to the rule at the same time.
  • addTokenTag(String token, String tag)
    • Uses RuleFactory to add a tag to a previously registered token.

There are many eval methods that are used to filter the token/tag data to return specific information. The org.openmrs.logic.LogicCriteria class is used to setup the criteria for the filtering. The eval methods are basically a series of methods that massage the query to eventually use org.openmrs.logic.LogicContext to evaluate a rule with LogicCriteria for a single patient. The evaluations can be done for a single patient or a list of patients (cohort). The following are more details on the eval methods:

  • Single patient
    • eval(Patient who, String token)
      • Gets information for a given token for a given patient.
      • A LogicCriteria object is created based on the token.
      • Uses eval(Patient who, LogicCriteria lc) discussed below.
    • eval(Patient who, String token, Map<String, Object> parameters)
      • Gets information for a given token and parameters for a given patient.
      • A LogicCriteria object is created based on the token and parameters.
      • Uses eval(Patient who, LogicCriteria lc) discussed below.
    • eval(Patient who, LogicCriteria criteria)
      • Evaluates a query for a given patient.
      • Uses eval(Patient who, LogicCriteria criteria, Map<String, Object> parameters) discussed below.
    • eval(Patient who, LogicCriteria criteria, Map<String, Object> parameters)
      • Creates a LogicContext object with the given patient.
      • The query is evaluated via the LogicContext object and an org.openmrs.logic.result.Result is returned.
  • List of patients
    • eval(Cohort who, String token)
      • Gets information for a given token for a list of patients.
      • A LogicCriteria object is created based on the token.
      • Uses eval(Cohort who, LogicCriteria criteria) discussed below.
    • eval(Cohort who, String token, Map<String, Object> parameters)
      • Gets information for a given token and parameters for a list of patients.
      • A LogicCriteria objects is created based on the token and parameters.
      • Uses eval(Cohort who, LogicCriteria criteria) discussed below.
    • eval(Cohort who, LogicCriteria criteria)
      • Evaluates a query for a list of patients.
      • Uses eval (Cohort who, LogicCriteria criteria, Map<String, Object> parameters) discussed below.
    • eval(Cohort who, LogicCriteria criteria, Map<String, Object> parameters)
      • Creates a LogicContext object with the list of patients.
      • The query is evaluated for via the LogicContext object for each patient id and a Map of Result and patient id pairs are returned.
    • eval(Cohort who, List<LogicCriteria> criterias)
      • This is similiar to the previous eval discussed above except that it evaluates a collection of queries instead of a single query for a set of patients.
      • Each query is evaluated via the LogicContext object for each id and a Map of each LogicCriteria is paired with a Map of Result and patient id pairs.

The LogicContext is what does the "work" for all the above discussed eval methods of the LogicService implementation. It evaluates a rule with criteria and parameters for a single patient. Each rule is evaluated as appropritate depending on the implementing class (AgeRule, EnrolledBeforeDateRule, HIVPositiveRule, and ReferenceRule). The Result from each class is put in a patient id / Result map. The LogicCriteria is then applied to these results via the private applyCriteria() method of the LogicContext class. For now, this method looks like it doesn't do anything and just returns the results without applying the criteria.

The following are some other miscellaneous methods:

  • findToken(String token)
    • Returns all known tokens based on the lookup string passed.
    • Uses the RuleFactory class to look through the registered tokens and return the Set that matches the lookup string.
  • findTags(String partialTag)
    • Returns a Set of tags based on the lookup string passed.
    • Uses the RuleFactory class to look through the tags and find tags that match the lookup string.
  • updateRule(String token, Rule rule)
    • Looks up the rule based on the token passed and replaces it with the rule passed.
    • The RuleFactory class makes sure that it exists and updates with the new rule if it does.
  • removeRule(String token)
    • Removes a rule for the token passed.
    • The RuleFactory class makes sure that the rule to be removed exists and removes it from the rule map if it does.
  • removeTokenTag(String token, String tag)
    • Removes a token's previously assigned tag.
    • The RuleFactory class makes sure that the the token had the given tag and then removes it.
  • registerLogicDataSource(String name, LogicDataSource logicDataSource)
    • Adds a LogicDataSource to the logic service with the given name.
  • setLogicDataSources()
    • Adds multiple LogicDataSource objects to the current data sources in the logic service.
    • Uses registerLogicDataSource method discussed above.
  • removeLogicDataSource()
    • Removes a logic data source from the list.

Monday, May 19, 2008

Trying Out Last Year's GSOC OpenMRS ODA

I started out by following the instructions at http://openmrs.org/wiki/BIRT_ODA_Plugin_User_Guide in an attempt to setup and test the ODA with the Logic Web Service module.

In the OpenMRS Administration interface, it indicates to change module.allow_web_admin to true in the runtime properties in order to upload modules. After a rebuild, it still wouldn't let me upload the module. I went to the "Module Properties" page and it indicated that I needed to have module.allow_upload set to true. I set this in the runtime properties and did another rebuild. This still didn't work for me, so I just dropped the module in C:\Application Data\OpenMRS\modules and restarted. The module showed up in the "Manage Module" section so it looks like it worked.

I dropped the ODA jars in my BIRT installation and setup a new OpenMRS data source. As my mentor, Justin, suspected, the plugin did not work with the current version of the Logic Web Service and it threw an "Expected API functions do not respond correctly" Exception .

I checked out the OpenMRS ODA plugin code and saw that the failure was happening in the canAccessAPI() method of the Connection class. I added some Exception handling to log some info if the problem was an IOException, built and deployed the modified OpenMRS ODA, and restarted Eclipse. This new Exception handling showed me that the failure was happening when trying to access http://localhost:8080/openmrs/moduleServlet/logicws/api/getFilters. Accessing this in my browser gave me a NoSuchMethodError for "org.openmrs.api.context.Context.getReportService()Lorg/openmrs/reporting/ReportService". Tracking this down in the LWS code seems to point to the getAllPatientFilters method. I am wondering if I need to install the ReportService with my OpenMRS deployment?

Based on my conversations with Justin, he was expecting problems like this. So, looks like one of the first steps for this project will be to get a mock web service working so that the ODA development can commence. I was pointed to the following resources for information on modifying/creating a new LWS module:

We also plan on nailing down more of the requirements for this project this week.

Sunday, May 11, 2008

Getting Familiar with BIRT ODA

To really get familiar with an Eclipse ODA, I decided to go through the three part "ODA Extensions and BIRT" series by Scott Rosenbaum and Jason Weathersby. It is not publicly linked but can be accessed here by doing some registration. The tutorial is spread across three parts in volume 8, 9, and 14 of the Eclipse magazine.

The end result of the ODA is the ability to use Google Spreadsheets as a data source. This is particularly appropriate for me because I am wrapping up a big networking project where we have been storing the simulation results in a Google Spreadsheet. The project involves the effects of node density on packet delivery ratio for three different wireless routing protocols.

Going through this ODA guide exposed me to Eclipse plugin and ODA concepts and terminology which will be helpful for this project. With respect to ODA, a simple but important point seems to be that of separating design and runtime for the ODA. The first part of the article mainly focuses on the runtime, the second part with the design time (GUI), and the third part with adding logging, optimization, data types, and parameters.

I think the most valuable lesson I learned was with troubleshooting an ODA. Setting up the Logger for the ODA was crucial to solving a snag I ran into. I had the piece of the ODA working that identifies all the user's spreadsheets and then allows you to drill down and select an individual sheet for the query. However, the data preview kept failing with a CannotExecuteStatement error. Well, needless to say, this generic error didn't help identify where the root cause of the problem was. Looking into what the ODA was logging out showed it was throwing a com.google.gdata.util.ServiceException exception. A few alterations to the ODA and I could see the entire stack indicating it was throwing a com.google.gdata.util.InvalidEntryException exception (extends ServiceException) which typically indicates a bad or malformed request. The final step was to log out the actual query that was being made and I could see that the problem was that the filterClause data set parameter was defaulting to 'dummy default value'. Changing this to null allowed the data preview to work. See the image above that shows the node density and delivery ratio data from my project spreadsheet.

The next thing I am going to do is get, build, and become familiar with the OpenMRS ODA. I've also started adding links that are useful to this project to the right navigation area of this blog.

Monday, May 5, 2008

OpenMRS Fired Up and Running on the Laptop

I finally have a little breathing room from my huge school project and wanted to setup OpenMRS on my laptop. I basically followed the steps from http://openmrs.org/wiki/Step-by-Step_Installation_for_Developers and got things up and running. Basic stuff about the versions of the various software I setup:

  • Fresh install of Eclipse 3.3.2 and setup Subclipse 1.2.4. Running Java 1.6.0_05-b13 but set compliance level to 1.5.
  • Installed MySQL 5.0.51b and configured as a multifunctional db with 20 concurrent connections. Decided to make UTF8 the default character set.
  • Installed the MySQL GUI tools so I could easily look at the db model if required.
  • ant 1.7.0
  • Tomcat 6.0.16

To make sure things were all working correctly, I made my self a patient in the system:

Alright, back to running network simulations for school. Only 10 more days!