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}