
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.jpa.api.config.JpaStorageSettings; 027import ca.uhn.fhir.jpa.api.dao.IDao; 028import ca.uhn.fhir.jpa.dao.data.IResourceHistoryProvenanceDao; 029import ca.uhn.fhir.jpa.dao.data.IResourceHistoryTableDao; 030import ca.uhn.fhir.jpa.entity.PartitionEntity; 031import ca.uhn.fhir.jpa.esr.ExternallyStoredResourceServiceRegistry; 032import ca.uhn.fhir.jpa.esr.IExternallyStoredResourceService; 033import ca.uhn.fhir.jpa.model.config.PartitionSettings; 034import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource; 035import ca.uhn.fhir.jpa.model.dao.JpaPid; 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<JpaPid>) 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 = myResourceHistoryProvenanceDao.findById( 168 history.getId().asIdAndPartitionId()); 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( 184 theEntity.getResourceId().toFk(), version); 185 ((ResourceTable) theEntity).setCurrentVersionEntity(history); 186 187 while (history == null) { 188 if (version > 1L) { 189 version--; 190 history = myResourceHistoryTableDao.findForIdAndVersion( 191 theEntity.getResourceId().toFk(), version); 192 } else { 193 return null; 194 } 195 } 196 } 197 198 resourceBytes = history.getResource(); 199 resourceEncoding = history.getEncoding(); 200 resourceText = history.getResourceTextVc(); 201 switch (myStorageSettings.getTagStorageMode()) { 202 case VERSIONED: 203 case NON_VERSIONED: 204 if (resource.isHasTags()) { 205 tagList = resource.getTags(); 206 } else { 207 tagList = List.of(); 208 } 209 break; 210 case INLINE: 211 default: 212 tagList = null; 213 break; 214 } 215 version = history.getVersion(); 216 provenanceSourceUri = history.getSourceUri(); 217 provenanceRequestId = history.getRequestId(); 218 if (isBlank(provenanceSourceUri) && isBlank(provenanceRequestId)) { 219 if (myStorageSettings.isAccessMetaSourceInformationFromProvenanceTable()) { 220 Optional<ResourceHistoryProvenanceEntity> provenanceOpt = myResourceHistoryProvenanceDao.findById( 221 history.getId().asIdAndPartitionId()); 222 if (provenanceOpt.isPresent()) { 223 ResourceHistoryProvenanceEntity provenance = provenanceOpt.get(); 224 provenanceRequestId = provenance.getRequestId(); 225 provenanceSourceUri = provenance.getSourceUri(); 226 } 227 } 228 } 229 } else { 230 // something wrong 231 return null; 232 } 233 234 // 2. get The text 235 String decodedResourceText = decodedResourceText(resourceBytes, resourceText, resourceEncoding); 236 237 // 3. Use the appropriate custom type if one is specified in the context 238 Class<R> resourceType = determineTypeToParse(theResourceType, tagList); 239 240 // 4. parse the text to FHIR 241 R retVal = parseResource(theEntity, resourceEncoding, decodedResourceText, resourceType); 242 243 // 5. fill MetaData 244 retVal = populateResourceMetadata(theEntity, theForHistoryOperation, tagList, version, retVal); 245 246 // 6. Handle source (provenance) 247 MetaUtil.populateResourceSource(myFhirContext, provenanceSourceUri, provenanceRequestId, retVal); 248 249 // 7. Add partition information 250 populateResourcePartitionInformation(theEntity, retVal); 251 252 // 8. sort tags, security labels and profiles 253 myMetaTagSorter.sort(retVal.getMeta()); 254 255 return retVal; 256 } 257 258 private <R extends IBaseResource> void populateResourcePartitionInformation( 259 IBaseResourceEntity theEntity, R retVal) { 260 if (myPartitionSettings.isPartitioningEnabled()) { 261 PartitionablePartitionId partitionId = theEntity.getPartitionId(); 262 if (partitionId != null && partitionId.getPartitionId() != null) { 263 PartitionEntity persistedPartition = 264 myPartitionLookupSvc.getPartitionById(partitionId.getPartitionId()); 265 retVal.setUserData(Constants.RESOURCE_PARTITION_ID, persistedPartition.toRequestPartitionId()); 266 } else { 267 retVal.setUserData(Constants.RESOURCE_PARTITION_ID, myPartitionSettings.getDefaultRequestPartitionId()); 268 } 269 } 270 } 271 272 @SuppressWarnings("unchecked") 273 private <R extends IBaseResource> R parseResource( 274 IBaseResourceEntity<?> theEntity, 275 ResourceEncodingEnum theResourceEncoding, 276 String theDecodedResourceText, 277 Class<R> theResourceType) { 278 R retVal; 279 if (theResourceEncoding == ResourceEncodingEnum.ESR) { 280 281 int colonIndex = theDecodedResourceText.indexOf(':'); 282 Validate.isTrue(colonIndex > 0, "Invalid ESR address: %s", theDecodedResourceText); 283 String providerId = theDecodedResourceText.substring(0, colonIndex); 284 String address = theDecodedResourceText.substring(colonIndex + 1); 285 Validate.notBlank(providerId, "No provider ID in ESR address: %s", theDecodedResourceText); 286 Validate.notBlank(address, "No address in ESR address: %s", theDecodedResourceText); 287 IExternallyStoredResourceService provider = 288 myExternallyStoredResourceServiceRegistry.getProvider(providerId); 289 retVal = (R) provider.fetchResource(address); 290 291 } else if (theResourceEncoding != ResourceEncodingEnum.DEL) { 292 293 IParser parser = new TolerantJsonParser( 294 getContext(theEntity.getFhirVersion()), LENIENT_ERROR_HANDLER, theEntity.getResourceId()); 295 296 try { 297 retVal = parser.parseResource(theResourceType, theDecodedResourceText); 298 } catch (Exception e) { 299 StringBuilder b = new StringBuilder(); 300 b.append("Failed to parse database resource["); 301 b.append(myFhirContext.getResourceType(theResourceType)); 302 b.append("/"); 303 b.append(theEntity.getIdDt().getIdPart()); 304 b.append(" (pid "); 305 b.append(theEntity.getId()); 306 b.append(", version "); 307 b.append(theEntity.getFhirVersion().name()); 308 b.append("): "); 309 b.append(e.getMessage()); 310 String msg = b.toString(); 311 ourLog.error(msg, e); 312 throw new DataFormatException(Msg.code(928) + msg, e); 313 } 314 315 } else { 316 317 retVal = (R) myFhirContext 318 .getResourceDefinition(theEntity.getResourceType()) 319 .newInstance(); 320 } 321 return retVal; 322 } 323 324 @SuppressWarnings("unchecked") 325 private <R extends IBaseResource> Class<R> determineTypeToParse( 326 Class<R> theResourceType, @Nullable Collection<? extends BaseTag> tagList) { 327 Class<R> resourceType = theResourceType; 328 if (tagList != null) { 329 if (myFhirContext.hasDefaultTypeForProfile()) { 330 for (BaseTag nextTag : tagList) { 331 if (nextTag.getTag().getTagType() == TagTypeEnum.PROFILE) { 332 String profile = nextTag.getTag().getCode(); 333 if (isNotBlank(profile)) { 334 Class<? extends IBaseResource> newType = myFhirContext.getDefaultTypeForProfile(profile); 335 if (newType != null && theResourceType.isAssignableFrom(newType)) { 336 ourLog.debug("Using custom type {} for profile: {}", newType.getName(), profile); 337 resourceType = (Class<R>) newType; 338 break; 339 } 340 } 341 } 342 } 343 } 344 } 345 return resourceType; 346 } 347 348 @SuppressWarnings("unchecked") 349 @Override 350 public <R extends IBaseResource> R populateResourceMetadata( 351 IBaseResourceEntity<?> theEntitySource, 352 boolean theForHistoryOperation, 353 @Nullable Collection<? extends BaseTag> tagList, 354 long theVersion, 355 R theResourceTarget) { 356 if (theResourceTarget instanceof IResource) { 357 IResource res = (IResource) theResourceTarget; 358 theResourceTarget = 359 (R) populateResourceMetadataHapi(theEntitySource, tagList, theForHistoryOperation, res, theVersion); 360 } else { 361 IAnyResource res = (IAnyResource) theResourceTarget; 362 theResourceTarget = 363 populateResourceMetadataRi(theEntitySource, tagList, theForHistoryOperation, res, theVersion); 364 } 365 return theResourceTarget; 366 } 367 368 @SuppressWarnings("unchecked") 369 private <R extends IResource> R populateResourceMetadataHapi( 370 IBaseResourceEntity<?> theEntity, 371 @Nullable Collection<? extends BaseTag> theTagList, 372 boolean theForHistoryOperation, 373 R res, 374 Long theVersion) { 375 R retVal = res; 376 if (theEntity.getDeleted() != null) { 377 res = (R) myFhirContext.getResourceDefinition(res).newInstance(); 378 retVal = res; 379 ResourceMetadataKeyEnum.DELETED_AT.put(res, new InstantDt(theEntity.getDeleted())); 380 if (theForHistoryOperation) { 381 ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, BundleEntryTransactionMethodEnum.DELETE); 382 } 383 } else if (theForHistoryOperation) { 384 /* 385 * 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. 386 */ 387 Date published = theEntity.getPublished().getValue(); 388 Date updated = theEntity.getUpdated().getValue(); 389 if (published.equals(updated)) { 390 ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, BundleEntryTransactionMethodEnum.POST); 391 } else { 392 ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, BundleEntryTransactionMethodEnum.PUT); 393 } 394 } 395 396 res.setId(theEntity.getIdDt().withVersion(theVersion.toString())); 397 398 ResourceMetadataKeyEnum.VERSION.put(res, Long.toString(theEntity.getVersion())); 399 ResourceMetadataKeyEnum.PUBLISHED.put(res, theEntity.getPublished()); 400 ResourceMetadataKeyEnum.UPDATED.put(res, theEntity.getUpdated()); 401 IDao.RESOURCE_PID.put(res, theEntity.getResourceId()); 402 403 if (theTagList != null) { 404 if (theEntity.isHasTags()) { 405 TagList tagList = new TagList(); 406 List<IBaseCoding> securityLabels = new ArrayList<>(); 407 List<IdDt> profiles = new ArrayList<>(); 408 for (BaseTag next : theTagList) { 409 TagDefinition nextTag = next.getTag(); 410 switch (nextTag.getTagType()) { 411 case PROFILE: 412 profiles.add(new IdDt(nextTag.getCode())); 413 break; 414 case SECURITY_LABEL: 415 IBaseCoding secLabel = 416 (IBaseCoding) myFhirContext.getVersion().newCodingDt(); 417 secLabel.setSystem(nextTag.getSystem()); 418 secLabel.setCode(nextTag.getCode()); 419 secLabel.setDisplay(nextTag.getDisplay()); 420 secLabel.setVersion(nextTag.getVersion()); 421 Boolean userSelected = nextTag.getUserSelected(); 422 if (userSelected != null) { 423 secLabel.setUserSelected(userSelected); 424 } 425 securityLabels.add(secLabel); 426 break; 427 case TAG: 428 Tag e = new Tag(nextTag.getSystem(), nextTag.getCode(), nextTag.getDisplay()); 429 e.setVersion(nextTag.getVersion()); 430 // careful! These are Boolean, not boolean. 431 e.setUserSelectedBoolean(nextTag.getUserSelected()); 432 tagList.add(e); 433 break; 434 } 435 } 436 if (tagList.size() > 0) { 437 ResourceMetadataKeyEnum.TAG_LIST.put(res, tagList); 438 } 439 if (securityLabels.size() > 0) { 440 ResourceMetadataKeyEnum.SECURITY_LABELS.put(res, toBaseCodingList(securityLabels)); 441 } 442 if (profiles.size() > 0) { 443 ResourceMetadataKeyEnum.PROFILES.put(res, profiles); 444 } 445 } 446 } 447 448 return retVal; 449 } 450 451 @SuppressWarnings("unchecked") 452 private <R extends IBaseResource> R populateResourceMetadataRi( 453 IBaseResourceEntity theEntity, 454 @Nullable Collection<? extends BaseTag> theTagList, 455 boolean theForHistoryOperation, 456 IAnyResource res, 457 Long theVersion) { 458 R retVal = (R) res; 459 if (theEntity.getDeleted() != null) { 460 res = (IAnyResource) myFhirContext.getResourceDefinition(res).newInstance(); 461 retVal = (R) res; 462 ResourceMetadataKeyEnum.DELETED_AT.put(res, new InstantDt(theEntity.getDeleted())); 463 if (theForHistoryOperation) { 464 ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, BundleEntryTransactionMethodEnum.DELETE); 465 } 466 } else if (theForHistoryOperation) { 467 /* 468 * 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. 469 */ 470 Date published = theEntity.getPublished().getValue(); 471 Date updated = theEntity.getUpdated().getValue(); 472 if (published.equals(updated)) { 473 ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, BundleEntryTransactionMethodEnum.POST); 474 } else { 475 ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, BundleEntryTransactionMethodEnum.PUT); 476 } 477 } 478 479 res.getMeta().setLastUpdated(null); 480 res.getMeta().setVersionId(null); 481 482 updateResourceMetadata(theEntity, res); 483 res.setId(res.getIdElement().withVersion(theVersion.toString())); 484 485 res.getMeta().setLastUpdated(theEntity.getUpdatedDate()); 486 IDao.RESOURCE_PID.put(res, theEntity.getResourceId()); 487 488 if (CollectionUtils.isNotEmpty(theTagList)) { 489 res.getMeta().getTag().clear(); 490 res.getMeta().getProfile().clear(); 491 res.getMeta().getSecurity().clear(); 492 493 boolean haveWarnedForMissingTag = false; 494 for (BaseTag next : theTagList) { 495 if (next.getTag() == null) { 496 if (!haveWarnedForMissingTag) { 497 ourLog.warn( 498 "Tag definition HFJ_TAG_DEF#{} is missing, returned Resource.meta may not be complete", 499 next.getTagId()); 500 haveWarnedForMissingTag = true; 501 } 502 continue; 503 } 504 505 switch (next.getTag().getTagType()) { 506 case PROFILE: 507 res.getMeta().addProfile(next.getTag().getCode()); 508 break; 509 case SECURITY_LABEL: 510 IBaseCoding sec = res.getMeta().addSecurity(); 511 sec.setSystem(next.getTag().getSystem()); 512 sec.setCode(next.getTag().getCode()); 513 sec.setDisplay(next.getTag().getDisplay()); 514 break; 515 case TAG: 516 IBaseCoding tag = res.getMeta().addTag(); 517 tag.setSystem(next.getTag().getSystem()); 518 tag.setCode(next.getTag().getCode()); 519 tag.setDisplay(next.getTag().getDisplay()); 520 tag.setVersion(next.getTag().getVersion()); 521 Boolean userSelected = next.getTag().getUserSelected(); 522 // the tag is created with a null userSelected, but the api is primitive boolean. 523 // Only update if we are non-null. 524 if (nonNull(userSelected)) { 525 tag.setUserSelected(userSelected); 526 } 527 break; 528 } 529 } 530 } 531 532 return retVal; 533 } 534 535 @Override 536 public void updateResourceMetadata(IBaseResourceEntity<?> theEntitySource, IBaseResource theResourceTarget) { 537 IIdType id = theEntitySource.getIdDt(); 538 if (myFhirContext.getVersion().getVersion().isRi()) { 539 id = myFhirContext.getVersion().newIdType().setValue(id.getValue()); 540 } 541 542 if (id.hasResourceType() == false) { 543 id = id.withResourceType(theEntitySource.getResourceType()); 544 } 545 546 theResourceTarget.setId(id); 547 if (theResourceTarget instanceof IResource) { 548 ResourceMetadataKeyEnum.VERSION.put(theResourceTarget, id.getVersionIdPart()); 549 ResourceMetadataKeyEnum.UPDATED.put(theResourceTarget, theEntitySource.getUpdated()); 550 } else { 551 IBaseMetaType meta = theResourceTarget.getMeta(); 552 meta.setVersionId(id.getVersionIdPart()); 553 meta.setLastUpdated(theEntitySource.getUpdatedDate()); 554 } 555 556 populateResourcePartitionInformation(theEntitySource, theResourceTarget); 557 } 558 559 private FhirContext getContext(FhirVersionEnum theVersion) { 560 Validate.notNull(theVersion, "theVersion must not be null"); 561 if (theVersion == myFhirContext.getVersion().getVersion()) { 562 return myFhirContext; 563 } 564 return FhirContext.forCached(theVersion); 565 } 566 567 private static String decodedResourceText( 568 byte[] resourceBytes, String resourceText, ResourceEncodingEnum resourceEncoding) { 569 String decodedResourceText; 570 if (resourceText != null) { 571 decodedResourceText = resourceText; 572 } else { 573 decodedResourceText = decodeResource(resourceBytes, resourceEncoding); 574 } 575 return decodedResourceText; 576 } 577 578 private static List<BaseCodingDt> toBaseCodingList(List<IBaseCoding> theSecurityLabels) { 579 ArrayList<BaseCodingDt> retVal = new ArrayList<>(theSecurityLabels.size()); 580 for (IBaseCoding next : theSecurityLabels) { 581 retVal.add((BaseCodingDt) next); 582 } 583 return retVal; 584 } 585}