From b219da0c3e9b4c625eb5c36aafc6042d85d6051b Mon Sep 17 00:00:00 2001 From: "salah.hasan" Date: Wed, 24 Jul 2024 12:06:24 -0400 Subject: [PATCH] Added class to handle reference search within Claim resource. And logic to check references against resources in Bundle --- .../hl7/davinci/priorauth/FhirScanner.java | 126 ++++++++++++++++++ .../priorauth/endpoint/ClaimEndpoint.java | 59 +++++--- 2 files changed, 169 insertions(+), 16 deletions(-) create mode 100644 src/main/java/org/hl7/davinci/priorauth/FhirScanner.java diff --git a/src/main/java/org/hl7/davinci/priorauth/FhirScanner.java b/src/main/java/org/hl7/davinci/priorauth/FhirScanner.java new file mode 100644 index 00000000..079d0399 --- /dev/null +++ b/src/main/java/org/hl7/davinci/priorauth/FhirScanner.java @@ -0,0 +1,126 @@ +package org.hl7.davinci.priorauth; + +import org.hl7.fhir.r4.model.CodeableConcept; +import org.hl7.fhir.r4.model.Coding; +import org.hl7.fhir.r4.model.Period; +import org.hl7.fhir.r4.model.Reference; + +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.*; + +public class FhirScanner { + + /** + * Finds any instance (recursively) of a Reference within the specified object + * + * @param obj The object to search + * @return A list of Reference instances found in the object + */ + public static List findReferences(Object obj) { + List references = new ArrayList<>(); + scanInstance(obj, Reference.class, Collections.newSetFromMap(new IdentityHashMap<>()), references); + return references; + } + + public static List findPeriods(Object obj) { + List periods = new ArrayList<>(); + scanInstance(obj, Period.class, Collections.newSetFromMap(new IdentityHashMap<>()), periods); + return periods; + } + + /** + * Finds any instance (recursively) of a CodeableConcept or Coding within the specified object + * + * @param obj The object to search + * @return A list of CodeableConcept or Coding instances found in the object + */ + public static List findCodings(Object obj) { + List codes = new ArrayList<>(); + scanInstance(obj, Coding.class, Collections.newSetFromMap(new IdentityHashMap<>()), codes); + return codes; + } + + public static List findCodeableConcepts(Object obj) { + List codeableConcepts = new ArrayList<>(); + scanInstance(obj, CodeableConcept.class, Collections.newSetFromMap(new IdentityHashMap<>()), codeableConcepts); + return codeableConcepts; + } + + /** + * Scans an object recursively to find any instances of the specified type + * + * @param objectToScan The object to scan + * @param lookingFor The class/type to find instances of + * @param scanned A pre-initialized set that is used internally to determine what has already been scanned to avoid endless recursion on self-referencing objects + * @param results A pre-initialized collection/list that will be populated with the results of the scan + * @param The type of class to look for instances of that must match the initialized results collection + * @implNote Found this code online from https://stackoverflow.com/questions/57758392/is-there-are-any-way-to-get-all-the-instances-of-type-x-by-reflection-utils + */ + private static void scanInstance(Object objectToScan, Class lookingFor, Set scanned, Collection results) { + if (objectToScan == null) { + return; + } + if (!scanned.add(objectToScan)) { // to prevent any endless scan loops + return; + } + // you might need some extra code if you want to correctly support scanning for primitive types + if (lookingFor.isInstance(objectToScan)) { + results.add(lookingFor.cast(objectToScan)); + // either return or continue to scan of target object might contains references to other objects of this type + } + // we won't find anything intresting in Strings, and it is pretty popular type + if (objectToScan instanceof String) { + return; + } + // LINK-805: avoid illegal reflective access + // consider adding checks for other types we don't want to recurse into + else if (objectToScan instanceof Enum) { + return; + } + // basic support for popular java types to prevent scanning too much of java internals in most common cases, but might cause + // side-effects in some cases + else if (objectToScan instanceof Iterable) { + ((Iterable) objectToScan).forEach(obj -> scanInstance(obj, lookingFor, scanned, results)); + } else if (objectToScan instanceof Map) { + ((Map) objectToScan).forEach((key, value) -> { + scanInstance(key, lookingFor, scanned, results); + scanInstance(value, lookingFor, scanned, results); + }); + } + // remember about arrays, if you want to support primitive types remember to use Array class instead. + else if (objectToScan instanceof Object[]) { + int length = Array.getLength(objectToScan); + for (int i = 0; i < length; i++) { + scanInstance(Array.get(objectToScan, i), lookingFor, scanned, results); + } + } else if (objectToScan.getClass().isArray()) { + return; // primitive array + } else { + Class currentClass = objectToScan.getClass(); + while (currentClass != Object.class) { + for (Field declaredField : currentClass.getDeclaredFields()) { + // skip static fields + if (Modifier.isStatic(declaredField.getModifiers())) { + continue; + } + // skip primitives, to prevent wrapping of "int" to "Integer" and then trying to scan its "value" field and loop endlessly. + if (declaredField.getType().isPrimitive()) { + return; + } + try { + if (!declaredField.trySetAccessible()) { + // either throw error, skip, or use more black magic like Unsafe class to make field accessible anyways. + continue; // I will just skip it, it's probably some internal one. + } + scanInstance(declaredField.get(objectToScan), lookingFor, scanned, results); + } catch (IllegalAccessException | SecurityException ignored) { + continue; + } + } + currentClass = currentClass.getSuperclass(); + } + } + } +} diff --git a/src/main/java/org/hl7/davinci/priorauth/endpoint/ClaimEndpoint.java b/src/main/java/org/hl7/davinci/priorauth/endpoint/ClaimEndpoint.java index cc1dd55f..1c5a6211 100644 --- a/src/main/java/org/hl7/davinci/priorauth/endpoint/ClaimEndpoint.java +++ b/src/main/java/org/hl7/davinci/priorauth/endpoint/ClaimEndpoint.java @@ -1,11 +1,15 @@ package org.hl7.davinci.priorauth.endpoint; +import java.lang.reflect.Array; +import java.sql.Ref; import java.util.*; import java.util.logging.Level; import java.util.logging.Logger; +import java.util.stream.Collectors; import javax.servlet.http.HttpServletRequest; +import org.hl7.davinci.priorauth.*; import org.hl7.fhir.r4.model.*; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; @@ -20,13 +24,6 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.hl7.davinci.priorauth.authorization.AuthUtils; -import org.hl7.davinci.priorauth.App; -import org.hl7.davinci.priorauth.Audit; -import org.hl7.davinci.priorauth.ClaimResponseFactory; -import org.hl7.davinci.priorauth.FhirUtils; -import org.hl7.davinci.priorauth.PALogger; -import org.hl7.davinci.priorauth.ProcessClaimItemTask; -import org.hl7.davinci.priorauth.UpdateClaimTask; import org.hl7.davinci.priorauth.Audit.AuditEventOutcome; import org.hl7.davinci.priorauth.Audit.AuditEventType; import org.hl7.davinci.priorauth.Database.Table; @@ -135,6 +132,7 @@ private ResponseEntity submitOperation(String body, RequestType requestT Bundle bundle = (Bundle) resource; if (bundle.hasEntry() && (!bundle.getEntry().isEmpty()) && bundle.getEntry().get(0).hasResource() && bundle.getEntry().get(0).getResource().getResourceType() == ResourceType.Claim) { + if(validateReferences(bundle.getEntry().get(0).getResource(), bundle)) { if (validateSupportingInfoSequence(bundle)) { Bundle responseBundle = processBundle(bundle); if (responseBundle == null) { @@ -151,6 +149,11 @@ private ResponseEntity submitOperation(String body, RequestType requestT status = HttpStatus.CREATED; auditOutcome = AuditEventOutcome.SUCCESS; } + } else { + OperationOutcome error = FhirUtils.buildOutcome(IssueSeverity.ERROR, IssueType.INVALID, REQUIRES_BUNDLE); + formattedData = FhirUtils.getFormattedData(error, requestType); + logger.severe("ClaimEndpoint::References in the Claim resource do not exist in the bundle as a Resource"); + } } else { OperationOutcome error = FhirUtils.buildOutcome(IssueSeverity.ERROR, IssueType.INVALID, REQUIRES_BUNDLE); formattedData = FhirUtils.getFormattedData(error, requestType); @@ -188,11 +191,6 @@ private ResponseEntity submitOperation(String body, RequestType requestT } protected boolean validateSupportingInfoSequence(Bundle bundle) { - // for loop that runs until you run out of Claim.supportingInfo.sequence - // check if Hashset.get(sequence) - // if false, add sequence - // if true, throw an error - // return false Claim claim = (Claim) bundle.getEntry().get(0).getResource(); Set infoSet = new HashSet<>(); if (claim.hasSupportingInfo()) { @@ -208,13 +206,42 @@ protected boolean validateSupportingInfoSequence(Bundle bundle) { return true; } - protected void validateReferences(Resource resource, Bundle bundle) { + protected boolean validateReferences(Resource resource, Bundle bundle) { - //get every reference in this resource + // retrieve all References + List referenceList = FhirScanner.findReferences(resource); - // check that resource exists in bundle + // function produces a null value at index 0, removing it + List nullReferenceRemovedList = referenceList.stream() + .filter(reference -> reference.hasReference()).collect(Collectors.toList()); + + // function duplicates some values, removing duplicates + Set referenceSet= new HashSet<>(); + List duplicateReferencesRemovedList = new ArrayList(); + + for (Reference reference : nullReferenceRemovedList) { + if (referenceSet.add(reference.getReference())) { + duplicateReferencesRemovedList.add(reference); + } + } - //if resources exists, recursively call this function on that resource + // Check References against Resources in Bundle + List entries = bundle.getEntry(); + List sizeCheck = new ArrayList(); + for (Reference reference: duplicateReferencesRemovedList) { + for (Bundle.BundleEntryComponent entry: entries) { + if (reference.getResource() == entry.getResource()) { + sizeCheck.add(reference); + } + } + } + + + if (sizeCheck.containsAll(duplicateReferencesRemovedList)) { + return true; + } + + return false; } /** * Process the $submit operation Bundle. Theoretically, this is where business