hapifhir / hapi-fhir

🔥 HAPI FHIR - Java API for HL7 FHIR Clients and Servers
http://hapifhir.io
Apache License 2.0
2k stars 1.31k forks source link

Cannot generate a /MyFhirResource?_id=111,222,333 Query with IQueryParameterType/IGenericClient #2465

Open granadacoder opened 3 years ago

granadacoder commented 3 years ago

So the _id parameter. Most implementations seem to support multiple values.

Below examples have 3 comma separated values (and actually brings back results when I wrote this)

http://hapi.fhir.org/baseR4/Patient?_id=b24ee88c-a396-4bdc-8352-7a2bc392e08d,0712b251-1071-4d77-93a5-4cc37c0d314e,a5325ff2-b6a1-4379-ae3f-fbc097e1e508

https://vonk.fire.ly/R4/Patient?_id=111,222,223

http://wildfhir4.aegis.net/fhir4-0-0/Patient/?_id=99b1a3a8-b871-41a8-bd6b-65eec2d6cdc4&_id=54cd03d31c704327aae493302b6cde88&_id=bac66735217842a0b839fed35a7a1fc4&_id=500377072fa14833bf38b55dfbab37cf

I am unable to get this kind of query using IQueryParameterType

I tried this:

            import ca.uhn.fhir.model.api.IQueryParameterType;
            import org.hl7.fhir.instance.model.api.IAnyResource;

            Map<String, List<IQueryParameterType>> existingSearchParameterMap = new (blah blah blah);

            Collection<String> ids = new ArrayList<>();
            // aegis values below
            ids.add("54cd03d31c704327aae493302b6cde88");
            ids.add("bac66735217842a0b839fed35a7a1fc4");
            ids.add("500377072fa14833bf38b55dfbab37cf");
            // below vonk value 
            ids.add("111");
            ids.add("222");
            ids.add("223");

            List<IQueryParameterType> idChainedFilters = new ArrayList<>();

            for(String currentId : ids) {
                StringParam sp1 = new StringParam(currentId, true);
                idChainedFilters.add(sp1);
            }

            existingSearchParameterMap.put(IAnyResource.SP_RES_ID, idChainedFilters);

The gives me:

Patient?_id=99b1a3a8-b871-41a8-bd6b-65eec2d6cdc4&_id=54cd03d31c704327aae493302b6cde88&_id=bac66735217842a0b839fed35a7a1fc4&_id=500377072fa14833bf38b55dfbab37cf

(the above result is kinda expected of course..........but does not do the _id=111,222,223 thing.)

===

Then I tried a comma sep list (csv).

            Map<String, List<IQueryParameterType>> existingSearchParameterMap = new (blah blah);

            Collection<String> ids = new ArrayList<>();
            // aegis values below
            ids.add("54cd03d31c704327aae493302b6cde88");
            ids.add("bac66735217842a0b839fed35a7a1fc4");
            ids.add("500377072fa14833bf38b55dfbab37cf");
            // below vonk value 
            ids.add("111");
            ids.add("222");
            ids.add("223");
            String csvIds = StringUtils.join(ids, ',');

            List<IQueryParameterType> idChainedFilters = new ArrayList<>();
            StringParam sp1 = new StringParam(csvIds, true);
            idChainedFilters.add(sp1);
            existingSearchParameterMap.put(IAnyResource.SP_RES_ID, idChainedFilters);

The above gives me:

Patient?_id=54cd03d31c704327aae493302b6cde88%5C%2Cbac66735217842a0b839fed35a7a1fc4%5C%2C500377072fa14833bf38b55dfbab37cf

or vonk-ints

https://server.fire.ly/R4/Patient?_id=111**%5C**,222**%5C**,223

(The above ones go hay wire :) with the result (500) )

If there is an existing way to do it, and I just did not figure out the syntax sugar, my apologies.


I looked through the other classes off of:

Interface IQueryParameterType

All Known Implementing Classes: BaseCodingDt, BaseIdentifierDt, BaseParam, BaseParamWithPrefix, BaseQuantityDt, CompositeParam, DateParam, HasParam, InternalCodingDt, MarkdownDt, NumberParam, QuantityParam, ReferenceParam, SpecialParam, StringDt, StringParam, TimeDt, TokenParam, UriParam

I don't see another candidate in the above for _id. But I'm still a learning-newbie with IGenericClient.

=====

Append

I tried this.

String csvIds = StringUtils.join(ids, "%2C");

No go there.

stringMagic

Above code using the below version(s).

implementation group: 'ca.uhn.hapi.fhir', name: 'hapi-fhir-structures-r4', version: hapiFhirVersion
implementation group: 'ca.uhn.hapi.fhir', name: 'hapi-fhir-client-okhttp', version: hapiFhirVersion

    hapiFhirVersion = '5.2.0'

APPEND

Ok, with a little digging into the code....below are some breadcrumbs.

image

image

And that tracks down to

public class StringParam extends BaseParam implements IQueryParameterType {

image

And (Ding Ding Ding)

image

and because you/someone left a good comment...I see the "tension"

(start HL7 quote)

3.1.1.4.19 Escaping Search Parameters In the rules described above, special rules are defined for the characters $, ,, and |. As a consequence, if these characters appear in an actual parameter value, they must be differentiated from their use as separator characters. When any of these characters appear in an actual parameter value, they must be prepended by the character \, which also must be used to prepend itself. Therefore, param=xxx$xxx indicates that it is a composite parameter, while param=xx\$xx indicates that the parameter has the literal value xx$xx. The parameter value xx\xx is illegal, and the parameter value param=xx\xx indicates a literal value of xx\xx. This means that:

GET [base]/Observation?code=a,b is a request for any Observation that has a code of either a or b, whereas:

GET [base]/Observation?code=a\,b is a request for any Observation that has a code of a,b.

(end HL7 quote)

Hmmm.

Would adding a

boolean useExactValue (for StringParam) be a possibility for this tension point?

or is that short sighted in some regard?

@Override
String doGetValueAsQueryToken(FhirContext theContext) {
            if ( this.useExactValue ) {
                return this.myValue;
            } else {
    return ParameterUtil.escape(myValue);
           }
}
jamesagnew commented 3 years ago

To generate that specific query pattern, this is the only way with the generic client:

client
    .search()
    .forResource("Patient")
    .where(IAnyResource.RES_ID
        .exactly()
        .codes(
            "b24ee88c-a396-4bdc-8352-7a2bc392e08d",
            "0712b251-1071-4d77-93a5-4cc37c0d314e",
            "a5325ff2-b6a1-4379-ae3f-fbc097e1e508"))
    .returnBundle(Bundle.class)
    .execute();

TokenParam is intended to model a single value, it can't be used for multiple values. TokenOrListParam and TokenAndListParam are the comma-seaparated list equivalents. Unfortunately there isn't a way to pass that class into the client currently (it's only used in the server side).

PR to add support would gladly be considered...

granadacoder commented 3 years ago

Ok. Thanks for the hint (code you showed) as a non-string-magic workaround.

I will try to find time to work on a PR.

As we've heard in the conferences, it was like "What is Fhir?", "What is Fhir?", "What is Fhir?"...........and then "WHY AREN'T ALL OUR SYSTEMS FHIR SYSTEMS???!!".

Thanks.

granadacoder commented 3 years ago

https://groups.google.com/g/hapi-fhir/c/-KAURRW3nOc

How do I get added as a contributor?

Thanks.