001/*-
002 * #%L
003 * HAPI FHIR Storage api
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.api.svc;
021
022import ca.uhn.fhir.context.FhirContext;
023import ca.uhn.fhir.i18n.Msg;
024import ca.uhn.fhir.interceptor.model.RequestPartitionId;
025import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
026import ca.uhn.fhir.jpa.api.model.PersistentIdToForcedIdMap;
027import ca.uhn.fhir.jpa.model.cross.IResourceLookup;
028import ca.uhn.fhir.rest.api.server.storage.IResourcePersistentId;
029import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
030import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
031import jakarta.annotation.Nonnull;
032import jakarta.annotation.Nullable;
033import org.hl7.fhir.instance.model.api.IAnyResource;
034import org.hl7.fhir.instance.model.api.IBaseResource;
035import org.hl7.fhir.instance.model.api.IIdType;
036
037import java.util.Collection;
038import java.util.Date;
039import java.util.List;
040import java.util.Map;
041import java.util.Optional;
042import java.util.Set;
043import java.util.stream.Collectors;
044
045/**
046 * This interface is used to translate between {@link IResourcePersistentId}
047 * and actual resource IDs.
048 */
049public interface IIdHelperService<T extends IResourcePersistentId<?>> {
050
051        /**
052         * Given a persistent ID, returns the associated resource ID
053         */
054        @Nonnull
055        IIdType translatePidIdToForcedId(FhirContext theCtx, String theResourceType, T theId);
056
057        /**
058         * @param theResourceType Note that it is inefficient to call this method
059         *                        with a null resource type, so this should be avoided
060         *                        unless strictly necessary.
061         * @throws ResourceNotFoundException If the ID can not be found
062         */
063        @Nonnull
064        IResourceLookup<T> resolveResourceIdentity(
065                        @Nonnull RequestPartitionId theRequestPartitionId,
066                        @Nullable String theResourceType,
067                        @Nonnull String theResourceId,
068                        @Nonnull ResolveIdentityMode theMode)
069                        throws ResourceNotFoundException;
070
071        /**
072         * @param theResourceType Note that it is inefficient to call this method
073         *                        with a null resource type, so this should be avoided
074         *                        unless strictly necessary.
075         * @throws ResourceNotFoundException If the ID can not be found
076         */
077        @Nonnull
078        default T resolveResourceIdentityPid(
079                        @Nonnull RequestPartitionId theRequestPartitionId,
080                        @Nullable String theResourceType,
081                        @Nonnull String theResourceId,
082                        @Nonnull ResolveIdentityMode theMode)
083                        throws ResourceNotFoundException {
084                return resolveResourceIdentity(theRequestPartitionId, theResourceType, theResourceId, theMode)
085                                .getPersistentId();
086        }
087
088        /**
089         * Given a collection of resource IDs, resolve the resource identities, including the persistent ID,
090         * deleted status, resource type, etc.
091         *
092         * @since 8.0.0
093         */
094        @Nonnull
095        Map<IIdType, IResourceLookup<T>> resolveResourceIdentities(
096                        @Nonnull RequestPartitionId theRequestPartitionId, Collection<IIdType> theIds, ResolveIdentityMode theMode);
097
098        /**
099         * Given a collection of resource IDs, resolve the resource persistent IDs.
100         *
101         * @since 8.0.0
102         */
103        default List<T> resolveResourcePids(
104                        RequestPartitionId theRequestPartitionId,
105                        List<IIdType> theTargetIds,
106                        ResolveIdentityMode theResolveIdentityMode) {
107                return resolveResourceIdentities(theRequestPartitionId, theTargetIds, theResolveIdentityMode).values().stream()
108                                .map(IResourceLookup::getPersistentId)
109                                .collect(Collectors.toList());
110        }
111
112        /**
113         * Returns true if the given resource ID should be stored in a forced ID. Under default config
114         * (meaning client ID strategy is {@link JpaStorageSettings.ClientIdStrategyEnum#ALPHANUMERIC})
115         * this will return true if the ID has any non-digit characters.
116         * <p>
117         * In {@link JpaStorageSettings.ClientIdStrategyEnum#ANY} mode it will always return true.
118         */
119        boolean idRequiresForcedId(String theId);
120
121        /**
122         * Value will be an empty Optional if the PID doesn't exist, or
123         * a typed resource ID if so (Patient/ABC).
124         */
125        Optional<String> translatePidIdToForcedIdWithCache(T theResourcePersistentId);
126
127        /**
128         * Values in the returned map are typed resource IDs (Patient/ABC)
129         */
130        PersistentIdToForcedIdMap<T> translatePidsToForcedIds(Set<T> theResourceIds);
131
132        /**
133         * This method can be called to pre-emptively add entries to the ID cache. It should
134         * be called by DAO methods if they are creating or changing the deleted status
135         * of a resource. This method returns immediately, but the data is not
136         * added to the internal caches until the current DB transaction is successfully
137         * committed, and nothing is added if the transaction rolls back.
138         */
139        void addResolvedPidToFhirIdAfterCommit(
140                        @Nonnull T theResourcePersistentId,
141                        @Nonnull RequestPartitionId theRequestPartitionId,
142                        @Nonnull String theResourceType,
143                        @Nonnull String theFhirId,
144                        @Nullable Date theDeletedAt);
145
146        @Nullable
147        T getPidOrNull(RequestPartitionId theRequestPartitionId, IBaseResource theResource);
148
149        @Nonnull
150        default T getPidOrThrowException(RequestPartitionId theRequestPartitionId, IIdType theId) {
151                IResourceLookup<T> identity = resolveResourceIdentity(
152                                theRequestPartitionId,
153                                theId.getResourceType(),
154                                theId.getIdPart(),
155                                ResolveIdentityMode.includeDeleted().cacheOk());
156                if (identity == null) {
157                        throw new InvalidRequestException(Msg.code(2295) + "Invalid ID was provided: [" + theId.getIdPart() + "]");
158                }
159                return identity.getPersistentId();
160        }
161
162        @Nonnull
163        T getPidOrThrowException(@Nonnull IAnyResource theResource);
164
165        IIdType resourceIdFromPidOrThrowException(T thePid, String theResourceType);
166
167        /**
168         * Given a set of PIDs, return a set of public FHIR Resource IDs.
169         * This function will resolve a forced ID if it resolves, and if it fails to resolve to a forced it, will just return the pid
170         * Example:
171         * Let's say we have Patient/1(pid == 1), Patient/pat1 (pid == 2), Patient/3 (pid == 3), their pids would resolve as follows:
172         * <p>
173         * [1,2,3] -> ["1","pat1","3"]
174         *
175         * @param thePids The Set of pids you would like to resolve to external FHIR Resource IDs.
176         * @return A Set of strings representing the FHIR IDs of the pids.
177         */
178        Set<String> translatePidsToFhirResourceIds(Set<T> thePids);
179
180        T newPid(Object thePid);
181
182        T newPidFromStringIdAndResourceName(String thePid, String theResourceType);
183}