001package org.hl7.fhir.common.hapi.validation.support;
002
003import ca.uhn.fhir.context.ConfigurationException;
004import ca.uhn.fhir.context.FhirContext;
005import ca.uhn.fhir.context.FhirVersionEnum;
006import ca.uhn.fhir.context.support.ConceptValidationOptions;
007import ca.uhn.fhir.context.support.IValidationSupport;
008import ca.uhn.fhir.context.support.LookupCodeRequest;
009import ca.uhn.fhir.context.support.ValidationSupportContext;
010import ca.uhn.fhir.i18n.Msg;
011import ca.uhn.fhir.util.ClasspathUtil;
012import ca.uhn.hapi.converters.canonical.VersionCanonicalizer;
013import com.fasterxml.jackson.core.JsonProcessingException;
014import com.fasterxml.jackson.databind.ObjectMapper;
015import com.fasterxml.jackson.databind.node.ArrayNode;
016import com.fasterxml.jackson.databind.node.ObjectNode;
017import jakarta.annotation.Nonnull;
018import jakarta.annotation.Nullable;
019import org.apache.commons.lang3.StringUtils;
020import org.fhir.ucum.UcumEssenceService;
021import org.fhir.ucum.UcumException;
022import org.hl7.fhir.convertors.advisors.impl.BaseAdvisor_30_40;
023import org.hl7.fhir.convertors.advisors.impl.BaseAdvisor_40_50;
024import org.hl7.fhir.convertors.advisors.impl.BaseAdvisor_43_50;
025import org.hl7.fhir.convertors.factory.VersionConvertorFactory_30_40;
026import org.hl7.fhir.convertors.factory.VersionConvertorFactory_40_50;
027import org.hl7.fhir.convertors.factory.VersionConvertorFactory_43_50;
028import org.hl7.fhir.dstu2.model.ValueSet;
029import org.hl7.fhir.instance.model.api.IBaseResource;
030import org.hl7.fhir.r4.model.CodeSystem;
031import org.hl7.fhir.r4.model.CodeSystem.CodeSystemContentMode;
032import org.hl7.fhir.r5.model.Resource;
033import org.hl7.fhir.r5.model.ValueSet.ConceptReferenceComponent;
034import org.slf4j.Logger;
035import org.slf4j.LoggerFactory;
036
037import java.io.IOException;
038import java.io.InputStream;
039import java.util.Collections;
040import java.util.HashMap;
041import java.util.Map;
042import java.util.Objects;
043import java.util.Optional;
044
045import static org.apache.commons.lang3.StringUtils.defaultString;
046import static org.apache.commons.lang3.StringUtils.isBlank;
047import static org.apache.commons.lang3.StringUtils.isNotBlank;
048
049/**
050 * This {@link IValidationSupport validation support module} can be used to validate codes against common
051 * CodeSystems that are commonly used, but are not distriuted with the FHIR specification for various reasons
052 * (size, complexity, etc.).
053 * <p>
054 * See <a href="https://hapifhir.io/hapi-fhir/docs/validation/validation_support_modules.html#CommonCodeSystemsTerminologyService">CommonCodeSystemsTerminologyService</a> in the HAPI FHIR documentation
055 * for details about what is and isn't covered by this class.
056 * </p>
057 */
058@SuppressWarnings("EnhancedSwitchMigration")
059public class CommonCodeSystemsTerminologyService implements IValidationSupport {
060        public static final String LANGUAGES_VALUESET_URL = "http://hl7.org/fhir/ValueSet/languages";
061        public static final String LANGUAGES_CODESYSTEM_URL = "urn:ietf:bcp:47";
062        public static final String MIMETYPES_VALUESET_URL = "http://hl7.org/fhir/ValueSet/mimetypes";
063        public static final String MIMETYPES_CODESYSTEM_URL = "urn:ietf:bcp:13";
064        public static final String CURRENCIES_CODESYSTEM_URL = "urn:iso:std:iso:4217";
065        public static final String CURRENCIES_VALUESET_URL = "http://hl7.org/fhir/ValueSet/currencies";
066        public static final String COUNTRIES_CODESYSTEM_URL = "urn:iso:std:iso:3166";
067        public static final String UCUM_CODESYSTEM_URL = "http://unitsofmeasure.org";
068        public static final String UCUM_VALUESET_URL = "http://hl7.org/fhir/ValueSet/ucum-units";
069        public static final String ALL_LANGUAGES_VALUESET_URL = "http://hl7.org/fhir/ValueSet/all-languages";
070        public static final String USPS_CODESYSTEM_URL = "https://www.usps.com/";
071        public static final String USPS_VALUESET_URL = "http://hl7.org/fhir/us/core/ValueSet/us-core-usps-state";
072        private static final Logger ourLog = LoggerFactory.getLogger(CommonCodeSystemsTerminologyService.class);
073        private static final Map<String, String> USPS_CODES = Collections.unmodifiableMap(buildUspsCodes());
074        private static final Map<String, String> ISO_4217_CODES = Collections.unmodifiableMap(buildIso4217Codes());
075        private static final Map<String, String> ISO_3166_CODES = Collections.unmodifiableMap(buildIso3166Codes());
076        private final FhirContext myFhirContext;
077        private final VersionCanonicalizer myVersionCanonicalizer;
078        private volatile org.hl7.fhir.r5.model.ValueSet myLanguagesVs;
079        private volatile Map<String, String> myLanguagesLanugageMap;
080        private volatile Map<String, String> myLanguagesRegionMap;
081
082        /**
083         * Constructor
084         */
085        public CommonCodeSystemsTerminologyService(FhirContext theFhirContext) {
086                Objects.requireNonNull(theFhirContext);
087                myFhirContext = theFhirContext;
088                myVersionCanonicalizer = new VersionCanonicalizer(theFhirContext);
089        }
090
091        @Override
092        public String getName() {
093                return myFhirContext.getVersion().getVersion() + " Common Code Systems Validation Support";
094        }
095
096        @Override
097        public CodeValidationResult validateCodeInValueSet(
098                        ValidationSupportContext theValidationSupportContext,
099                        ConceptValidationOptions theOptions,
100                        String theCodeSystem,
101                        String theCode,
102                        String theDisplay,
103                        @Nonnull IBaseResource theValueSet) {
104                String url = getValueSetUrl(getFhirContext(), theValueSet);
105                return validateCode(theValidationSupportContext, theOptions, theCodeSystem, theCode, theDisplay, url);
106        }
107
108        @Override
109        public CodeValidationResult validateCode(
110                        @Nonnull ValidationSupportContext theValidationSupportContext,
111                        @Nonnull ConceptValidationOptions theOptions,
112                        final String theCodeSystem,
113                        final String theCode,
114                        final String theDisplay,
115                        final String theValueSetUrl) {
116                /* **************************************************************************************
117                 * NOTE: Update validation_support_modules.md if any of the support in this module
118                 * changes in any way!
119                 * **************************************************************************************/
120
121                String valueSet = defaultString(theValueSetUrl);
122                String system = defaultString(theCodeSystem);
123
124                if (!isBlank(valueSet)) {
125                        final String expectSystem = getCodeSystemForValueSet(valueSet);
126                        if (!isBlank(system) && !system.equals(expectSystem)) {
127                                return getValidateCodeResultInError("mismatchCodeSystem", system, valueSet);
128                        }
129                        system = expectSystem;
130                }
131
132                switch (valueSet) {
133                        case USPS_VALUESET_URL:
134                                return validateCodeUsingCodeMap(theCode, system, USPS_CODES);
135                        case CURRENCIES_VALUESET_URL:
136                                return validateCodeUsingCodeMap(theCode, system, ISO_4217_CODES);
137                        case LANGUAGES_VALUESET_URL:
138                                return validateLanguageCodeInValueSet(theValidationSupportContext, theCode);
139                        case ALL_LANGUAGES_VALUESET_URL:
140                                return validateLanguageCode(theCode, valueSet);
141                        case MIMETYPES_VALUESET_URL:
142                                // This is a pretty naive implementation - Should be enhanced in future
143                                return getValidateCodeResultOk(theCode, theDisplay);
144                        case UCUM_VALUESET_URL: {
145                                return validateCodeUsingSystemLookup(theValidationSupportContext, theCode, system);
146                        }
147                }
148
149                if (isBlank(valueSet)) {
150                        return validateCodeUsingSystemLookup(theValidationSupportContext, theCode, system);
151                }
152
153                return null;
154        }
155
156        private static String getCodeSystemForValueSet(final String theValueSetUrl) {
157                String theCodeSystem = null;
158                switch (defaultString(theValueSetUrl)) {
159                        case USPS_VALUESET_URL:
160                                theCodeSystem = USPS_CODESYSTEM_URL;
161                                break;
162                        case CURRENCIES_VALUESET_URL:
163                                theCodeSystem = CURRENCIES_CODESYSTEM_URL;
164                                break;
165                        case LANGUAGES_VALUESET_URL:
166                        case ALL_LANGUAGES_VALUESET_URL:
167                                theCodeSystem = LANGUAGES_CODESYSTEM_URL;
168                                break;
169                        case MIMETYPES_VALUESET_URL:
170                                theCodeSystem = MIMETYPES_CODESYSTEM_URL;
171                                break;
172                        case UCUM_VALUESET_URL:
173                                theCodeSystem = UCUM_CODESYSTEM_URL;
174                                break;
175                }
176                return theCodeSystem;
177        }
178
179        protected CodeValidationResult getValidateCodeResultInError(
180                        final String errorCode, final String theFirstParam, final String theSecondParam) {
181                String message = getErrorMessage(errorCode, theFirstParam, theSecondParam);
182                return getValidateCodeResultError(message);
183        }
184
185        protected CodeValidationResult getValidateCodeResultOk(final String theCode, final String theDisplay) {
186                return new CodeValidationResult().setCode(theCode).setDisplay(theDisplay);
187        }
188
189        protected CodeValidationResult getValidateCodeResultError(final String theMessage) {
190                return new CodeValidationResult()
191                                .setSeverity(IssueSeverity.ERROR)
192                                .setMessage(theMessage)
193                                .setIssues(Collections.singletonList(new CodeValidationIssue(
194                                                theMessage,
195                                                IssueSeverity.ERROR,
196                                                CodeValidationIssueCode.INVALID,
197                                                CodeValidationIssueCoding.INVALID_CODE)));
198        }
199
200        private CodeValidationResult validateCodeUsingCodeMap(
201                        final String theCode, final String theSystem, final Map<String, String> theCodeMap) {
202                if (theCodeMap.containsKey(theCode)) {
203                        return getValidateCodeResultOk(theCode, theCodeMap.get(theCode));
204                } else {
205                        return getValidateCodeResultInError("unknownCodeInSystem", theSystem, theCode);
206                }
207        }
208
209        @Nullable
210        public CodeValidationResult validateCodeUsingSystemLookup(
211                        final ValidationSupportContext theValidationSupportContext, final String theCode, final String theSystem) {
212                LookupCodeResult result = lookupCode(theValidationSupportContext, new LookupCodeRequest(theSystem, theCode));
213                if (result == null) {
214                        return getValidateCodeResultInError("unknownCodeInSystem", theSystem, theCode);
215                }
216                if (result.isFound()) {
217                        return getValidateCodeResultOk(theCode, result.getCodeDisplay());
218                } else if (result.getErrorMessage() != null) {
219                        String errorMessageWithSystemInfo = result.getErrorMessage() + " (for '" + theSystem + "#" + theCode + "')";
220                        return getValidateCodeResultError(errorMessageWithSystemInfo);
221                }
222                return null;
223        }
224
225        @Override
226        public LookupCodeResult lookupCode(
227                        ValidationSupportContext theValidationSupportContext, @Nonnull LookupCodeRequest theLookupCodeRequest) {
228                final String code = theLookupCodeRequest.getCode();
229                final String system = theLookupCodeRequest.getSystem();
230
231                Map<String, String> map;
232                switch (system) {
233                        case LANGUAGES_CODESYSTEM_URL:
234                                return lookupLanguageCode(code);
235                        case UCUM_CODESYSTEM_URL:
236                                return lookupUcumCode(code);
237                        case MIMETYPES_CODESYSTEM_URL:
238                                return lookupMimetypeCode(code);
239                        case COUNTRIES_CODESYSTEM_URL:
240                                map = ISO_3166_CODES;
241                                break;
242                        case CURRENCIES_CODESYSTEM_URL:
243                                map = ISO_4217_CODES;
244                                break;
245                        case USPS_CODESYSTEM_URL:
246                                map = USPS_CODES;
247                                break;
248                        default:
249                                return null;
250                }
251
252                LookupCodeResult retVal = new LookupCodeResult();
253                retVal.setSearchedForCode(code);
254                retVal.setSearchedForSystem(system);
255
256                String display = map.get(code);
257                if (isNotBlank(display)) {
258                        retVal.setFound(true);
259                        retVal.setCodeDisplay(display);
260                } else {
261                        // If we get here it means we know the CodeSystem but the code was bad
262                        retVal.setFound(false);
263                        String invalidCodeMessage = getErrorMessage("invalidCodeInSystem", code, system);
264                        retVal.setErrorMessage(invalidCodeMessage);
265                }
266
267                return retVal;
268        }
269
270        private CodeValidationResult validateLanguageCode(final String theCode, final String theValueSetUrl) {
271                LookupCodeResult outcome = lookupLanguageCode(theCode);
272                if (outcome.isFound()) {
273                        return getValidateCodeResultOk(theCode, outcome.getCodeDisplay());
274                } else {
275                        return getValidateCodeResultInError("codeNotFoundInValueSet", theCode, theValueSetUrl);
276                }
277        }
278
279        private CodeValidationResult validateLanguageCodeInValueSet(
280                        final ValidationSupportContext theValidationSupportContext, final String theCode) {
281                final String valueSet = LANGUAGES_VALUESET_URL;
282                if (myLanguagesVs == null) {
283                        IBaseResource languagesVs =
284                                        theValidationSupportContext.getRootValidationSupport().fetchValueSet(valueSet);
285                        myLanguagesVs = myVersionCanonicalizer.valueSetToValidatorCanonical(languagesVs);
286                }
287                Optional<ConceptReferenceComponent> match = myLanguagesVs.getCompose().getInclude().stream()
288                                .flatMap(t -> t.getConcept().stream())
289                                .filter(t -> theCode.equals(t.getCode()))
290                                .findFirst();
291                if (match.isPresent()) {
292                        return getValidateCodeResultOk(theCode, match.get().getDisplay());
293                } else {
294                        return getValidateCodeResultInError("codeNotFoundInValueSet", theCode, valueSet);
295                }
296        }
297
298        private LookupCodeResult lookupLanguageCode(String theCode) {
299                if (myLanguagesLanugageMap == null || myLanguagesRegionMap == null) {
300                        initializeBcp47LanguageMap();
301                }
302
303                final LookupCodeResult lookupCodeResult = new LookupCodeResult();
304                lookupCodeResult.setSearchedForSystem(LANGUAGES_CODESYSTEM_URL);
305                lookupCodeResult.setSearchedForCode(theCode);
306
307                final int langRegionSeparatorIndex = StringUtils.indexOfAny(theCode, '-', '_');
308                final boolean hasRegionAndCodeSegments = langRegionSeparatorIndex > 0;
309
310                final boolean found;
311                final String display;
312
313                if (hasRegionAndCodeSegments) {
314                        // we look for languages in lowercase only
315                        // this will allow case insensitivity for language portion of code
316                        String language = myLanguagesLanugageMap.get(
317                                        theCode.substring(0, langRegionSeparatorIndex).toLowerCase());
318                        String region = myLanguagesRegionMap.get(
319                                        theCode.substring(langRegionSeparatorIndex + 1).toUpperCase());
320
321                        // In case the user provides both a language and a region, they must both be valid for the lookup to
322                        // succeed.
323                        found = language != null && region != null;
324                        display = found ? language + " " + region : null;
325                        if (!found) {
326                                ourLog.warn("Couldn't find a valid bcp47 language-region combination from code: {}", theCode);
327                        }
328                } else {
329                        // In case user has only provided a language, we build the lookup from only that.
330                        // NB: we only use the lowercase version of the language
331                        String language = myLanguagesLanugageMap.get(theCode.toLowerCase());
332                        found = language != null;
333                        display = language;
334                        if (!found) {
335                                ourLog.warn("Couldn't find a valid bcp47 language from code: {}", theCode);
336                        }
337                }
338
339                lookupCodeResult.setFound(found);
340                lookupCodeResult.setCodeDisplay(display);
341
342                return lookupCodeResult;
343        }
344
345        private void initializeBcp47LanguageMap() {
346                Map<String, String> regionsMap;
347                Map<String, String> languagesMap;
348                ourLog.info("Loading BCP47 Language Registry");
349
350                String input = ClasspathUtil.loadResource("org/hl7/fhir/common/hapi/validation/support/registry.json");
351                ArrayNode map;
352                try {
353                        map = (ArrayNode) new ObjectMapper().readTree(input);
354                } catch (JsonProcessingException e) {
355                        throw new ConfigurationException(Msg.code(694) + e);
356                }
357
358                languagesMap = new HashMap<>();
359                regionsMap = new HashMap<>();
360
361                for (int i = 0; i < map.size(); i++) {
362                        ObjectNode next = (ObjectNode) map.get(i);
363                        String type = next.get("Type").asText();
364                        if ("language".equals(type)) {
365                                populateSubTagMap(languagesMap, next);
366                        }
367                        if ("region".equals(type)) {
368                                populateSubTagMap(regionsMap, next);
369                        }
370                }
371
372                ourLog.info("Have {} languages and {} regions", languagesMap.size(), regionsMap.size());
373
374                myLanguagesLanugageMap = languagesMap;
375                myLanguagesRegionMap = regionsMap;
376        }
377
378        private void populateSubTagMap(Map<String, String> theLanguagesMap, ObjectNode theNext) {
379                String language = theNext.get("Subtag").asText();
380                ArrayNode descriptions = (ArrayNode) theNext.get("Description");
381                String description = null;
382                if (!descriptions.isEmpty()) {
383                        description = descriptions.get(0).asText();
384                }
385                theLanguagesMap.put(language, description);
386        }
387
388        @Nonnull
389        private LookupCodeResult lookupMimetypeCode(String theCode) {
390                // This is a pretty naive implementation - Should be enhanced in future
391                LookupCodeResult mimeRetVal = new LookupCodeResult();
392                mimeRetVal.setSearchedForCode(theCode);
393                mimeRetVal.setSearchedForSystem(MIMETYPES_CODESYSTEM_URL);
394                mimeRetVal.setFound(true);
395                return mimeRetVal;
396        }
397
398        @Nonnull
399        private LookupCodeResult lookupUcumCode(String theCode) {
400                LookupCodeResult retVal = new LookupCodeResult();
401                retVal.setSearchedForCode(theCode);
402                retVal.setSearchedForSystem(UCUM_CODESYSTEM_URL);
403
404                try (InputStream input = ClasspathUtil.loadResourceAsStream("/ucum-essence.xml")) {
405                        UcumEssenceService svc = new UcumEssenceService(input);
406                        String outcome = svc.analyse(theCode);
407                        if (outcome != null) {
408                                retVal.setFound(true);
409                                retVal.setCodeDisplay(outcome);
410                        }
411                } catch (UcumException | IOException e) {
412                        ourLog.debug("Failed parse UCUM code: {}", theCode, e);
413                        retVal.setErrorMessage(e.getMessage());
414                }
415                return retVal;
416        }
417
418        @Override
419        public IBaseResource fetchCodeSystem(String theSystem) {
420                final CodeSystemContentMode content;
421                Map<String, String> map;
422                switch (defaultString(theSystem)) {
423                        case COUNTRIES_CODESYSTEM_URL:
424                                map = ISO_3166_CODES;
425                                content = CodeSystemContentMode.COMPLETE;
426                                break;
427                        case CURRENCIES_CODESYSTEM_URL:
428                                map = ISO_4217_CODES;
429                                content = CodeSystemContentMode.COMPLETE;
430                                break;
431                        case MIMETYPES_CODESYSTEM_URL:
432                                map = Collections.emptyMap();
433                                content = CodeSystemContentMode.NOTPRESENT;
434                                break;
435                        default:
436                                return null;
437                }
438
439                CodeSystem retVal = new CodeSystem();
440                retVal.setContent(content);
441                retVal.setUrl(theSystem);
442                for (Map.Entry<String, String> nextEntry : map.entrySet()) {
443                        retVal.addConcept().setCode(nextEntry.getKey()).setDisplay(nextEntry.getValue());
444                }
445
446                IBaseResource normalized = null;
447                switch (getFhirContext().getVersion().getVersion()) {
448                        case DSTU2:
449                        case DSTU2_HL7ORG:
450                        case DSTU2_1:
451                                return null;
452                        case DSTU3:
453                                normalized = VersionConvertorFactory_30_40.convertResource(retVal, new BaseAdvisor_30_40(false));
454                                break;
455                        case R4:
456                                normalized = retVal;
457                                break;
458                        case R4B:
459                                Resource normalized50 =
460                                                VersionConvertorFactory_40_50.convertResource(retVal, new BaseAdvisor_40_50(false));
461                                normalized = VersionConvertorFactory_43_50.convertResource(normalized50, new BaseAdvisor_43_50());
462                                break;
463                        case R5:
464                                normalized = VersionConvertorFactory_40_50.convertResource(retVal, new BaseAdvisor_40_50(false));
465                                break;
466                }
467
468                Objects.requireNonNull(normalized);
469
470                return normalized;
471        }
472
473        @Override
474        public boolean isCodeSystemSupported(ValidationSupportContext theValidationSupportContext, String theSystem) {
475                if (theSystem == null) {
476                        return false;
477                }
478                switch (theSystem) {
479                        case COUNTRIES_CODESYSTEM_URL:
480                        case UCUM_CODESYSTEM_URL:
481                        case MIMETYPES_CODESYSTEM_URL:
482                        case USPS_CODESYSTEM_URL:
483                        case LANGUAGES_CODESYSTEM_URL:
484                                return true;
485                }
486
487                return false;
488        }
489
490        @Override
491        public boolean isValueSetSupported(ValidationSupportContext theValidationSupportContext, String theValueSetUrl) {
492
493                switch (theValueSetUrl) {
494                        case CURRENCIES_VALUESET_URL:
495                        case LANGUAGES_VALUESET_URL:
496                        case ALL_LANGUAGES_VALUESET_URL:
497                        case MIMETYPES_VALUESET_URL:
498                        case UCUM_VALUESET_URL:
499                        case USPS_VALUESET_URL:
500                                return true;
501                }
502
503                return false;
504        }
505
506        @Override
507        public FhirContext getFhirContext() {
508                return myFhirContext;
509        }
510
511        public static String getValueSetUrl(FhirContext theFhirContext, @Nonnull IBaseResource theValueSet) {
512                String url;
513                FhirVersionEnum structureFhirVersionEnum = getFhirVersionEnum(theFhirContext, theValueSet);
514                switch (structureFhirVersionEnum) {
515                        case DSTU2: {
516                                url = ((ca.uhn.fhir.model.dstu2.resource.ValueSet) theValueSet).getUrl();
517                                break;
518                        }
519                        case DSTU2_HL7ORG: {
520                                url = ((ValueSet) theValueSet).getUrl();
521                                break;
522                        }
523                        case DSTU3: {
524                                url = ((org.hl7.fhir.dstu3.model.ValueSet) theValueSet).getUrl();
525                                break;
526                        }
527                        case R4: {
528                                url = ((org.hl7.fhir.r4.model.ValueSet) theValueSet).getUrl();
529                                break;
530                        }
531                        case R4B: {
532                                url = ((org.hl7.fhir.r4b.model.ValueSet) theValueSet).getUrl();
533                                break;
534                        }
535                        case R5: {
536                                url = ((org.hl7.fhir.r5.model.ValueSet) theValueSet).getUrl();
537                                break;
538                        }
539                        case DSTU2_1:
540                        default:
541                                throw new IllegalArgumentException(
542                                                Msg.code(695) + "Can not handle version: " + structureFhirVersionEnum);
543                }
544                return url;
545        }
546
547        public static String getCodeSystemUrl(@Nonnull FhirContext theFhirContext, @Nonnull IBaseResource theCodeSystem) {
548                String url;
549                FhirVersionEnum structureFhirVersionEnum = getFhirVersionEnum(theFhirContext, theCodeSystem);
550                switch (structureFhirVersionEnum) {
551                        case R4: {
552                                url = ((org.hl7.fhir.r4.model.CodeSystem) theCodeSystem).getUrl();
553                                break;
554                        }
555                        case R4B: {
556                                url = ((org.hl7.fhir.r4b.model.CodeSystem) theCodeSystem).getUrl();
557                                break;
558                        }
559                        case R5: {
560                                url = ((org.hl7.fhir.r5.model.CodeSystem) theCodeSystem).getUrl();
561                                break;
562                        }
563                        case DSTU3:
564                        default:
565                                throw new IllegalArgumentException(
566                                                Msg.code(696) + "Can not handle version: " + structureFhirVersionEnum);
567                }
568                return url;
569        }
570
571        public static String getValueSetVersion(@Nonnull FhirContext theFhirContext, @Nonnull IBaseResource theValueSet) {
572                String version;
573                switch (getFhirVersionEnum(theFhirContext, theValueSet)) {
574                        case DSTU3: {
575                                version = ((org.hl7.fhir.dstu3.model.ValueSet) theValueSet).getVersion();
576                                break;
577                        }
578                        case R4: {
579                                version = ((org.hl7.fhir.r4.model.ValueSet) theValueSet).getVersion();
580                                break;
581                        }
582                        case R4B: {
583                                version = ((org.hl7.fhir.r4b.model.ValueSet) theValueSet).getVersion();
584                                break;
585                        }
586                        case R5: {
587                                version = ((org.hl7.fhir.r5.model.ValueSet) theValueSet).getVersion();
588                                break;
589                        }
590                        case DSTU2:
591                        case DSTU2_HL7ORG:
592                        case DSTU2_1:
593                        default:
594                                version = null;
595                }
596                return version;
597        }
598
599        /**
600         * N.B.:  We are keeping this as a shim due to the upgrade we did to core 5.6.97+
601         */
602        public static FhirVersionEnum getFhirVersionEnum(
603                        @Nonnull FhirContext theFhirContext, @Nonnull IBaseResource theResource) {
604                FhirVersionEnum structureFhirVersionEnum = theResource.getStructureFhirVersionEnum();
605                // TODO: Address this when core lib version is bumped
606                if (theResource.getStructureFhirVersionEnum() == FhirVersionEnum.R5
607                                && theFhirContext.getVersion().getVersion() == FhirVersionEnum.R4B) {
608                        if (!(theResource instanceof org.hl7.fhir.r5.model.Resource)) {
609                                structureFhirVersionEnum = FhirVersionEnum.R4B;
610                        }
611                }
612                return structureFhirVersionEnum;
613        }
614
615        private static HashMap<String, String> buildUspsCodes() {
616                HashMap<String, String> uspsCodes = new HashMap<>();
617                uspsCodes.put("AK", "Alaska");
618                uspsCodes.put("AL", "Alabama");
619                uspsCodes.put("AR", "Arkansas");
620                uspsCodes.put("AS", "American Samoa");
621                uspsCodes.put("AZ", "Arizona");
622                uspsCodes.put("CA", "California");
623                uspsCodes.put("CO", "Colorado");
624                uspsCodes.put("CT", "Connecticut");
625                uspsCodes.put("DC", "District of Columbia");
626                uspsCodes.put("DE", "Delaware");
627                uspsCodes.put("FL", "Florida");
628                uspsCodes.put("FM", "Federated States of Micronesia");
629                uspsCodes.put("GA", "Georgia");
630                uspsCodes.put("GU", "Guam");
631                uspsCodes.put("HI", "Hawaii");
632                uspsCodes.put("IA", "Iowa");
633                uspsCodes.put("ID", "Idaho");
634                uspsCodes.put("IL", "Illinois");
635                uspsCodes.put("IN", "Indiana");
636                uspsCodes.put("KS", "Kansas");
637                uspsCodes.put("KY", "Kentucky");
638                uspsCodes.put("LA", "Louisiana");
639                uspsCodes.put("MA", "Massachusetts");
640                uspsCodes.put("MD", "Maryland");
641                uspsCodes.put("ME", "Maine");
642                uspsCodes.put("MH", "Marshall Islands");
643                uspsCodes.put("MI", "Michigan");
644                uspsCodes.put("MN", "Minnesota");
645                uspsCodes.put("MO", "Missouri");
646                uspsCodes.put("MP", "Northern Mariana Islands");
647                uspsCodes.put("MS", "Mississippi");
648                uspsCodes.put("MT", "Montana");
649                uspsCodes.put("NC", "North Carolina");
650                uspsCodes.put("ND", "North Dakota");
651                uspsCodes.put("NE", "Nebraska");
652                uspsCodes.put("NH", "New Hampshire");
653                uspsCodes.put("NJ", "New Jersey");
654                uspsCodes.put("NM", "New Mexico");
655                uspsCodes.put("NV", "Nevada");
656                uspsCodes.put("NY", "New York");
657                uspsCodes.put("OH", "Ohio");
658                uspsCodes.put("OK", "Oklahoma");
659                uspsCodes.put("OR", "Oregon");
660                uspsCodes.put("PA", "Pennsylvania");
661                uspsCodes.put("PR", "Puerto Rico");
662                uspsCodes.put("PW", "Palau");
663                uspsCodes.put("RI", "Rhode Island");
664                uspsCodes.put("SC", "South Carolina");
665                uspsCodes.put("SD", "South Dakota");
666                uspsCodes.put("TN", "Tennessee");
667                uspsCodes.put("TX", "Texas");
668                uspsCodes.put("UM", "U.S. Minor Outlying Islands");
669                uspsCodes.put("UT", "Utah");
670                uspsCodes.put("VA", "Virginia");
671                uspsCodes.put("VI", "Virgin Islands of the U.S.");
672                uspsCodes.put("VT", "Vermont");
673                uspsCodes.put("WA", "Washington");
674                uspsCodes.put("WI", "Wisconsin");
675                uspsCodes.put("WV", "West Virginia");
676                uspsCodes.put("WY", "Wyoming");
677                return uspsCodes;
678        }
679
680        private static HashMap<String, String> buildIso4217Codes() {
681                HashMap<String, String> iso4217Codes = new HashMap<>();
682                iso4217Codes.put("AED", "United Arab Emirates dirham");
683                iso4217Codes.put("AFN", "Afghan afghani");
684                iso4217Codes.put("ALL", "Albanian lek");
685                iso4217Codes.put("AMD", "Armenian dram");
686                iso4217Codes.put("ANG", "Netherlands Antillean guilder");
687                iso4217Codes.put("AOA", "Angolan kwanza");
688                iso4217Codes.put("ARS", "Argentine peso");
689                iso4217Codes.put("AUD", "Australian dollar");
690                iso4217Codes.put("AWG", "Aruban florin");
691                iso4217Codes.put("AZN", "Azerbaijani manat");
692                iso4217Codes.put("BAM", "Bosnia and Herzegovina convertible mark");
693                iso4217Codes.put("BBD", "Barbados dollar");
694                iso4217Codes.put("BDT", "Bangladeshi taka");
695                iso4217Codes.put("BGN", "Bulgarian lev");
696                iso4217Codes.put("BHD", "Bahraini dinar");
697                iso4217Codes.put("BIF", "Burundian franc");
698                iso4217Codes.put("BMD", "Bermudian dollar");
699                iso4217Codes.put("BND", "Brunei dollar");
700                iso4217Codes.put("BOB", "Boliviano");
701                iso4217Codes.put("BOV", "Bolivian Mvdol (funds code)");
702                iso4217Codes.put("BRL", "Brazilian real");
703                iso4217Codes.put("BSD", "Bahamian dollar");
704                iso4217Codes.put("BTN", "Bhutanese ngultrum");
705                iso4217Codes.put("BWP", "Botswana pula");
706                iso4217Codes.put("BYN", "Belarusian ruble");
707                iso4217Codes.put("BZD", "Belize dollar");
708                iso4217Codes.put("CAD", "Canadian dollar");
709                iso4217Codes.put("CDF", "Congolese franc");
710                iso4217Codes.put("CHE", "WIR Euro (complementary currency)");
711                iso4217Codes.put("CHF", "Swiss franc");
712                iso4217Codes.put("CHW", "WIR Franc (complementary currency)");
713                iso4217Codes.put("CLF", "Unidad de Fomento (funds code)");
714                iso4217Codes.put("CLP", "Chilean peso");
715                iso4217Codes.put("CNY", "Renminbi (Chinese) yuan[8]");
716                iso4217Codes.put("COP", "Colombian peso");
717                iso4217Codes.put("COU", "Unidad de Valor Real (UVR) (funds code)[9]");
718                iso4217Codes.put("CRC", "Costa Rican colon");
719                iso4217Codes.put("CUC", "Cuban convertible peso");
720                iso4217Codes.put("CUP", "Cuban peso");
721                iso4217Codes.put("CVE", "Cape Verde escudo");
722                iso4217Codes.put("CZK", "Czech koruna");
723                iso4217Codes.put("DJF", "Djiboutian franc");
724                iso4217Codes.put("DKK", "Danish krone");
725                iso4217Codes.put("DOP", "Dominican peso");
726                iso4217Codes.put("DZD", "Algerian dinar");
727                iso4217Codes.put("EGP", "Egyptian pound");
728                iso4217Codes.put("ERN", "Eritrean nakfa");
729                iso4217Codes.put("ETB", "Ethiopian birr");
730                iso4217Codes.put("EUR", "Euro");
731                iso4217Codes.put("FJD", "Fiji dollar");
732                iso4217Codes.put("FKP", "Falkland Islands pound");
733                iso4217Codes.put("GBP", "Pound sterling");
734                iso4217Codes.put("GEL", "Georgian lari");
735                iso4217Codes.put("GGP", "Guernsey Pound");
736                iso4217Codes.put("GHS", "Ghanaian cedi");
737                iso4217Codes.put("GIP", "Gibraltar pound");
738                iso4217Codes.put("GMD", "Gambian dalasi");
739                iso4217Codes.put("GNF", "Guinean franc");
740                iso4217Codes.put("GTQ", "Guatemalan quetzal");
741                iso4217Codes.put("GYD", "Guyanese dollar");
742                iso4217Codes.put("HKD", "Hong Kong dollar");
743                iso4217Codes.put("HNL", "Honduran lempira");
744                iso4217Codes.put("HRK", "Croatian kuna");
745                iso4217Codes.put("HTG", "Haitian gourde");
746                iso4217Codes.put("HUF", "Hungarian forint");
747                iso4217Codes.put("IDR", "Indonesian rupiah");
748                iso4217Codes.put("ILS", "Israeli new shekel");
749                iso4217Codes.put("IMP", "Isle of Man Pound");
750                iso4217Codes.put("INR", "Indian rupee");
751                iso4217Codes.put("IQD", "Iraqi dinar");
752                iso4217Codes.put("IRR", "Iranian rial");
753                iso4217Codes.put("ISK", "Icelandic króna");
754                iso4217Codes.put("JEP", "Jersey Pound");
755                iso4217Codes.put("JMD", "Jamaican dollar");
756                iso4217Codes.put("JOD", "Jordanian dinar");
757                iso4217Codes.put("JPY", "Japanese yen");
758                iso4217Codes.put("KES", "Kenyan shilling");
759                iso4217Codes.put("KGS", "Kyrgyzstani som");
760                iso4217Codes.put("KHR", "Cambodian riel");
761                iso4217Codes.put("KMF", "Comoro franc");
762                iso4217Codes.put("KPW", "North Korean won");
763                iso4217Codes.put("KRW", "South Korean won");
764                iso4217Codes.put("KWD", "Kuwaiti dinar");
765                iso4217Codes.put("KYD", "Cayman Islands dollar");
766                iso4217Codes.put("KZT", "Kazakhstani tenge");
767                iso4217Codes.put("LAK", "Lao kip");
768                iso4217Codes.put("LBP", "Lebanese pound");
769                iso4217Codes.put("LKR", "Sri Lankan rupee");
770                iso4217Codes.put("LRD", "Liberian dollar");
771                iso4217Codes.put("LSL", "Lesotho loti");
772                iso4217Codes.put("LYD", "Libyan dinar");
773                iso4217Codes.put("MAD", "Moroccan dirham");
774                iso4217Codes.put("MDL", "Moldovan leu");
775                iso4217Codes.put("MGA", "Malagasy ariary");
776                iso4217Codes.put("MKD", "Macedonian denar");
777                iso4217Codes.put("MMK", "Myanmar kyat");
778                iso4217Codes.put("MNT", "Mongolian tögrög");
779                iso4217Codes.put("MOP", "Macanese pataca");
780                iso4217Codes.put("MRU", "Mauritanian ouguiya");
781                iso4217Codes.put("MUR", "Mauritian rupee");
782                iso4217Codes.put("MVR", "Maldivian rufiyaa");
783                iso4217Codes.put("MWK", "Malawian kwacha");
784                iso4217Codes.put("MXN", "Mexican peso");
785                iso4217Codes.put("MXV", "Mexican Unidad de Inversion (UDI) (funds code)");
786                iso4217Codes.put("MYR", "Malaysian ringgit");
787                iso4217Codes.put("MZN", "Mozambican metical");
788                iso4217Codes.put("NAD", "Namibian dollar");
789                iso4217Codes.put("NGN", "Nigerian naira");
790                iso4217Codes.put("NIO", "Nicaraguan córdoba");
791                iso4217Codes.put("NOK", "Norwegian krone");
792                iso4217Codes.put("NPR", "Nepalese rupee");
793                iso4217Codes.put("NZD", "New Zealand dollar");
794                iso4217Codes.put("OMR", "Omani rial");
795                iso4217Codes.put("PAB", "Panamanian balboa");
796                iso4217Codes.put("PEN", "Peruvian Sol");
797                iso4217Codes.put("PGK", "Papua New Guinean kina");
798                iso4217Codes.put("PHP", "Philippine piso[13]");
799                iso4217Codes.put("PKR", "Pakistani rupee");
800                iso4217Codes.put("PLN", "Polish z?oty");
801                iso4217Codes.put("PYG", "Paraguayan guaraní");
802                iso4217Codes.put("QAR", "Qatari riyal");
803                iso4217Codes.put("RON", "Romanian leu");
804                iso4217Codes.put("RSD", "Serbian dinar");
805                iso4217Codes.put("RUB", "Russian ruble");
806                iso4217Codes.put("RWF", "Rwandan franc");
807                iso4217Codes.put("SAR", "Saudi riyal");
808                iso4217Codes.put("SBD", "Solomon Islands dollar");
809                iso4217Codes.put("SCR", "Seychelles rupee");
810                iso4217Codes.put("SDG", "Sudanese pound");
811                iso4217Codes.put("SEK", "Swedish krona/kronor");
812                iso4217Codes.put("SGD", "Singapore dollar");
813                iso4217Codes.put("SHP", "Saint Helena pound");
814                iso4217Codes.put("SLL", "Sierra Leonean leone");
815                iso4217Codes.put("SOS", "Somali shilling");
816                iso4217Codes.put("SRD", "Surinamese dollar");
817                iso4217Codes.put("SSP", "South Sudanese pound");
818                iso4217Codes.put("STN", "São Tomé and Príncipe dobra");
819                iso4217Codes.put("SVC", "Salvadoran colón");
820                iso4217Codes.put("SYP", "Syrian pound");
821                iso4217Codes.put("SZL", "Swazi lilangeni");
822                iso4217Codes.put("THB", "Thai baht");
823                iso4217Codes.put("TJS", "Tajikistani somoni");
824                iso4217Codes.put("TMT", "Turkmenistan manat");
825                iso4217Codes.put("TND", "Tunisian dinar");
826                iso4217Codes.put("TOP", "Tongan pa?anga");
827                iso4217Codes.put("TRY", "Turkish lira");
828                iso4217Codes.put("TTD", "Trinidad and Tobago dollar");
829                iso4217Codes.put("TVD", "Tuvalu Dollar");
830                iso4217Codes.put("TWD", "New Taiwan dollar");
831                iso4217Codes.put("TZS", "Tanzanian shilling");
832                iso4217Codes.put("UAH", "Ukrainian hryvnia");
833                iso4217Codes.put("UGX", "Ugandan shilling");
834                iso4217Codes.put("USD", "United States dollar");
835                iso4217Codes.put("USN", "United States dollar (next day) (funds code)");
836                iso4217Codes.put("UYI", "Uruguay Peso en Unidades Indexadas (URUIURUI) (funds code)");
837                iso4217Codes.put("UYU", "Uruguayan peso");
838                iso4217Codes.put("UZS", "Uzbekistan som");
839                iso4217Codes.put("VEF", "Venezuelan bolívar");
840                iso4217Codes.put("VND", "Vietnamese ??ng");
841                iso4217Codes.put("VUV", "Vanuatu vatu");
842                iso4217Codes.put("WST", "Samoan tala");
843                iso4217Codes.put("XAF", "CFA franc BEAC");
844                iso4217Codes.put("XAG", "Silver (one troy ounce)");
845                iso4217Codes.put("XAU", "Gold (one troy ounce)");
846                iso4217Codes.put("XBA", "European Composite Unit (EURCO) (bond market unit)");
847                iso4217Codes.put("XBB", "European Monetary Unit (E.M.U.-6) (bond market unit)");
848                iso4217Codes.put("XBC", "European Unit of Account 9 (E.U.A.-9) (bond market unit)");
849                iso4217Codes.put("XBD", "European Unit of Account 17 (E.U.A.-17) (bond market unit)");
850                iso4217Codes.put("XCD", "East Caribbean dollar");
851                iso4217Codes.put("XDR", "Special drawing rights");
852                iso4217Codes.put("XOF", "CFA franc BCEAO");
853                iso4217Codes.put("XPD", "Palladium (one troy ounce)");
854                iso4217Codes.put("XPF", "CFP franc (franc Pacifique)");
855                iso4217Codes.put("XPT", "Platinum (one troy ounce)");
856                iso4217Codes.put("XSU", "SUCRE");
857                iso4217Codes.put("XTS", "Code reserved for testing purposes");
858                iso4217Codes.put("XUA", "ADB Unit of Account");
859                iso4217Codes.put("XXX", "No currency");
860                iso4217Codes.put("YER", "Yemeni rial");
861                iso4217Codes.put("ZAR", "South African rand");
862                iso4217Codes.put("ZMW", "Zambian kwacha");
863                iso4217Codes.put("ZWL", "Zimbabwean dollar A/10");
864                return iso4217Codes;
865        }
866
867        private static HashMap<String, String> buildIso3166Codes() {
868                HashMap<String, String> codes = new HashMap<>();
869
870                // 2 letter codes
871                codes.put("AF", "Afghanistan");
872                codes.put("AX", "Åland Islands");
873                codes.put("AL", "Albania");
874                codes.put("DZ", "Algeria");
875                codes.put("AS", "American Samoa");
876                codes.put("AD", "Andorra");
877                codes.put("AO", "Angola");
878                codes.put("AI", "Anguilla");
879                codes.put("AQ", "Antarctica");
880                codes.put("AG", "Antigua & Barbuda");
881                codes.put("AR", "Argentina");
882                codes.put("AM", "Armenia");
883                codes.put("AW", "Aruba");
884                codes.put("AU", "Australia");
885                codes.put("AT", "Austria");
886                codes.put("AZ", "Azerbaijan");
887                codes.put("BS", "Bahamas");
888                codes.put("BH", "Bahrain");
889                codes.put("BD", "Bangladesh");
890                codes.put("BB", "Barbados");
891                codes.put("BY", "Belarus");
892                codes.put("BE", "Belgium");
893                codes.put("BZ", "Belize");
894                codes.put("BJ", "Benin");
895                codes.put("BM", "Bermuda");
896                codes.put("BT", "Bhutan");
897                codes.put("BO", "Bolivia");
898                codes.put("BA", "Bosnia & Herzegovina");
899                codes.put("BW", "Botswana");
900                codes.put("BV", "Bouvet Island");
901                codes.put("BR", "Brazil");
902                codes.put("IO", "British Indian Ocean Territory");
903                codes.put("VG", "British Virgin Islands");
904                codes.put("BN", "Brunei");
905                codes.put("BG", "Bulgaria");
906                codes.put("BF", "Burkina Faso");
907                codes.put("BI", "Burundi");
908                codes.put("KH", "Cambodia");
909                codes.put("CM", "Cameroon");
910                codes.put("CA", "Canada");
911                codes.put("CV", "Cape Verde");
912                codes.put("BQ", "Caribbean Netherlands");
913                codes.put("KY", "Cayman Islands");
914                codes.put("CF", "Central African Republic");
915                codes.put("TD", "Chad");
916                codes.put("CL", "Chile");
917                codes.put("CN", "China");
918                codes.put("CX", "Christmas Island");
919                codes.put("CC", "Cocos (Keeling) Islands");
920                codes.put("CO", "Colombia");
921                codes.put("KM", "Comoros");
922                codes.put("CG", "Congo - Brazzaville");
923                codes.put("CD", "Congo - Kinshasa");
924                codes.put("CK", "Cook Islands");
925                codes.put("CR", "Costa Rica");
926                codes.put("CI", "Côte d?Ivoire");
927                codes.put("HR", "Croatia");
928                codes.put("CU", "Cuba");
929                codes.put("CW", "Curaçao");
930                codes.put("CY", "Cyprus");
931                codes.put("CZ", "Czechia");
932                codes.put("DK", "Denmark");
933                codes.put("DJ", "Djibouti");
934                codes.put("DM", "Dominica");
935                codes.put("DO", "Dominican Republic");
936                codes.put("EC", "Ecuador");
937                codes.put("EG", "Egypt");
938                codes.put("SV", "El Salvador");
939                codes.put("GQ", "Equatorial Guinea");
940                codes.put("ER", "Eritrea");
941                codes.put("EE", "Estonia");
942                codes.put("SZ", "Eswatini");
943                codes.put("ET", "Ethiopia");
944                codes.put("FK", "Falkland Islands");
945                codes.put("FO", "Faroe Islands");
946                codes.put("FJ", "Fiji");
947                codes.put("FI", "Finland");
948                codes.put("FR", "France");
949                codes.put("GF", "French Guiana");
950                codes.put("PF", "French Polynesia");
951                codes.put("TF", "French Southern Territories");
952                codes.put("GA", "Gabon");
953                codes.put("GM", "Gambia");
954                codes.put("GE", "Georgia");
955                codes.put("DE", "Germany");
956                codes.put("GH", "Ghana");
957                codes.put("GI", "Gibraltar");
958                codes.put("GR", "Greece");
959                codes.put("GL", "Greenland");
960                codes.put("GD", "Grenada");
961                codes.put("GP", "Guadeloupe");
962                codes.put("GU", "Guam");
963                codes.put("GT", "Guatemala");
964                codes.put("GG", "Guernsey");
965                codes.put("GN", "Guinea");
966                codes.put("GW", "Guinea-Bissau");
967                codes.put("GY", "Guyana");
968                codes.put("HT", "Haiti");
969                codes.put("HM", "Heard & McDonald Islands");
970                codes.put("HN", "Honduras");
971                codes.put("HK", "Hong Kong SAR China");
972                codes.put("HU", "Hungary");
973                codes.put("IS", "Iceland");
974                codes.put("IN", "India");
975                codes.put("ID", "Indonesia");
976                codes.put("IR", "Iran");
977                codes.put("IQ", "Iraq");
978                codes.put("IE", "Ireland");
979                codes.put("IM", "Isle of Man");
980                codes.put("IL", "Israel");
981                codes.put("IT", "Italy");
982                codes.put("JM", "Jamaica");
983                codes.put("JP", "Japan");
984                codes.put("JE", "Jersey");
985                codes.put("JO", "Jordan");
986                codes.put("KZ", "Kazakhstan");
987                codes.put("KE", "Kenya");
988                codes.put("KI", "Kiribati");
989                codes.put("KW", "Kuwait");
990                codes.put("KG", "Kyrgyzstan");
991                codes.put("LA", "Laos");
992                codes.put("LV", "Latvia");
993                codes.put("LB", "Lebanon");
994                codes.put("LS", "Lesotho");
995                codes.put("LR", "Liberia");
996                codes.put("LY", "Libya");
997                codes.put("LI", "Liechtenstein");
998                codes.put("LT", "Lithuania");
999                codes.put("LU", "Luxembourg");
1000                codes.put("MO", "Macao SAR China");
1001                codes.put("MG", "Madagascar");
1002                codes.put("MW", "Malawi");
1003                codes.put("MY", "Malaysia");
1004                codes.put("MV", "Maldives");
1005                codes.put("ML", "Mali");
1006                codes.put("MT", "Malta");
1007                codes.put("MH", "Marshall Islands");
1008                codes.put("MQ", "Martinique");
1009                codes.put("MR", "Mauritania");
1010                codes.put("MU", "Mauritius");
1011                codes.put("YT", "Mayotte");
1012                codes.put("MX", "Mexico");
1013                codes.put("FM", "Micronesia");
1014                codes.put("MD", "Moldova");
1015                codes.put("MC", "Monaco");
1016                codes.put("MN", "Mongolia");
1017                codes.put("ME", "Montenegro");
1018                codes.put("MS", "Montserrat");
1019                codes.put("MA", "Morocco");
1020                codes.put("MZ", "Mozambique");
1021                codes.put("MM", "Myanmar (Burma)");
1022                codes.put("NA", "Namibia");
1023                codes.put("NR", "Nauru");
1024                codes.put("NP", "Nepal");
1025                codes.put("NL", "Netherlands");
1026                codes.put("NC", "New Caledonia");
1027                codes.put("NZ", "New Zealand");
1028                codes.put("NI", "Nicaragua");
1029                codes.put("NE", "Niger");
1030                codes.put("NG", "Nigeria");
1031                codes.put("NU", "Niue");
1032                codes.put("NF", "Norfolk Island");
1033                codes.put("KP", "North Korea");
1034                codes.put("MK", "North Macedonia");
1035                codes.put("MP", "Northern Mariana Islands");
1036                codes.put("NO", "Norway");
1037                codes.put("OM", "Oman");
1038                codes.put("PK", "Pakistan");
1039                codes.put("PW", "Palau");
1040                codes.put("PS", "Palestinian Territories");
1041                codes.put("PA", "Panama");
1042                codes.put("PG", "Papua New Guinea");
1043                codes.put("PY", "Paraguay");
1044                codes.put("PE", "Peru");
1045                codes.put("PH", "Philippines");
1046                codes.put("PN", "Pitcairn Islands");
1047                codes.put("PL", "Poland");
1048                codes.put("PT", "Portugal");
1049                codes.put("PR", "Puerto Rico");
1050                codes.put("QA", "Qatar");
1051                codes.put("RE", "Réunion");
1052                codes.put("RO", "Romania");
1053                codes.put("RU", "Russia");
1054                codes.put("RW", "Rwanda");
1055                codes.put("WS", "Samoa");
1056                codes.put("SM", "San Marino");
1057                codes.put("ST", "São Tomé & Príncipe");
1058                codes.put("SA", "Saudi Arabia");
1059                codes.put("SN", "Senegal");
1060                codes.put("RS", "Serbia");
1061                codes.put("SC", "Seychelles");
1062                codes.put("SL", "Sierra Leone");
1063                codes.put("SG", "Singapore");
1064                codes.put("SX", "Sint Maarten");
1065                codes.put("SK", "Slovakia");
1066                codes.put("SI", "Slovenia");
1067                codes.put("SB", "Solomon Islands");
1068                codes.put("SO", "Somalia");
1069                codes.put("ZA", "South Africa");
1070                codes.put("GS", "South Georgia & South Sandwich Islands");
1071                codes.put("KR", "South Korea");
1072                codes.put("SS", "South Sudan");
1073                codes.put("ES", "Spain");
1074                codes.put("LK", "Sri Lanka");
1075                codes.put("BL", "St. Barthélemy");
1076                codes.put("SH", "St. Helena");
1077                codes.put("KN", "St. Kitts & Nevis");
1078                codes.put("LC", "St. Lucia");
1079                codes.put("MF", "St. Martin");
1080                codes.put("PM", "St. Pierre & Miquelon");
1081                codes.put("VC", "St. Vincent & Grenadines");
1082                codes.put("SD", "Sudan");
1083                codes.put("SR", "Suriname");
1084                codes.put("SJ", "Svalbard & Jan Mayen");
1085                codes.put("SE", "Sweden");
1086                codes.put("CH", "Switzerland");
1087                codes.put("SY", "Syria");
1088                codes.put("TW", "Taiwan");
1089                codes.put("TJ", "Tajikistan");
1090                codes.put("TZ", "Tanzania");
1091                codes.put("TH", "Thailand");
1092                codes.put("TL", "Timor-Leste");
1093                codes.put("TG", "Togo");
1094                codes.put("TK", "Tokelau");
1095                codes.put("TO", "Tonga");
1096                codes.put("TT", "Trinidad & Tobago");
1097                codes.put("TN", "Tunisia");
1098                codes.put("TR", "Turkey");
1099                codes.put("TM", "Turkmenistan");
1100                codes.put("TC", "Turks & Caicos Islands");
1101                codes.put("TV", "Tuvalu");
1102                codes.put("UM", "U.S. Outlying Islands");
1103                codes.put("VI", "U.S. Virgin Islands");
1104                codes.put("UG", "Uganda");
1105                codes.put("UA", "Ukraine");
1106                codes.put("AE", "United Arab Emirates");
1107                codes.put("GB", "United Kingdom");
1108                codes.put("US", "United States");
1109                codes.put("UY", "Uruguay");
1110                codes.put("UZ", "Uzbekistan");
1111                codes.put("VU", "Vanuatu");
1112                codes.put("VA", "Vatican City");
1113                codes.put("VE", "Venezuela");
1114                codes.put("VN", "Vietnam");
1115                codes.put("WF", "Wallis & Futuna");
1116                codes.put("EH", "Western Sahara");
1117                codes.put("YE", "Yemen");
1118                codes.put("ZM", "Zambia");
1119                codes.put("ZW", "Zimbabwe");
1120
1121                // 3 letter codes
1122                codes.put("ABW", "Aruba");
1123                codes.put("AFG", "Afghanistan");
1124                codes.put("AGO", "Angola");
1125                codes.put("AIA", "Anguilla");
1126                codes.put("ALA", "Åland Islands");
1127                codes.put("ALB", "Albania");
1128                codes.put("AND", "Andorra");
1129                codes.put("ARE", "United Arab Emirates");
1130                codes.put("ARG", "Argentina");
1131                codes.put("ARM", "Armenia");
1132                codes.put("ASM", "American Samoa");
1133                codes.put("ATA", "Antarctica");
1134                codes.put("ATF", "French Southern Territories");
1135                codes.put("ATG", "Antigua and Barbuda");
1136                codes.put("AUS", "Australia");
1137                codes.put("AUT", "Austria");
1138                codes.put("AZE", "Azerbaijan");
1139                codes.put("BDI", "Burundi");
1140                codes.put("BEL", "Belgium");
1141                codes.put("BEN", "Benin");
1142                codes.put("BES", "Bonaire, Sint Eustatius and Saba");
1143                codes.put("BFA", "Burkina Faso");
1144                codes.put("BGD", "Bangladesh");
1145                codes.put("BGR", "Bulgaria");
1146                codes.put("BHR", "Bahrain");
1147                codes.put("BHS", "Bahamas");
1148                codes.put("BIH", "Bosnia and Herzegovina");
1149                codes.put("BLM", "Saint Barthélemy");
1150                codes.put("BLR", "Belarus");
1151                codes.put("BLZ", "Belize");
1152                codes.put("BMU", "Bermuda");
1153                codes.put("BOL", "Bolivia, Plurinational State of");
1154                codes.put("BRA", "Brazil");
1155                codes.put("BRB", "Barbados");
1156                codes.put("BRN", "Brunei Darussalam");
1157                codes.put("BTN", "Bhutan");
1158                codes.put("BVT", "Bouvet Island");
1159                codes.put("BWA", "Botswana");
1160                codes.put("CAF", "Central African Republic");
1161                codes.put("CAN", "Canada");
1162                codes.put("CCK", "Cocos (Keeling) Islands");
1163                codes.put("CHE", "Switzerland");
1164                codes.put("CHL", "Chile");
1165                codes.put("CHN", "China");
1166                codes.put("CIV", "Côte d'Ivoire");
1167                codes.put("CMR", "Cameroon");
1168                codes.put("COD", "Congo, the Democratic Republic of the");
1169                codes.put("COG", "Congo");
1170                codes.put("COK", "Cook Islands");
1171                codes.put("COL", "Colombia");
1172                codes.put("COM", "Comoros");
1173                codes.put("CPV", "Cabo Verde");
1174                codes.put("CRI", "Costa Rica");
1175                codes.put("CUB", "Cuba");
1176                codes.put("CUW", "Curaçao");
1177                codes.put("CXR", "Christmas Island");
1178                codes.put("CYM", "Cayman Islands");
1179                codes.put("CYP", "Cyprus");
1180                codes.put("CZE", "Czechia");
1181                codes.put("DEU", "Germany");
1182                codes.put("DJI", "Djibouti");
1183                codes.put("DMA", "Dominica");
1184                codes.put("DNK", "Denmark");
1185                codes.put("DOM", "Dominican Republic");
1186                codes.put("DZA", "Algeria");
1187                codes.put("ECU", "Ecuador");
1188                codes.put("EGY", "Egypt");
1189                codes.put("ERI", "Eritrea");
1190                codes.put("ESH", "Western Sahara");
1191                codes.put("ESP", "Spain");
1192                codes.put("EST", "Estonia");
1193                codes.put("ETH", "Ethiopia");
1194                codes.put("FIN", "Finland");
1195                codes.put("FJI", "Fiji");
1196                codes.put("FLK", "Falkland Islands (Malvinas)");
1197                codes.put("FRA", "France");
1198                codes.put("FRO", "Faroe Islands");
1199                codes.put("FSM", "Micronesia, Federated States of");
1200                codes.put("GAB", "Gabon");
1201                codes.put("GBR", "United Kingdom");
1202                codes.put("GEO", "Georgia");
1203                codes.put("GGY", "Guernsey");
1204                codes.put("GHA", "Ghana");
1205                codes.put("GIB", "Gibraltar");
1206                codes.put("GIN", "Guinea");
1207                codes.put("GLP", "Guadeloupe");
1208                codes.put("GMB", "Gambia");
1209                codes.put("GNB", "Guinea-Bissau");
1210                codes.put("GNQ", "Equatorial Guinea");
1211                codes.put("GRC", "Greece");
1212                codes.put("GRD", "Grenada");
1213                codes.put("GRL", "Greenland");
1214                codes.put("GTM", "Guatemala");
1215                codes.put("GUF", "French Guiana");
1216                codes.put("GUM", "Guam");
1217                codes.put("GUY", "Guyana");
1218                codes.put("HKG", "Hong Kong");
1219                codes.put("HMD", "Heard Island and McDonald Islands");
1220                codes.put("HND", "Honduras");
1221                codes.put("HRV", "Croatia");
1222                codes.put("HTI", "Haiti");
1223                codes.put("HUN", "Hungary");
1224                codes.put("IDN", "Indonesia");
1225                codes.put("IMN", "Isle of Man");
1226                codes.put("IND", "India");
1227                codes.put("IOT", "British Indian Ocean Territory");
1228                codes.put("IRL", "Ireland");
1229                codes.put("IRN", "Iran, Islamic Republic of");
1230                codes.put("IRQ", "Iraq");
1231                codes.put("ISL", "Iceland");
1232                codes.put("ISR", "Israel");
1233                codes.put("ITA", "Italy");
1234                codes.put("JAM", "Jamaica");
1235                codes.put("JEY", "Jersey");
1236                codes.put("JOR", "Jordan");
1237                codes.put("JPN", "Japan");
1238                codes.put("KAZ", "Kazakhstan");
1239                codes.put("KEN", "Kenya");
1240                codes.put("KGZ", "Kyrgyzstan");
1241                codes.put("KHM", "Cambodia");
1242                codes.put("KIR", "Kiribati");
1243                codes.put("KNA", "Saint Kitts and Nevis");
1244                codes.put("KOR", "Korea, Republic of");
1245                codes.put("KWT", "Kuwait");
1246                codes.put("LAO", "Lao People's Democratic Republic");
1247                codes.put("LBN", "Lebanon");
1248                codes.put("LBR", "Liberia");
1249                codes.put("LBY", "Libya");
1250                codes.put("LCA", "Saint Lucia");
1251                codes.put("LIE", "Liechtenstein");
1252                codes.put("LKA", "Sri Lanka");
1253                codes.put("LSO", "Lesotho");
1254                codes.put("LTU", "Lithuania");
1255                codes.put("LUX", "Luxembourg");
1256                codes.put("LVA", "Latvia");
1257                codes.put("MAC", "Macao");
1258                codes.put("MAF", "Saint Martin (French part)");
1259                codes.put("MAR", "Morocco");
1260                codes.put("MCO", "Monaco");
1261                codes.put("MDA", "Moldova, Republic of");
1262                codes.put("MDG", "Madagascar");
1263                codes.put("MDV", "Maldives");
1264                codes.put("MEX", "Mexico");
1265                codes.put("MHL", "Marshall Islands");
1266                codes.put("MKD", "Macedonia, the former Yugoslav Republic of");
1267                codes.put("MLI", "Mali");
1268                codes.put("MLT", "Malta");
1269                codes.put("MMR", "Myanmar");
1270                codes.put("MNE", "Montenegro");
1271                codes.put("MNG", "Mongolia");
1272                codes.put("MNP", "Northern Mariana Islands");
1273                codes.put("MOZ", "Mozambique");
1274                codes.put("MRT", "Mauritania");
1275                codes.put("MSR", "Montserrat");
1276                codes.put("MTQ", "Martinique");
1277                codes.put("MUS", "Mauritius");
1278                codes.put("MWI", "Malawi");
1279                codes.put("MYS", "Malaysia");
1280                codes.put("MYT", "Mayotte");
1281                codes.put("NAM", "Namibia");
1282                codes.put("NCL", "New Caledonia");
1283                codes.put("NER", "Niger");
1284                codes.put("NFK", "Norfolk Island");
1285                codes.put("NGA", "Nigeria");
1286                codes.put("NIC", "Nicaragua");
1287                codes.put("NIU", "Niue");
1288                codes.put("NLD", "Netherlands");
1289                codes.put("NOR", "Norway");
1290                codes.put("NPL", "Nepal");
1291                codes.put("NRU", "Nauru");
1292                codes.put("NZL", "New Zealand");
1293                codes.put("OMN", "Oman");
1294                codes.put("PAK", "Pakistan");
1295                codes.put("PAN", "Panama");
1296                codes.put("PCN", "Pitcairn");
1297                codes.put("PER", "Peru");
1298                codes.put("PHL", "Philippines");
1299                codes.put("PLW", "Palau");
1300                codes.put("PNG", "Papua New Guinea");
1301                codes.put("POL", "Poland");
1302                codes.put("PRI", "Puerto Rico");
1303                codes.put("PRK", "Korea, Democratic People's Republic of");
1304                codes.put("PRT", "Portugal");
1305                codes.put("PRY", "Paraguay");
1306                codes.put("PSE", "Palestine, State of");
1307                codes.put("PYF", "French Polynesia");
1308                codes.put("QAT", "Qatar");
1309                codes.put("REU", "Réunion");
1310                codes.put("ROU", "Romania");
1311                codes.put("RUS", "Russian Federation");
1312                codes.put("RWA", "Rwanda");
1313                codes.put("SAU", "Saudi Arabia");
1314                codes.put("SDN", "Sudan");
1315                codes.put("SEN", "Senegal");
1316                codes.put("SGP", "Singapore");
1317                codes.put("SGS", "South Georgia and the South Sandwich Islands");
1318                codes.put("SHN", "Saint Helena, Ascension and Tristan da Cunha");
1319                codes.put("SJM", "Svalbard and Jan Mayen");
1320                codes.put("SLB", "Solomon Islands");
1321                codes.put("SLE", "Sierra Leone");
1322                codes.put("SLV", "El Salvador");
1323                codes.put("SMR", "San Marino");
1324                codes.put("SOM", "Somalia");
1325                codes.put("SPM", "Saint Pierre and Miquelon");
1326                codes.put("SRB", "Serbia");
1327                codes.put("SSD", "South Sudan");
1328                codes.put("STP", "Sao Tome and Principe");
1329                codes.put("SUR", "Suriname");
1330                codes.put("SVK", "Slovakia");
1331                codes.put("SVN", "Slovenia");
1332                codes.put("SWE", "Sweden");
1333                codes.put("SWZ", "Swaziland");
1334                codes.put("SXM", "Sint Maarten (Dutch part)");
1335                codes.put("SYC", "Seychelles");
1336                codes.put("SYR", "Syrian Arab Republic");
1337                codes.put("TCA", "Turks and Caicos Islands");
1338                codes.put("TCD", "Chad");
1339                codes.put("TGO", "Togo");
1340                codes.put("THA", "Thailand");
1341                codes.put("TJK", "Tajikistan");
1342                codes.put("TKL", "Tokelau");
1343                codes.put("TKM", "Turkmenistan");
1344                codes.put("TLS", "Timor-Leste");
1345                codes.put("TON", "Tonga");
1346                codes.put("TTO", "Trinidad and Tobago");
1347                codes.put("TUN", "Tunisia");
1348                codes.put("TUR", "Turkey");
1349                codes.put("TUV", "Tuvalu");
1350                codes.put("TWN", "Taiwan, Province of China");
1351                codes.put("TZA", "Tanzania, United Republic of");
1352                codes.put("UGA", "Uganda");
1353                codes.put("UKR", "Ukraine");
1354                codes.put("UMI", "United States Minor Outlying Islands");
1355                codes.put("URY", "Uruguay");
1356                codes.put("USA", "United States of America");
1357                codes.put("UZB", "Uzbekistan");
1358                codes.put("VAT", "Holy See");
1359                codes.put("VCT", "Saint Vincent and the Grenadines");
1360                codes.put("VEN", "Venezuela, Bolivarian Republic of");
1361                codes.put("VGB", "Virgin Islands, British");
1362                codes.put("VIR", "Virgin Islands, U.S.");
1363                codes.put("VNM", "Viet Nam");
1364                codes.put("VUT", "Vanuatu");
1365                codes.put("WLF", "Wallis and Futuna");
1366                codes.put("WSM", "Samoa");
1367                codes.put("YEM", "Yemen");
1368                codes.put("ZAF", "South Africa");
1369                codes.put("ZMB", "Zambia");
1370                codes.put("ZWE", "Zimbabwe");
1371                return codes;
1372        }
1373
1374        protected String getErrorMessage(String errorCode, String theFirstParam, String theSecondParam) {
1375                return myFhirContext.getLocalizer().getMessage(getClass(), errorCode, theFirstParam, theSecondParam);
1376        }
1377}