001/*-
002 * #%L
003 * HAPI FHIR - Core Library
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.util;
021
022import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
023import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
024import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
025import ca.uhn.fhir.context.ConfigurationException;
026import ca.uhn.fhir.context.FhirContext;
027import ca.uhn.fhir.context.FhirVersionEnum;
028import ca.uhn.fhir.context.RuntimeResourceDefinition;
029import ca.uhn.fhir.context.RuntimeSearchParam;
030import ca.uhn.fhir.i18n.Msg;
031import ca.uhn.fhir.model.api.annotation.Compartment;
032import ca.uhn.fhir.model.api.annotation.SearchParamDefinition;
033import ca.uhn.fhir.parser.DataFormatException;
034import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
035import jakarta.annotation.Nonnull;
036import jakarta.annotation.Nullable;
037import org.apache.commons.collections4.CollectionUtils;
038import org.apache.commons.lang3.StringUtils;
039import org.apache.commons.lang3.Validate;
040import org.hl7.fhir.instance.model.api.IBase;
041import org.hl7.fhir.instance.model.api.IBaseResource;
042import org.hl7.fhir.instance.model.api.IPrimitiveType;
043
044import java.util.ArrayList;
045import java.util.Arrays;
046import java.util.Collections;
047import java.util.HashMap;
048import java.util.HashSet;
049import java.util.List;
050import java.util.Map;
051import java.util.Optional;
052import java.util.Set;
053import java.util.stream.Collectors;
054
055import static org.apache.commons.lang3.StringUtils.isBlank;
056
057public class SearchParameterUtil {
058        private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchParameterUtil.class);
059
060        /**
061         * This is a list of resources that are in the
062         * <a href="https://build.fhir.org/compartmentdefinition-patient.html">Patient Compartment</a>
063         * but we are omitting anyways for security reasons.
064         *
065         * See <a href="https://github.com/hapifhir/hapi-fhir/issues/7118">this issue</a> for why.
066         */
067        public static final Map<String, Set<String>> RESOURCE_TYPES_TO_SP_TO_OMIT_FROM_PATIENT_COMPARTMENT =
068                        new HashMap<>();
069
070        static {
071                RESOURCE_TYPES_TO_SP_TO_OMIT_FROM_PATIENT_COMPARTMENT.put("Group", new HashSet<>());
072                RESOURCE_TYPES_TO_SP_TO_OMIT_FROM_PATIENT_COMPARTMENT.put("List", new HashSet<>());
073
074                // group
075                RESOURCE_TYPES_TO_SP_TO_OMIT_FROM_PATIENT_COMPARTMENT.get("Group").add("member");
076
077                // list
078                RESOURCE_TYPES_TO_SP_TO_OMIT_FROM_PATIENT_COMPARTMENT.get("List").add("subject");
079                RESOURCE_TYPES_TO_SP_TO_OMIT_FROM_PATIENT_COMPARTMENT.get("List").add("source");
080                RESOURCE_TYPES_TO_SP_TO_OMIT_FROM_PATIENT_COMPARTMENT.get("List").add("patient");
081        }
082
083        /**
084         * Returns true if the named compartment (Patient, Practitioner, etc) should
085         * include the provided Search Parameter (SP)
086         */
087        private static boolean shouldCompartmentIncludeSP(
088                        String theCompartmentName, SearchParamDefinition theSearchParamDef) {
089
090                // default
091                return Arrays.stream(theSearchParamDef.providesMembershipIn())
092                                .anyMatch(p -> p.name().equals(theCompartmentName));
093        }
094
095        /**
096         * Retrieves the set of compartments for the provided Resource class and SP definition.
097         * Includes special case handling
098         *
099         * @param theResourceClazz the resource class (ie, Patient.class, Group.class, etc)
100         * @param theSearchParamDefinition the SP definition (from the fields)
101         * @return a set of valid compartments for the provided search parameter.
102         */
103        public static Set<String> getMembershipCompartmentsForSearchParameter(
104                        Class<? extends IBase> theResourceClazz, SearchParamDefinition theSearchParamDefinition) {
105                RestSearchParameterTypeEnum paramType = RestSearchParameterTypeEnum.forCode(
106                                theSearchParamDefinition.type().toLowerCase());
107                if (paramType == null) {
108                        throw new ConfigurationException(Msg.code(2786) + "Search param " + theSearchParamDefinition.name()
109                                        + " has an invalid type: " + theSearchParamDefinition.type());
110                }
111
112                Set<String> validCompartments = new HashSet<>();
113                for (Compartment compartment : theSearchParamDefinition.providesMembershipIn()) {
114                        if (paramType != RestSearchParameterTypeEnum.REFERENCE) {
115                                StringBuilder b = new StringBuilder();
116                                b.append("Search param ");
117                                b.append(theSearchParamDefinition.name());
118                                b.append(" on resource type ");
119                                b.append(theResourceClazz.getName());
120                                b.append(" provides compartment membership but is not of type 'reference'");
121                                ourLog.warn(b.toString());
122                                continue;
123                        }
124
125                        if (SearchParameterUtil.shouldCompartmentIncludeSP(compartment.name(), theSearchParamDefinition)) {
126                                String compartmentName = getCleansedCompartmentName(compartment.name());
127                                validCompartments.add(compartmentName);
128                        }
129                }
130
131                /*
132                 * In the base FHIR R4 specification, the Device resource is not a part of the Patient compartment.
133                 * However, it is a patient-specific resource that most users expect to be, and several derivative
134                 * specifications including g(10) testing expect it to be, and the fact that it is not has led to many
135                 * bug reports in HAPI FHIR. As of HAPI FHIR 8.0.0 it is being manually added in response to those
136                 * requests.
137                 * See https://github.com/hapifhir/hapi-fhir/issues/6536 for more information.
138                 */
139                if (theSearchParamDefinition.name().equals("patient")
140                                && theSearchParamDefinition.path().equals("Device.patient")) {
141                        validCompartments.add("Patient");
142                }
143
144                return validCompartments;
145        }
146
147        private static String getCleansedCompartmentName(String theCompartmentName) {
148                // As of 2021-12-28 the R5 structures incorrectly have this prefix
149                if (theCompartmentName.startsWith("Base FHIR compartment definition for ")) {
150                        return theCompartmentName.substring("Base FHIR compartment definition for ".length());
151                }
152                return theCompartmentName;
153        }
154
155        public static List<String> getBaseAsStrings(FhirContext theContext, IBaseResource theResource) {
156                Validate.notNull(theContext, "theContext must not be null");
157                Validate.notNull(theResource, "theResource must not be null");
158                RuntimeResourceDefinition def = theContext.getResourceDefinition(theResource);
159
160                BaseRuntimeChildDefinition base = def.getChildByName("base");
161                List<IBase> baseValues = base.getAccessor().getValues(theResource);
162                List<String> retVal = new ArrayList<>();
163                for (IBase next : baseValues) {
164                        IPrimitiveType<?> nextPrimitive = (IPrimitiveType<?>) next;
165                        retVal.add(nextPrimitive.getValueAsString());
166                }
167
168                return retVal;
169        }
170
171        /**
172         * Given the resource type, fetch its patient-based search parameter name
173         * 1. Attempt to find one called 'patient'
174         * 2. If that fails, find one called 'subject'
175         * 3. If that fails, find one by Patient Compartment.
176         * 3.1 If that returns exactly 1 result then return it
177         * 3.2 If that doesn't return exactly 1 result and is R4, fall to 3.3, otherwise, 3.5
178         * 3.3 If that returns >1 result, throw an error
179         * 3.4 If that returns 1 result, return it
180         * 3.5 Find the search parameters by patient compartment using the R4 FHIR path, and return it if there is 1 result,
181         * otherwise, fall to 3.3
182         */
183        public static Optional<RuntimeSearchParam> getOnlyPatientSearchParamForResourceType(
184                        FhirContext theFhirContext, String theResourceType) {
185                RuntimeSearchParam myPatientSearchParam = null;
186                RuntimeResourceDefinition runtimeResourceDefinition = theFhirContext.getResourceDefinition(theResourceType);
187                myPatientSearchParam = runtimeResourceDefinition.getSearchParam("patient");
188                if (myPatientSearchParam == null) {
189                        myPatientSearchParam = runtimeResourceDefinition.getSearchParam("subject");
190                        if (myPatientSearchParam == null) {
191                                final List<RuntimeSearchParam> searchParamsForCurrentVersion =
192                                                runtimeResourceDefinition.getSearchParamsForCompartmentName("Patient");
193                                final List<RuntimeSearchParam> searchParamsToUse;
194                                // We want to handle a narrow code path in which attempting to process SearchParameters for a non-R4
195                                // resource would have failed, and instead make another attempt to process them with the R4-equivalent
196                                // FHIR path.
197                                if (FhirVersionEnum.R4 == theFhirContext.getVersion().getVersion()
198                                                || searchParamsForCurrentVersion.size() == 1) {
199                                        searchParamsToUse = searchParamsForCurrentVersion;
200                                } else {
201                                        searchParamsToUse =
202                                                        checkR4PatientCompartmentForMatchingSearchParam(runtimeResourceDefinition, theResourceType);
203                                }
204                                myPatientSearchParam =
205                                                validateSearchParamsAndReturnOnlyOne(runtimeResourceDefinition, searchParamsToUse);
206                        }
207                }
208                return Optional.of(myPatientSearchParam);
209        }
210
211        @Nonnull
212        private static List<RuntimeSearchParam> checkR4PatientCompartmentForMatchingSearchParam(
213                        RuntimeResourceDefinition theRuntimeResourceDefinition, String theResourceType) {
214                final RuntimeSearchParam patientSearchParamForR4 =
215                                FhirContext.forR4Cached().getResourceDefinition(theResourceType).getSearchParam("patient");
216
217                return Optional.ofNullable(patientSearchParamForR4)
218                                .map(patientSearchParamForR4NonNull ->
219                                                theRuntimeResourceDefinition.getSearchParamsForCompartmentName("Patient").stream()
220                                                                .filter(searchParam -> searchParam.getPath() != null)
221                                                                .filter(searchParam ->
222                                                                                searchParam.getPath().equals(patientSearchParamForR4NonNull.getPath()))
223                                                                .collect(Collectors.toList()))
224                                .orElse(Collections.emptyList());
225        }
226
227        /**
228         * Given the resource type, fetch all its patient-based search parameter name that's available
229         */
230        public static Set<String> getPatientSearchParamsForResourceType(
231                        FhirContext theFhirContext, String theResourceType) {
232                RuntimeResourceDefinition runtimeResourceDefinition = theFhirContext.getResourceDefinition(theResourceType);
233
234                List<RuntimeSearchParam> searchParams =
235                                new ArrayList<>(runtimeResourceDefinition.getSearchParamsForCompartmentName("Patient"));
236                // add patient search parameter for resources that's not in the compartment
237                RuntimeSearchParam myPatientSearchParam = runtimeResourceDefinition.getSearchParam("patient");
238                if (myPatientSearchParam != null) {
239                        searchParams.add(myPatientSearchParam);
240                }
241                RuntimeSearchParam mySubjectSearchParam = runtimeResourceDefinition.getSearchParam("subject");
242                if (mySubjectSearchParam != null) {
243                        searchParams.add(mySubjectSearchParam);
244                }
245                if (CollectionUtils.isEmpty(searchParams)) {
246                        String errorMessage = String.format(
247                                        "Resource type [%s] is not eligible for this type of export, as it contains no Patient compartment, and no `patient` or `subject` search parameter",
248                                        runtimeResourceDefinition.getId());
249                        throw new IllegalArgumentException(Msg.code(2222) + errorMessage);
250                }
251                // deduplicate list of searchParams and get their names
252                return searchParams.stream().map(RuntimeSearchParam::getName).collect(Collectors.toSet());
253        }
254
255        /**
256         * Search the resource definition for a compartment named 'patient' and return its related Search Parameter.
257         */
258        public static RuntimeSearchParam getOnlyPatientCompartmentRuntimeSearchParam(
259                        FhirContext theFhirContext, String theResourceType) {
260                RuntimeResourceDefinition resourceDefinition = theFhirContext.getResourceDefinition(theResourceType);
261                return getOnlyPatientCompartmentRuntimeSearchParam(resourceDefinition);
262        }
263
264        public static RuntimeSearchParam getOnlyPatientCompartmentRuntimeSearchParam(
265                        RuntimeResourceDefinition runtimeResourceDefinition) {
266                return validateSearchParamsAndReturnOnlyOne(
267                                runtimeResourceDefinition, runtimeResourceDefinition.getSearchParamsForCompartmentName("Patient"));
268        }
269
270        public static RuntimeSearchParam getOnlyPatientCompartmentRuntimeSearchParam(
271                        RuntimeResourceDefinition runtimeResourceDefinition, List<RuntimeSearchParam> theSearchParams) {
272                return validateSearchParamsAndReturnOnlyOne(runtimeResourceDefinition, theSearchParams);
273        }
274
275        @Nonnull
276        private static RuntimeSearchParam validateSearchParamsAndReturnOnlyOne(
277                        RuntimeResourceDefinition theRuntimeResourceDefinition, List<RuntimeSearchParam> theSearchParams) {
278                final RuntimeSearchParam patientSearchParam;
279                if (CollectionUtils.isEmpty(theSearchParams)) {
280                        String errorMessage = String.format(
281                                        "Resource type [%s] for ID [%s] and version: [%s] is not eligible for this type of export, as it contains no Patient compartment, and no `patient` or `subject` search parameter",
282                                        theRuntimeResourceDefinition.getName(),
283                                        theRuntimeResourceDefinition.getId(),
284                                        theRuntimeResourceDefinition.getStructureVersion());
285                        throw new IllegalArgumentException(Msg.code(1774) + errorMessage);
286                } else if (theSearchParams.size() == 1) {
287                        patientSearchParam = theSearchParams.get(0);
288                } else {
289                        String errorMessage = String.format(
290                                        "Resource type [%s] for ID [%s] and version: [%s] has more than one Search Param which references a patient compartment. We are unable to disambiguate which patient search parameter we should be searching by.",
291                                        theRuntimeResourceDefinition.getName(),
292                                        theRuntimeResourceDefinition.getId(),
293                                        theRuntimeResourceDefinition.getStructureVersion());
294                        throw new IllegalArgumentException(Msg.code(1775) + errorMessage);
295                }
296                return patientSearchParam;
297        }
298
299        public static List<RuntimeSearchParam> getAllPatientCompartmentRuntimeSearchParamsForResourceType(
300                        FhirContext theFhirContext, String theResourceType) {
301                RuntimeResourceDefinition runtimeResourceDefinition = theFhirContext.getResourceDefinition(theResourceType);
302                return getAllPatientCompartmentRuntimeSearchParams(runtimeResourceDefinition);
303        }
304
305        public static List<RuntimeSearchParam> getAllPatientCompartmenRuntimeSearchParams(FhirContext theFhirContext) {
306                return theFhirContext.getResourceTypes().stream()
307                                .flatMap(type ->
308                                                getAllPatientCompartmentRuntimeSearchParamsForResourceType(theFhirContext, type).stream())
309                                .collect(Collectors.toList());
310        }
311
312        public static Set<String> getAllResourceTypesThatAreInPatientCompartment(FhirContext theFhirContext) {
313                return theFhirContext.getResourceTypes().stream()
314                                .filter(type -> CollectionUtils.isNotEmpty(
315                                                getAllPatientCompartmentRuntimeSearchParamsForResourceType(theFhirContext, type)))
316                                .collect(Collectors.toSet());
317        }
318
319        private static List<RuntimeSearchParam> getAllPatientCompartmentRuntimeSearchParams(
320                        RuntimeResourceDefinition theRuntimeResourceDefinition) {
321                List<RuntimeSearchParam> patient = theRuntimeResourceDefinition.getSearchParamsForCompartmentName("Patient");
322                return patient;
323        }
324
325        /**
326         * Return true if any search parameter in the resource can point at a patient, false otherwise
327         */
328        public static boolean isResourceTypeInPatientCompartment(FhirContext theFhirContext, String theResourceType) {
329                RuntimeResourceDefinition runtimeResourceDefinition = theFhirContext.getResourceDefinition(theResourceType);
330                return CollectionUtils.isNotEmpty(getAllPatientCompartmentRuntimeSearchParams(runtimeResourceDefinition));
331        }
332
333        @Nullable
334        public static String getCode(FhirContext theContext, IBaseResource theResource) {
335                return getStringChild(theContext, theResource, "code");
336        }
337
338        @Nullable
339        public static String getURL(FhirContext theContext, IBaseResource theResource) {
340                return getStringChild(theContext, theResource, "url");
341        }
342
343        @Nullable
344        public static String getExpression(FhirContext theFhirContext, IBaseResource theResource) {
345                return getStringChild(theFhirContext, theResource, "expression");
346        }
347
348        private static String getStringChild(FhirContext theFhirContext, IBaseResource theResource, String theChildName) {
349                Validate.notNull(theFhirContext, "theContext must not be null");
350                Validate.notNull(theResource, "theResource must not be null");
351                RuntimeResourceDefinition def = theFhirContext.getResourceDefinition(theResource);
352
353                BaseRuntimeChildDefinition base = def.getChildByName(theChildName);
354                return base.getAccessor()
355                                .getFirstValueOrNull(theResource)
356                                .map(t -> ((IPrimitiveType<?>) t))
357                                .map(t -> t.getValueAsString())
358                                .orElse(null);
359        }
360
361        public static String stripModifier(String theSearchParam) {
362                String retval;
363                int colonIndex = theSearchParam.indexOf(":");
364                if (colonIndex == -1) {
365                        retval = theSearchParam;
366                } else {
367                        retval = theSearchParam.substring(0, colonIndex);
368                }
369                return retval;
370        }
371
372        /**
373         * Many SearchParameters combine a series of potential expressions into a single concatenated
374         * expression. For example, in FHIR R5 the "encounter" search parameter has an expression like:
375         * <code>AuditEvent.encounter | CarePlan.encounter | ChargeItem.encounter | ......</code>.
376         * This method takes such a FHIRPath expression and splits it into a series of separate
377         * expressions. To achieve this, we iteratively splits a string on any <code> or </code> or <code>|</code> that
378         * is <b>not</b> contained inside a set of parentheses. e.g.
379         * <p>
380         * "Patient.select(a or b)" -->  ["Patient.select(a or b)"]
381         * "Patient.select(a or b) or Patient.select(c or d )" --> ["Patient.select(a or b)", "Patient.select(c or d)"]
382         * "Patient.select(a|b) or Patient.select(c or d )" --> ["Patient.select(a|b)", "Patient.select(c or d)"]
383         * "Patient.select(b) | Patient.select(c)" -->  ["Patient.select(b)", "Patient.select(c)"]
384         *
385         * @param thePaths The FHIRPath expression to split
386         * @return The split strings
387         */
388        public static String[] splitSearchParameterExpressions(String thePaths) {
389                if (!StringUtils.containsAny(thePaths, " or ", " |")) {
390                        return new String[] {thePaths};
391                }
392                List<String> topLevelOrExpressions = splitOutOfParensToken(thePaths, " or ");
393                return topLevelOrExpressions.stream()
394                                .flatMap(s -> splitOutOfParensToken(s, " |").stream())
395                                .toArray(String[]::new);
396        }
397
398        private static List<String> splitOutOfParensToken(String thePath, String theToken) {
399                int tokenLength = theToken.length();
400                int index = thePath.indexOf(theToken);
401                int rightIndex = 0;
402                List<String> retVal = new ArrayList<>();
403                while (index > -1) {
404                        String left = thePath.substring(rightIndex, index);
405                        if (allParensHaveBeenClosed(left)) {
406                                retVal.add(left.trim());
407                                rightIndex = index + tokenLength;
408                        }
409                        index = thePath.indexOf(theToken, index + tokenLength);
410                }
411                String pathTrimmed = thePath.substring(rightIndex).trim();
412                if (!pathTrimmed.isEmpty()) {
413                        retVal.add(pathTrimmed);
414                }
415                return retVal;
416        }
417
418        private static boolean allParensHaveBeenClosed(String thePaths) {
419                int open = StringUtils.countMatches(thePaths, "(");
420                int close = StringUtils.countMatches(thePaths, ")");
421                return open == close;
422        }
423
424        /**
425         * Given a FHIRPath expression which presumably addresses a FHIR reference or
426         * canonical reference element (i.e. a FHIRPath expression used in a "reference"
427         * SearchParameter), tries to determine whether the path could potentially resolve
428         * to a canonical reference.
429         * <p>
430         * Just because a SearchParameter is a Reference SP, doesn't necessarily mean that it
431         * can reference a canonical. So first we try to rule out the SP based on the path it
432         * contains. This matters because a SearchParameter of type Reference can point to
433         * a canonical element (in which case we need to _include any canonical targets). Or it
434         * can point to a Reference element (in which case we only need to _include actual
435         * references by ID).
436         * </p>
437         * <p>
438         * This isn't perfect because there's really no definitive and comprehensive
439         * way of determining the datatype that a SearchParameter or a FHIRPath point to. But
440         * we do our best if the path is simple enough to just manually check the type it
441         * points to, or if it ends in an explicit type declaration.
442         * </p>
443         * <p>
444         * Because it is not possible to deterministically determine the datatype for a
445         * FHIRPath expression in all cases, this method is cautious: it will return
446         * {@literal true} if it isn't sure.
447         * </p>
448         *
449         * @return Returns {@literal true} if the path could return a {@literal canonical} or {@literal CanonicalReference}, or returns {@literal false} if the path could only return a {@literal Reference}.
450         */
451        public static boolean referencePathCouldPotentiallyReferenceCanonicalElement(
452                        FhirContext theContext, String theResourceType, String thePath, boolean theReverse) {
453
454                // If this path explicitly wants a reference and not a canonical, we can ignore it since we're
455                // only looking for canonicals here
456                if (thePath.endsWith(".ofType(Reference)")) {
457                        return false;
458                }
459
460                BaseRuntimeElementCompositeDefinition<?> currentDef = theContext.getResourceDefinition(theResourceType);
461
462                String remainingPath = thePath;
463                boolean firstSegment = true;
464                while (remainingPath != null) {
465
466                        int dotIdx = remainingPath.indexOf(".");
467                        String currentSegment;
468                        if (dotIdx == -1) {
469                                currentSegment = remainingPath;
470                                remainingPath = null;
471                        } else {
472                                currentSegment = remainingPath.substring(0, dotIdx);
473                                remainingPath = remainingPath.substring(dotIdx + 1);
474                        }
475
476                        if (isBlank(currentSegment)) {
477                                return true;
478                        }
479
480                        if (firstSegment) {
481                                firstSegment = false;
482                                // The first segment is typically just the resource name, and we assume it is
483                                // as long as the first character is upper case.
484                                if (Character.isUpperCase(currentSegment.charAt(0))) {
485
486                                        // If we're in forward-include mode, and the first segment is the resource type,
487                                        // we can ignore any paths where the resource type isn't actually the one
488                                        // we're matching on
489                                        if (!theReverse && !theResourceType.equals(currentSegment)) {
490                                                return false;
491                                        }
492
493                                        // Set the working definition to be the resource type specified in the path
494                                        try {
495                                                currentDef = theContext.getResourceDefinition(currentSegment);
496                                        } catch (DataFormatException e) {
497                                                ourLog.warn("Search parameter path defines an invalid resource type: {}", currentSegment);
498                                        }
499                                        continue;
500                                }
501                        }
502
503                        BaseRuntimeChildDefinition child = currentDef.getChildByName(currentSegment);
504                        if (child == null) {
505                                return true;
506                        }
507                        BaseRuntimeElementDefinition<?> def = child.getChildByName(currentSegment);
508                        if (def == null) {
509                                return true;
510                        }
511                        if (def.getName().equals("Reference")) {
512                                return false;
513                        }
514                        if (!(def instanceof BaseRuntimeElementCompositeDefinition)) {
515                                return true;
516                        }
517
518                        currentDef = (BaseRuntimeElementCompositeDefinition<?>) def;
519                }
520
521                return true;
522        }
523}