001/*-
002 * #%L
003 * HAPI FHIR JPA Server
004 * %%
005 * Copyright (C) 2014 - 2024 Smile CDR, Inc.
006 * %%
007 * Licensed under the Apache License, Version 2.0 (the "License");
008 * you may not use this file except in compliance with the License.
009 * You may obtain a copy of the License at
010 *
011 *      http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 * #L%
019 */
020package ca.uhn.fhir.jpa.dao;
021
022import ca.uhn.fhir.context.FhirContext;
023import ca.uhn.fhir.context.FhirVersionEnum;
024import ca.uhn.fhir.context.RuntimeResourceDefinition;
025import ca.uhn.fhir.i18n.Msg;
026import ca.uhn.fhir.interceptor.model.RequestPartitionId;
027import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
028import ca.uhn.fhir.jpa.api.dao.IDao;
029import ca.uhn.fhir.jpa.dao.data.IResourceHistoryProvenanceDao;
030import ca.uhn.fhir.jpa.dao.data.IResourceHistoryTableDao;
031import ca.uhn.fhir.jpa.entity.PartitionEntity;
032import ca.uhn.fhir.jpa.esr.ExternallyStoredResourceServiceRegistry;
033import ca.uhn.fhir.jpa.esr.IExternallyStoredResourceService;
034import ca.uhn.fhir.jpa.model.config.PartitionSettings;
035import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource;
036import ca.uhn.fhir.jpa.model.entity.BaseTag;
037import ca.uhn.fhir.jpa.model.entity.IBaseResourceEntity;
038import ca.uhn.fhir.jpa.model.entity.PartitionablePartitionId;
039import ca.uhn.fhir.jpa.model.entity.ResourceEncodingEnum;
040import ca.uhn.fhir.jpa.model.entity.ResourceHistoryProvenanceEntity;
041import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable;
042import ca.uhn.fhir.jpa.model.entity.ResourceTable;
043import ca.uhn.fhir.jpa.model.entity.TagDefinition;
044import ca.uhn.fhir.jpa.model.entity.TagTypeEnum;
045import ca.uhn.fhir.jpa.partition.IPartitionLookupSvc;
046import ca.uhn.fhir.model.api.IResource;
047import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
048import ca.uhn.fhir.model.api.Tag;
049import ca.uhn.fhir.model.api.TagList;
050import ca.uhn.fhir.model.base.composite.BaseCodingDt;
051import ca.uhn.fhir.model.primitive.IdDt;
052import ca.uhn.fhir.model.primitive.InstantDt;
053import ca.uhn.fhir.model.valueset.BundleEntryTransactionMethodEnum;
054import ca.uhn.fhir.parser.DataFormatException;
055import ca.uhn.fhir.parser.IParser;
056import ca.uhn.fhir.parser.LenientErrorHandler;
057import ca.uhn.fhir.rest.api.Constants;
058import ca.uhn.fhir.util.IMetaTagSorter;
059import ca.uhn.fhir.util.MetaUtil;
060import jakarta.annotation.Nullable;
061import org.apache.commons.collections4.CollectionUtils;
062import org.apache.commons.lang3.Validate;
063import org.hl7.fhir.instance.model.api.IAnyResource;
064import org.hl7.fhir.instance.model.api.IBaseCoding;
065import org.hl7.fhir.instance.model.api.IBaseMetaType;
066import org.hl7.fhir.instance.model.api.IBaseResource;
067import org.hl7.fhir.instance.model.api.IIdType;
068import org.slf4j.Logger;
069import org.slf4j.LoggerFactory;
070import org.springframework.beans.factory.annotation.Autowired;
071
072import java.util.ArrayList;
073import java.util.Collection;
074import java.util.Date;
075import java.util.List;
076import java.util.Optional;
077
078import static ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.decodeResource;
079import static java.util.Objects.nonNull;
080import static org.apache.commons.lang3.StringUtils.isBlank;
081import static org.apache.commons.lang3.StringUtils.isNotBlank;
082
083public class JpaStorageResourceParser implements IJpaStorageResourceParser {
084        public static final LenientErrorHandler LENIENT_ERROR_HANDLER = new LenientErrorHandler(false).disableAllErrors();
085        private static final Logger ourLog = LoggerFactory.getLogger(JpaStorageResourceParser.class);
086
087        @Autowired
088        private FhirContext myFhirContext;
089
090        @Autowired
091        private JpaStorageSettings myStorageSettings;
092
093        @Autowired
094        private IResourceHistoryTableDao myResourceHistoryTableDao;
095
096        @Autowired
097        private IResourceHistoryProvenanceDao myResourceHistoryProvenanceDao;
098
099        @Autowired
100        private PartitionSettings myPartitionSettings;
101
102        @Autowired
103        private IPartitionLookupSvc myPartitionLookupSvc;
104
105        @Autowired
106        private ExternallyStoredResourceServiceRegistry myExternallyStoredResourceServiceRegistry;
107
108        @Autowired
109        IMetaTagSorter myMetaTagSorter;
110
111        @Override
112        public IBaseResource toResource(IBasePersistedResource theEntity, boolean theForHistoryOperation) {
113                RuntimeResourceDefinition type = myFhirContext.getResourceDefinition(theEntity.getResourceType());
114                Class<? extends IBaseResource> resourceType = type.getImplementingClass();
115                return toResource(resourceType, (IBaseResourceEntity) theEntity, null, theForHistoryOperation);
116        }
117
118        @Override
119        public <R extends IBaseResource> R toResource(
120                        Class<R> theResourceType,
121                        IBaseResourceEntity theEntity,
122                        Collection<BaseTag> theTagList,
123                        boolean theForHistoryOperation) {
124
125                // 1. get resource, it's encoding and the tags if any
126                byte[] resourceBytes;
127                String resourceText;
128                ResourceEncodingEnum resourceEncoding;
129                @Nullable Collection<? extends BaseTag> tagList;
130                long version;
131                String provenanceSourceUri = null;
132                String provenanceRequestId = null;
133
134                if (theEntity instanceof ResourceHistoryTable) {
135                        ResourceHistoryTable history = (ResourceHistoryTable) theEntity;
136                        resourceBytes = history.getResource();
137                        resourceText = history.getResourceTextVc();
138                        resourceEncoding = history.getEncoding();
139
140                        // For search results we get the list of tags passed in because we load it
141                        // in bulk for all resources we're going to return, but for read results
142                        // we don't get the list passed in so we need to load it here.
143                        tagList = theTagList;
144                        if (tagList == null) {
145                                switch (myStorageSettings.getTagStorageMode()) {
146                                        case VERSIONED:
147                                        default:
148                                                if (history.isHasTags()) {
149                                                        tagList = history.getTags();
150                                                }
151                                                break;
152                                        case NON_VERSIONED:
153                                                if (history.getResourceTable().isHasTags()) {
154                                                        tagList = history.getResourceTable().getTags();
155                                                }
156                                                break;
157                                        case INLINE:
158                                                tagList = null;
159                                }
160                        }
161
162                        version = history.getVersion();
163                        provenanceSourceUri = history.getSourceUri();
164                        provenanceRequestId = history.getRequestId();
165                        if (isBlank(provenanceSourceUri) && isBlank(provenanceRequestId)) {
166                                if (myStorageSettings.isAccessMetaSourceInformationFromProvenanceTable()) {
167                                        Optional<ResourceHistoryProvenanceEntity> provenanceOpt =
168                                                        myResourceHistoryProvenanceDao.findById(history.getId());
169                                        if (provenanceOpt.isPresent()) {
170                                                ResourceHistoryProvenanceEntity provenance = provenanceOpt.get();
171                                                provenanceRequestId = provenance.getRequestId();
172                                                provenanceSourceUri = provenance.getSourceUri();
173                                        }
174                                }
175                        }
176                } else if (theEntity instanceof ResourceTable) {
177                        ResourceTable resource = (ResourceTable) theEntity;
178                        ResourceHistoryTable history;
179                        if (resource.getCurrentVersionEntity() != null) {
180                                history = resource.getCurrentVersionEntity();
181                        } else {
182                                version = theEntity.getVersion();
183                                history = myResourceHistoryTableDao.findForIdAndVersion(theEntity.getResourceId(), version);
184                                ((ResourceTable) theEntity).setCurrentVersionEntity(history);
185
186                                while (history == null) {
187                                        if (version > 1L) {
188                                                version--;
189                                                history = myResourceHistoryTableDao.findForIdAndVersion(theEntity.getResourceId(), version);
190                                        } else {
191                                                return null;
192                                        }
193                                }
194                        }
195
196                        resourceBytes = history.getResource();
197                        resourceEncoding = history.getEncoding();
198                        resourceText = history.getResourceTextVc();
199                        switch (myStorageSettings.getTagStorageMode()) {
200                                case VERSIONED:
201                                case NON_VERSIONED:
202                                        if (resource.isHasTags()) {
203                                                tagList = resource.getTags();
204                                        } else {
205                                                tagList = List.of();
206                                        }
207                                        break;
208                                case INLINE:
209                                default:
210                                        tagList = null;
211                                        break;
212                        }
213                        version = history.getVersion();
214                        provenanceSourceUri = history.getSourceUri();
215                        provenanceRequestId = history.getRequestId();
216                        if (isBlank(provenanceSourceUri) && isBlank(provenanceRequestId)) {
217                                if (myStorageSettings.isAccessMetaSourceInformationFromProvenanceTable()) {
218                                        Optional<ResourceHistoryProvenanceEntity> provenanceOpt =
219                                                        myResourceHistoryProvenanceDao.findById(history.getId());
220                                        if (provenanceOpt.isPresent()) {
221                                                ResourceHistoryProvenanceEntity provenance = provenanceOpt.get();
222                                                provenanceRequestId = provenance.getRequestId();
223                                                provenanceSourceUri = provenance.getSourceUri();
224                                        }
225                                }
226                        }
227                } else {
228                        // something wrong
229                        return null;
230                }
231
232                // 2. get The text
233                String decodedResourceText = decodedResourceText(resourceBytes, resourceText, resourceEncoding);
234
235                // 3. Use the appropriate custom type if one is specified in the context
236                Class<R> resourceType = determineTypeToParse(theResourceType, tagList);
237
238                // 4. parse the text to FHIR
239                R retVal = parseResource(theEntity, resourceEncoding, decodedResourceText, resourceType);
240
241                // 5. fill MetaData
242                retVal = populateResourceMetadata(theEntity, theForHistoryOperation, tagList, version, retVal);
243
244                // 6. Handle source (provenance)
245                MetaUtil.populateResourceSource(myFhirContext, provenanceSourceUri, provenanceRequestId, retVal);
246
247                // 7. Add partition information
248                populateResourcePartitionInformation(theEntity, retVal);
249
250                // 8. sort tags, security labels and profiles
251                myMetaTagSorter.sort(retVal.getMeta());
252
253                return retVal;
254        }
255
256        private <R extends IBaseResource> void populateResourcePartitionInformation(
257                        IBaseResourceEntity theEntity, R retVal) {
258                if (myPartitionSettings.isPartitioningEnabled()) {
259                        PartitionablePartitionId partitionId = theEntity.getPartitionId();
260                        if (partitionId != null && partitionId.getPartitionId() != null) {
261                                PartitionEntity persistedPartition =
262                                                myPartitionLookupSvc.getPartitionById(partitionId.getPartitionId());
263                                retVal.setUserData(Constants.RESOURCE_PARTITION_ID, persistedPartition.toRequestPartitionId());
264                        } else {
265                                retVal.setUserData(Constants.RESOURCE_PARTITION_ID, RequestPartitionId.defaultPartition());
266                        }
267                }
268        }
269
270        @SuppressWarnings("unchecked")
271        private <R extends IBaseResource> R parseResource(
272                        IBaseResourceEntity theEntity,
273                        ResourceEncodingEnum theResourceEncoding,
274                        String theDecodedResourceText,
275                        Class<R> theResourceType) {
276                R retVal;
277                if (theResourceEncoding == ResourceEncodingEnum.ESR) {
278
279                        int colonIndex = theDecodedResourceText.indexOf(':');
280                        Validate.isTrue(colonIndex > 0, "Invalid ESR address: %s", theDecodedResourceText);
281                        String providerId = theDecodedResourceText.substring(0, colonIndex);
282                        String address = theDecodedResourceText.substring(colonIndex + 1);
283                        Validate.notBlank(providerId, "No provider ID in ESR address: %s", theDecodedResourceText);
284                        Validate.notBlank(address, "No address in ESR address: %s", theDecodedResourceText);
285                        IExternallyStoredResourceService provider =
286                                        myExternallyStoredResourceServiceRegistry.getProvider(providerId);
287                        retVal = (R) provider.fetchResource(address);
288
289                } else if (theResourceEncoding != ResourceEncodingEnum.DEL) {
290
291                        IParser parser = new TolerantJsonParser(
292                                        getContext(theEntity.getFhirVersion()), LENIENT_ERROR_HANDLER, theEntity.getResourceId());
293
294                        try {
295                                retVal = parser.parseResource(theResourceType, theDecodedResourceText);
296                        } catch (Exception e) {
297                                StringBuilder b = new StringBuilder();
298                                b.append("Failed to parse database resource[");
299                                b.append(myFhirContext.getResourceType(theResourceType));
300                                b.append("/");
301                                b.append(theEntity.getIdDt().getIdPart());
302                                b.append(" (pid ");
303                                b.append(theEntity.getId());
304                                b.append(", version ");
305                                b.append(theEntity.getFhirVersion().name());
306                                b.append("): ");
307                                b.append(e.getMessage());
308                                String msg = b.toString();
309                                ourLog.error(msg, e);
310                                throw new DataFormatException(Msg.code(928) + msg, e);
311                        }
312
313                } else {
314
315                        retVal = (R) myFhirContext
316                                        .getResourceDefinition(theEntity.getResourceType())
317                                        .newInstance();
318                }
319                return retVal;
320        }
321
322        @SuppressWarnings("unchecked")
323        private <R extends IBaseResource> Class<R> determineTypeToParse(
324                        Class<R> theResourceType, @Nullable Collection<? extends BaseTag> tagList) {
325                Class<R> resourceType = theResourceType;
326                if (tagList != null) {
327                        if (myFhirContext.hasDefaultTypeForProfile()) {
328                                for (BaseTag nextTag : tagList) {
329                                        if (nextTag.getTag().getTagType() == TagTypeEnum.PROFILE) {
330                                                String profile = nextTag.getTag().getCode();
331                                                if (isNotBlank(profile)) {
332                                                        Class<? extends IBaseResource> newType = myFhirContext.getDefaultTypeForProfile(profile);
333                                                        if (newType != null && theResourceType.isAssignableFrom(newType)) {
334                                                                ourLog.debug("Using custom type {} for profile: {}", newType.getName(), profile);
335                                                                resourceType = (Class<R>) newType;
336                                                                break;
337                                                        }
338                                                }
339                                        }
340                                }
341                        }
342                }
343                return resourceType;
344        }
345
346        @SuppressWarnings("unchecked")
347        @Override
348        public <R extends IBaseResource> R populateResourceMetadata(
349                        IBaseResourceEntity theEntitySource,
350                        boolean theForHistoryOperation,
351                        @Nullable Collection<? extends BaseTag> tagList,
352                        long theVersion,
353                        R theResourceTarget) {
354                if (theResourceTarget instanceof IResource) {
355                        IResource res = (IResource) theResourceTarget;
356                        theResourceTarget =
357                                        (R) populateResourceMetadataHapi(theEntitySource, tagList, theForHistoryOperation, res, theVersion);
358                } else {
359                        IAnyResource res = (IAnyResource) theResourceTarget;
360                        theResourceTarget =
361                                        populateResourceMetadataRi(theEntitySource, tagList, theForHistoryOperation, res, theVersion);
362                }
363                return theResourceTarget;
364        }
365
366        @SuppressWarnings("unchecked")
367        private <R extends IResource> R populateResourceMetadataHapi(
368                        IBaseResourceEntity theEntity,
369                        @Nullable Collection<? extends BaseTag> theTagList,
370                        boolean theForHistoryOperation,
371                        R res,
372                        Long theVersion) {
373                R retVal = res;
374                if (theEntity.getDeleted() != null) {
375                        res = (R) myFhirContext.getResourceDefinition(res).newInstance();
376                        retVal = res;
377                        ResourceMetadataKeyEnum.DELETED_AT.put(res, new InstantDt(theEntity.getDeleted()));
378                        if (theForHistoryOperation) {
379                                ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, BundleEntryTransactionMethodEnum.DELETE);
380                        }
381                } else if (theForHistoryOperation) {
382                        /*
383                         * If the create and update times match, this was when the resource was created so we should mark it as a POST. Otherwise, it's a PUT.
384                         */
385                        Date published = theEntity.getPublished().getValue();
386                        Date updated = theEntity.getUpdated().getValue();
387                        if (published.equals(updated)) {
388                                ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, BundleEntryTransactionMethodEnum.POST);
389                        } else {
390                                ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, BundleEntryTransactionMethodEnum.PUT);
391                        }
392                }
393
394                res.setId(theEntity.getIdDt().withVersion(theVersion.toString()));
395
396                ResourceMetadataKeyEnum.VERSION.put(res, Long.toString(theEntity.getVersion()));
397                ResourceMetadataKeyEnum.PUBLISHED.put(res, theEntity.getPublished());
398                ResourceMetadataKeyEnum.UPDATED.put(res, theEntity.getUpdated());
399                IDao.RESOURCE_PID.put(res, theEntity.getResourceId());
400
401                if (theTagList != null) {
402                        if (theEntity.isHasTags()) {
403                                TagList tagList = new TagList();
404                                List<IBaseCoding> securityLabels = new ArrayList<>();
405                                List<IdDt> profiles = new ArrayList<>();
406                                for (BaseTag next : theTagList) {
407                                        TagDefinition nextTag = next.getTag();
408                                        switch (nextTag.getTagType()) {
409                                                case PROFILE:
410                                                        profiles.add(new IdDt(nextTag.getCode()));
411                                                        break;
412                                                case SECURITY_LABEL:
413                                                        IBaseCoding secLabel =
414                                                                        (IBaseCoding) myFhirContext.getVersion().newCodingDt();
415                                                        secLabel.setSystem(nextTag.getSystem());
416                                                        secLabel.setCode(nextTag.getCode());
417                                                        secLabel.setDisplay(nextTag.getDisplay());
418                                                        secLabel.setVersion(nextTag.getVersion());
419                                                        Boolean userSelected = nextTag.getUserSelected();
420                                                        if (userSelected != null) {
421                                                                secLabel.setUserSelected(userSelected);
422                                                        }
423                                                        securityLabels.add(secLabel);
424                                                        break;
425                                                case TAG:
426                                                        Tag e = new Tag(nextTag.getSystem(), nextTag.getCode(), nextTag.getDisplay());
427                                                        e.setVersion(nextTag.getVersion());
428                                                        // careful! These are Boolean, not boolean.
429                                                        e.setUserSelectedBoolean(nextTag.getUserSelected());
430                                                        tagList.add(e);
431                                                        break;
432                                        }
433                                }
434                                if (tagList.size() > 0) {
435                                        ResourceMetadataKeyEnum.TAG_LIST.put(res, tagList);
436                                }
437                                if (securityLabels.size() > 0) {
438                                        ResourceMetadataKeyEnum.SECURITY_LABELS.put(res, toBaseCodingList(securityLabels));
439                                }
440                                if (profiles.size() > 0) {
441                                        ResourceMetadataKeyEnum.PROFILES.put(res, profiles);
442                                }
443                        }
444                }
445
446                return retVal;
447        }
448
449        @SuppressWarnings("unchecked")
450        private <R extends IBaseResource> R populateResourceMetadataRi(
451                        IBaseResourceEntity theEntity,
452                        @Nullable Collection<? extends BaseTag> theTagList,
453                        boolean theForHistoryOperation,
454                        IAnyResource res,
455                        Long theVersion) {
456                R retVal = (R) res;
457                if (theEntity.getDeleted() != null) {
458                        res = (IAnyResource) myFhirContext.getResourceDefinition(res).newInstance();
459                        retVal = (R) res;
460                        ResourceMetadataKeyEnum.DELETED_AT.put(res, new InstantDt(theEntity.getDeleted()));
461                        if (theForHistoryOperation) {
462                                ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, BundleEntryTransactionMethodEnum.DELETE);
463                        }
464                } else if (theForHistoryOperation) {
465                        /*
466                         * If the create and update times match, this was when the resource was created so we should mark it as a POST. Otherwise, it's a PUT.
467                         */
468                        Date published = theEntity.getPublished().getValue();
469                        Date updated = theEntity.getUpdated().getValue();
470                        if (published.equals(updated)) {
471                                ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, BundleEntryTransactionMethodEnum.POST);
472                        } else {
473                                ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, BundleEntryTransactionMethodEnum.PUT);
474                        }
475                }
476
477                res.getMeta().setLastUpdated(null);
478                res.getMeta().setVersionId(null);
479
480                updateResourceMetadata(theEntity, res);
481                res.setId(res.getIdElement().withVersion(theVersion.toString()));
482
483                res.getMeta().setLastUpdated(theEntity.getUpdatedDate());
484                IDao.RESOURCE_PID.put(res, theEntity.getResourceId());
485
486                if (CollectionUtils.isNotEmpty(theTagList)) {
487                        res.getMeta().getTag().clear();
488                        res.getMeta().getProfile().clear();
489                        res.getMeta().getSecurity().clear();
490                        for (BaseTag next : theTagList) {
491                                switch (next.getTag().getTagType()) {
492                                        case PROFILE:
493                                                res.getMeta().addProfile(next.getTag().getCode());
494                                                break;
495                                        case SECURITY_LABEL:
496                                                IBaseCoding sec = res.getMeta().addSecurity();
497                                                sec.setSystem(next.getTag().getSystem());
498                                                sec.setCode(next.getTag().getCode());
499                                                sec.setDisplay(next.getTag().getDisplay());
500                                                break;
501                                        case TAG:
502                                                IBaseCoding tag = res.getMeta().addTag();
503                                                tag.setSystem(next.getTag().getSystem());
504                                                tag.setCode(next.getTag().getCode());
505                                                tag.setDisplay(next.getTag().getDisplay());
506                                                tag.setVersion(next.getTag().getVersion());
507                                                Boolean userSelected = next.getTag().getUserSelected();
508                                                // the tag is created with a null userSelected, but the api is primitive boolean.
509                                                // Only update if we are non-null.
510                                                if (nonNull(userSelected)) {
511                                                        tag.setUserSelected(userSelected);
512                                                }
513                                                break;
514                                }
515                        }
516                }
517
518                return retVal;
519        }
520
521        @Override
522        public void updateResourceMetadata(IBaseResourceEntity theEntitySource, IBaseResource theResourceTarget) {
523                IIdType id = theEntitySource.getIdDt();
524                if (myFhirContext.getVersion().getVersion().isRi()) {
525                        id = myFhirContext.getVersion().newIdType().setValue(id.getValue());
526                }
527
528                if (id.hasResourceType() == false) {
529                        id = id.withResourceType(theEntitySource.getResourceType());
530                }
531
532                theResourceTarget.setId(id);
533                if (theResourceTarget instanceof IResource) {
534                        ResourceMetadataKeyEnum.VERSION.put(theResourceTarget, id.getVersionIdPart());
535                        ResourceMetadataKeyEnum.UPDATED.put(theResourceTarget, theEntitySource.getUpdated());
536                } else {
537                        IBaseMetaType meta = theResourceTarget.getMeta();
538                        meta.setVersionId(id.getVersionIdPart());
539                        meta.setLastUpdated(theEntitySource.getUpdatedDate());
540                }
541        }
542
543        private FhirContext getContext(FhirVersionEnum theVersion) {
544                Validate.notNull(theVersion, "theVersion must not be null");
545                if (theVersion == myFhirContext.getVersion().getVersion()) {
546                        return myFhirContext;
547                }
548                return FhirContext.forCached(theVersion);
549        }
550
551        private static String decodedResourceText(
552                        byte[] resourceBytes, String resourceText, ResourceEncodingEnum resourceEncoding) {
553                String decodedResourceText;
554                if (resourceText != null) {
555                        decodedResourceText = resourceText;
556                } else {
557                        decodedResourceText = decodeResource(resourceBytes, resourceEncoding);
558                }
559                return decodedResourceText;
560        }
561
562        private static List<BaseCodingDt> toBaseCodingList(List<IBaseCoding> theSecurityLabels) {
563                ArrayList<BaseCodingDt> retVal = new ArrayList<>(theSecurityLabels.size());
564                for (IBaseCoding next : theSecurityLabels) {
565                        retVal.add((BaseCodingDt) next);
566                }
567                return retVal;
568        }
569}