CSC/ECE 517 Spring 2013/OSS M604: Difference between revisions
(35 intermediate revisions by 3 users not shown) | |||
Line 1: | Line 1: | ||
=OpenMRS Change Patient Relationships= | =OpenMRS Change Patient Relationships= | ||
==Example Video== | |||
A user example video can be found here [http://www.youtube.com/watch?v=Tzgz80T_H0s&feature=youtu.be]. Be sure to select the 720p HD option and full-screen for optimal viewing. | |||
== README == | == README == | ||
The design document for the project can be found | The design document for the project can be found [https://wiki.openmrs.org/display/projects/Change+relationship+for+multiple+patients+at+once here] | ||
The project is currently hosted at a long term VCL image found | The project is currently hosted at a long term VCL image found [http://152.46.17.15:8081/openmrs-standalone/index.htm here] | ||
The code for our module is hosted on github | The code for our module is hosted on github [https://github.com/ashrayn/openmrs-module-m604 here] | ||
The module's github readme file can be seen [https://github.com/ashrayn/openmrs-module-m604/blob/master/README.txt here] | |||
<ol><li>Login with user:Admin password:Admin123.</li> | |||
Quick Start: | |||
<ol><li>Login to OpenMRS with user:Admin password:Admin123.</li> | |||
<li>Click the 'Administration' button in the top-right corner.</li> | <li>Click the 'Administration' button in the top-right corner.</li> | ||
<li>In the bottom-right corner, under 'patients' there should be a link for "change patient relationships".</li> | <li>In the bottom-right corner, under 'patients' there should be a link for "change patient relationships".</li> | ||
Line 16: | Line 20: | ||
To see a list of possible patients, it may be useful to open a separate tab with the 'Find/Create Patient" menu from the main menu bar. | To see a list of possible patients, it may be useful to open a separate tab with the 'Find/Create Patient" menu from the main menu bar. | ||
To open a list of all data created for demonstration purposes, one would have to access the code [https://github.com/ashrayn/openmrs-module-m604/blob/master/changerelationships/api/src/main/java/org/openmrs/data/SampleData.java here]. If the user decides to add his/her data of his own, please refer to the section [https://github.com/ashrayn/openmrs-module-m604/blob/master/README.txt User Guide in readme.txt] | |||
This is not necessary if in case the user already has the relevant data on his/her local machine. | |||
==Writing Exercise 2== | ==Writing Exercise 2== | ||
===Design Overview of the Module=== | ===Design Overview of the Module=== | ||
OpenMRS modules utilize | OpenMRS modules utilize a modified Spring MVC framework [http://en.wikipedia.org/wiki/Spring_Framework]. The skeleton for the module is first generated using convenient maven[http://maven.apache.org/] artifacts provided here [https://wiki.openmrs.org/display/docs/Creating+Your+First+Module]. | ||
The 'change patient relationships' module utilizes 5 basic files. | The 'change patient relationships' module utilizes 5 basic files. | ||
Line 25: | Line 31: | ||
<li>The main page is a .jsp [http://en.wikipedia.org/wiki/JavaServer_Pages] page, the code can be found at [https://github.com/ashrayn/openmrs-module-m604/blob/master/changerelationships/omod/src/main/webapp/manage.jsp].</li> | <li>The main page is a .jsp [http://en.wikipedia.org/wiki/JavaServer_Pages] page, the code can be found at [https://github.com/ashrayn/openmrs-module-m604/blob/master/changerelationships/omod/src/main/webapp/manage.jsp].</li> | ||
<li>This .jsp page is linked to a spring annotated controller [http://javapapers.com/spring/spring-annotation-based-controllers/], the code can be found at [https://github.com/ashrayn/openmrs-module-m604/blob/master/changerelationships/omod/src/main/java/org/openmrs/module/changerelationships/web/controller/ChangeRelationshipsManageController.java]. The annotated controller maps the manage.jsp form actions using the '@RequestMapping' annotations. This pattern can be observed by viewing the manage.jsp code and comparing the <form> tags to the @RequestMapping annotations in | <li>This .jsp page is linked to a spring annotated controller [http://javapapers.com/spring/spring-annotation-based-controllers/], the code can be found at [https://github.com/ashrayn/openmrs-module-m604/blob/master/changerelationships/omod/src/main/java/org/openmrs/module/changerelationships/web/controller/ChangeRelationshipsManageController.java]. The annotated controller maps the manage.jsp form actions using the '@RequestMapping' annotations. This pattern can be observed by viewing the manage.jsp code and comparing the <form> tags to the @RequestMapping annotations in the java code.</li> | ||
<li/><li>The controller along with the spring framework link the form actions from the 'manage.jsp' page to java objects defined in the following .java files. [https://github.com/ashrayn/openmrs-module-m604/blob/master/changerelationships/omod/src/main/java/org/openmrs/module/changerelationships/PatientSearch.java] and [https://github.com/ashrayn/openmrs-module-m604/blob/master/changerelationships/omod/src/main/java/org/openmrs/module/changerelationships/UpdateRecord.java]</li> | <li/><li>The controller along with the spring framework link the form actions from the 'manage.jsp' page to java objects defined in the following .java files. [https://github.com/ashrayn/openmrs-module-m604/blob/master/changerelationships/omod/src/main/java/org/openmrs/module/changerelationships/PatientSearch.java] and [https://github.com/ashrayn/openmrs-module-m604/blob/master/changerelationships/omod/src/main/java/org/openmrs/module/changerelationships/UpdateRecord.java]</li> | ||
Line 31: | Line 37: | ||
<li>The module utilizes OpenMRS's services infrastructure to create services with the following code [https://github.com/ashrayn/openmrs-module-m604/blob/master/changerelationships/api/src/main/java/org/openmrs/module/changerelationships/api/impl/ChangeRelationshipsServiceImpl.java]. The services found within this class are retrieved from the OpenMRS context object inside of the above controller code (#2). The ChangePatientRelationshipsServiceImpl.java class utilizes OpenMRS database APIs [https://wiki.openmrs.org/display/docs/API] to actually change the patient data in the OpenMRS database. </li> | <li>The module utilizes OpenMRS's services infrastructure to create services with the following code [https://github.com/ashrayn/openmrs-module-m604/blob/master/changerelationships/api/src/main/java/org/openmrs/module/changerelationships/api/impl/ChangeRelationshipsServiceImpl.java]. The services found within this class are retrieved from the OpenMRS context object inside of the above controller code (#2). The ChangePatientRelationshipsServiceImpl.java class utilizes OpenMRS database APIs [https://wiki.openmrs.org/display/docs/API] to actually change the patient data in the OpenMRS database. </li> | ||
</ol> | </ol> | ||
The entire module is packaged in an OpenMRS .omod file using maven and pre-created packaging scripts. The .omod file can be found here [https://github.com/ashrayn/openmrs-module-m604/tree/master/changerelationships/omod/target] and installed into any OpenMRS server using the 'Administration'>Manage modules page. | |||
===Object-Oriented Design Principles=== | |||
<b>Encapsulation</b> | |||
<br> | |||
There were several instances of encapsulation [http://en.wikipedia.org/wiki/Encapsulation_(object-oriented_programming)] used through out our implementation code in the OpenMRS module. For example, to get access to relationships between people in the database in OpenMRS, we first had to use the Context class to get a PersonService object type and then use that object type to access a list of relationships: | |||
personService = Context.getPersonService(); | |||
Here, getPersonService() is a static method in class Context that doesn't require instantiating a Context object to call the method. getPersonService() can be directly used as a class method without having to know the details of how the method has been implemented and designed. | |||
<br> | |||
Another example is making helper methods and instance variables private. | |||
private void printAllRelatedPeopleDetails() | |||
{ | |||
for(Relationship r : allRelatedPeople) | |||
{ | |||
myLogger.print(r.getPersonB().getFamilyName() + " relationshiptype " + r.getRelationshipType().getaIsToB() + "/" + r.getRelationshipType().getbIsToA()); | |||
} | |||
} | |||
This is a helper method to print the relationships in the Relationship List allRelatedPeople to the log file. As it is not directly used in the manage.jsp page for the user-interface, it is made private to restrict access to it. | |||
private ChangeRelationshipsDAO dao; | |||
private List<Person> people; | |||
private List<Relationship> allRelatedPeople; | |||
private List<RelationshipType> existingRelationshipTypes; | |||
private int noOfRelatedPeople; | |||
These instance variables are also made private in our module class to prevent the potential corruption of data if they were to be accessed publicly. | |||
<br> | |||
<br> | |||
<b>Open-Closed Principle</b> | |||
<br> | |||
In the OpenMRS module, existing classes that had already been tested were not modified, which exemplifies the open-closed principle [http://en.wikipedia.org/wiki/Open/closed_principle]. This module is an extension of the OpenMRS functionality. Our ChangeRelationshipsServiceImpl class is an added class to specifically add the functionality of being able to update the relationships of several patients at one time. Specifically, in our the ChangeRelationshipServiceImpl, we extend the class BaseOpenmrsService to implement the new functionality and we are able to utilize methods from BaseOpenmrsService through inheritance[http://en.wikipedia.org/wiki/Inheritance_(object-oriented_programming)]: | |||
public class ChangeRelationshipsServiceImpl extends BaseOpenmrsService implements ChangeRelationshipsService | |||
<br> | |||
<b>Principle of Least Astonishment</b><br> | |||
The user interface for our module was designed to be simple and intuitive for the user and encompass the basic functionality of updating relationships of patients to doctors, which adheres to the Principle of Least Astonishment [http://en.wikipedia.org/wiki/Principle_of_least_astonishment]. The Change Relationship page allows a user to enter the person and relationship to update and then allows the user to enter the person and relationship to be updated to. <br> | |||
The same goes for the naming conventions of the functions in our ChangeRelationshipServiceImpl class, they are named so that it is easier for a person viewing the code to understand what the function does without really having to study the code: | |||
public Person getPersonObjectFromInputname(String fromPerson); | |||
public RelationshipType findRelationshipTypeFromInput(String relation); | |||
int numberOfRelationships(Person fromPerson,RelationshipType fromPersonOldRelationshipTypeObject); | |||
int numberOfRelationships(Person fromPerson); | |||
public boolean updateRelativesToNewPerson(Person p, RelationshipType rst); | |||
public List<Relationship> getAllRelatedPeople(); | |||
==Design Patterns== | |||
Quite a few design patterns were used to design this module. | |||
===Model View Controller=== | |||
As there was significant UI and Database functionality, the use of MVC was inevitable [http://domino.watson.ibm.com/library/cyberdig.nsf/1e4115aea78b6e7c85256b360066f0d4/696cfba5d4b1e68985256a1e00626e27!OpenDocument MVC] | |||
The class [https://github.com/ashrayn/openmrs-module-m604/tree/master/changerelationships/api/src/main/java/org/openmrs/module/changerelationships/api/impl ChangeRelationshipServiceImpl] corresponds to the model. This class contains the functions to fire queries to the database and also insert new records/update existing records. Following is the code snippet for the class | |||
public class ChangeRelationshipsServiceImpl extends BaseOpenmrsService implements ChangeRelationshipsService { | |||
public Person getPersonObjectFromInputname(String fromPerson) { | |||
/*Matches the input string to an existing person object and returns the first person*/ | |||
} | |||
public RelationshipType findRelationshipTypeFromInput(String relation) { | |||
/*Matches the input string to an existing relationship type*/ | |||
} | |||
public int numberOfRelationships(Person fromPerson, RelationshipType fromPersonRelationship) { | |||
/*Function returns the number of people who are related to fromPerson as a fromPersonRelationshiptype */ | |||
} | |||
public int numberOfRelationships(Person fromPerson) { | |||
/*Function returns the number of people who are related to fromPerson in any way */ | |||
} | |||
public boolean updateRelativesToNewPerson(Person toPerson, RelationshipType toRelationshipType){ | |||
/*Updates relations of all relatives of old person to new toRelationshipType of toPerson. If unable to update the records of any of the | |||
relatives, the failure message is logged and the rest of the records are updated*/ | |||
} | |||
The class [https://github.com/ashrayn/openmrs-module-m604/blob/master/changerelationships/omod/src/main/java/org/openmrs/module/changerelationships/web/controller/ChangeRelationshipsManageController.java ChangeRelationshipsManageController] is the controller. This class contains the functions and attributes required for the view and also acts as an interface between the model and view. Code snippet for this class follows | |||
public void manage(ModelMap model) { | |||
/*Function to render the view and add attributes that are accessible in the view*/ | |||
} | |||
public String checkPatients(){ | |||
/*Function accepts string input of name of person and relationship type and maps it to function in the model that returns the number of | |||
related people*/ | |||
} | |||
public String updateRecord(){ | |||
/*Function accepts string input of name of new person and new relationship type and maps it to the function in the model that updates the | |||
records to reflect the relationships*/ | |||
} | |||
The file [https://github.com/ashrayn/openmrs-module-m604/blob/master/changerelationships/omod/src/main/webapp/manage.jsp manage.jsp] is responsible for the view and contains 2 forms. One to search for all the related people and the other to confirm the changes that have to be made with respect to the new person/new relationshiptype or both | |||
===Front Controller Pattern=== | |||
Similar to the MVC pattern, the [http://en.wikipedia.org/wiki/Front_Controller_pattern Front Controller Pattern] provides a centralized entry point for handling requests.[2] | |||
The class ChangeRelationshipsManageController (link to source code above) demonstrates this design pattern. As all the requests from the manage page are mapped onto functions in this page, this class is the centralized controller in the design. | |||
===Iterator=== | |||
[http://en.wikipedia.org/wiki/Iterator_pattern Iterator] is a behavioral design pattern used to implement accessing elements of a list quite frequently in the code. A code snippet example would be | |||
for(Relationship relationship : allRelatedPeople) | |||
{ | |||
if(toPerson.equals(relationship.getPersonB())) | |||
{ | |||
areAllUpdatesSuccessful = false; //as Person1 related to Person1, so avoid by skipping. Boundary case. | |||
myLogger.print("Unable to update record for " + toPerson); | |||
continue; | |||
} | |||
relationship.setPersonA(toPerson); | |||
relationship.setRelationshipType(toRelationshipType); | |||
personService.saveRelationship(relationship); | |||
} | |||
===Strategy=== | |||
Although, this pattern is not a strong contender for a design pattern used for this code, the [http://en.wikipedia.org/wiki/Strategy_pattern Strategy Pattern] does show itself in the controller function checkPatients() | |||
public String checkPatients(@ModelAttribute("patientSearch") PatientSearch patientSearch){ | |||
if(fromPersonRelation.equals("All")) //in case all relations of a person have to be changed call | |||
{ // a different overloaded function | |||
noOfRelations = changeRelationshipService.numberOfRelationships(fromPersonObject); | |||
} | |||
else | |||
{ | |||
RelationshipType fromPersonOldRelationshipTypeObject = | |||
changeRelationshipService.findRelationshipTypeFromInput(fromPersonRelation); | |||
noOfRelations = changeRelationshipService.numberOfRelationships(fromPersonObject, | |||
fromPersonOldRelationshipTypeObject); | |||
} | |||
} | |||
Depending on the input relationship type selected, two different functions are used to find all the related people. There may not a be a significantly different algorithm used to find the result, but at the database layer, the queries are different. | |||
==Testing== | |||
The framework used here for testing is [http://junit.org/ JUnit4] | |||
All the code for the test functions are at [https://github.com/ashrayn/openmrs-module-m604/blob/master/changerelationships/api/src/test/java/org/openmrs/module/changerelationships/api/ChangeRelationshipsServiceTest.java ChangeRelationshipsServiceTest.java] | |||
The test class consists of simple unit tests to test the working of each function used in the ChangeRelationshipService class. | |||
===Setup()=== | |||
The setup() function is the one responsible for setting up the background before testing all the functions. It initializes all the objects used for testing later. The code for setup() is as | |||
private void setup() | |||
{ | |||
personService = Context.getPersonService(); | |||
this.testCangeRelationshipService = Context.getService(ChangeRelationshipsService.class); | |||
this.testPeople = new ArrayList(); | |||
this.testRelations = new ArrayList(); | |||
this.testRelationshipTypes = new ArrayList(); | |||
} | |||
===Test Data=== | |||
The createTestPeopleAndRelations() functions is similar to the setup function, as it too sets up the background data required for some of the testing, but since not all of the unit tests require a sample data, they are in a separate function and only those functions that require the data call them. Code snippet for the function : | |||
private void createTestPeopleAndRelations() | |||
{ | |||
setup(); | |||
testPeople.add(createAndSavePerson("Person1", "For1", "Testing1", "Male", 1967, 8,3 )); | |||
testPeople.add(createAndSavePerson("Person2", "For2", "Testing2", "Male", 1967, 8,3 )); | |||
//some more sample person objects | |||
testRelationshipTypes.add(createNewRelationType("TestRType1", "TestRType2")); | |||
//some more sample relationshiptypes objects | |||
testRelations.add(new Relationship(testPeople.get(0), testPeople.get(1), testRelationshipTypes.get(0) )); | |||
//some more sample relationships objects | |||
} | |||
Similarly the function deleteDataCreatedForTests() is a cleanup function that removes all the data created specifically for the tests. Code snippet for the function : | |||
private void deleteDataCreatedForTests() { | |||
/*Purge all relations*/ | |||
/*Purge all relationshipTypes*/ | |||
/*Purge all persons*/ | |||
} | |||
The functions createNewRelationType() and createAndSavePerson() are helper functions for the test data setup functions mentioned above. The createAndSavePerson() saves each person to the database while the createNewRelationType() function saves each new relationship type to the database | |||
===Unit test : shouldSetupContext() and shouldSetupPersonService()=== | |||
Makes sure that the service objects are initialized and not null. | |||
public void shouldSetupContext() { | |||
assertNotNull(Context.getService(ChangeRelationshipsService.class)); | |||
} | |||
public void shouldSetupPersonService(){ | |||
setup(); | |||
===Unit test : getPersonObjectFromInputnameTest === | |||
Asserts that the function getPersonObjectFromInputname() returns exactly one person in case a record matching the given name is found, else returns a record not found error | |||
public void getPersonObjectFromInputnameTest() | |||
{ | |||
setup(); | |||
Person testPerson = createAndSavePerson("Person4", "For4", "Testing4", "Male", 1909, 12, 12); | |||
Person testPerson2 = this.testCangeRelationshipService.getPersonObjectFromInputname(testPerson.getFamilyName()); | |||
assertNotNull(testPerson2); | |||
assertArrayEquals(testPerson2.getFamilyName().toCharArray(), testPerson.getFamilyName().toCharArray()); | |||
//Some more similar test cases | |||
} | |||
===Unit test : findRelationshipTypeFromInputTest()=== | |||
Asserts that the function findRelationshipTypeFromInputTest() returns the relationship type in case a record matching the given type name is found, else returns a record not found error. | |||
public void findRelationshipTypeFromInputTest() | |||
{ | |||
setup(); | |||
String relationName; | |||
List<RelationshipType> testTypes = personService.getAllRelationshipTypes(); | |||
for(RelationshipType trstype : testTypes) | |||
{ | |||
relationName = trstype.getaIsToB() + "/" + trstype.getbIsToA(); | |||
assertNotNull(this.testCangeRelationshipService.findRelationshipTypeFromInput(relationName)); | |||
} | |||
} | |||
===unit Test : numberOfRelationshipGivenPersonAndRelationshipTypeTest()=== | |||
Tests wether the function numberOfRelationships() returns the right number of related people given the name of a person and the relationship type. | |||
public void numberOfRelationshipGivenPersonAndRelationshipTypeTest() | |||
{ | |||
setup(); | |||
createTestPeopleAndRelations(); | |||
assertEquals(3, this.testCangeRelationshipService.numberOfRelationships(testPeople.get(0), testRelationshipTypes.get(0))); | |||
} | |||
===Unit Test : numberOfRelationshipsWhenAllSelected()=== | |||
In case the user chose to change all the relationships for a given person, this test case checks to see if the function numberOfRelationships() returns the right number of people related to the given person in any possible way. | |||
public void numberOfRelationshipsWhenAllSelected() | |||
{ | |||
setup(); | |||
createTestPeopleAndRelations(); | |||
assertEquals(4, this.testCangeRelationshipService.numberOfRelationships(testPeople.get(0))); | |||
assertEquals(0, this.testCangeRelationshipService.numberOfRelationships(testPeople.get(3))); | |||
deleteDataCreatedForTests(); | |||
} | |||
===Unit Test : updateRelativesToNewPersonTest()=== | |||
Checks if the required updates are being made in the database correctly | |||
public void updateRelativesToNewPersonTest() | |||
{ | |||
setup(); | |||
createTestPeopleAndRelations(); | |||
this.testCangeRelationshipService.numberOfRelationships(testPeople.get(0), testRelationshipTypes.get(1)); | |||
boolean updateSuccessful = this.testCangeRelationshipService.updateRelativesToNewPerson(testPeople.get(2), testRelationshipTypes.get(0)); | |||
assertTrue(updateSuccessful); | |||
this.testCangeRelationshipService.numberOfRelationships(testPeople.get(0)); | |||
updateSuccessful = this.testCangeRelationshipService.updateRelativesToNewPerson(testPeople.get(2), testRelationshipTypes.get(0)); | |||
assertFalse(updateSuccessful); | |||
deleteDataCreatedForTests(); | |||
} | |||
==References== | |||
[1] [http://domino.watson.ibm.com/library/cyberdig.nsf/1e4115aea78b6e7c85256b360066f0d4/696cfba5d4b1e68985256a1e00626e27!OpenDocument Model View Controller] | |||
[2] [http://en.wikipedia.org/wiki/Front_Controller_pattern Front Controller Pattern] | |||
[3] [http://en.wikipedia.org/wiki/Iterator_pattern Iterator] |
Latest revision as of 22:20, 5 April 2013
OpenMRS Change Patient Relationships
Example Video
A user example video can be found here [1]. Be sure to select the 720p HD option and full-screen for optimal viewing.
README
The design document for the project can be found here
The project is currently hosted at a long term VCL image found here
The code for our module is hosted on github here
The module's github readme file can be seen here
Quick Start:
- Login to OpenMRS with user:Admin password:Admin123.
- Click the 'Administration' button in the top-right corner.
- In the bottom-right corner, under 'patients' there should be a link for "change patient relationships".
- Click on the 'change patient relationships' link
- The content of the change patient relationships module should be displayed.
To see a list of possible patients, it may be useful to open a separate tab with the 'Find/Create Patient" menu from the main menu bar. To open a list of all data created for demonstration purposes, one would have to access the code here. If the user decides to add his/her data of his own, please refer to the section User Guide in readme.txt This is not necessary if in case the user already has the relevant data on his/her local machine.
Writing Exercise 2
Design Overview of the Module
OpenMRS modules utilize a modified Spring MVC framework [2]. The skeleton for the module is first generated using convenient maven[3] artifacts provided here [4].
The 'change patient relationships' module utilizes 5 basic files.
- The main page is a .jsp [5] page, the code can be found at [6].
- This .jsp page is linked to a spring annotated controller [7], the code can be found at [8]. The annotated controller maps the manage.jsp form actions using the '@RequestMapping' annotations. This pattern can be observed by viewing the manage.jsp code and comparing the <form> tags to the @RequestMapping annotations in the java code.
- The controller along with the spring framework link the form actions from the 'manage.jsp' page to java objects defined in the following .java files. [9] and [10]
- The module utilizes OpenMRS's services infrastructure to create services with the following code [11]. The services found within this class are retrieved from the OpenMRS context object inside of the above controller code (#2). The ChangePatientRelationshipsServiceImpl.java class utilizes OpenMRS database APIs [12] to actually change the patient data in the OpenMRS database.
The entire module is packaged in an OpenMRS .omod file using maven and pre-created packaging scripts. The .omod file can be found here [13] and installed into any OpenMRS server using the 'Administration'>Manage modules page.
Object-Oriented Design Principles
Encapsulation
There were several instances of encapsulation [14] used through out our implementation code in the OpenMRS module. For example, to get access to relationships between people in the database in OpenMRS, we first had to use the Context class to get a PersonService object type and then use that object type to access a list of relationships:
personService = Context.getPersonService();
Here, getPersonService() is a static method in class Context that doesn't require instantiating a Context object to call the method. getPersonService() can be directly used as a class method without having to know the details of how the method has been implemented and designed.
Another example is making helper methods and instance variables private.
private void printAllRelatedPeopleDetails() { for(Relationship r : allRelatedPeople) { myLogger.print(r.getPersonB().getFamilyName() + " relationshiptype " + r.getRelationshipType().getaIsToB() + "/" + r.getRelationshipType().getbIsToA()); } }
This is a helper method to print the relationships in the Relationship List allRelatedPeople to the log file. As it is not directly used in the manage.jsp page for the user-interface, it is made private to restrict access to it.
private ChangeRelationshipsDAO dao; private List<Person> people; private List<Relationship> allRelatedPeople; private List<RelationshipType> existingRelationshipTypes; private int noOfRelatedPeople;
These instance variables are also made private in our module class to prevent the potential corruption of data if they were to be accessed publicly.
Open-Closed Principle
In the OpenMRS module, existing classes that had already been tested were not modified, which exemplifies the open-closed principle [15]. This module is an extension of the OpenMRS functionality. Our ChangeRelationshipsServiceImpl class is an added class to specifically add the functionality of being able to update the relationships of several patients at one time. Specifically, in our the ChangeRelationshipServiceImpl, we extend the class BaseOpenmrsService to implement the new functionality and we are able to utilize methods from BaseOpenmrsService through inheritance[16]:
public class ChangeRelationshipsServiceImpl extends BaseOpenmrsService implements ChangeRelationshipsService
Principle of Least Astonishment
The user interface for our module was designed to be simple and intuitive for the user and encompass the basic functionality of updating relationships of patients to doctors, which adheres to the Principle of Least Astonishment [17]. The Change Relationship page allows a user to enter the person and relationship to update and then allows the user to enter the person and relationship to be updated to.
The same goes for the naming conventions of the functions in our ChangeRelationshipServiceImpl class, they are named so that it is easier for a person viewing the code to understand what the function does without really having to study the code:
public Person getPersonObjectFromInputname(String fromPerson); public RelationshipType findRelationshipTypeFromInput(String relation); int numberOfRelationships(Person fromPerson,RelationshipType fromPersonOldRelationshipTypeObject); int numberOfRelationships(Person fromPerson); public boolean updateRelativesToNewPerson(Person p, RelationshipType rst); public List<Relationship> getAllRelatedPeople();
Design Patterns
Quite a few design patterns were used to design this module.
Model View Controller
As there was significant UI and Database functionality, the use of MVC was inevitable MVC The class ChangeRelationshipServiceImpl corresponds to the model. This class contains the functions to fire queries to the database and also insert new records/update existing records. Following is the code snippet for the class
public class ChangeRelationshipsServiceImpl extends BaseOpenmrsService implements ChangeRelationshipsService { public Person getPersonObjectFromInputname(String fromPerson) { /*Matches the input string to an existing person object and returns the first person*/ } public RelationshipType findRelationshipTypeFromInput(String relation) { /*Matches the input string to an existing relationship type*/ } public int numberOfRelationships(Person fromPerson, RelationshipType fromPersonRelationship) { /*Function returns the number of people who are related to fromPerson as a fromPersonRelationshiptype */ } public int numberOfRelationships(Person fromPerson) { /*Function returns the number of people who are related to fromPerson in any way */ }
public boolean updateRelativesToNewPerson(Person toPerson, RelationshipType toRelationshipType){ /*Updates relations of all relatives of old person to new toRelationshipType of toPerson. If unable to update the records of any of the relatives, the failure message is logged and the rest of the records are updated*/ }
The class ChangeRelationshipsManageController is the controller. This class contains the functions and attributes required for the view and also acts as an interface between the model and view. Code snippet for this class follows
public void manage(ModelMap model) { /*Function to render the view and add attributes that are accessible in the view*/ } public String checkPatients(){ /*Function accepts string input of name of person and relationship type and maps it to function in the model that returns the number of related people*/ }
public String updateRecord(){ /*Function accepts string input of name of new person and new relationship type and maps it to the function in the model that updates the records to reflect the relationships*/ }
The file manage.jsp is responsible for the view and contains 2 forms. One to search for all the related people and the other to confirm the changes that have to be made with respect to the new person/new relationshiptype or both
Front Controller Pattern
Similar to the MVC pattern, the Front Controller Pattern provides a centralized entry point for handling requests.[2] The class ChangeRelationshipsManageController (link to source code above) demonstrates this design pattern. As all the requests from the manage page are mapped onto functions in this page, this class is the centralized controller in the design.
Iterator
Iterator is a behavioral design pattern used to implement accessing elements of a list quite frequently in the code. A code snippet example would be
for(Relationship relationship : allRelatedPeople) { if(toPerson.equals(relationship.getPersonB())) { areAllUpdatesSuccessful = false; //as Person1 related to Person1, so avoid by skipping. Boundary case. myLogger.print("Unable to update record for " + toPerson); continue; } relationship.setPersonA(toPerson); relationship.setRelationshipType(toRelationshipType); personService.saveRelationship(relationship); }
Strategy
Although, this pattern is not a strong contender for a design pattern used for this code, the Strategy Pattern does show itself in the controller function checkPatients()
public String checkPatients(@ModelAttribute("patientSearch") PatientSearch patientSearch){ if(fromPersonRelation.equals("All")) //in case all relations of a person have to be changed call { // a different overloaded function noOfRelations = changeRelationshipService.numberOfRelationships(fromPersonObject); } else { RelationshipType fromPersonOldRelationshipTypeObject = changeRelationshipService.findRelationshipTypeFromInput(fromPersonRelation); noOfRelations = changeRelationshipService.numberOfRelationships(fromPersonObject, fromPersonOldRelationshipTypeObject); } }
Depending on the input relationship type selected, two different functions are used to find all the related people. There may not a be a significantly different algorithm used to find the result, but at the database layer, the queries are different.
Testing
The framework used here for testing is JUnit4 All the code for the test functions are at ChangeRelationshipsServiceTest.java
The test class consists of simple unit tests to test the working of each function used in the ChangeRelationshipService class.
Setup()
The setup() function is the one responsible for setting up the background before testing all the functions. It initializes all the objects used for testing later. The code for setup() is as
private void setup() { personService = Context.getPersonService(); this.testCangeRelationshipService = Context.getService(ChangeRelationshipsService.class); this.testPeople = new ArrayList(); this.testRelations = new ArrayList(); this.testRelationshipTypes = new ArrayList(); }
Test Data
The createTestPeopleAndRelations() functions is similar to the setup function, as it too sets up the background data required for some of the testing, but since not all of the unit tests require a sample data, they are in a separate function and only those functions that require the data call them. Code snippet for the function :
private void createTestPeopleAndRelations() { setup(); testPeople.add(createAndSavePerson("Person1", "For1", "Testing1", "Male", 1967, 8,3 )); testPeople.add(createAndSavePerson("Person2", "For2", "Testing2", "Male", 1967, 8,3 )); //some more sample person objects testRelationshipTypes.add(createNewRelationType("TestRType1", "TestRType2")); //some more sample relationshiptypes objects testRelations.add(new Relationship(testPeople.get(0), testPeople.get(1), testRelationshipTypes.get(0) )); //some more sample relationships objects }
Similarly the function deleteDataCreatedForTests() is a cleanup function that removes all the data created specifically for the tests. Code snippet for the function :
private void deleteDataCreatedForTests() { /*Purge all relations*/ /*Purge all relationshipTypes*/ /*Purge all persons*/ }
The functions createNewRelationType() and createAndSavePerson() are helper functions for the test data setup functions mentioned above. The createAndSavePerson() saves each person to the database while the createNewRelationType() function saves each new relationship type to the database
Unit test : shouldSetupContext() and shouldSetupPersonService()
Makes sure that the service objects are initialized and not null.
public void shouldSetupContext() { assertNotNull(Context.getService(ChangeRelationshipsService.class)); } public void shouldSetupPersonService(){ setup();
Unit test : getPersonObjectFromInputnameTest
Asserts that the function getPersonObjectFromInputname() returns exactly one person in case a record matching the given name is found, else returns a record not found error
public void getPersonObjectFromInputnameTest() { setup(); Person testPerson = createAndSavePerson("Person4", "For4", "Testing4", "Male", 1909, 12, 12); Person testPerson2 = this.testCangeRelationshipService.getPersonObjectFromInputname(testPerson.getFamilyName()); assertNotNull(testPerson2); assertArrayEquals(testPerson2.getFamilyName().toCharArray(), testPerson.getFamilyName().toCharArray()); //Some more similar test cases }
Unit test : findRelationshipTypeFromInputTest()
Asserts that the function findRelationshipTypeFromInputTest() returns the relationship type in case a record matching the given type name is found, else returns a record not found error.
public void findRelationshipTypeFromInputTest() { setup(); String relationName; List<RelationshipType> testTypes = personService.getAllRelationshipTypes(); for(RelationshipType trstype : testTypes) { relationName = trstype.getaIsToB() + "/" + trstype.getbIsToA(); assertNotNull(this.testCangeRelationshipService.findRelationshipTypeFromInput(relationName)); } }
unit Test : numberOfRelationshipGivenPersonAndRelationshipTypeTest()
Tests wether the function numberOfRelationships() returns the right number of related people given the name of a person and the relationship type.
public void numberOfRelationshipGivenPersonAndRelationshipTypeTest() { setup(); createTestPeopleAndRelations(); assertEquals(3, this.testCangeRelationshipService.numberOfRelationships(testPeople.get(0), testRelationshipTypes.get(0))); }
Unit Test : numberOfRelationshipsWhenAllSelected()
In case the user chose to change all the relationships for a given person, this test case checks to see if the function numberOfRelationships() returns the right number of people related to the given person in any possible way.
public void numberOfRelationshipsWhenAllSelected() { setup(); createTestPeopleAndRelations(); assertEquals(4, this.testCangeRelationshipService.numberOfRelationships(testPeople.get(0))); assertEquals(0, this.testCangeRelationshipService.numberOfRelationships(testPeople.get(3))); deleteDataCreatedForTests(); }
Unit Test : updateRelativesToNewPersonTest()
Checks if the required updates are being made in the database correctly
public void updateRelativesToNewPersonTest() { setup(); createTestPeopleAndRelations(); this.testCangeRelationshipService.numberOfRelationships(testPeople.get(0), testRelationshipTypes.get(1)); boolean updateSuccessful = this.testCangeRelationshipService.updateRelativesToNewPerson(testPeople.get(2), testRelationshipTypes.get(0)); assertTrue(updateSuccessful); this.testCangeRelationshipService.numberOfRelationships(testPeople.get(0)); updateSuccessful = this.testCangeRelationshipService.updateRelativesToNewPerson(testPeople.get(2), testRelationshipTypes.get(0)); assertFalse(updateSuccessful); deleteDataCreatedForTests(); }
References
[3] Iterator