All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.ibm.fhir.cql.engine.searchparam.SearchParameterResolver Maven / Gradle / Ivy

The newest version!
/*
 * (C) Copyright IBM Corp. 2021, 2022
 *
 * SPDX-License-Identifier: Apache-2.0
 */
package com.ibm.fhir.cql.engine.searchparam;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang3.tuple.Pair;

import com.ibm.fhir.model.resource.SearchParameter;
import com.ibm.fhir.model.type.code.ResourceType;
import com.ibm.fhir.model.type.code.SearchParamType;
import com.ibm.fhir.search.util.SearchHelper;

public class SearchParameterResolver {

    private final SearchHelper searchHelper;

    public SearchParameterResolver(SearchHelper searchHelper) {
        this.searchHelper = searchHelper;
    }

    public SearchParameter getSearchParameterDefinition(String resourceType, String path) throws Exception {
        return getSearchParameterDefinition(resourceType, path, null);
    }

    public SearchParameter getSearchParameterDefinition(String resourceType, String path, SearchParamType paramType)
            throws Exception {
        if (resourceType == null || path == null) {
            return null;
        }

        String name = null;
        if (path.equals("id")) {
            name = "_id";
            path = "";
        }

        // should this use the registry (all parameters) or the filtered set of search parameters for a given tenant?
        Map params = searchHelper.getSearchParameters(resourceType);

        for (SearchParameter param : params.values()) {
            if (name != null && param.getCode().getValue().equals(name)) {
                return param;
            }

            if (paramType == null || param.getType().equals(paramType)) {
                Set normalizedPath = normalizePath(resourceType, param.getExpression().getValue());
                if (normalizedPath.contains(path)) {
                    return param;
                }
            }
        }

        return null;
    }

    public Pair createSearchParameter(String context, String resourceType, String path, String value)
            throws Exception {

        Pair result = null;

        SearchParameter searchParam = this.getSearchParameterDefinition(resourceType, path);
        if (searchParam != null) {

            String name = searchParam.getCode().getValue();

            if (SearchParamType.TOKEN.equals(searchParam.getType())) {
                result = Pair.of(name, new TokenParameter(value).setName(name));
            } else if (SearchParamType.REFERENCE.equals(searchParam.getType())) {
                result = Pair.of(name, new ReferenceParameter(ResourceType.of(context), value).setName(name));
            } else if (SearchParamType.QUANTITY.equals(searchParam.getType())) {
                result = Pair.of(name, new QuantityParameter(new BigDecimal(value)).setName(name));
            } else if (SearchParamType.STRING.equals(searchParam.getType())) {
                result = Pair.of(name, new StringParameter(value).setName(name));
            } else if (SearchParamType.NUMBER.equals(searchParam.getType())) {
                result = Pair.of(name, new NumberParameter(new BigDecimal(value)).setName(name));
            } else if (SearchParamType.URI.equals(searchParam.getType())) {
                result = Pair.of(name, new UriParameter(value).setName(name));
            } else {
                throw new UnsupportedOperationException(String.format("SearchParameter type '%s' is not supported", searchParam.getType().toString()));
            }
        }

        return result;
    }

    // This is actually a lot of processing. We should cache search parameter
    // resolutions.
    private Set normalizePath(String resourceType, String path) {
        // TODO: What we really need is FhirPath parsing to just get the path
        // MedicationAdministration.medication.as(CodeableConcept)
        // MedicationAdministration.medication.as(Reference)
        // (MedicationAdministration.medication as CodeableConcept)
        // (MedicationAdministration.medication as Reference)
        // Condition.onset.as(Age) | Condition.onset.as(Range)
        // Observation.code | Observation.component.code

        // Trim off outer parens
        path = removeParens(path);

        Set normalizedParts = new HashSet();
        String[] orParts = path.split("\\|");
        for (String part : orParts) {
            part = part.trim();
            part = removeParens(part);

            // Trim off DataType
            if (part.startsWith(resourceType)) {
                part = part.substring(part.indexOf(".") + 1, part.length());

                // Split into components
                String[] pathSplit = part.split("\\.");
                List newPathParts = new ArrayList<>();

                for (String p : pathSplit) {
                    // Skip the "as(X)" part.
                    if (p.startsWith("as(")) {
                        continue;
                    }

                    // Skip the "[x]" part.
                    if (p.startsWith("[x]")) {
                        continue;
                    }

                    // Filter out spaces and everything after "medication as Reference"
                    String[] ps = p.split(" ");
                    if (ps != null && ps.length > 0) {
                        newPathParts.add(ps[0]);
                    }
                }

                part = String.join(".", newPathParts);
                normalizedParts.add(part);
            }
        }

        // This handles cases such as /Condition?onset-age and /Condition?onset-date
        // where there are multiple underlying representations of the same property
        // (e.g. Condition.onset.as(Age) | Condition.onset.as(Range)), but
        // will punt on something like /Observation?combo-code where the underlying
        // representation maps to multiple places in a nested hierarchy (e.g.
        // Observation.code | Observation.component.code ).
//        if (normalizedParts.contains(path)) {
//            return path;
//        } else {
//            return null;
//        }
        return normalizedParts;
    }

    private String removeParens(String path) {
        if (path.startsWith("(")) {
            path = path.substring(1, path.length() - 1);
        }
        return path;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy