diff --git a/.gitignore b/.gitignore
old mode 100644
new mode 100755
index a1c2a23..128621a
--- a/.gitignore
+++ b/.gitignore
@@ -21,3 +21,20 @@
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
+
+# Default ignored files
+/shelf/
+/workspace.xml
+target/
+.project
+.classpath
+.settings/
+.DS_Store
+.idea/
+*.iml
+*.ipr
+*.iws
+logfile
+*.log*
+.springBeans
+.gradle/
diff --git a/README.md b/README.md
old mode 100644
new mode 100755
diff --git a/pom.xml b/pom.xml
new file mode 100755
index 0000000..d1bce25
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,89 @@
+
+
+ 4.0.0
+
+ org.smartregister
+ hapi-fhir-opensrp-extensions
+ jar
+ 0.0.1-SNAPSHOT
+ hapi-fhir-opensrp-extensions
+ This repository holds all the code extensions on top of Hapi-FHIR
+ https://github.com/opensrp/hapi-fhir-opensrp-extensions
+
+
+ 8
+ 8
+ 5.2.4.RELEASE
+
+
+
+
+ nexus-snapshots
+ Nexus Snapshots Repository
+ https://oss.sonatype.org/content/repositories/snapshots
+ false
+
+
+ nexus-releases
+ https://oss.sonatype.org/service/local/staging/deploy/maven2
+
+
+
+
+
+ org.springframework
+ spring-web
+ ${spring.version}
+
+
+
+ org.springframework
+ spring-jdbc
+ ${spring.version}
+
+
+
+ javax.servlet
+ servlet-api
+ 2.5
+ provided
+
+
+
+
+ ca.uhn.hapi.fhir
+ hapi-fhir-base
+ 5.4.0
+
+
+
+
+ ca.uhn.hapi.fhir
+ hapi-fhir-jpaserver-base
+ 5.4.0
+
+
+ org.springframework
+ spring-jcl
+
+
+ commons-logging
+ commons-logging
+
+
+
+
+
+
+ junit
+ junit
+ 4.13.1
+ test
+
+
+
+
+
+
diff --git a/src/main/java/org/smartregister/extension/model/ChildTreeNode.java b/src/main/java/org/smartregister/extension/model/ChildTreeNode.java
new file mode 100755
index 0000000..e6d3f4f
--- /dev/null
+++ b/src/main/java/org/smartregister/extension/model/ChildTreeNode.java
@@ -0,0 +1,60 @@
+package org.smartregister.extension.model;
+
+import ca.uhn.fhir.model.api.annotation.Child;
+import ca.uhn.fhir.model.api.annotation.DatatypeDef;
+import ca.uhn.fhir.util.ElementUtil;
+import org.hl7.fhir.instance.model.api.ICompositeType;
+import org.hl7.fhir.r4.model.StringType;
+import org.hl7.fhir.r4.model.Type;
+
+@DatatypeDef(name = "ChildTreeNode")
+public class ChildTreeNode extends Type implements ICompositeType {
+
+ @Child(name = "childId", type = { StringType.class }, order = 0, min = 1, max = 1, modifier = false, summary = false)
+ private StringType childId;
+
+ @Child(name = "treeNode", type = { TreeNode.class })
+ private TreeNode treeNode;
+
+ public ChildTreeNode() {
+ treeNode = new TreeNode();
+ }
+
+ public StringType getChildId() {
+ return childId;
+ }
+
+ public ChildTreeNode setChildId(StringType childId) {
+ this.childId = childId;
+ return this;
+ }
+
+ public TreeNode getChildren() {
+ if (treeNode == null) {
+ treeNode = new TreeNode();
+ }
+ return treeNode;
+ }
+
+ public ChildTreeNode setChildren(TreeNode children) {
+ this.treeNode = children;
+ return this;
+ }
+
+ @Override
+ public Type copy() {
+ ChildTreeNode childTreeNode = new ChildTreeNode();
+ copyValues(childTreeNode);
+ return childTreeNode;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return ElementUtil.isEmpty(childId);
+ }
+
+ @Override
+ protected Type typedCopy() {
+ return copy();
+ }
+}
diff --git a/src/main/java/org/smartregister/extension/model/LocationHierarchy.java b/src/main/java/org/smartregister/extension/model/LocationHierarchy.java
new file mode 100755
index 0000000..e4a5c59
--- /dev/null
+++ b/src/main/java/org/smartregister/extension/model/LocationHierarchy.java
@@ -0,0 +1,71 @@
+package org.smartregister.extension.model;
+
+import ca.uhn.fhir.model.api.annotation.Child;
+import ca.uhn.fhir.model.api.annotation.Description;
+import ca.uhn.fhir.model.api.annotation.ResourceDef;
+import org.hl7.fhir.r4.model.*;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@ResourceDef(name = "LocationHierarchy", profile = "http://hl7.org/fhir/profiles/custom-resource")
+public class LocationHierarchy extends Location {
+
+ @Child(
+ name = "locationId",
+ type = { StringType.class },
+ order = 5,
+ min = 0,
+ max = 1,
+ modifier = false,
+ summary = true
+ )
+ @Description(
+ shortDefinition = "Unique id to the location",
+ formalDefinition = "Id of the location whose location hierarchy will be displayed."
+ )
+ protected StringType locationId;
+
+ @Child(name = "LocationHierarchyTree", type = { LocationHierarchyTree.class })
+ @Description(
+ shortDefinition = "Complete Location Hierarchy Tree",
+ formalDefinition = "Consists of Location Hierarchy Tree and Parent Child Identifiers List"
+ )
+ private LocationHierarchyTree locationHierarchyTree;
+
+ @Override
+ public Location copy() {
+ Location location = new Location();
+ Bundle bundle = new Bundle();
+ List theEntry = new ArrayList<>();
+ Bundle.BundleEntryComponent entryComponent = new Bundle.BundleEntryComponent();
+ entryComponent.setResource(new Bundle());
+ theEntry.add(entryComponent);
+ bundle.setEntry(theEntry);
+ this.copyValues(location);
+ return location;
+ }
+
+ @Override
+ public ResourceType getResourceType() {
+ return ResourceType.Bundle;
+ }
+
+ public StringType getLocationId() {
+ return locationId;
+ }
+
+ public void setLocationId(StringType locationId) {
+ this.locationId = locationId;
+
+ }
+
+ public LocationHierarchyTree getLocationHierarchyTree() {
+ return locationHierarchyTree;
+ }
+
+ public void setLocationHierarchyTree(LocationHierarchyTree locationHierarchyTree) {
+ this.locationHierarchyTree = locationHierarchyTree;
+ }
+
+}
diff --git a/src/main/java/org/smartregister/extension/model/LocationHierarchyTree.java b/src/main/java/org/smartregister/extension/model/LocationHierarchyTree.java
new file mode 100755
index 0000000..5a73778
--- /dev/null
+++ b/src/main/java/org/smartregister/extension/model/LocationHierarchyTree.java
@@ -0,0 +1,76 @@
+package org.smartregister.extension.model;
+
+import ca.uhn.fhir.model.api.annotation.Child;
+import ca.uhn.fhir.model.api.annotation.DatatypeDef;
+import ca.uhn.fhir.util.ElementUtil;
+import org.hl7.fhir.instance.model.api.ICompositeType;
+import org.hl7.fhir.r4.model.Location;
+import org.hl7.fhir.r4.model.StringType;
+import org.hl7.fhir.r4.model.Type;
+import org.thymeleaf.util.StringUtils;
+
+import java.util.List;
+
+@DatatypeDef(name = "LocationHierarchyTree")
+public class LocationHierarchyTree extends Type implements ICompositeType {
+
+ @Child(name = "locationsHierarchy")
+ private Tree locationsHierarchy;
+
+ public LocationHierarchyTree() {
+ this.locationsHierarchy = new Tree();
+ }
+
+ public void addLocation(Location l) {
+ StringType idString = new StringType();
+ idString.setValue(l.getId());
+ if (!locationsHierarchy.hasNode(idString.getValue())) {
+ if (l.getPartOf() == null || StringUtils.isEmpty(l.getPartOf().getReference())) {
+ locationsHierarchy.addNode(idString.getValue(), l.getName(), l, null);
+ } else {
+ //get Parent Location
+ StringType parentId = new StringType();
+ parentId.setValue(l.getPartOf().getReference());
+ locationsHierarchy.addNode(idString.getValue(), l.getName(), l, parentId.getValue());
+ }
+ }
+ }
+
+ /**
+ * WARNING: Overrides existing locations
+ *
+ * @param locations
+ */
+ public void buildTreeFromList(List locations) {
+ for (Location location : locations) {
+ addLocation(location);
+ }
+ }
+
+ public Tree getLocationsHierarchy() {
+ return locationsHierarchy;
+ }
+
+ public LocationHierarchyTree setLocationsHierarchy(Tree locationsHierarchy) {
+ this.locationsHierarchy = locationsHierarchy;
+ return this;
+ }
+
+ @Override
+ public Type copy() {
+ LocationHierarchyTree locationHierarchyTree = new LocationHierarchyTree();
+ copyValues(locationHierarchyTree);
+ return locationHierarchyTree;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return ElementUtil.isEmpty(locationsHierarchy);
+ }
+
+ @Override
+ protected Type typedCopy() {
+ return copy();
+ }
+}
+
diff --git a/src/main/java/org/smartregister/extension/model/ParentChildrenMap.java b/src/main/java/org/smartregister/extension/model/ParentChildrenMap.java
new file mode 100755
index 0000000..fc40c69
--- /dev/null
+++ b/src/main/java/org/smartregister/extension/model/ParentChildrenMap.java
@@ -0,0 +1,61 @@
+package org.smartregister.extension.model;
+
+import ca.uhn.fhir.model.api.annotation.Child;
+import ca.uhn.fhir.model.api.annotation.DatatypeDef;
+import ca.uhn.fhir.util.ElementUtil;
+import org.hl7.fhir.instance.model.api.ICompositeType;
+import org.hl7.fhir.r4.model.StringType;
+import org.hl7.fhir.r4.model.Type;
+
+import java.util.List;
+
+@DatatypeDef(name = "ParentChildrenMap")
+public class ParentChildrenMap extends Type implements ICompositeType {
+
+ @Child(name = "identifier", type = { StringType.class }, order = 0, min = 1, max = 1, modifier = false, summary = false)
+ private StringType identifier;
+
+ @Child(name = "childIdentifiers", type = { StringType.class },
+ order = 1,
+ min = 0,
+ max = -1,
+ modifier = false,
+ summary = false)
+ private List childIdentifiers;
+
+ public StringType getIdentifier() {
+ return identifier;
+ }
+
+ public ParentChildrenMap setIdentifier(StringType identifier) {
+ this.identifier = identifier;
+ return this;
+ }
+
+ public List getChildIdentifiers() {
+ return childIdentifiers;
+ }
+
+ public ParentChildrenMap setChildIdentifiers(List childIdentifiers) {
+ this.childIdentifiers = childIdentifiers;
+ return this;
+ }
+
+ @Override
+ public Type copy() {
+ ParentChildrenMap parentChildrenMap = new ParentChildrenMap();
+ copyValues(parentChildrenMap);
+ return parentChildrenMap;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return ElementUtil.isEmpty(identifier);
+ }
+
+ @Override
+ protected Type typedCopy() {
+ return copy();
+ }
+
+}
diff --git a/src/main/java/org/smartregister/extension/model/SingleTreeNode.java b/src/main/java/org/smartregister/extension/model/SingleTreeNode.java
new file mode 100755
index 0000000..b0f6392
--- /dev/null
+++ b/src/main/java/org/smartregister/extension/model/SingleTreeNode.java
@@ -0,0 +1,58 @@
+package org.smartregister.extension.model;
+
+import ca.uhn.fhir.model.api.annotation.Child;
+import ca.uhn.fhir.model.api.annotation.DatatypeDef;
+import ca.uhn.fhir.util.ElementUtil;
+import org.hl7.fhir.instance.model.api.ICompositeType;
+import org.hl7.fhir.r4.model.StringType;
+import org.hl7.fhir.r4.model.Type;
+
+@DatatypeDef(name = "SingleTreeNode")
+public class SingleTreeNode extends Type implements ICompositeType {
+
+ @Child(name = "treeNodeId", type = { StringType.class }, order = 0)
+ private StringType treeNodeId;
+
+ @Child(name = "treeNode", type = { TreeNode.class },
+ order = 1,
+ min = 0,
+ max = -1,
+ modifier = false,
+ summary = false)
+ private TreeNode treeNode;
+
+ @Override
+ public Type copy() {
+ SingleTreeNode singleTreeNode = new SingleTreeNode();
+ copyValues(singleTreeNode);
+ return singleTreeNode;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return ElementUtil.isEmpty(treeNodeId, treeNode);
+ }
+
+ @Override
+ protected Type typedCopy() {
+ return copy();
+ }
+
+ public StringType getTreeNodeId() {
+ return treeNodeId;
+ }
+
+ public SingleTreeNode setTreeNodeId(StringType treeNodeId) {
+ this.treeNodeId = treeNodeId;
+ return this;
+ }
+
+ public TreeNode getTreeNode() {
+ return treeNode;
+ }
+
+ public SingleTreeNode setTreeNode(TreeNode treeNode) {
+ this.treeNode = treeNode;
+ return this;
+ }
+}
diff --git a/src/main/java/org/smartregister/extension/model/Tree.java b/src/main/java/org/smartregister/extension/model/Tree.java
new file mode 100755
index 0000000..9dde450
--- /dev/null
+++ b/src/main/java/org/smartregister/extension/model/Tree.java
@@ -0,0 +1,243 @@
+package org.smartregister.extension.model;
+
+import ca.uhn.fhir.model.api.annotation.Child;
+import ca.uhn.fhir.model.api.annotation.DatatypeDef;
+import ca.uhn.fhir.util.ElementUtil;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.hl7.fhir.instance.model.api.ICompositeType;
+import org.hl7.fhir.r4.model.Location;
+import org.hl7.fhir.r4.model.StringType;
+import org.hl7.fhir.r4.model.Type;
+
+import java.util.*;
+
+import static org.smartregister.extension.utils.Constants.SLASH_UNDERSCORE;
+
+@DatatypeDef(name = "Tree")
+public class Tree extends Type implements ICompositeType {
+
+ @Child(name = "listOfNodes", type = { SingleTreeNode.class })
+ private SingleTreeNode listOfNodes;
+
+ @Child(name = "parentChildren",
+ type = { ParentChildrenMap.class },
+ order = 1,
+ min = 0,
+ max = -1,
+ modifier = false,
+ summary = false)
+ private List parentChildren;
+
+ private static Logger logger = LogManager.getLogger(Tree.class.toString());
+
+ public SingleTreeNode getTree() {
+ return listOfNodes;
+ }
+
+ public Tree() {
+ listOfNodes = new SingleTreeNode();
+ parentChildren = new ArrayList<>();
+ }
+
+ private void addToParentChildRelation(String parent, String id) {
+ if (parentChildren == null) {
+ parentChildren = new ArrayList<>();
+ }
+ List kids = null;
+ if (parentChildren != null) {
+ for (int i = 0; i < parentChildren.size(); i++) {
+ kids = parentChildren.get(i) != null && parentChildren.get(i).getIdentifier() != null &&
+ StringUtils.isNotBlank(parentChildren.get(i).getIdentifier().getValue()) &&
+ parentChildren.get(i).getIdentifier().getValue().equals(parent) ?
+ parentChildren.get(i).getChildIdentifiers() :
+ null;
+ logger.info("Kids are : " + kids);
+ if (kids != null) {
+ break;
+ }
+ }
+ }
+
+ if (kids == null) {
+ kids = new ArrayList<>();
+ }
+ StringType idStringType = new StringType();
+ String idString = id;
+ if (idString.contains(SLASH_UNDERSCORE)) {
+ idString = idString.substring(0, idString.indexOf(SLASH_UNDERSCORE));
+ }
+ idStringType.setValue(idString);
+
+ StringType parentStringType = new StringType();
+ parentStringType.setValue(parent);
+ kids.add(idStringType);
+ Boolean setParentChildMap = false;
+ for (int i = 0; i < parentChildren.size(); i++) {
+ if (parentChildren.get(i) != null && parentChildren.get(i).getIdentifier() != null
+ && StringUtils.isNotBlank(parentChildren.get(i).getIdentifier().getValue()) &&
+ parentChildren.get(i).getIdentifier().getValue().equals(parent)) {
+ parentChildren.get(i).setChildIdentifiers(kids);
+ setParentChildMap = true;
+ }
+ }
+
+ if (!setParentChildMap) {
+ ParentChildrenMap parentChildrenMap = new ParentChildrenMap();
+ parentChildrenMap.setIdentifier(parentStringType);
+ parentChildrenMap.setChildIdentifiers(kids);
+ parentChildren.add(parentChildrenMap);
+ }
+
+ }
+
+ public void addNode(String id, String label, Location node, String parentId) {
+ if (listOfNodes == null) {
+ listOfNodes = new SingleTreeNode();
+ }
+
+ // if node exists we should break since user should write optimized code and also tree can not have duplicates
+ if (hasNode(id)) {
+ throw new IllegalArgumentException("Node with ID " + id + " already exists in tree");
+ }
+
+ TreeNode treeNode = makeNode(id, label, node, parentId);
+
+ if (parentId != null) {
+ addToParentChildRelation(parentId, id);
+
+ TreeNode parentNode = getNode(parentId);
+
+ //if parent exists add to it otherwise add as root for now
+ if (parentNode != null) {
+ parentNode.addChild(treeNode);
+ } else {
+ // if no parent exists add it as root node
+ String idString = (String) id;
+ if (idString.contains(SLASH_UNDERSCORE)) {
+ idString = idString.substring(0, idString.indexOf(SLASH_UNDERSCORE));
+ }
+ SingleTreeNode singleTreeNode = new SingleTreeNode();
+ StringType treeNodeId = new StringType();
+ treeNodeId.setValue(idString);
+ singleTreeNode.setTreeNodeId(treeNodeId);
+ singleTreeNode.setTreeNode(treeNode);
+ listOfNodes = singleTreeNode;
+ }
+ } else {
+ // if no parent add it as root node
+ String idString = id;
+ if (idString.contains(SLASH_UNDERSCORE)) {
+ idString = idString.substring(0, idString.indexOf(SLASH_UNDERSCORE));
+ }
+
+ SingleTreeNode singleTreeNode = new SingleTreeNode();
+ StringType treeNodeId = new StringType();
+ treeNodeId.setValue(idString);
+ singleTreeNode.setTreeNodeId(treeNodeId);
+ singleTreeNode.setTreeNode(treeNode);
+ listOfNodes = singleTreeNode;
+ }
+ }
+
+ private TreeNode makeNode(String id, String label, Location node, String parentId) {
+ TreeNode treenode = getNode(id);
+ if (treenode == null) {
+ treenode = new TreeNode();
+ StringType nodeId = new StringType();
+ String idString = (String) id;
+ if (idString.contains(SLASH_UNDERSCORE)) {
+ idString = idString.substring(0, idString.indexOf(SLASH_UNDERSCORE));
+ }
+ nodeId.setValue((String) idString);
+ treenode.setNodeId(nodeId);
+ StringType labelString = new StringType();
+ labelString.setValue(label);
+ treenode.setLabel(labelString);
+ treenode.setNode(node);
+ StringType parentIdString = new StringType();
+ String parentIdStringVar = parentId;
+
+ if (parentIdStringVar != null && parentIdStringVar.contains(SLASH_UNDERSCORE)) {
+ parentIdStringVar = parentIdStringVar.substring(0, parentIdStringVar.indexOf(SLASH_UNDERSCORE));
+ }
+ parentIdString.setValue(parentIdStringVar);
+ treenode.setParent(parentIdString);
+ }
+ return treenode;
+ }
+
+ public TreeNode getNode(String id) {
+ // Check if id is any root node
+ String idString = id;
+ if (idString != null && idString.contains(SLASH_UNDERSCORE)) {
+ idString = idString.substring(0, idString.indexOf(SLASH_UNDERSCORE));
+ }
+
+ if (listOfNodes.getTreeNodeId() != null && StringUtils.isNotBlank(listOfNodes.getTreeNodeId().getValue()) &&
+ listOfNodes.getTreeNodeId().getValue().equals(idString)) {
+ return listOfNodes.getTreeNode();
+
+ } else {
+ if (listOfNodes != null && listOfNodes.getTreeNode() != null
+ && listOfNodes.getTreeNode().getChildren() != null) {
+ return recursivelyFindNode(idString, listOfNodes.getTreeNode().getChildren());
+ }
+ }
+ return null;
+ }
+
+ public boolean hasNode(String id) {
+ return getNode(id) != null;
+ }
+
+ public SingleTreeNode getListOfNodes() {
+ return listOfNodes;
+ }
+
+ public void setListOfNodes(SingleTreeNode listOfNodes) {
+ this.listOfNodes = listOfNodes;
+ }
+
+ public List getParentChildren() {
+ return parentChildren;
+ }
+
+ public void setParentChildren(List parentChildren) {
+ this.parentChildren = parentChildren;
+ }
+
+ @Override
+ public Type copy() {
+ Tree tree = new Tree();
+ copyValues(tree);
+ return tree;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return ElementUtil.isEmpty(listOfNodes);
+ }
+
+ @Override
+ protected Type typedCopy() {
+ return copy();
+ }
+
+ private TreeNode recursivelyFindNode(String idString, List childTreeNodeList) {
+ for (ChildTreeNode childTreeNode : childTreeNodeList) {
+ TreeNode treeNode = childTreeNode.getChildren();
+ if (treeNode != null && treeNode.getNodeId() != null && StringUtils.isNotBlank(treeNode.getNodeId().getValue()) &&
+ treeNode.getNodeId().getValue().equals(idString)) {
+ return treeNode;
+ } else {
+ if (treeNode != null && treeNode.getChildren() != null && treeNode.getChildren().size() > 0) {
+ return recursivelyFindNode(idString, treeNode.getChildren());
+ }
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/src/main/java/org/smartregister/extension/model/TreeNode.java b/src/main/java/org/smartregister/extension/model/TreeNode.java
new file mode 100755
index 0000000..95fc4c3
--- /dev/null
+++ b/src/main/java/org/smartregister/extension/model/TreeNode.java
@@ -0,0 +1,175 @@
+package org.smartregister.extension.model;
+
+import ca.uhn.fhir.model.api.annotation.Child;
+import ca.uhn.fhir.model.api.annotation.DatatypeDef;
+import ca.uhn.fhir.util.ElementUtil;
+import org.apache.commons.lang3.StringUtils;
+import org.hl7.fhir.instance.model.api.ICompositeType;
+import org.hl7.fhir.r4.model.*;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+
+import static org.smartregister.extension.utils.Constants.SLASH_UNDERSCORE;
+
+@DatatypeDef(name = "TreeNode")
+public class TreeNode extends Type implements ICompositeType {
+
+ @Child(name = "name", type = { StringType.class }, order = 0, min = 1, max = 1, modifier = false, summary = false)
+ protected StringType name;
+
+ @Child(name = "nodeId", type = { StringType.class }, order = 2)
+ private StringType nodeId;
+
+ @Child(name = "label", type = { StringType.class }, order = 3)
+ private StringType label;
+
+ @Child(name = "node", type = { Location.class }, order = 4)
+ private Location node;
+
+ @Child(name = "parent", type = { StringType.class }, order = 5)
+ private StringType parent;
+
+ @Child(name = "children", type = { ChildTreeNode.class },
+ order = 6,
+ min = 0,
+ max = -1,
+ modifier = false,
+ summary = false)
+ private List children;
+
+ public TreeNode() {
+ children = new ArrayList<>();
+ }
+
+ public TreeNode(StringType name, StringType nodeId, StringType label, Location node, StringType parent) {
+ this.name = name;
+ this.nodeId = nodeId;
+ this.label = label;
+ this.node = node;
+ this.parent = parent;
+ }
+
+ public StringType getName() {
+ if (name == null) {
+ name = new StringType();
+ }
+ return name;
+ }
+
+ public TreeNode setName(StringType name) {
+ this.name = name;
+ return this;
+ }
+
+ public StringType getLabel() {
+ return label;
+ }
+
+ public TreeNode setLabel(StringType label) {
+ this.label = label;
+ return this;
+ }
+
+ @Override
+ public Type copy() {
+ TreeNode treeNode = new TreeNode();
+ copyValues(treeNode);
+ return treeNode;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return ElementUtil.isEmpty(node);
+ }
+
+ @Override
+ protected Type typedCopy() {
+ return copy();
+ }
+
+ public StringType getNodeId() {
+ return nodeId;
+ }
+
+ public TreeNode setNodeId(StringType nodeId) {
+ this.nodeId = nodeId;
+ return this;
+ }
+
+ public Location getNode() {
+ return node;
+ }
+
+ public TreeNode setNode(Location node) {
+ this.node = node;
+ return this;
+ }
+
+ public StringType getParent() {
+ return parent;
+ }
+
+ public TreeNode setParent(StringType parent) {
+ this.parent = parent;
+ return this;
+ }
+
+ public List getChildren() {
+ if (children == null) {
+ children = new ArrayList<>();
+ }
+ return children;
+ }
+
+ public TreeNode setChildren(List children) {
+ this.children = children;
+ return this;
+ }
+
+ public void addChild(TreeNode node) {
+ if (children == null) {
+ children = new ArrayList<>();
+ }
+ ChildTreeNode childTreeNode = new ChildTreeNode();
+ childTreeNode.setChildId(node.getNodeId());
+ List treeNodeList = new ArrayList<>();
+ TreeNode treeNode = new TreeNode();
+ treeNode.setNode(node.getNode());
+ treeNode.setNodeId(node.getNodeId());
+ treeNode.setLabel(node.getLabel());
+ treeNode.setParent(node.getParent());
+ treeNodeList.add(treeNode);
+ childTreeNode.setChildren(treeNode);
+ children.add(childTreeNode);
+ }
+
+ public TreeNode findChild(String id) {
+ String idString = (String) id;
+ if (idString.contains(SLASH_UNDERSCORE)) {
+ idString = idString.substring(0, idString.indexOf(SLASH_UNDERSCORE));
+ }
+ if (children != null && children.size() > 0) {
+ for (int i = 0; i < children.size(); i++) {
+ if (children.get(i) != null) {
+ for (ChildTreeNode child : children) {
+ if (child != null && child.getChildren() != null
+ && child.getChildren().getNodeId() != null && StringUtils.isNotBlank(
+ child.getChildren().getNodeId().getValue())
+ && child.getChildren().getNodeId().getValue()
+ .equals(idString)) {
+ return child.getChildren();
+ } else if (child != null && child != null) {
+ TreeNode node = child.getChildren().findChild(idString);
+ if (node != null)
+ return node;
+ }
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/src/main/java/org/smartregister/extension/rest/LocationHierarchyResourceProvider.java b/src/main/java/org/smartregister/extension/rest/LocationHierarchyResourceProvider.java
new file mode 100755
index 0000000..4a9ed37
--- /dev/null
+++ b/src/main/java/org/smartregister/extension/rest/LocationHierarchyResourceProvider.java
@@ -0,0 +1,95 @@
+package org.smartregister.extension.rest;
+
+import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
+import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
+import ca.uhn.fhir.rest.annotation.*;
+import ca.uhn.fhir.rest.api.server.IBundleProvider;
+import ca.uhn.fhir.rest.param.*;
+import ca.uhn.fhir.rest.server.IResourceProvider;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.smartregister.extension.model.LocationHierarchy;
+import org.smartregister.extension.model.LocationHierarchyTree;
+import org.hl7.fhir.instance.model.api.IBaseResource;
+import org.hl7.fhir.r4.model.*;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import java.util.*;
+
+import static org.smartregister.extension.utils.Constants.*;
+
+public class LocationHierarchyResourceProvider implements IResourceProvider {
+
+ @Autowired
+ IFhirResourceDao locationIFhirResourceDao;
+
+ private static Logger logger = LogManager.getLogger(LocationHierarchyResourceProvider.class.toString());
+
+ @Override
+ public Class extends IBaseResource> getResourceType() {
+ return LocationHierarchy.class;
+ }
+
+ @Search
+ public LocationHierarchy getLocationHierarchy(@RequiredParam(name = IDENTIFIER) TokenParam identifier) {
+
+ SearchParameterMap paramMap = new SearchParameterMap();
+ paramMap.add(IDENTIFIER, identifier);
+
+ IBundleProvider locationBundle = locationIFhirResourceDao.search(paramMap);
+ List locations = locationBundle != null ?
+ locationBundle.getResources(0, locationBundle.size()) : new ArrayList<>();
+ Long id = null;
+ if (locations.size() > 0) {
+ id = locations.get(0) != null && locations.get(0).getIdElement() != null ? locations.get(0).getIdElement().getIdPartAsLong() : null;
+ }
+
+ LocationHierarchyTree locationHierarchyTree = new LocationHierarchyTree();
+ LocationHierarchy locationHierarchy = new LocationHierarchy();
+ if (id != null && locations.size() > 0) {
+ logger.info("Building Location Hierarchy of Location Id : " + id);
+ locationHierarchyTree.buildTreeFromList(getLocationHierarchy(id, locations.get(0)));
+ StringType locationIdString = new StringType().setId(id.toString()).getIdElement();
+ locationHierarchy.setLocationId(locationIdString);
+ locationHierarchy.setId(LOCATION_RESOURCE + id);
+
+ locationHierarchy.setLocationHierarchyTree(locationHierarchyTree);
+ } else {
+ locationHierarchy.setId(LOCATION_RESOURCE_NOT_FOUND);
+ }
+ return locationHierarchy;
+
+ }
+
+ private List getLocationHierarchy(Long id, IBaseResource parentLocation) {
+ return descendants(id, parentLocation);
+ }
+
+ public List descendants(Long id, IBaseResource parentLocation) {
+
+ SearchParameterMap paramMap = new SearchParameterMap();
+ ReferenceAndListParam thePartOf = new ReferenceAndListParam();
+ ReferenceParam partOf = new ReferenceParam();
+ partOf.setValue(LOCATION + FORWARD_SLASH + id);
+ ReferenceOrListParam referenceOrListParam = new ReferenceOrListParam();
+ referenceOrListParam.add(partOf);
+ thePartOf.addValue(referenceOrListParam);
+ paramMap.add(PART_OF, thePartOf);
+
+ IBundleProvider childLocationBundle = locationIFhirResourceDao.search(paramMap);
+ List allLocations = new ArrayList<>();
+ if (parentLocation != null) {
+ allLocations.add((Location) parentLocation);
+ }
+ for (IBaseResource childLocation : childLocationBundle.getResources(0, childLocationBundle.size())) {
+ Location childLocationEntity = (Location) childLocation;
+ allLocations.add(childLocationEntity);
+ allLocations.addAll(descendants(childLocation.getIdElement().getIdPartAsLong(), null));
+ }
+ return allLocations;
+ }
+
+ public void setLocationIFhirResourceDao(IFhirResourceDao locationIFhirResourceDao) {
+ this.locationIFhirResourceDao = locationIFhirResourceDao;
+ }
+}
diff --git a/src/main/java/org/smartregister/extension/utils/Constants.java b/src/main/java/org/smartregister/extension/utils/Constants.java
new file mode 100644
index 0000000..eaf35a6
--- /dev/null
+++ b/src/main/java/org/smartregister/extension/utils/Constants.java
@@ -0,0 +1,12 @@
+package org.smartregister.extension.utils;
+
+public interface Constants {
+
+ String SLASH_UNDERSCORE = "/_";
+ String LOCATION = "Location";
+ String FORWARD_SLASH = "/";
+ String IDENTIFIER = "identifier";
+ String LOCATION_RESOURCE_NOT_FOUND = "Location Resource : Not Found";
+ String LOCATION_RESOURCE = "Location Resource : ";
+ String PART_OF = "partof";
+}
diff --git a/src/test/java/org/smartregister/extension/model/LocationHierarchyTreeTest.java b/src/test/java/org/smartregister/extension/model/LocationHierarchyTreeTest.java
new file mode 100644
index 0000000..fdb4bee
--- /dev/null
+++ b/src/test/java/org/smartregister/extension/model/LocationHierarchyTreeTest.java
@@ -0,0 +1,104 @@
+package org.smartregister.extension.model;
+
+import ca.uhn.fhir.rest.param.ReferenceParam;
+import org.hl7.fhir.r4.model.Location;
+import org.hl7.fhir.r4.model.Reference;
+import org.junit.Test;
+
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNull;
+import static org.junit.Assert.assertNotNull;
+
+public class LocationHierarchyTreeTest {
+
+ @Test
+ public void testAddLocationWithoutChildLocations() {
+ Location location = new Location();
+ location.setId("Location/1");
+ location.setName("Test Location");
+ Reference partOfReference = new Reference();
+ partOfReference.setReference("");
+ location.setPartOf(partOfReference);
+ LocationHierarchyTree locationHierarchyTree = new LocationHierarchyTree();
+ locationHierarchyTree.addLocation(location);
+
+ Tree tree = locationHierarchyTree.getLocationsHierarchy();
+ assertNotNull(tree);
+ assertNotNull(tree.getTree());
+ assertEquals("Location/1", tree.getTree().getTreeNodeId().getValue());
+ assertEquals("Location/1", tree.getTree().getTreeNode().getNodeId().getValue());
+ assertEquals("Test Location", tree.getTree().getTreeNode().getLabel().getValue());
+ assertNull(tree.getTree().getTreeNode().getParent().getValue());
+ assertEquals(0, tree.getTree().getTreeNode().getChildren().size());
+ }
+
+ @Test
+ public void testBuildTreeFromList() {
+ Location location1 = new Location();
+ location1.setId("Location/1");
+ location1.setName("Test Location");
+ Reference partOfReference = new Reference();
+ partOfReference.setReference("");
+ location1.setPartOf(partOfReference);
+
+ Location location2 = new Location();
+ location2.setId("Location/2");
+ location2.setName("Test Location 2");
+ partOfReference = new Reference();
+ partOfReference.setReference("Location/1");
+ location2.setPartOf(partOfReference);
+
+ Location location3 = new Location();
+ location3.setId("Location/3");
+ location3.setName("Test Location 3");
+ partOfReference = new Reference();
+ partOfReference.setReference("Location/2");
+ location3.setPartOf(partOfReference);
+
+ LocationHierarchyTree locationHierarchyTree = new LocationHierarchyTree();
+
+ List locationList = new ArrayList<>();
+ locationList.add(location1);
+ locationList.add(location2);
+ locationList.add(location3);
+
+ locationHierarchyTree.buildTreeFromList(locationList);
+ Tree tree = locationHierarchyTree.getLocationsHierarchy();
+ assertNotNull(tree);
+ assertNotNull(tree.getTree());
+ assertEquals("Location/1", tree.getTree().getTreeNodeId().getValue());
+ assertEquals("Location/1", tree.getTree().getTreeNode().getNodeId().getValue());
+ assertEquals("Test Location", tree.getTree().getTreeNode().getLabel().getValue());
+ assertNull(tree.getTree().getTreeNode().getParent().getValue());
+ assertEquals(1, tree.getTree().getTreeNode().getChildren().size());
+
+ assertEquals("Location/2", tree.getTree().getTreeNode().getChildren().get(0).getChildren().getNodeId().getValue());
+ assertEquals("Test Location 2", tree.getTree().getTreeNode().getChildren().get(0).getChildren().getLabel().getValue());
+ assertNotNull(tree.getTree().getTreeNode().getChildren().get(0).getChildren().getParent().getValue());
+ assertEquals("Location/1", tree.getTree().getTreeNode().getChildren().get(0).getChildren().getParent().getValue());
+ assertEquals(1, tree.getTree().getTreeNode().getChildren().get(0).getChildren().getChildren().size());
+
+ assertEquals("Location/3", tree.getTree().getTreeNode().getChildren().get(0).getChildren().getChildren().get(0).getChildren().getNodeId().getValue());
+ assertEquals("Test Location 3", tree.getTree().getTreeNode().getChildren().get(0).getChildren().getChildren().get(0).getChildren().getLabel().getValue());
+ assertNotNull(tree.getTree().getTreeNode().getChildren().get(0).getChildren().getChildren().get(0).getChildren().getParent().getValue());
+ assertEquals("Location/2", tree.getTree().getTreeNode().getChildren().get(0).getChildren().getChildren().get(0).getChildren().getParent().getValue());
+ assertEquals(0, tree.getTree().getTreeNode().getChildren().get(0).getChildren().getChildren().get(0).getChildren().getChildren().size());
+
+ assertNotNull(locationHierarchyTree.getLocationsHierarchy().getParentChildren());
+ assertEquals(2, locationHierarchyTree.getLocationsHierarchy().getParentChildren().size());
+ assertEquals("Location/1", locationHierarchyTree.getLocationsHierarchy().getParentChildren().get(0).getIdentifier().getValue());
+ assertEquals(1, locationHierarchyTree.getLocationsHierarchy().getParentChildren().get(0).getChildIdentifiers().size());
+ assertEquals("Location/2", locationHierarchyTree.getLocationsHierarchy().getParentChildren().get(0).getChildIdentifiers().get(0).getValue());
+
+ assertEquals("Location/2", locationHierarchyTree.getLocationsHierarchy().getParentChildren().get(1).getIdentifier().getValue());
+ assertEquals(1, locationHierarchyTree.getLocationsHierarchy().getParentChildren().get(1).getChildIdentifiers().size());
+ assertEquals("Location/3", locationHierarchyTree.getLocationsHierarchy().getParentChildren().get(1).getChildIdentifiers().get(0).getValue());
+
+
+ }
+
+}
diff --git a/src/test/java/org/smartregister/extension/model/TreeNodeTest.java b/src/test/java/org/smartregister/extension/model/TreeNodeTest.java
new file mode 100644
index 0000000..f4df891
--- /dev/null
+++ b/src/test/java/org/smartregister/extension/model/TreeNodeTest.java
@@ -0,0 +1,65 @@
+package org.smartregister.extension.model;
+
+import org.hl7.fhir.r4.model.Location;
+import org.hl7.fhir.r4.model.StringType;
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNull;
+
+public class TreeNodeTest {
+
+ @Test
+ public void testAddingChild() {
+
+ StringType rootNodeName = new StringType();
+ rootNodeName.setValue("Root Node");
+
+ StringType rootNodeId = new StringType();
+ rootNodeId.setValue("Location/1");
+
+ StringType rootNodeLabel = new StringType();
+ rootNodeLabel.setValue("Root Location Node");
+
+ Location location = new Location();
+ location.setId("Location/1");
+
+ StringType childNodeName = new StringType();
+ childNodeName.setValue("Child Node");
+
+ StringType childNodeId = new StringType();
+ childNodeId.setValue("Location/2");
+
+ StringType childNodeLabel = new StringType();
+ childNodeLabel.setValue("Child Location Node");
+
+ TreeNode rootNode = new TreeNode(rootNodeName, rootNodeId, rootNodeLabel, location, null);
+ TreeNode childNode = new TreeNode(childNodeName, childNodeId, childNodeLabel, location, rootNodeId);
+ rootNode.addChild(childNode);
+
+ assertEquals(childNodeId.getValue(), rootNode.findChild(childNodeId.getValue()).getNodeId().getValue());
+ assertEquals(rootNodeId.getValue(), rootNode.findChild(childNodeId.getValue()).getParent().getValue());
+ }
+
+ @Test
+ public void findInvalidChildren() {
+ StringType rootNodeName = new StringType();
+ rootNodeName.setValue("Root Node");
+
+ StringType rootNodeId = new StringType();
+ rootNodeId.setValue("Location/1");
+
+ StringType rootNodeLabel = new StringType();
+ rootNodeLabel.setValue("Root Location Node");
+
+ Location location = new Location();
+ location.setId("Location/1");
+
+ TreeNode rootNode = new TreeNode(rootNodeName, rootNodeId, rootNodeLabel, location, null);
+ assertEquals(0, rootNode.getChildren().size());
+ assertNull(rootNode.findChild("Location/2"));
+ }
+}
diff --git a/src/test/java/org/smartregister/extension/model/TreeTest.java b/src/test/java/org/smartregister/extension/model/TreeTest.java
new file mode 100644
index 0000000..00cf9cb
--- /dev/null
+++ b/src/test/java/org/smartregister/extension/model/TreeTest.java
@@ -0,0 +1,57 @@
+package org.smartregister.extension.model;
+
+
+import org.hl7.fhir.r4.model.Location;
+import org.junit.Test;
+
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNull;
+import static org.junit.Assert.assertNotNull;
+
+public class TreeTest {
+
+ @Test
+ public void testAddingNodeWithOutParent() {
+ Tree tree = new Tree();
+ Location location = new Location();
+ location.setId("testId");
+ tree.addNode("Location/1", "test", location, null);
+
+ TreeNode treeNode = tree.getNode("Location/1");
+
+ assertEquals("Location/1", treeNode.getNodeId().getValue());
+ assertEquals("test", treeNode.getLabel().getValue());
+ assertEquals(location, treeNode.getNode());
+ assertNull(treeNode.getParent().getValue());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testCannotReAddExistingNode() {
+ Tree tree = new Tree();
+ Location location = new Location();
+ location.setId("testId");
+ tree.addNode("Location/1", "test", location, null);
+ tree.addNode("Location/1", "test", location, null);
+ }
+
+ @Test
+ public void testAddingNodeWithValidParent() {
+ Tree tree = new Tree();
+ Location location = new Location();
+ location.setId("testId");
+ tree.addNode("Location/1", "test", location, null);
+ tree.addNode("Location/2", "test2", location, "Location/1");
+
+ TreeNode childNode = tree.getNode("Location/2");
+
+ assertEquals("Location/2", childNode.getNodeId().getValue());
+ assertEquals("test2", childNode.getLabel().getValue());
+ assertEquals(location, childNode.getNode());
+ assertNotNull(childNode.getParent().getValue());
+
+ String parentNodeId = childNode.getParent().getValue();
+
+ assertEquals("Location/1", parentNodeId);
+ }
+}