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