001package org.hl7.fhir.common.hapi.validation.support;
002
003import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
004import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
005import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
006import ca.uhn.fhir.context.RuntimePrimitiveDatatypeDefinition;
007import ca.uhn.fhir.context.support.TranslateConceptResult;
008import ca.uhn.fhir.context.support.TranslateConceptResults;
009import ca.uhn.fhir.i18n.Msg;
010import ca.uhn.fhir.context.FhirContext;
011import ca.uhn.fhir.context.FhirVersionEnum;
012import ca.uhn.fhir.context.support.ConceptValidationOptions;
013import ca.uhn.fhir.context.support.DefaultProfileValidationSupport;
014import ca.uhn.fhir.context.support.IValidationSupport;
015import ca.uhn.fhir.context.support.ValidationSupportContext;
016import ca.uhn.fhir.rest.client.api.IGenericClient;
017import ca.uhn.fhir.util.BundleUtil;
018import ca.uhn.fhir.util.JsonUtil;
019import ca.uhn.fhir.util.ParametersUtil;
020import ca.uhn.fhir.util.StringUtil;
021import org.apache.commons.lang3.StringUtils;
022import org.apache.commons.lang3.Validate;
023import org.hl7.fhir.instance.model.api.IBase;
024import org.hl7.fhir.instance.model.api.IBaseBundle;
025import org.hl7.fhir.instance.model.api.IBaseCoding;
026import org.hl7.fhir.instance.model.api.IBaseParameters;
027import org.hl7.fhir.instance.model.api.IBaseResource;
028import org.hl7.fhir.instance.model.api.IPrimitiveType;
029import org.hl7.fhir.r4.model.CodeSystem;
030import org.hl7.fhir.r4.model.Coding;
031import org.hl7.fhir.r4.model.Parameters;
032import org.hl7.fhir.r4.model.ValueSet;
033import org.slf4j.Logger;
034import org.slf4j.LoggerFactory;
035
036import javax.annotation.Nonnull;
037import java.io.IOException;
038import java.sql.Array;
039import java.util.ArrayList;
040import java.util.List;
041import java.util.Objects;
042import java.util.Optional;
043
044import static org.apache.commons.lang3.StringUtils.isBlank;
045import static org.apache.commons.lang3.StringUtils.isNotBlank;
046
047/**
048 * This class is an implementation of {@link IValidationSupport} that fetches validation codes
049 * from a remote FHIR based terminology server. It will invoke the FHIR
050 * <a href="http://hl7.org/fhir/valueset-operation-validate-code.html">ValueSet/$validate-code</a>
051 * operation in order to validate codes.
052 */
053public class RemoteTerminologyServiceValidationSupport extends BaseValidationSupport implements IValidationSupport {
054        private static final Logger ourLog = LoggerFactory.getLogger(RemoteTerminologyServiceValidationSupport.class);
055
056        private String myBaseUrl;
057        private List<Object> myClientInterceptors = new ArrayList<>();
058
059        /**
060         * Constructor
061         *
062         * @param theFhirContext The FhirContext object to use
063         */
064        public RemoteTerminologyServiceValidationSupport(FhirContext theFhirContext) {
065                super(theFhirContext);
066        }
067
068        public RemoteTerminologyServiceValidationSupport(FhirContext theFhirContext, String theBaseUrl) {
069                super(theFhirContext);
070                myBaseUrl = theBaseUrl;
071        }
072
073        @Override
074        public CodeValidationResult validateCode(@Nonnull ValidationSupportContext theValidationSupportContext, @Nonnull ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) {
075                return invokeRemoteValidateCode(theCodeSystem, theCode, theDisplay, theValueSetUrl, null);
076        }
077
078        @Override
079        public CodeValidationResult validateCodeInValueSet(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, @Nonnull IBaseResource theValueSet) {
080
081                IBaseResource valueSet = theValueSet;
082
083                // some external validators require the system when the code is passed
084                // so let's try to get it from the VS if is is not present
085                String codeSystem = theCodeSystem;
086                if (isNotBlank(theCode) && isBlank(codeSystem)) {
087                        codeSystem = extractCodeSystemForCode((ValueSet) theValueSet, theCode);
088                }
089
090                // Remote terminology services shouldn't be used to validate codes with an implied system
091                if (isBlank(codeSystem)) { return null; }
092
093                String valueSetUrl = DefaultProfileValidationSupport.getConformanceResourceUrl(myCtx, valueSet);
094                if (isNotBlank(valueSetUrl)) {
095                        valueSet = null;
096                } else {
097                        valueSetUrl = null;
098                }
099                return invokeRemoteValidateCode(codeSystem, theCode, theDisplay, valueSetUrl, valueSet);
100        }
101
102        /**
103         * Try to obtain the codeSystem of the received code from the received ValueSet
104         */
105        private String extractCodeSystemForCode(ValueSet theValueSet, String theCode) {
106                if (theValueSet.getCompose() == null || theValueSet.getCompose().getInclude() == null
107                        || theValueSet.getCompose().getInclude().isEmpty()) {
108                        return null;
109                }
110
111                if (theValueSet.getCompose().getInclude().size() == 1) {
112                        ValueSet.ConceptSetComponent include = theValueSet.getCompose().getInclude().iterator().next();
113                        return getVersionedCodeSystem(include);
114                }
115
116                // when component has more than one include, their codeSystem(s) could be different, so we need to make sure
117                // that we are picking up the system for the include to which the code corresponds
118                for (ValueSet.ConceptSetComponent include: theValueSet.getCompose().getInclude()) {
119                        if (include.hasSystem()) {
120                                for (ValueSet.ConceptReferenceComponent concept : include.getConcept()) {
121                                        if (concept.hasCodeElement() && concept.getCode().equals(theCode)) {
122                                                return getVersionedCodeSystem(include);
123                                        }
124                                }
125                        }
126                }
127
128                // at this point codeSystem couldn't be extracted for a multi-include ValueSet. Just on case it was
129                // because the format was not well handled, let's allow to watch the VS by an easy logging change
130                ourLog.trace("CodeSystem couldn't be extracted for code: {} for ValueSet: {}", theCode, theValueSet.getId());
131                return null;
132        }
133
134        private String getVersionedCodeSystem(ValueSet.ConceptSetComponent theComponent) {
135                        String codeSystem = theComponent.getSystem();
136                        if ( ! codeSystem.contains("|") && theComponent.hasVersion()) {
137                                codeSystem += "|" + theComponent.getVersion();
138                        }
139                        return codeSystem;
140        }
141
142        @Override
143        public IBaseResource fetchCodeSystem(String theSystem) {
144                IGenericClient client = provideClient();
145                Class<? extends IBaseBundle> bundleType = myCtx.getResourceDefinition("Bundle").getImplementingClass(IBaseBundle.class);
146                IBaseBundle results = client
147                        .search()
148                        .forResource("CodeSystem")
149                        .where(CodeSystem.URL.matches().value(theSystem))
150                        .returnBundle(bundleType)
151                        .execute();
152                List<IBaseResource> resultsList = BundleUtil.toListOfResources(myCtx, results);
153                if (resultsList.size() > 0) {
154                        return resultsList.get(0);
155                }
156
157                return null;
158        }
159
160        @Override
161        public LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode, String theDisplayLanguage) {
162                Validate.notBlank(theCode, "theCode must be provided");
163
164                IGenericClient client = provideClient();
165                FhirContext fhirContext = client.getFhirContext();
166                FhirVersionEnum fhirVersion = fhirContext.getVersion().getVersion();
167
168                switch (fhirVersion) {
169                        case DSTU3:
170                        case R4:
171                                IBaseParameters params = ParametersUtil.newInstance(fhirContext);
172                                ParametersUtil.addParameterToParametersString(fhirContext, params, "code", theCode);
173                                if (!StringUtils.isEmpty(theSystem)) {
174                                        ParametersUtil.addParameterToParametersString(fhirContext, params, "system", theSystem);
175                                }
176                                if (!StringUtils.isEmpty(theDisplayLanguage)) {
177                                        ParametersUtil.addParameterToParametersString(fhirContext, params, "language", theDisplayLanguage);
178                                }
179                                Class<?> codeSystemClass = myCtx.getResourceDefinition("CodeSystem").getImplementingClass();
180                                IBaseParameters outcome = client
181                                        .operation()
182                                        .onType((Class<? extends IBaseResource>) codeSystemClass)
183                                        .named("$lookup")
184                                        .withParameters(params)
185                                        .useHttpGet()
186                                        .execute();
187                                if (outcome != null && !outcome.isEmpty()) {
188                                        switch (fhirVersion) {
189                                                case DSTU3:
190                                                        return generateLookupCodeResultDSTU3(theCode, theSystem, (org.hl7.fhir.dstu3.model.Parameters)outcome);
191                                                case R4:
192                                                        return generateLookupCodeResultR4(theCode, theSystem, (org.hl7.fhir.r4.model.Parameters)outcome);
193                                        }
194                                }
195                                break;
196                        default:
197                                throw new UnsupportedOperationException(Msg.code(710) + "Unsupported FHIR version '" + fhirVersion.getFhirVersionString() +
198                                        "'. Only DSTU3 and R4 are supported.");
199                }
200                return null;
201        }
202
203        private LookupCodeResult generateLookupCodeResultDSTU3(String theCode, String theSystem, org.hl7.fhir.dstu3.model.Parameters outcomeDSTU3) {
204                // NOTE: I wanted to put all of this logic into the IValidationSupport Class, but it would've required adding
205                //       several new dependencies on version-specific libraries and that is explicitly forbidden (see comment in POM).
206                LookupCodeResult result = new LookupCodeResult();
207                result.setSearchedForCode(theCode);
208                result.setSearchedForSystem(theSystem);
209                result.setFound(true);
210                for (org.hl7.fhir.dstu3.model.Parameters.ParametersParameterComponent parameterComponent : outcomeDSTU3.getParameter()) {
211                        switch (parameterComponent.getName()) {
212                                case "property":
213                                        org.hl7.fhir.dstu3.model.Property part = parameterComponent.getChildByName("part");
214                                        // The assumption here is that we may only have 2 elements in this part, and if so, these 2 will be saved
215                                        if (part != null && part.hasValues() && part.getValues().size() >= 2) {
216                                                String key = ((org.hl7.fhir.dstu3.model.Parameters.ParametersParameterComponent) part.getValues().get(0)).getValue().toString();
217                                                String value = ((org.hl7.fhir.dstu3.model.Parameters.ParametersParameterComponent) part.getValues().get(1)).getValue().toString();
218                                                if (!StringUtils.isEmpty(key) && !StringUtils.isEmpty(value)) {
219                                                        result.getProperties().add(new StringConceptProperty(key, value));
220                                                }
221                                        }
222                                        break;
223                                case "designation":
224                                        ConceptDesignation conceptDesignation = new ConceptDesignation();
225                                        for (org.hl7.fhir.dstu3.model.Parameters.ParametersParameterComponent designationComponent : parameterComponent.getPart()) {
226                                                switch(designationComponent.getName()) {
227                                                        case "language":
228                                                                conceptDesignation.setLanguage(designationComponent.getValue().toString());
229                                                                break;
230                                                        case "use":
231                                                                org.hl7.fhir.dstu3.model.Coding coding = (org.hl7.fhir.dstu3.model.Coding)designationComponent.getValue();
232                                                                if (coding != null) {
233                                                                        conceptDesignation.setUseSystem(coding.getSystem());
234                                                                        conceptDesignation.setUseCode(coding.getCode());
235                                                                        conceptDesignation.setUseDisplay(coding.getDisplay());
236                                                                }
237                                                                break;
238                                                        case "value":
239                                                                conceptDesignation.setValue(((designationComponent.getValue() == null)?null:designationComponent.getValue().toString()));
240                                                                break;
241                                                }
242                                        }
243                                        result.getDesignations().add(conceptDesignation);
244                                        break;
245                                case "name":
246                                        result.setCodeSystemDisplayName(((parameterComponent.getValue() == null)?null:parameterComponent.getValue().toString()));
247                                        break;
248                                case "version":
249                                        result.setCodeSystemVersion(((parameterComponent.getValue() == null)?null:parameterComponent.getValue().toString()));
250                                        break;
251                                case "display":
252                                        result.setCodeDisplay(((parameterComponent.getValue() == null)?null:parameterComponent.getValue().toString()));
253                                        break;
254                                case "abstract":
255                                        result.setCodeIsAbstract(((parameterComponent.getValue() == null)?false:Boolean.parseBoolean(parameterComponent.getValue().toString())));
256                                        break;
257                        }
258                }
259                return result;
260        }
261
262        private LookupCodeResult generateLookupCodeResultR4(String theCode, String theSystem, org.hl7.fhir.r4.model.Parameters outcomeR4) {
263                // NOTE: I wanted to put all of this logic into the IValidationSupport Class, but it would've required adding
264                //       several new dependencies on version-specific libraries and that is explicitly forbidden (see comment in POM).
265                LookupCodeResult result = new LookupCodeResult();
266                result.setSearchedForCode(theCode);
267                result.setSearchedForSystem(theSystem);
268                result.setFound(true);
269                for (org.hl7.fhir.r4.model.Parameters.ParametersParameterComponent parameterComponent : outcomeR4.getParameter()) {
270                        switch (parameterComponent.getName()) {
271                                case "property":
272                                        org.hl7.fhir.r4.model.Property part = parameterComponent.getChildByName("part");
273                                        // The assumption here is that we may only have 2 elements in this part, and if so, these 2 will be saved
274                                        if (part != null && part.hasValues() && part.getValues().size() >= 2) {
275                                                String key = ((org.hl7.fhir.r4.model.Parameters.ParametersParameterComponent) part.getValues().get(0)).getValue().toString();
276                                                String value = ((org.hl7.fhir.r4.model.Parameters.ParametersParameterComponent) part.getValues().get(1)).getValue().toString();
277                                                if (!StringUtils.isEmpty(key) && !StringUtils.isEmpty(value)) {
278                                                        result.getProperties().add(new StringConceptProperty(key, value));
279                                                }
280                                        }
281                                        break;
282                                case "designation":
283                                        ConceptDesignation conceptDesignation = new ConceptDesignation();
284                                        for (org.hl7.fhir.r4.model.Parameters.ParametersParameterComponent designationComponent : parameterComponent.getPart()) {
285                                                switch(designationComponent.getName()) {
286                                                        case "language":
287                                                                conceptDesignation.setLanguage(designationComponent.getValue().toString());
288                                                                break;
289                                                        case "use":
290                                                                org.hl7.fhir.r4.model.Coding coding = (org.hl7.fhir.r4.model.Coding)designationComponent.getValue();
291                                                                if (coding != null) {
292                                                                        conceptDesignation.setUseSystem(coding.getSystem());
293                                                                        conceptDesignation.setUseCode(coding.getCode());
294                                                                        conceptDesignation.setUseDisplay(coding.getDisplay());
295                                                                }
296                                                                break;
297                                                        case "value":
298                                                                conceptDesignation.setValue(((designationComponent.getValue() == null)?null:designationComponent.getValue().toString()));
299                                                                break;
300                                                }
301                                        }
302                                        result.getDesignations().add(conceptDesignation);
303                                        break;
304                                case "name":
305                                        result.setCodeSystemDisplayName(((parameterComponent.getValue() == null)?null:parameterComponent.getValue().toString()));
306                                        break;
307                                case "version":
308                                        result.setCodeSystemVersion(((parameterComponent.getValue() == null)?null:parameterComponent.getValue().toString()));
309                                        break;
310                                case "display":
311                                        result.setCodeDisplay(((parameterComponent.getValue() == null)?null:parameterComponent.getValue().toString()));
312                                        break;
313                                case "abstract":
314                                        result.setCodeIsAbstract(((parameterComponent.getValue() == null)?false:Boolean.parseBoolean(parameterComponent.getValue().toString())));
315                                        break;
316                        }
317                }
318                return result;
319        }
320
321        @Override
322        public IBaseResource fetchValueSet(String theValueSetUrl) {
323                IGenericClient client = provideClient();
324                Class<? extends IBaseBundle> bundleType = myCtx.getResourceDefinition("Bundle").getImplementingClass(IBaseBundle.class);
325                IBaseBundle results = client
326                        .search()
327                        .forResource("ValueSet")
328                        .where(CodeSystem.URL.matches().value(theValueSetUrl))
329                        .returnBundle(bundleType)
330                        .execute();
331                List<IBaseResource> resultsList = BundleUtil.toListOfResources(myCtx, results);
332                if (resultsList.size() > 0) {
333                        return resultsList.get(0);
334                }
335
336                return null;
337        }
338
339        @Override
340        public boolean isCodeSystemSupported(ValidationSupportContext theValidationSupportContext, String theSystem) {
341                return fetchCodeSystem(theSystem) != null;
342        }
343
344        @Override
345        public boolean isValueSetSupported(ValidationSupportContext theValidationSupportContext, String theValueSetUrl) {
346                return fetchValueSet(theValueSetUrl) != null;
347        }
348
349        @Override
350        public TranslateConceptResults translateConcept(TranslateCodeRequest theRequest) {
351                IGenericClient client = provideClient();
352                FhirContext fhirContext = client.getFhirContext();
353
354                IBaseParameters params = buildTranslateInputParameters(fhirContext, theRequest);
355
356                IBaseParameters outcome = client
357                        .operation()
358                        .onType("ConceptMap")
359                        .named("$translate")
360                        .withParameters(params)
361                        .execute();
362
363                return translateOutcomeToResults(fhirContext, outcome);
364        }
365
366        private IGenericClient provideClient() {
367                IGenericClient retVal = myCtx.newRestfulGenericClient(myBaseUrl);
368                for (Object next : myClientInterceptors) {
369                        retVal.registerInterceptor(next);
370                }
371                return retVal;
372        }
373
374        protected CodeValidationResult invokeRemoteValidateCode(String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl, IBaseResource theValueSet) {
375                if (isBlank(theCode)) {
376                        return null;
377                }
378
379                IGenericClient client = provideClient();
380
381                IBaseParameters input = buildValidateCodeInputParameters(theCodeSystem, theCode, theDisplay, theValueSetUrl, theValueSet);
382
383                String resourceType = "ValueSet";
384                if (theValueSet == null && theValueSetUrl == null) {
385                        resourceType = "CodeSystem";
386                }
387
388                IBaseParameters output = client
389                        .operation()
390                        .onType(resourceType)
391                        .named("validate-code")
392                        .withParameters(input)
393                        .execute();
394
395                List<String> resultValues = ParametersUtil.getNamedParameterValuesAsString(getFhirContext(), output, "result");
396                if (resultValues.size() < 1 || isBlank(resultValues.get(0))) {
397                        return null;
398                }
399                Validate.isTrue(resultValues.size() == 1, "Response contained %d 'result' values", resultValues.size());
400
401                boolean success = "true".equalsIgnoreCase(resultValues.get(0));
402
403                CodeValidationResult retVal = new CodeValidationResult();
404                if (success) {
405
406                        retVal.setCode(theCode);
407                        List<String> displayValues = ParametersUtil.getNamedParameterValuesAsString(getFhirContext(), output, "display");
408                        if (displayValues.size() > 0) {
409                                retVal.setDisplay(displayValues.get(0));
410                        }
411
412                } else {
413
414                        retVal.setSeverity(IssueSeverity.ERROR);
415                        List<String> messageValues = ParametersUtil.getNamedParameterValuesAsString(getFhirContext(), output, "message");
416                        if (messageValues.size() > 0) {
417                                retVal.setMessage(messageValues.get(0));
418                        }
419
420                }
421                return retVal;
422        }
423
424        protected IBaseParameters buildValidateCodeInputParameters(String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl, IBaseResource theValueSet) {
425                IBaseParameters params = ParametersUtil.newInstance(getFhirContext());
426
427                if (theValueSet == null && theValueSetUrl == null) {
428                        ParametersUtil.addParameterToParametersUri(getFhirContext(), params, "url", theCodeSystem);
429                        ParametersUtil.addParameterToParametersString(getFhirContext(), params, "code", theCode);
430                        if (isNotBlank(theDisplay)) {
431                                ParametersUtil.addParameterToParametersString(getFhirContext(), params, "display", theDisplay);
432                        }
433                        return params;
434                }
435
436                if (isNotBlank(theValueSetUrl)) {
437                        ParametersUtil.addParameterToParametersUri(getFhirContext(), params, "url", theValueSetUrl);
438                }
439                ParametersUtil.addParameterToParametersString(getFhirContext(), params, "code", theCode);
440                if (isNotBlank(theCodeSystem)) {
441                        ParametersUtil.addParameterToParametersUri(getFhirContext(), params, "system", theCodeSystem);
442                }
443                if (isNotBlank(theDisplay)) {
444                        ParametersUtil.addParameterToParametersString(getFhirContext(), params, "display", theDisplay);
445                }
446                if (theValueSet != null) {
447                        ParametersUtil.addParameterToParameters(getFhirContext(), params, "valueSet", theValueSet);
448                }
449                return params;
450        }
451
452
453        /**
454         * Sets the FHIR Terminology Server base URL
455         *
456         * @param theBaseUrl The base URL, e.g. "https://hapi.fhir.org/baseR4"
457         */
458        public void setBaseUrl(String theBaseUrl) {
459                Validate.notBlank(theBaseUrl, "theBaseUrl must be provided");
460                myBaseUrl = theBaseUrl;
461        }
462
463        /**
464         * Adds an interceptor that will be registered to all clients.
465         * <p>
466         * Note that this method is not thread-safe and should only be called prior to this module
467         * being used.
468         * </p>
469         *
470         * @param theClientInterceptor The interceptor (must not be null)
471         */
472        public void addClientInterceptor(@Nonnull Object theClientInterceptor) {
473                Validate.notNull(theClientInterceptor, "theClientInterceptor must not be null");
474                myClientInterceptors.add(theClientInterceptor);
475        }
476
477        private IBaseParameters buildTranslateInputParameters(FhirContext fhirContext, TranslateCodeRequest theRequest) {
478                IBaseParameters params = ParametersUtil.newInstance(fhirContext);
479                if (!StringUtils.isEmpty(theRequest.getConceptMapUrl())) {
480                        ParametersUtil.addParameterToParametersUri(fhirContext, params, "url", theRequest.getConceptMapUrl());
481                }
482                if (!StringUtils.isEmpty(theRequest.getConceptMapVersion())) {
483                        ParametersUtil.addParameterToParametersString(fhirContext, params, "conceptMapVersion", theRequest.getConceptMapVersion());
484                }
485                if (theRequest.getCodings() != null) {
486                        addCodingsToTranslateParameters(fhirContext, theRequest.getCodings(), params);
487                }
488                if (!StringUtils.isEmpty(theRequest.getSourceValueSetUrl())) {
489                        ParametersUtil.addParameterToParametersUri(fhirContext, params, "source", theRequest.getSourceValueSetUrl());
490                }
491                if (!StringUtils.isEmpty(theRequest.getTargetValueSetUrl())) {
492                        ParametersUtil.addParameterToParametersUri(fhirContext, params, "target", theRequest.getTargetValueSetUrl());
493                }
494                if (!StringUtils.isEmpty(theRequest.getTargetSystemUrl())) {
495                        ParametersUtil.addParameterToParametersUri(fhirContext, params, "targetsystem", theRequest.getTargetSystemUrl());
496                }
497                if (theRequest.isReverse()) {
498                        ParametersUtil.addParameterToParametersBoolean(fhirContext, params, "reverse", theRequest.isReverse());
499                }
500
501                return params;
502        }
503
504        private void addCodingsToTranslateParameters(FhirContext fhirContext, List<IBaseCoding> theCodings, IBaseParameters theParams) {
505                BaseRuntimeElementCompositeDefinition<?> codeableConceptDef = (BaseRuntimeElementCompositeDefinition<?>) Objects.requireNonNull(fhirContext.getElementDefinition("CodeableConcept"));
506                BaseRuntimeChildDefinition codings = codeableConceptDef.getChildByName("coding");
507                BaseRuntimeElementCompositeDefinition<?> codingDef = (BaseRuntimeElementCompositeDefinition<?>) Objects.requireNonNull(fhirContext.getElementDefinition("Coding"));
508                BaseRuntimeChildDefinition codingSystemChild = codingDef.getChildByName("system");
509                BaseRuntimeChildDefinition codingCodeChild = codingDef.getChildByName("code");
510                BaseRuntimeElementDefinition<IPrimitiveType<?>> systemDef = (RuntimePrimitiveDatatypeDefinition) fhirContext.getElementDefinition("uri");
511                BaseRuntimeElementDefinition<IPrimitiveType<?>> codeDef = (RuntimePrimitiveDatatypeDefinition) fhirContext.getElementDefinition("code");
512
513                IBase codeableConcept = codeableConceptDef.newInstance();
514
515                for (IBaseCoding aCoding : theCodings) {
516                        IBaseCoding newCoding = (IBaseCoding) codingDef.newInstance();
517
518                        IPrimitiveType<?> newSystem = systemDef.newInstance(aCoding.getSystem());
519                        codingSystemChild.getMutator().addValue(newCoding, newSystem);
520                        IPrimitiveType<?> newCode = codeDef.newInstance(aCoding.getCode());
521                        codingCodeChild.getMutator().addValue(newCoding, newCode);
522
523                        codings.getMutator().addValue(codeableConcept, newCoding);
524                }
525
526                ParametersUtil.addParameterToParameters(fhirContext, theParams, "codeableConcept", codeableConcept);
527        }
528
529        private TranslateConceptResults translateOutcomeToResults(FhirContext fhirContext, IBaseParameters outcome) {
530                Optional<String> result = ParametersUtil.getNamedParameterValueAsString(fhirContext, outcome, "result");
531                Optional<String> message = ParametersUtil.getNamedParameterValueAsString(fhirContext, outcome, "message");
532                List<IBase> matches = ParametersUtil.getNamedParameters(fhirContext, outcome, "match");
533
534                TranslateConceptResults retVal = new TranslateConceptResults();
535                if (result.isPresent()) {
536                        retVal.setResult(Boolean.parseBoolean(result.get()));
537                }
538                if (message.isPresent()) {
539                        retVal.setMessage(message.get());
540                }
541                if (!matches.isEmpty()) {
542                        retVal.setResults(matchesToTranslateConceptResults(fhirContext, matches));
543                }
544
545                return retVal;
546        }
547
548        private List<TranslateConceptResult> matchesToTranslateConceptResults(FhirContext fhirContext, List<IBase> theMatches) {
549                List<TranslateConceptResult> resultList = new ArrayList();
550                for (IBase m : theMatches) {
551                        TranslateConceptResult match = new TranslateConceptResult();
552                        String equivalence = ParametersUtil.getParameterPartValueAsString(fhirContext, m, "equivalence");
553                        Optional<IBase> concept = ParametersUtil.getParameterPartValue(fhirContext, m, "concept");
554                        String source = ParametersUtil.getParameterPartValueAsString(fhirContext, m, "source");
555
556                        if (StringUtils.isNotBlank(equivalence)) {
557                                match.setEquivalence(equivalence);
558                        }
559
560                        if (concept.isPresent()) {
561                                IBaseCoding matchedCoding = (IBaseCoding) concept.get();
562                                match.setSystem(matchedCoding.getSystem());
563                                match.setCode(matchedCoding.getCode());
564                                match.setDisplay(matchedCoding.getDisplay());
565
566                                if (StringUtils.isNotBlank(source)) {
567                                        match.setConceptMapUrl(source);
568                                }
569
570                                resultList.add(match);
571                        }
572                }
573                return resultList;
574        }
575
576}