001/*-
002 * #%L
003 * HAPI FHIR Storage api
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.index;
021
022import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
023import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
024import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
025import ca.uhn.fhir.context.FhirContext;
026import ca.uhn.fhir.context.RuntimeResourceDefinition;
027import ca.uhn.fhir.i18n.Msg;
028import ca.uhn.fhir.interceptor.api.HookParams;
029import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
030import ca.uhn.fhir.interceptor.api.Pointcut;
031import ca.uhn.fhir.interceptor.model.RequestPartitionId;
032import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
033import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
034import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
035import ca.uhn.fhir.jpa.api.svc.IIdHelperService;
036import ca.uhn.fhir.jpa.api.svc.ResolveIdentityMode;
037import ca.uhn.fhir.jpa.dao.tx.IHapiTransactionService;
038import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource;
039import ca.uhn.fhir.jpa.model.cross.IResourceLookup;
040import ca.uhn.fhir.jpa.model.entity.PartitionablePartitionId;
041import ca.uhn.fhir.jpa.searchparam.extractor.IResourceLinkResolver;
042import ca.uhn.fhir.jpa.searchparam.extractor.PathAndRef;
043import ca.uhn.fhir.rest.api.server.RequestDetails;
044import ca.uhn.fhir.rest.api.server.storage.IResourcePersistentId;
045import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
046import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
047import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
048import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
049import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
050import ca.uhn.fhir.rest.server.util.CompositeInterceptorBroadcaster;
051import ca.uhn.fhir.storage.interceptor.AutoCreatePlaceholderReferenceTargetRequest;
052import ca.uhn.fhir.storage.interceptor.AutoCreatePlaceholderReferenceTargetResponse;
053import ca.uhn.fhir.util.CanonicalIdentifier;
054import ca.uhn.fhir.util.HapiExtensions;
055import ca.uhn.fhir.util.TerserUtil;
056import jakarta.annotation.Nonnull;
057import jakarta.annotation.Nullable;
058import org.apache.commons.lang3.Validate;
059import org.apache.http.NameValuePair;
060import org.apache.http.client.utils.URLEncodedUtils;
061import org.hl7.fhir.instance.model.api.IBase;
062import org.hl7.fhir.instance.model.api.IBaseExtension;
063import org.hl7.fhir.instance.model.api.IBaseHasExtensions;
064import org.hl7.fhir.instance.model.api.IBaseReference;
065import org.hl7.fhir.instance.model.api.IBaseResource;
066import org.hl7.fhir.instance.model.api.IIdType;
067import org.hl7.fhir.instance.model.api.IPrimitiveType;
068import org.springframework.beans.factory.annotation.Autowired;
069
070import java.nio.charset.StandardCharsets;
071import java.util.Date;
072import java.util.List;
073import java.util.Optional;
074
075import static org.apache.commons.lang3.StringUtils.isBlank;
076
077public class DaoResourceLinkResolver<T extends IResourcePersistentId<?>> implements IResourceLinkResolver {
078        private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(DaoResourceLinkResolver.class);
079
080        @Autowired
081        private JpaStorageSettings myStorageSettings;
082
083        @Autowired
084        private FhirContext myContext;
085
086        @Autowired
087        private IIdHelperService<T> myIdHelperService;
088
089        @Autowired
090        private DaoRegistry myDaoRegistry;
091
092        @Autowired
093        private IHapiTransactionService myTransactionService;
094
095        @Autowired
096        private IInterceptorBroadcaster myInterceptorBroadcaster;
097
098        @Override
099        public IResourceLookup findTargetResource(
100                        @Nonnull RequestPartitionId theRequestPartitionId,
101                        String theSourceResourceName,
102                        PathAndRef thePathAndRef,
103                        RequestDetails theRequest,
104                        TransactionDetails theTransactionDetails) {
105
106                IBaseReference targetReference = thePathAndRef.getRef();
107                String sourcePath = thePathAndRef.getPath();
108
109                IIdType targetResourceId = targetReference.getReferenceElement();
110                if (targetResourceId.isEmpty() && targetReference.getResource() != null) {
111                        targetResourceId = targetReference.getResource().getIdElement();
112                }
113
114                String resourceType = targetResourceId.getResourceType();
115                RuntimeResourceDefinition resourceDef = myContext.getResourceDefinition(resourceType);
116                Class<? extends IBaseResource> type = resourceDef.getImplementingClass();
117
118                T persistentId = null;
119                if (theTransactionDetails != null) {
120                        T resolvedResourceId = (T) theTransactionDetails.getResolvedResourceId(targetResourceId);
121                        if (resolvedResourceId != null
122                                        && resolvedResourceId.getId() != null
123                                        && resolvedResourceId.getAssociatedResourceId() != null) {
124                                persistentId = resolvedResourceId;
125                        }
126                }
127
128                IResourceLookup<?> resolvedResource;
129                String idPart = targetResourceId.getIdPart();
130                try {
131                        if (persistentId == null) {
132
133                                // If we previously looked up the ID, and it was not found, don't bother
134                                // looking it up again
135                                if (theTransactionDetails != null
136                                                && theTransactionDetails.hasNullResolvedResourceId(targetResourceId)) {
137                                        throw new ResourceNotFoundException(Msg.code(2602));
138                                }
139
140                                resolvedResource = myIdHelperService.resolveResourceIdentity(
141                                                theRequestPartitionId,
142                                                resourceType,
143                                                idPart,
144                                                ResolveIdentityMode.excludeDeleted().noCacheUnlessDeletesDisabled());
145                                ourLog.trace("Translated {}/{} to resource PID {}", type, idPart, resolvedResource);
146                        } else {
147                                resolvedResource = new ResourceLookupPersistentIdWrapper<>(persistentId);
148                        }
149                } catch (ResourceNotFoundException e) {
150
151                        Optional<IBasePersistedResource> createdTableOpt = createPlaceholderTargetIfConfiguredToDoSo(
152                                        type, targetReference, idPart, theRequest, theTransactionDetails);
153                        if (!createdTableOpt.isPresent()) {
154
155                                if (!myStorageSettings.isEnforceReferentialIntegrityOnWrite()) {
156                                        return null;
157                                }
158
159                                RuntimeResourceDefinition missingResourceDef = myContext.getResourceDefinition(type);
160                                String resName = missingResourceDef.getName();
161
162                                // Check if this was a deleted resource
163                                try {
164                                        resolvedResource = myIdHelperService.resolveResourceIdentity(
165                                                        theRequestPartitionId,
166                                                        resourceType,
167                                                        idPart,
168                                                        ResolveIdentityMode.includeDeleted().noCacheUnlessDeletesDisabled());
169                                        handleDeletedTarget(resourceType, idPart, sourcePath);
170                                } catch (ResourceNotFoundException e2) {
171                                        resolvedResource = null;
172                                }
173
174                                if (resolvedResource == null) {
175                                        throw new InvalidRequestException(Msg.code(1094) + "Resource " + resName + "/" + idPart
176                                                        + " not found, specified in path: " + sourcePath);
177                                }
178                        }
179
180                        resolvedResource = createdTableOpt.get();
181                }
182
183                ourLog.trace(
184                                "Resolved resource of type {} as PID: {}",
185                                resolvedResource.getResourceType(),
186                                resolvedResource.getPersistentId());
187                if (!validateResolvedResourceOrThrow(resourceType, resolvedResource, targetResourceId, idPart, sourcePath)) {
188                        return null;
189                }
190
191                if (persistentId == null) {
192                        Object id = resolvedResource.getPersistentId().getId();
193                        Integer partitionId = null;
194                        if (resolvedResource.getPartitionId() != null) {
195                                partitionId = resolvedResource.getPartitionId().getPartitionId();
196                        }
197                        persistentId = myIdHelperService.newPid(id, partitionId);
198                        persistentId.setAssociatedResourceId(targetResourceId);
199                        if (theTransactionDetails != null) {
200                                theTransactionDetails.addResolvedResourceId(targetResourceId, persistentId);
201                        }
202                }
203
204                return resolvedResource;
205        }
206
207        /**
208         * Validates the resolved resource.
209         * If 'Enforce Referential Integrity on Write' is enabled:
210         * Throws <code>UnprocessableEntityException</code> when resource types do not match
211         * Throws <code>InvalidRequestException</code> when the resolved resource was deleted
212         * <p>
213         * Otherwise, return false when resource types do not match or resource was deleted
214         * and return true if the resolved resource is valid.
215         */
216        private boolean validateResolvedResourceOrThrow(
217                        String resourceType,
218                        IResourceLookup resolvedResource,
219                        IIdType targetResourceId,
220                        String idPart,
221                        String sourcePath) {
222                if (!resourceType.equals(resolvedResource.getResourceType())) {
223                        ourLog.error(
224                                        "Resource with PID {} was of type {} and wanted {}",
225                                        resolvedResource.getPersistentId(),
226                                        resourceType,
227                                        resolvedResource.getResourceType());
228                        if (!myStorageSettings.isEnforceReferentialIntegrityOnWrite()) {
229                                return false;
230                        }
231                        throw new UnprocessableEntityException(Msg.code(1095)
232                                        + "Resource contains reference to unknown resource ID " + targetResourceId.getValue());
233                }
234
235                if (resolvedResource.getDeleted() != null) {
236                        return handleDeletedTarget(resolvedResource.getResourceType(), idPart, sourcePath);
237                }
238                return true;
239        }
240
241        private boolean handleDeletedTarget(String resType, String idPart, String sourcePath) {
242                if (!myStorageSettings.isEnforceReferentialIntegrityOnWrite()) {
243                        return false;
244                }
245                String resName = resType;
246                throw new InvalidRequestException(Msg.code(1096) + "Resource " + resName + "/" + idPart
247                                + " is deleted, specified in path: " + sourcePath);
248        }
249
250        @Nullable
251        @Override
252        public IBaseResource loadTargetResource(
253                        @Nonnull RequestPartitionId theRequestPartitionId,
254                        String theSourceResourceName,
255                        PathAndRef thePathAndRef,
256                        RequestDetails theRequest,
257                        TransactionDetails theTransactionDetails) {
258                return myTransactionService
259                                .withRequest(theRequest)
260                                .withTransactionDetails(theTransactionDetails)
261                                .withRequestPartitionId(theRequestPartitionId)
262                                .execute(() -> {
263                                        IIdType targetId = thePathAndRef.getRef().getReferenceElement();
264                                        IFhirResourceDao<?> dao = myDaoRegistry.getResourceDao(targetId.getResourceType());
265                                        return dao.read(targetId, theRequest);
266                                });
267        }
268
269        /**
270         * @param theIdToAssignToPlaceholder If specified, the placeholder resource created will be given a specific ID
271         */
272        public <T extends IBaseResource> Optional<IBasePersistedResource> createPlaceholderTargetIfConfiguredToDoSo(
273                        Class<T> theType,
274                        IBaseReference theReference,
275                        @Nullable String theIdToAssignToPlaceholder,
276                        RequestDetails theRequest,
277                        TransactionDetails theTransactionDetails) {
278                IBasePersistedResource valueOf = null;
279
280                if (myStorageSettings.isAutoCreatePlaceholderReferenceTargets()) {
281                        RuntimeResourceDefinition missingResourceDef = myContext.getResourceDefinition(theType);
282                        String resName = missingResourceDef.getName();
283
284                        @SuppressWarnings("unchecked")
285                        T newResource = (T) missingResourceDef.newInstance();
286
287                        tryToAddPlaceholderExtensionToResource(newResource);
288
289                        IFhirResourceDao<T> placeholderResourceDao = myDaoRegistry.getResourceDao(theType);
290                        ourLog.debug(
291                                        "Automatically creating empty placeholder resource: {}",
292                                        newResource.getIdElement().getValue());
293
294                        if (myStorageSettings.isPopulateIdentifierInAutoCreatedPlaceholderReferenceTargets()) {
295                                tryToCopyIdentifierFromReferenceToTargetResource(theReference, missingResourceDef, newResource);
296                        }
297
298                        if (theIdToAssignToPlaceholder != null) {
299                                if (theTransactionDetails != null) {
300                                        String existingId = newResource.getIdElement().getValue();
301                                        theTransactionDetails.addRollbackUndoAction(() -> newResource.setId(existingId));
302                                }
303                                newResource.setId(resName + "/" + theIdToAssignToPlaceholder);
304                        }
305
306                        // Interceptor: STORAGE_PRE_AUTO_CREATE_PLACEHOLDER_REFERENCE
307                        IInterceptorBroadcaster interceptorBroadcaster =
308                                        CompositeInterceptorBroadcaster.newCompositeBroadcaster(myInterceptorBroadcaster, theRequest);
309                        if (interceptorBroadcaster.hasHooks(Pointcut.STORAGE_PRE_AUTO_CREATE_PLACEHOLDER_REFERENCE)) {
310                                AutoCreatePlaceholderReferenceTargetRequest request =
311                                                new AutoCreatePlaceholderReferenceTargetRequest(newResource);
312                                HookParams params = new HookParams()
313                                                .add(AutoCreatePlaceholderReferenceTargetRequest.class, request)
314                                                .add(RequestDetails.class, theRequest)
315                                                .addIfMatchesType(ServletRequestDetails.class, theRequest);
316                                AutoCreatePlaceholderReferenceTargetResponse response =
317                                                (AutoCreatePlaceholderReferenceTargetResponse) interceptorBroadcaster.callHooksAndReturnObject(
318                                                                Pointcut.STORAGE_PRE_AUTO_CREATE_PLACEHOLDER_REFERENCE, params);
319                                if (response != null) {
320                                        if (response.isDoNotCreateTarget()) {
321                                                return Optional.empty();
322                                        }
323                                }
324
325                                // Sanity check: Make sure that interceptors haven't changed the ID
326                                if (theIdToAssignToPlaceholder != null) {
327                                        Validate.isTrue(
328                                                        theIdToAssignToPlaceholder.equals(
329                                                                        newResource.getIdElement().getIdPart()),
330                                                        "Interceptors must not modify the ID of auto-created placeholder reference targets");
331                                } else {
332                                        Validate.isTrue(
333                                                        isBlank(newResource.getIdElement().getIdPart()),
334                                                        "Interceptors must not modify the ID of auto-created placeholder reference targets");
335                                }
336                        }
337
338                        if (theIdToAssignToPlaceholder != null) {
339                                valueOf = placeholderResourceDao
340                                                .update(newResource, null, true, false, theRequest, theTransactionDetails)
341                                                .getEntity();
342                        } else {
343                                valueOf = placeholderResourceDao.create(newResource, theRequest).getEntity();
344                        }
345
346                        IResourcePersistentId persistentId = valueOf.getPersistentId();
347                        persistentId = myIdHelperService.newPid(persistentId.getId());
348                        persistentId.setAssociatedResourceId(valueOf.getIdDt());
349                        theTransactionDetails.addResolvedResourceId(persistentId.getAssociatedResourceId(), persistentId);
350                        theTransactionDetails.addAutoCreatedPlaceholderResource(newResource.getIdElement());
351                }
352
353                return Optional.ofNullable(valueOf);
354        }
355
356        private <T extends IBaseResource> void tryToAddPlaceholderExtensionToResource(T newResource) {
357                if (newResource instanceof IBaseHasExtensions) {
358                        IBaseExtension<?, ?> extension = ((IBaseHasExtensions) newResource).addExtension();
359                        extension.setUrl(HapiExtensions.EXT_RESOURCE_PLACEHOLDER);
360                        extension.setValue(myContext.newPrimitiveBoolean(true));
361                }
362        }
363
364        private <T extends IBaseResource> void tryToCopyIdentifierFromReferenceToTargetResource(
365                        IBaseReference theSourceReference, RuntimeResourceDefinition theTargetResourceDef, T theTargetResource) {
366                //              boolean referenceHasIdentifier = theSourceReference.hasIdentifier();
367                CanonicalIdentifier referenceMatchUrlIdentifier = extractIdentifierFromUrl(
368                                theSourceReference.getReferenceElement().getValue());
369                CanonicalIdentifier referenceIdentifier = extractIdentifierReference(theSourceReference);
370
371                if (referenceIdentifier == null && referenceMatchUrlIdentifier != null) {
372                        addMatchUrlIdentifierToTargetResource(theTargetResourceDef, theTargetResource, referenceMatchUrlIdentifier);
373                } else if (referenceIdentifier != null && referenceMatchUrlIdentifier == null) {
374                        addSubjectIdentifierToTargetResource(theSourceReference, theTargetResourceDef, theTargetResource);
375                } else if (referenceIdentifier != null && referenceMatchUrlIdentifier != null) {
376                        if (referenceIdentifier.equals(referenceMatchUrlIdentifier)) {
377                                addSubjectIdentifierToTargetResource(theSourceReference, theTargetResourceDef, theTargetResource);
378                        } else {
379                                addSubjectIdentifierToTargetResource(theSourceReference, theTargetResourceDef, theTargetResource);
380                                addMatchUrlIdentifierToTargetResource(
381                                                theTargetResourceDef, theTargetResource, referenceMatchUrlIdentifier);
382                        }
383                }
384        }
385
386        private <T extends IBaseResource> void addSubjectIdentifierToTargetResource(
387                        IBaseReference theSourceReference, RuntimeResourceDefinition theTargetResourceDef, T theTargetResource) {
388                BaseRuntimeChildDefinition targetIdentifier = theTargetResourceDef.getChildByName("identifier");
389                if (targetIdentifier != null) {
390                        BaseRuntimeElementDefinition<?> identifierElement = targetIdentifier.getChildByName("identifier");
391                        String identifierElementName = identifierElement.getName();
392                        boolean targetHasIdentifierElement = identifierElementName.equals("Identifier");
393                        if (targetHasIdentifierElement) {
394
395                                BaseRuntimeElementCompositeDefinition<?> referenceElement = (BaseRuntimeElementCompositeDefinition<?>)
396                                                myContext.getElementDefinition(theSourceReference.getClass());
397                                BaseRuntimeChildDefinition referenceIdentifierChild = referenceElement.getChildByName("identifier");
398                                Optional<IBase> identifierOpt =
399                                                referenceIdentifierChild.getAccessor().getFirstValueOrNull(theSourceReference);
400                                identifierOpt.ifPresent(
401                                                theIBase -> targetIdentifier.getMutator().addValue(theTargetResource, theIBase));
402                        }
403                }
404        }
405
406        private <T extends IBaseResource> void addMatchUrlIdentifierToTargetResource(
407                        RuntimeResourceDefinition theTargetResourceDef,
408                        T theTargetResource,
409                        CanonicalIdentifier referenceMatchUrlIdentifier) {
410                BaseRuntimeChildDefinition identifierDefinition = theTargetResourceDef.getChildByName("identifier");
411                IBase identifierIBase = identifierDefinition
412                                .getChildByName("identifier")
413                                .newInstance(identifierDefinition.getInstanceConstructorArguments());
414                IBase systemIBase = TerserUtil.newElement(
415                                myContext, "uri", referenceMatchUrlIdentifier.getSystemElement().getValueAsString());
416                IBase valueIBase = TerserUtil.newElement(
417                                myContext,
418                                "string",
419                                referenceMatchUrlIdentifier.getValueElement().getValueAsString());
420                // Set system in the IBase Identifier
421
422                BaseRuntimeElementDefinition<?> elementDefinition = myContext.getElementDefinition(identifierIBase.getClass());
423
424                BaseRuntimeChildDefinition systemDefinition = elementDefinition.getChildByName("system");
425                systemDefinition.getMutator().setValue(identifierIBase, systemIBase);
426
427                BaseRuntimeChildDefinition valueDefinition = elementDefinition.getChildByName("value");
428                valueDefinition.getMutator().setValue(identifierIBase, valueIBase);
429
430                // Set Value in the IBase identifier
431                identifierDefinition.getMutator().addValue(theTargetResource, identifierIBase);
432        }
433
434        private CanonicalIdentifier extractIdentifierReference(IBaseReference theSourceReference) {
435                Optional<IBase> identifier =
436                                myContext.newFhirPath().evaluateFirst(theSourceReference, "identifier", IBase.class);
437                if (!identifier.isPresent()) {
438                        return null;
439                } else {
440                        CanonicalIdentifier canonicalIdentifier = new CanonicalIdentifier();
441                        Optional<IPrimitiveType> system =
442                                        myContext.newFhirPath().evaluateFirst(identifier.get(), "system", IPrimitiveType.class);
443                        Optional<IPrimitiveType> value =
444                                        myContext.newFhirPath().evaluateFirst(identifier.get(), "value", IPrimitiveType.class);
445
446                        system.ifPresent(theIPrimitiveType -> canonicalIdentifier.setSystem(theIPrimitiveType.getValueAsString()));
447                        value.ifPresent(theIPrimitiveType -> canonicalIdentifier.setValue(theIPrimitiveType.getValueAsString()));
448                        return canonicalIdentifier;
449                }
450        }
451
452        /**
453         * Extracts the first available identifier from the URL part
454         *
455         * @param theValue Part of the URL to extract identifiers from
456         * @return Returns the first available identifier in the canonical form or null if URL contains no identifier param
457         * @throws IllegalArgumentException IllegalArgumentException is thrown in case identifier parameter can not be split using <code>system|value</code> pattern.
458         */
459        protected CanonicalIdentifier extractIdentifierFromUrl(String theValue) {
460                int identifierIndex = theValue.indexOf("identifier=");
461                if (identifierIndex == -1) {
462                        return null;
463                }
464
465                List<NameValuePair> params =
466                                URLEncodedUtils.parse(theValue.substring(identifierIndex), StandardCharsets.UTF_8, '&', ';');
467                Optional<NameValuePair> idOptional =
468                                params.stream().filter(p -> p.getName().equals("identifier")).findFirst();
469                if (!idOptional.isPresent()) {
470                        return null;
471                }
472
473                NameValuePair id = idOptional.get();
474                String identifierString = id.getValue();
475                String[] split = identifierString.split("\\|");
476                if (split.length != 2) {
477                        throw new IllegalArgumentException(Msg.code(1097) + "Can't create a placeholder reference with identifier "
478                                        + theValue + ". It is not a valid identifier");
479                }
480
481                CanonicalIdentifier identifier = new CanonicalIdentifier();
482                identifier.setSystem(split[0]);
483                identifier.setValue(split[1]);
484                return identifier;
485        }
486
487        @Override
488        public void validateTypeOrThrowException(Class<? extends IBaseResource> theType) {
489                myDaoRegistry.getDaoOrThrow(theType);
490        }
491
492        private static class ResourceLookupPersistentIdWrapper<P extends IResourcePersistentId> implements IResourceLookup {
493                private final P myPersistentId;
494
495                public ResourceLookupPersistentIdWrapper(P thePersistentId) {
496                        myPersistentId = thePersistentId;
497                }
498
499                @Override
500                public String getResourceType() {
501                        return myPersistentId.getAssociatedResourceId().getResourceType();
502                }
503
504                @Override
505                public String getFhirId() {
506                        return myPersistentId.getAssociatedResourceId().getIdPart();
507                }
508
509                @Override
510                public Date getDeleted() {
511                        return null;
512                }
513
514                @Override
515                public P getPersistentId() {
516                        return myPersistentId;
517                }
518
519                @Override
520                public PartitionablePartitionId getPartitionId() {
521                        return new PartitionablePartitionId(myPersistentId.getPartitionId(), null);
522                }
523        }
524}