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 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); + } +}