2009 Implementers Group Meeting Program Advanced Module Development
Download the code
The module code that was written (and fixed/revised post meeting) can be downloaded: Basicmodule-benscoolnesslevel.zip
Introduction
This tutorial has code examples modified from code created in the previous tutorial in 2009_Implementers_Group_Meeting_Program-How_to_use_the_API.
Module Architecture was introduced in the 2009 Implementers Group Meeting Program-ModuleArchitecture
Follow the instructions at Creating_Your_First_OpenMRS_Module to setup a basic module.
Also check out the 2009_Implementers_Group_Meeting_Program-Development_Best_Practices tutorial notes.
Create a new BensCoolnessLevel object
/**
* Code for Advanced Module Tutorial by Ben Wolfe at 2009 OpenMRS developers meeting
* http://openmrs.org/wiki/2009_Implementers_Group_Meeting_Program/Advanced_Module_Development
*/
public class BensCoolnessLevel extends BaseOpenmrsObject {
/**
* @see org.openmrs.OpenmrsObject#getId()
*/
@Override
public Integer getId() {
// TODO Auto-generated method stub
return null;
}
/**
* @see org.openmrs.OpenmrsObject#setId(java.lang.Integer)
*/
@Override
public void setId(Integer arg0) {
// TODO Auto-generated method stub
}
private Integer bensCoolnessLevelId = null;
private Integer level = null;
public BensCoolnessLevel(Integer id) {
this.bensCoolnessLevelId = id;
}
/**
* @return the bensCoolnessLevelId
*/
public Integer getBensCoolnessLevelId() {
return bensCoolnessLevelId;
}
/**
* @param bensCoolnessLevelId the bensCoolnessLevelId to set
*/
public void setBensCoolnessLevelId(Integer bensCoolnessLevelId) {
this.bensCoolnessLevelId = bensCoolnessLevelId;
}
/**
* @return the level
*/
public Integer getLevel() {
return level;
}
/**
* @param level the level to set
*/
public void setLevel(Integer level) {
this.level = level;
}
}
If you created your module project using the BaseModule starter and are not able to import BaseOpenmrsObject, check if you have the 1.6 version of the openmrs-api.jar in the lib-common dir.
After extending BaseOpenmrsObject, let Eclipse's code completion the unimplemented methods for the id field.
Use eclipse's shift-Alt SR to generate the getters and setters.
Best Practice: If the object extends BaseOpenmrs object you can take advantage of things like sync. BaseOpenmrs provides id and uuid fields.
Create SQL Diff
Open metadata/sqldiff.xml and add a new diff containing the sql code to add a new table:
<sqldiff version="1.0">
<diff>
<version>1.0.0</version>
<author>Bob Dobalina</author>
<date>Sep 17, 2009</date>
<description>
Adding bens_coolness_level table
</description>
<sql>
CREATE TABLE IF NOT EXISTS `bens_coolness_level` (
`id` int(11) NOT NULL auto_increment,
`level` int(11) NOT NULL,
`uuid` varchar(36) NOT NULL default '',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
</sql>
</diff>
</sqldiff>
Can use sql ide like Squirrel to create sql syntax for table create = copy/paste into sql diff.
The first time that this module is loaded, this sql will run and the db will be created. Make sure the table does not already exist.
Hibernate mapping
Create a new hibernate mapping file at metadata/BensCoolnessLevel.hbm.xml
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" >
<hibernate-mapping package="@MODULE_PACKAGE@">
<class name="BensCoolnessLevel" table="bens_coolness_name">
<id name="id" type="int" column="id">
<generator class="native" />
</id>
<property name="level" type="java.lang.Integer" length="11"
column="patient_id" not-null="true" />
<property name="uuid" type="java.lang.String" column="uuid"
length="36" unique="true" />
</class>
</hibernate-mapping>
Tip: If the col name is same as field name, the "name" parameter is not required.
Config file depends on internet connection - delete the schema definition at top (!DOCTYPE hibernate-mapping... that has a public url such as http://hibernate.sourceforge.net...) if you don't have internet access.
Next step - make sure module runs
Overview to Next Steps
Create modifications to moduleApplicationContext.xml and include service Interceptors - transaction interceptor, logging interceptor, save handlers
Upload module in Admin interface
Global Properties and Background on Processing SQL Diffs
Look at the following global properties linked from the Administration page when OpenMRS is running:
module id
db id is the version of the sql diff that was run. makes it easy to roll back sql diff
module,started - If module should start again at next startup
mandatory - If module does not start should openmrs start?
If you need to alter tables, create a new sql diff and increment version number. When new user downloads and installs the module, all of the sql diffs are processed. This way users who update the module get the alter table statements - anything new.
Service methods
In previous session Ben created ModuleService
created method SaveBensCoolnessLevel(BenCoolnesslevel level)
Must implement this in the impl
Use the convenience wiz in eclipse to generate the method sig
saveBensCoolnessLevel(BensCoolnessLevel level) {
if (level h1. null) {
throw new APIException
}
}
Create save method in BasicModuleDAO
instead of creating docs, just point to the docs using @see
HibernateBasicModuleDAO - default implementation od DAO - there are defined in applicationContext - find in module wiki page
sessionFactory.getCurrentSession().saveOrUpdate(level);
Implement this method in public void saveBensCoolenssMehtod()Benscoolenssmethod level
Back to BasicModuleService
add a getter
Best Practice:
If you add a method onto a service, it's a place for another module can hook into. Another way is to use a util method, but other modules cannot hook into it.
Primarily services are used or getters and setters View layer - the jsph1. Typically name the file according to the url that calls it - in this case, BensCoolnessLevel.form
benscoolnesslevel is provided by Spring in the request. Reference in the jsp page as ${benscoolnesslevel.level}. The JSTL fields correspond to the getters in the class.Controllerh1. With annotations you can setup the controller easily. In module applicationContext, add call to controller
Then add the controller, which cannot extend any of the Spring controllers
Then compile.Debugging the moduleh1. Copy important beans to the module's applicationContext.xml magic that tells spring how to load some classes.
The module load failed because component.scan was not defined.
Had to delete the DOCTYPE due to an old doctype - older version of spring.
Failed cause error - id was null - throwing a null id to hibernate causes an error. Need ot tell module about the hibernate file created.
in the config file
Worse practices - don't use unit tests. Otherwise you watch modules loaad and unload, over and over...
Idea: Re-create demo by creating unit tests as we code.Modifications to applicationContext.xmlh1. Include service Interceptors - transaction interceptor, logging interceptor, save handlers
Upload module in Admin interface
So, when you run the app, the db will be created. Make sure the table does not already exist. Global Propertiesh1. Look at global properties -
module id,
db id is the version of the sql diff that was run. makes it easy to roll back sql diff
module,started - if module should start again at next startup
mandatory - if module does not start should pennmrs start.
Alter tables - create a new sql diff, increment version number
When new user downloads and installs the module, all of the sql diffs are processed. This way users who update the module get the alter table statements - anything new.Service methodsh1. In previous session Ben created ModuleService
created method SaveBensCoolnessLevel(BenCoolnesslevel level)
Must implement this in the impl
Use the convenience wiz in eclipse to generate the method sig
saveBensCoolnessLevel(BensCoolnessLevel level) {
if (level null) {
throw new APIException
}
}
Create save method in BasicModuleDAO
instead of creating docs, just point to the docs using @see
HibernateBasicModuleDAO - default implementation od DAO - there are defined in applicationContext - find in module wiki page
sessionFactory.getCurrentSession().saveOrUpdate(level);
Implement this method in public void saveBensCoolenssMehtod()Benscoolenssmethod level
Back to BasicModuleService
add a getter
Best Practice: If you add a method onto a service, it's a place for another module can hook into. Another way is to use a util method, but other modules cannot hook into it.
Primarily services are used or getters and sttters
View layer - the jsp
Typically name the file according to the url that calls it - BensCoolnessLevel.form
benscoolnesslevel is provided by Spring in the request. Reference in the jsp page as ${benscoolnesslevel.level}.. The JSTL fields correspond to the getters in the class.
Controller
With annotations you can setup the controller easily. In module applicationContext, add call to controller
Then add the controller, which cannot extend any of the Spring controllers
Then compile.
Debugging the module
Copy important beans to the module's applicationContext.- xml magic that tells spring how to load some classes.
The module load failed because component.scan was not defined.
had to delete the DCOTYPE due to an old doctpye - older version of spring.
failed cause error - id was null - throwing a null id to hibernate causes an error. Need ot tell module about the hibernate file created.
in the config file
Worse practices - dont' use unit tests. Otherwise you watch modules loaad and unload, over and over...
Idea: Re-create demo by creating unit tests as we code.