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