Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added class to handle reference search within Claim resource. And log… #363

Merged
merged 1 commit into from
Aug 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 126 additions & 0 deletions src/main/java/org/hl7/davinci/priorauth/FhirScanner.java
Original file line number Diff line number Diff line change
@@ -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<Reference> findReferences(Object obj) {
List<Reference> references = new ArrayList<>();
scanInstance(obj, Reference.class, Collections.newSetFromMap(new IdentityHashMap<>()), references);
return references;
}

public static List<Period> findPeriods(Object obj) {
List<Period> 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<Coding> findCodings(Object obj) {
List<Coding> codes = new ArrayList<>();
scanInstance(obj, Coding.class, Collections.newSetFromMap(new IdentityHashMap<>()), codes);
return codes;
}

public static List<CodeableConcept> findCodeableConcepts(Object obj) {
List<CodeableConcept> 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 <T> 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 <T> void scanInstance(Object objectToScan, Class<T> lookingFor, Set<? super Object> scanned, Collection<? super T> 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();
}
}
}
}
59 changes: 43 additions & 16 deletions src/main/java/org/hl7/davinci/priorauth/endpoint/ClaimEndpoint.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -135,6 +132,7 @@ private ResponseEntity<String> 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) {
Expand All @@ -151,6 +149,11 @@ private ResponseEntity<String> 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);
Expand Down Expand Up @@ -188,11 +191,6 @@ private ResponseEntity<String> 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<Integer> infoSet = new HashSet<>();
if (claim.hasSupportingInfo()) {
Expand All @@ -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<Reference> referenceList = FhirScanner.findReferences(resource);

// check that resource exists in bundle
// function produces a null value at index 0, removing it
List<Reference> nullReferenceRemovedList = referenceList.stream()
.filter(reference -> reference.hasReference()).collect(Collectors.toList());

// function duplicates some values, removing duplicates
Set<String> referenceSet= new HashSet<>();
List<Reference> duplicateReferencesRemovedList = new ArrayList<Reference>();

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<Bundle.BundleEntryComponent> entries = bundle.getEntry();
List<Reference> sizeCheck = new ArrayList<Reference>();
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
Expand Down
Loading