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