3693 Search by id is returning a deleted resource with a client-generated id (#3694)

- Added functionality to optionally filter out deleted resources when resolving forced ids to persistent ids
- Bumps version

Co-authored-by: nathaniel.doef <nathaniel.doef@smilecdr.com>
Co-authored-by: Ken Stevens <ken@smilecdr.com>
This commit is contained in:
Nathan Doef 2022-07-05 12:37:25 -04:00 committed by GitHub
parent 3a28920ea7
commit b68ccb373a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
73 changed files with 671 additions and 151 deletions

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -3,14 +3,14 @@
<modelVersion>4.0.0</modelVersion>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-bom</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
<packaging>pom</packaging>
<name>HAPI FHIR BOM</name>
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-cli</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
<relativePath>../../hapi-deployable-pom</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -0,0 +1,7 @@
---
type: fix
issue: 3693
jira: SMILE-4185
title: "Previously, deleted resources with client generated ids were being included in the
bundle total when searching by _id. This has been corrected by adding functionality to optionally filter
out deleted resources when resolving forced ids to persistent ids."

View File

@ -11,7 +11,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -1,12 +1,13 @@
package ca.uhn.fhir.jpa.dao.data;
import ca.uhn.fhir.jpa.dao.data.custom.IForcedIdQueries;
import ca.uhn.fhir.jpa.model.entity.ForcedId;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
@ -29,10 +30,10 @@ import java.util.Optional;
* limitations under the License.
* #L%
*/
@Repository
public interface IForcedIdDao extends JpaRepository<ForcedId, Long>, IHapiFhirJpaRepository, IForcedIdQueries {
public interface IForcedIdDao extends JpaRepository<ForcedId, Long>, IHapiFhirJpaRepository {
@Query("SELECT f FROM ForcedId f WHERE myResourcePid IN (:resource_pids)")
@Query("SELECT f FROM ForcedId f WHERE f.myResourcePid IN (:resource_pids)")
List<ForcedId> findAllByResourcePid(@Param("resource_pids") List<Long> theResourcePids);
@Query("SELECT f FROM ForcedId f WHERE f.myResourcePid = :resource_pid")
@ -41,54 +42,4 @@ public interface IForcedIdDao extends JpaRepository<ForcedId, Long>, IHapiFhirJp
@Modifying
@Query("DELETE FROM ForcedId t WHERE t.myId = :pid")
void deleteByPid(@Param("pid") Long theId);
/**
* This method returns a Collection where each row is an element in the collection. Each element in the collection
* is an object array, where the order matters (the array represents columns returned by the query). Be careful if you change this query in any way.
*/
@Query("" +
"SELECT " +
" f.myResourceType, f.myResourcePid, f.myForcedId, t.myDeleted " +
"FROM ForcedId f " +
"JOIN ResourceTable t ON t.myId = f.myResourcePid " +
"WHERE f.myResourceType = :resource_type AND f.myForcedId IN ( :forced_id )")
Collection<Object[]> findAndResolveByForcedIdWithNoType(@Param("resource_type") String theResourceType, @Param("forced_id") Collection<String> theForcedIds);
/**
* This method returns a Collection where each row is an element in the collection. Each element in the collection
* is an object array, where the order matters (the array represents columns returned by the query). Be careful if you change this query in any way.
*/
@Query("" +
"SELECT " +
" f.myResourceType, f.myResourcePid, f.myForcedId, t.myDeleted " +
"FROM ForcedId f " +
"JOIN ResourceTable t ON t.myId = f.myResourcePid " +
"WHERE f.myResourceType = :resource_type AND f.myForcedId IN ( :forced_id ) AND f.myPartitionIdValue IN :partition_id")
Collection<Object[]> findAndResolveByForcedIdWithNoTypeInPartition(@Param("resource_type") String theResourceType, @Param("forced_id") Collection<String> theForcedIds, @Param("partition_id") Collection<Integer> thePartitionId);
/**
* This method returns a Collection where each row is an element in the collection. Each element in the collection
* is an object array, where the order matters (the array represents columns returned by the query). Be careful if you change this query in any way.
*/
@Query("" +
"SELECT " +
" f.myResourceType, f.myResourcePid, f.myForcedId, t.myDeleted " +
"FROM ForcedId f " +
"JOIN ResourceTable t ON t.myId = f.myResourcePid " +
"WHERE f.myResourceType = :resource_type AND f.myForcedId IN ( :forced_id ) AND f.myPartitionIdValue IS NULL")
Collection<Object[]> findAndResolveByForcedIdWithNoTypeInPartitionNull(@Param("resource_type") String theResourceType, @Param("forced_id") Collection<String> theForcedIds);
/**
* This method returns a Collection where each row is an element in the collection. Each element in the collection
* is an object array, where the order matters (the array represents columns returned by the query). Be careful if you change this query in any way.
*/
@Query("" +
"SELECT " +
" f.myResourceType, f.myResourcePid, f.myForcedId, t.myDeleted " +
"FROM ForcedId f " +
"JOIN ResourceTable t ON t.myId = f.myResourcePid " +
"WHERE f.myResourceType = :resource_type AND f.myForcedId IN ( :forced_id ) AND (f.myPartitionIdValue IS NULL OR f.myPartitionIdValue IN :partition_id)")
Collection<Object[]> findAndResolveByForcedIdWithNoTypeInPartitionIdOrNullPartitionId(@Param("resource_type") String theNextResourceType, @Param("forced_id") Collection<String> theNextIds, @Param("forced_id") List<Integer> thePartitionIdsWithoutDefault);
}

View File

@ -0,0 +1,123 @@
package ca.uhn.fhir.jpa.dao.data.custom;
import org.springframework.stereotype.Component;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.util.Collection;
import java.util.List;
@Component
// Don't change the name of this class. Spring Data requires the name to match.
// See https://stackoverflow.com/questions/11880924/how-to-add-custom-method-to-spring-data-jpa
public class IForcedIdDaoImpl implements IForcedIdQueries {
@PersistenceContext
private EntityManager myEntityManager;
/**
* This method returns a Collection where each row is an element in the collection. Each element in the collection
* is an object array, where the order matters (the array represents columns returned by the query).
* Deleted resources are not filtered.
*/
public Collection<Object[]> findAndResolveByForcedIdWithNoTypeIncludeDeleted(String theResourceType, Collection<String> theForcedIds) {
return findAndResolveByForcedIdWithNoType(theResourceType, theForcedIds, false);
}
/**
* This method returns a Collection where each row is an element in the collection. Each element in the collection
* is an object array, where the order matters (the array represents columns returned by the query).
* Deleted resources are optionally filtered. Be careful if you change this query in any way.
*/
public Collection<Object[]> findAndResolveByForcedIdWithNoType(String theResourceType, Collection<String> theForcedIds, boolean theExcludeDeleted) {
String query = "" +
"SELECT " +
" f.myResourceType, f.myResourcePid, f.myForcedId, t.myDeleted " +
"FROM ForcedId f " +
"JOIN ResourceTable t ON t.myId = f.myResourcePid " +
"WHERE f.myResourceType = :resource_type AND f.myForcedId IN ( :forced_id )";
if (theExcludeDeleted) {
query += " AND t.myDeleted IS NULL";
}
return myEntityManager.createQuery(query)
.setParameter("resource_type", theResourceType)
.setParameter("forced_id", theForcedIds)
.getResultList();
}
/**
* This method returns a Collection where each row is an element in the collection. Each element in the collection
* is an object array, where the order matters (the array represents columns returned by the query).
* Deleted resources are optionally filtered. Be careful if you change this query in any way.
*/
public Collection<Object[]> findAndResolveByForcedIdWithNoTypeInPartition(String theResourceType, Collection<String> theForcedIds, Collection<Integer> thePartitionId, boolean theExcludeDeleted) {
String query = "" +
"SELECT " +
" f.myResourceType, f.myResourcePid, f.myForcedId, t.myDeleted " +
"FROM ForcedId f " +
"JOIN ResourceTable t ON t.myId = f.myResourcePid " +
"WHERE f.myResourceType = :resource_type AND f.myForcedId IN ( :forced_id ) AND f.myPartitionIdValue IN ( :partition_id )";
if (theExcludeDeleted) {
query += " AND t.myDeleted IS NULL";
}
return myEntityManager.createQuery(query)
.setParameter("resource_type", theResourceType)
.setParameter("forced_id", theForcedIds)
.setParameter("partition_id", thePartitionId)
.getResultList();
}
/**
* This method returns a Collection where each row is an element in the collection. Each element in the collection
* is an object array, where the order matters (the array represents columns returned by the query).
* Deleted resources are optionally filtered. Be careful if you change this query in any way.
*/
public Collection<Object[]> findAndResolveByForcedIdWithNoTypeInPartitionNull(String theResourceType, Collection<String> theForcedIds, boolean theExcludeDeleted) {
String query = "" +
"SELECT " +
" f.myResourceType, f.myResourcePid, f.myForcedId, t.myDeleted " +
"FROM ForcedId f " +
"JOIN ResourceTable t ON t.myId = f.myResourcePid " +
"WHERE f.myResourceType = :resource_type AND f.myForcedId IN ( :forced_id ) AND f.myPartitionIdValue IS NULL";
if (theExcludeDeleted) {
query += " AND t.myDeleted IS NULL";
}
return myEntityManager.createQuery(query)
.setParameter("resource_type", theResourceType)
.setParameter("forced_id", theForcedIds)
.getResultList();
}
/**
* This method returns a Collection where each row is an element in the collection. Each element in the collection
* is an object array, where the order matters (the array represents columns returned by the query).
* Deleted resources are optionally filtered. Be careful if you change this query in any way.
*/
public Collection<Object[]> findAndResolveByForcedIdWithNoTypeInPartitionIdOrNullPartitionId(String theResourceType, Collection<String> theForcedIds, List<Integer> thePartitionIdsWithoutDefault, boolean theExcludeDeleted) {
String query = "" +
"SELECT " +
" f.myResourceType, f.myResourcePid, f.myForcedId, t.myDeleted " +
"FROM ForcedId f " +
"JOIN ResourceTable t ON t.myId = f.myResourcePid " +
"WHERE f.myResourceType = :resource_type AND f.myForcedId IN ( :forced_id ) AND (f.myPartitionIdValue IS NULL OR f.myPartitionIdValue IN ( :partition_id ))";
if (theExcludeDeleted) {
query += " AND t.myDeleted IS NULL";
}
return myEntityManager.createQuery(query)
.setParameter("resource_type", theResourceType)
.setParameter("forced_id", theForcedIds)
.setParameter("partition_id", thePartitionIdsWithoutDefault)
.getResultList();
}
}

View File

@ -0,0 +1,63 @@
package ca.uhn.fhir.jpa.dao.data.custom;
import java.util.Collection;
import java.util.List;
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2022 Smile CDR, Inc.
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
public interface IForcedIdQueries {
/**
* This method returns a Collection where each row is an element in the collection. Each element in the collection
* is an object array, where the order matters (the array represents columns returned by the query).
* Deleted resources should not be filtered.
*/
Collection<Object[]> findAndResolveByForcedIdWithNoTypeIncludeDeleted(String theResourceType, Collection<String> theForcedIds);
/**
* This method returns a Collection where each row is an element in the collection. Each element in the collection
* is an object array, where the order matters (the array represents columns returned by the query).
* Deleted resources are optionally filtered.
*/
Collection<Object[]> findAndResolveByForcedIdWithNoType(String theResourceType, Collection<String> theForcedIds, boolean theExcludeDeleted);
/**
* This method returns a Collection where each row is an element in the collection. Each element in the collection
* is an object array, where the order matters (the array represents columns returned by the query).
* Deleted resources are optionally filtered.
*/
Collection<Object[]> findAndResolveByForcedIdWithNoTypeInPartition(String theResourceType, Collection<String> theForcedIds, Collection<Integer> thePartitionId, boolean theExcludeDeleted);
/**
* This method returns a Collection where each row is an element in the collection. Each element in the collection
* is an object array, where the order matters (the array represents columns returned by the query).
* Deleted resources are optionally filtered.
*/
Collection<Object[]> findAndResolveByForcedIdWithNoTypeInPartitionNull(String theResourceType, Collection<String> theForcedIds, boolean theExcludeDeleted);
/**
* This method returns a Collection where each row is an element in the collection. Each element in the collection
* is an object array, where the order matters (the array represents columns returned by the query).
* Deleted resources are optionally filtered.
*/
Collection<Object[]> findAndResolveByForcedIdWithNoTypeInPartitionIdOrNullPartitionId(String theNextResourceType, Collection<String> theNextIds, List<Integer> thePartitionIdsWithoutDefault, boolean theExcludeDeleted);
}

View File

@ -31,7 +31,6 @@ import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.cross.IResourceLookup;
import ca.uhn.fhir.jpa.model.cross.ResourceLookup;
import ca.uhn.fhir.jpa.model.entity.ForcedId;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.search.builder.SearchBuilder;
import ca.uhn.fhir.jpa.util.MemoryCacheService;
import ca.uhn.fhir.jpa.util.QueryChunker;
@ -128,12 +127,26 @@ public class IdHelperService implements IIdHelperService {
@Override
@Nonnull
public IResourceLookup resolveResourceIdentity(@Nonnull RequestPartitionId theRequestPartitionId, String theResourceType, String theResourceId) throws ResourceNotFoundException {
return resolveResourceIdentity(theRequestPartitionId, theResourceType, theResourceId, false);
}
/**
* Given a forced ID, convert it to its Long value. Since you are allowed to use string IDs for resources, we need to
* convert those to the underlying Long values that are stored, for lookup and comparison purposes.
* Optionally filters out deleted resources.
*
* @throws ResourceNotFoundException If the ID can not be found
*/
@Override
@Nonnull
public IResourceLookup resolveResourceIdentity(@Nonnull RequestPartitionId theRequestPartitionId, String theResourceType, String theResourceId, boolean theExcludeDeleted) throws ResourceNotFoundException {
assert myDontCheckActiveTransactionForUnitTest || TransactionSynchronizationManager.isSynchronizationActive();
assert theRequestPartitionId != null;
IdDt id = new IdDt(theResourceType, theResourceId);
Map<String, List<IResourceLookup>> matches = translateForcedIdToPids(theRequestPartitionId,
Collections.singletonList(id));
Collections.singletonList(id),
theExcludeDeleted);
// We only pass 1 input in so only 0..1 will come back
if (matches.isEmpty() || !matches.containsKey(theResourceId)) {
@ -155,14 +168,27 @@ public class IdHelperService implements IIdHelperService {
/**
* Returns a mapping of Id -> ResourcePersistentId.
* If any resource is not found, it will throw ResourceNotFound exception
* (and no map will be returned)
* If any resource is not found, it will throw ResourceNotFound exception (and no map will be returned)
*/
@Override
@Nonnull
public Map<String, ResourcePersistentId> resolveResourcePersistentIds(@Nonnull RequestPartitionId theRequestPartitionId,
String theResourceType,
List<String> theIds) {
return resolveResourcePersistentIds(theRequestPartitionId, theResourceType, theIds, false);
}
/**
* Returns a mapping of Id -> ResourcePersistentId.
* If any resource is not found, it will throw ResourceNotFound exception (and no map will be returned)
* Optionally filters out deleted resources.
*/
@Override
@Nonnull
public Map<String, ResourcePersistentId> resolveResourcePersistentIds(@Nonnull RequestPartitionId theRequestPartitionId,
String theResourceType,
List<String> theIds,
boolean theExcludeDeleted) {
assert myDontCheckActiveTransactionForUnitTest || TransactionSynchronizationManager.isSynchronizationActive();
Validate.notNull(theIds, "theIds cannot be null");
Validate.isTrue(!theIds.isEmpty(), "theIds must not be empty");
@ -179,7 +205,7 @@ public class IdHelperService implements IIdHelperService {
// is a forced id
// we must resolve!
if (myDaoConfig.isDeleteEnabled()) {
retVal = new ResourcePersistentId(resolveResourceIdentity(theRequestPartitionId, theResourceType, id).getResourceId());
retVal = new ResourcePersistentId(resolveResourceIdentity(theRequestPartitionId, theResourceType, id, theExcludeDeleted).getResourceId());
retVals.put(id, retVal);
} else {
// fetch from cache... adding to cache if not available
@ -209,11 +235,22 @@ public class IdHelperService implements IIdHelperService {
@Override
@Nonnull
public ResourcePersistentId resolveResourcePersistentIds(@Nonnull RequestPartitionId theRequestPartitionId, String theResourceType, String theId) {
return resolveResourcePersistentIds(theRequestPartitionId, theResourceType, theId, false);
}
/**
* Given a resource type and ID, determines the internal persistent ID for the resource.
* Optionally filters out deleted resources.
*
* @throws ResourceNotFoundException If the ID can not be found
*/
public ResourcePersistentId resolveResourcePersistentIds(@Nonnull RequestPartitionId theRequestPartitionId, String theResourceType, String theId, boolean theExcludeDeleted){
Validate.notNull(theId, "theId must not be null");
Map<String, ResourcePersistentId> retVal = resolveResourcePersistentIds(theRequestPartitionId,
theResourceType,
Collections.singletonList(theId));
Collections.singletonList(theId),
theExcludeDeleted);
return retVal.get(theId); // should be only one
}
@ -406,7 +443,7 @@ public class IdHelperService implements IIdHelperService {
return typeToIds;
}
private Map<String, List<IResourceLookup>> translateForcedIdToPids(@Nonnull RequestPartitionId theRequestPartitionId, Collection<IIdType> theId) {
private Map<String, List<IResourceLookup>> translateForcedIdToPids(@Nonnull RequestPartitionId theRequestPartitionId, Collection<IIdType> theId, boolean theExcludeDeleted) {
assert theRequestPartitionId != null;
theId.forEach(id -> Validate.isTrue(id.hasIdPart()));
@ -455,14 +492,14 @@ public class IdHelperService implements IIdHelperService {
assert isNotBlank(nextResourceType);
if (requestPartitionId.isAllPartitions()) {
views = myForcedIdDao.findAndResolveByForcedIdWithNoType(nextResourceType, nextIds);
views = myForcedIdDao.findAndResolveByForcedIdWithNoType(nextResourceType, nextIds, theExcludeDeleted);
} else {
if (requestPartitionId.isDefaultPartition()) {
views = myForcedIdDao.findAndResolveByForcedIdWithNoTypeInPartitionNull(nextResourceType, nextIds);
views = myForcedIdDao.findAndResolveByForcedIdWithNoTypeInPartitionNull(nextResourceType, nextIds, theExcludeDeleted);
} else if (requestPartitionId.hasDefaultPartitionId()) {
views = myForcedIdDao.findAndResolveByForcedIdWithNoTypeInPartitionIdOrNullPartitionId(nextResourceType, nextIds, requestPartitionId.getPartitionIdsWithoutDefault());
views = myForcedIdDao.findAndResolveByForcedIdWithNoTypeInPartitionIdOrNullPartitionId(nextResourceType, nextIds, requestPartitionId.getPartitionIdsWithoutDefault(), theExcludeDeleted);
} else {
views = myForcedIdDao.findAndResolveByForcedIdWithNoTypeInPartition(nextResourceType, nextIds, requestPartitionId.getPartitionIds());
views = myForcedIdDao.findAndResolveByForcedIdWithNoTypeInPartition(nextResourceType, nextIds, requestPartitionId.getPartitionIds(), theExcludeDeleted);
}
}
@ -471,6 +508,7 @@ public class IdHelperService implements IIdHelperService {
Long resourcePid = (Long) next[1];
String forcedId = (String) next[2];
Date deletedAt = (Date) next[3];
ResourceLookup lookup = new ResourceLookup(resourceType, resourcePid, deletedAt);
if (!retVal.containsKey(forcedId)) {
retVal.put(forcedId, new ArrayList<>());

View File

@ -22,7 +22,6 @@ package ca.uhn.fhir.jpa.search.builder.predicate;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.api.svc.IIdHelperService;
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
import ca.uhn.fhir.jpa.dao.predicate.SearchFilterParser;
import ca.uhn.fhir.jpa.search.builder.QueryStack;
import ca.uhn.fhir.jpa.search.builder.sql.SearchQueryBuilder;
@ -83,7 +82,8 @@ public class ResourceIdPredicateBuilder extends BasePredicateBuilder {
}
haveValue = true;
try {
ResourcePersistentId pid = myIdHelperService.resolveResourcePersistentIds(theRequestPartitionId, theResourceName, valueAsId.getIdPart());
boolean excludeDeleted = true;
ResourcePersistentId pid = myIdHelperService.resolveResourcePersistentIds(theRequestPartitionId, theResourceName, valueAsId.getIdPart(), excludeDeleted);
orPids.add(pid);
} catch (ResourceNotFoundException e) {
// This is not an error in a search, it just results in no matches

View File

@ -7,7 +7,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -2,9 +2,9 @@ package ca.uhn.fhir.jpa.dao.index;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.api.svc.IIdHelperService;
import ca.uhn.fhir.jpa.dao.data.IForcedIdDao;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.cross.IResourceLookup;
import ca.uhn.fhir.jpa.util.MemoryCacheService;
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
import org.hl7.fhir.r4.model.Patient;
@ -18,12 +18,21 @@ import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasSize;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
public class IdHelperServiceTest {
@ -88,10 +97,10 @@ public class IdHelperServiceTest {
};
// when
Mockito.when(myDaoConfig.isDeleteEnabled())
when(myDaoConfig.isDeleteEnabled())
.thenReturn(true);
Mockito.when(myForcedIdDao.findAndResolveByForcedIdWithNoType(Mockito.anyString(),
Mockito.anyList()))
when(myForcedIdDao.findAndResolveByForcedIdWithNoType(Mockito.anyString(),
Mockito.anyList(), Mockito.anyBoolean()))
.thenReturn(Collections.singletonList(redView))
.thenReturn(Collections.singletonList(blueView));
@ -119,9 +128,9 @@ public class IdHelperServiceTest {
ResourcePersistentId blue = new ResourcePersistentId("Patient", new Long(456l));
// we will pretend the lookup value is in the cache
Mockito.when(myMemoryCacheService.getThenPutAfterCommit(Mockito.any(MemoryCacheService.CacheEnum.class),
when(myMemoryCacheService.getThenPutAfterCommit(any(MemoryCacheService.CacheEnum.class),
Mockito.anyString(),
Mockito.any(Function.class)))
any(Function.class)))
.thenReturn(red)
.thenReturn(blue);
@ -139,4 +148,58 @@ public class IdHelperServiceTest {
Assertions.assertEquals(red, map.get("RED"));
Assertions.assertEquals(blue, map.get("BLUE"));
}
@Test
public void testResolveResourceIdentity_defaultFunctionality(){
RequestPartitionId partitionId = RequestPartitionId.fromPartitionIdAndName(1, "partition");
String resourceType = "Patient";
String resourceForcedId = "AAA";
Object[] forcedIdView = new Object[4];
forcedIdView[0] = resourceType;
forcedIdView[1] = 1L;
forcedIdView[2] = resourceForcedId;
forcedIdView[3] = null;
Collection<Object[]> testForcedIdViews = new ArrayList<>();
testForcedIdViews.add(forcedIdView);
when(myForcedIdDao.findAndResolveByForcedIdWithNoTypeInPartition(any(), any(), any(), anyBoolean())).thenReturn(testForcedIdViews);
IResourceLookup result = myHelperService.resolveResourceIdentity(partitionId, resourceType, resourceForcedId);
assertEquals(forcedIdView[0], result.getResourceType());
assertEquals(forcedIdView[1], result.getResourceId());
assertEquals(forcedIdView[3], result.getDeleted());
}
@Test
public void testResolveResourcePersistentIds_mapDefaultFunctionality(){
RequestPartitionId partitionId = RequestPartitionId.fromPartitionIdAndName(1, "partition");
String resourceType = "Patient";
List<String> ids = Arrays.asList("A", "B", "C");
ResourcePersistentId resourcePersistentId1 = new ResourcePersistentId("TEST1");
ResourcePersistentId resourcePersistentId2 = new ResourcePersistentId("TEST2");
ResourcePersistentId resourcePersistentId3 = new ResourcePersistentId("TEST3");
when(myMemoryCacheService.getThenPutAfterCommit(any(), any(), any()))
.thenReturn(resourcePersistentId1)
.thenReturn(resourcePersistentId2)
.thenReturn(resourcePersistentId3);
Map<String, ResourcePersistentId> result = myHelperService.resolveResourcePersistentIds(partitionId, resourceType, ids);
assertThat(result.keySet(), hasSize(3));
assertEquals("TEST1", result.get("A").getId());
assertEquals("TEST2", result.get("B").getId());
assertEquals("TEST3", result.get("C").getId());
}
@Test
public void testResolveResourcePersistentIds_resourcePidDefaultFunctionality(){
RequestPartitionId partitionId = RequestPartitionId.fromPartitionIdAndName(1, "partition");
String resourceType = "Patient";
String id = "A";
ResourcePersistentId resourcePersistentId1 = new ResourcePersistentId(id);
when(myMemoryCacheService.getThenPutAfterCommit(any(), any(), any())).thenReturn(resourcePersistentId1);
ResourcePersistentId result = myHelperService.resolveResourcePersistentIds(partitionId, resourceType, id);
assertEquals(id, result.getId());
}
}

View File

@ -877,8 +877,8 @@ public class FhirResourceDaoR4SearchOptimizedTest extends BaseJpaR4Test {
assertEquals(1, StringUtils.countMatches(selectQuery.toLowerCase(), "t0.res_deleted_at is null"), selectQuery);
}
// Delete the resource - The searches should generate similar SQL now, but
// not actually return the result
// Delete the resource - There should only be one search performed because deleted resources will
// be filtered out in the query that resolves forced ids to persistent ids
myObservationDao.delete(new IdType("Observation/A"));
myObservationDao.delete(new IdType("Observation/" + obs2id));
@ -892,14 +892,10 @@ public class FhirResourceDaoR4SearchOptimizedTest extends BaseJpaR4Test {
assertEquals(0, outcome.getResources(0, 999).size());
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertEquals(1, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size());
String selectQuery = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false);
assertEquals(1, StringUtils.countMatches(selectQuery.toLowerCase(), "forcedid0_.resource_type='observation'"), selectQuery);
assertEquals(1, StringUtils.countMatches(selectQuery.toLowerCase(), "forcedid0_.forced_id in ('a')"), selectQuery);
selectQuery = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(1).getSql(true, false);
assertEquals(1, StringUtils.countMatches(selectQuery.toLowerCase(), "select t0.res_id from hfj_resource t0"), selectQuery);
assertEquals(0, StringUtils.countMatches(selectQuery.toLowerCase(), "t0.res_type = 'observation'"), selectQuery);
assertEquals(0, StringUtils.countMatches(selectQuery.toLowerCase(), "t0.res_deleted_at is null"), selectQuery);
}
// Search by ID where at least one ID is a numeric ID

View File

@ -36,6 +36,8 @@ import org.springframework.beans.factory.annotation.Autowired;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import static org.hamcrest.MatcherAssert.assertThat;
@ -151,6 +153,187 @@ public class MultitenantServerR4Test extends BaseMultitenantResourceProviderR4Te
}
}
@Test
public void testFindAndResolveByForcedIdWithNoType() {
// Create patients
String patientId = "AAA";
IIdType idA = createPatient(withId(patientId));
// Search and include deleted
runInTransaction(() -> {
Collection<Object[]> forcedIds = myForcedIdDao.findAndResolveByForcedIdWithNoTypeIncludeDeleted(
"Patient", Arrays.asList(patientId)
);
assertContainsSingleForcedId(forcedIds, patientId);
});
// Search and filter deleted
runInTransaction(() -> {
Collection<Object[]> forcedIds = myForcedIdDao.findAndResolveByForcedIdWithNoType(
"Patient", Arrays.asList(patientId), true
);
assertContainsSingleForcedId(forcedIds, patientId);
});
// Delete resource
deletePatient(JpaConstants.DEFAULT_PARTITION_NAME, idA);
// Search and include deleted
runInTransaction(() -> {
Collection<Object[]> forcedIds = myForcedIdDao.findAndResolveByForcedIdWithNoTypeIncludeDeleted(
"Patient", Arrays.asList(patientId)
);
assertContainsSingleForcedId(forcedIds, patientId);
});
// Search and filter deleted
runInTransaction(() -> {
Collection<Object[]> forcedIds = myForcedIdDao.findAndResolveByForcedIdWithNoType(
"Patient", Arrays.asList(patientId), true
);
assertThat(forcedIds, hasSize(0));
});
}
@Test
public void findAndResolveByForcedIdWithNoTypeInPartitionNull() {
// Create patients
String patientId = "AAA";
IIdType idA = createPatient(withId(patientId));
// Search and include deleted
runInTransaction(() -> {
Collection<Object[]> forcedIds = myForcedIdDao.findAndResolveByForcedIdWithNoTypeInPartitionNull(
"Patient", Arrays.asList(patientId), false
);
assertContainsSingleForcedId(forcedIds, patientId);
});
// Search and filter deleted
runInTransaction(() -> {
Collection<Object[]> forcedIds = myForcedIdDao.findAndResolveByForcedIdWithNoTypeInPartitionNull(
"Patient", Arrays.asList(patientId), true
);
assertContainsSingleForcedId(forcedIds, patientId);
});
// Delete resource
deletePatient(JpaConstants.DEFAULT_PARTITION_NAME, idA);
// Search and include deleted
runInTransaction(() -> {
Collection<Object[]> forcedIds = myForcedIdDao.findAndResolveByForcedIdWithNoTypeInPartitionNull(
"Patient", Arrays.asList(patientId), false
);
assertContainsSingleForcedId(forcedIds, patientId);
});
// Search and filter deleted
runInTransaction(() -> {
Collection<Object[]> forcedIds = myForcedIdDao.findAndResolveByForcedIdWithNoTypeInPartitionNull(
"Patient", Arrays.asList(patientId), true
);
assertEquals(0, forcedIds.size());
});
}
@Test
public void testFindAndResolveByForcedIdWithNoTypeInPartitionIdOrNullPartitionId(){
// Create patients
String patientId = "AAA";
IIdType idA = createPatient(withTenant(TENANT_A), withId(patientId));
createPatient(withId("BBB"));
// Search and include deleted
runInTransaction(() -> {
Collection<Object[]> forcedIds = myForcedIdDao.findAndResolveByForcedIdWithNoTypeInPartitionIdOrNullPartitionId(
"Patient", Arrays.asList(patientId), Arrays.asList(TENANT_A_ID), false
);
assertContainsSingleForcedId(forcedIds, patientId);
});
// Search and filter deleted
runInTransaction(() -> {
Collection<Object[]> forcedIds = myForcedIdDao.findAndResolveByForcedIdWithNoTypeInPartitionIdOrNullPartitionId(
"Patient", Arrays.asList(patientId), Arrays.asList(TENANT_A_ID), true
);
assertContainsSingleForcedId(forcedIds, patientId);
});
deletePatient(TENANT_A, idA);
// Search and include deleted
runInTransaction(() -> {
Collection<Object[]> forcedIds = myForcedIdDao.findAndResolveByForcedIdWithNoTypeInPartitionIdOrNullPartitionId(
"Patient", Arrays.asList(patientId), Arrays.asList(TENANT_A_ID), false
);
assertContainsSingleForcedId(forcedIds, patientId);
});
// Search and filter deleted
runInTransaction(() -> {
Collection<Object[]> forcedIds = myForcedIdDao.findAndResolveByForcedIdWithNoTypeInPartitionIdOrNullPartitionId(
"Patient", Arrays.asList(patientId), Arrays.asList(TENANT_A_ID), true
);
assertEquals(0, forcedIds.size());
});
}
@Test
public void testFindAndResolveByForcedIdWithNoTypeInPartition() throws IOException {
// Create patients
String patientId = "AAA";
IIdType idA = createPatient(withTenant(TENANT_A), withId(patientId));
createPatient(withTenant(TENANT_B), withId("BBB"));
// Search and include deleted
runInTransaction(() -> {
Collection<Object[]> forcedIds = myForcedIdDao.findAndResolveByForcedIdWithNoTypeInPartition(
"Patient", Arrays.asList(patientId), Arrays.asList(TENANT_A_ID, TENANT_B_ID), false
);
assertContainsSingleForcedId(forcedIds, patientId);
});
// Search and filter deleted
runInTransaction(() -> {
Collection<Object[]> forcedIds = myForcedIdDao.findAndResolveByForcedIdWithNoTypeInPartition(
"Patient", Arrays.asList(patientId), Arrays.asList(TENANT_A_ID, TENANT_B_ID), true
);
assertContainsSingleForcedId(forcedIds, patientId);
});
deletePatient(TENANT_A, idA);
// Search and include deleted
runInTransaction(() -> {
Collection<Object[]> forcedIds = myForcedIdDao.findAndResolveByForcedIdWithNoTypeInPartition(
"Patient", Arrays.asList(patientId), Arrays.asList(TENANT_A_ID, TENANT_B_ID), false
);
assertContainsSingleForcedId(forcedIds, patientId);
});
// Search and filter deleted
runInTransaction(() -> {
Collection<Object[]> forcedIds = myForcedIdDao.findAndResolveByForcedIdWithNoTypeInPartition(
"Patient", Arrays.asList(patientId), Arrays.asList(TENANT_A_ID, TENANT_B_ID), true
);
assertEquals(0, forcedIds.size());
});
}
private void assertContainsSingleForcedId(Collection<Object[]> forcedIds, String patientId){
assertEquals(1, forcedIds.size());
assertEquals(patientId, forcedIds.stream().toList().get(0)[2]);
}
private void deletePatient(String tenantId, IIdType patientId){
SystemRequestDetails requestDetails = new SystemRequestDetails();
requestDetails.setTenantId(tenantId);
myPatientDao.delete(patientId, requestDetails);
}
@Test
public void testCreateAndRead_NonPartitionableResource_DefaultTenant() {

View File

@ -45,6 +45,7 @@ import ca.uhn.fhir.util.StopWatch;
import ca.uhn.fhir.util.UrlUtil;
import com.google.common.base.Charsets;
import com.google.common.collect.Lists;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
@ -3986,6 +3987,74 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
}
@Test
public void testSearchByIdForDeletedResourceWithClientAssignedId() throws IOException {
// Create with client assigned ID
Patient p = new Patient();
String patientId = "AAA";
p.setId(patientId);
MethodOutcome outcome = myClient.update().resource(p).execute();
assertTrue(outcome.getCreated());
Patient createdPatient = (Patient) outcome.getResource();
// Search
Bundle search1 = (Bundle) myClient.search()
.forResource(Patient.class)
.where(Patient.RES_ID.exactly().identifier(patientId))
.execute();
assertEquals(1, search1.getTotal());
assertEquals(patientId, search1.getEntry().get(0).getResource().getIdElement().getIdPart());
// Delete
outcome = myClient.delete().resource(createdPatient).execute();
assertNull(outcome.getResource());
// Search
Bundle search2 = (Bundle) myClient.search()
.forResource(Patient.class)
.where(Patient.RES_ID.exactly().identifier(patientId))
.execute();
assertTrue(CollectionUtils.isEmpty(search2.getEntry()));
assertEquals(0, search2.getTotal());
}
@Test
public void testSearchByIdForDeletedResourceWithServerAssignedId() throws IOException {
// Create with server assigned ID
Patient p = new Patient();
MethodOutcome outcome = myClient.create().resource(p).execute();
assertTrue(outcome.getCreated());
Patient createdPatient = (Patient) outcome.getResource();
String patientId = createdPatient.getIdElement().getIdPart();
// Search
Bundle search1 = (Bundle) myClient.search()
.forResource(Patient.class)
.where(Patient.RES_ID.exactly().identifier(patientId))
.execute();
assertEquals(1, search1.getTotal());
assertEquals(patientId, search1.getEntry().get(0).getResource().getIdElement().getIdPart());
// Delete
outcome = myClient.delete().resource(createdPatient).execute();
assertNull(outcome.getResource());
// Search
Bundle search2 = (Bundle) myClient.search()
.forResource(Patient.class)
.where(Patient.RES_ID.exactly().identifier(patientId))
.execute();
assertTrue(CollectionUtils.isEmpty(search2.getEntry()));
assertEquals(0, search2.getTotal());
}
@Test
public void testSearchByIdentifier() {
Patient p1 = new Patient();

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
</parent>
<artifactId>hapi-fhir-spring-boot-sample-client-apache</artifactId>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
</parent>
<artifactId>hapi-fhir-spring-boot-sample-client-okhttp</artifactId>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
</parent>
<artifactId>hapi-fhir-spring-boot-sample-server-jersey</artifactId>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-spring-boot</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
</parent>
<artifactId>hapi-fhir-spring-boot-samples</artifactId>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -60,6 +60,15 @@ public interface IIdHelperService {
@Nonnull
ResourcePersistentId resolveResourcePersistentIds(@Nonnull RequestPartitionId theRequestPartitionId, String theResourceType, String theId);
/**
* Given a resource type and ID, determines the internal persistent ID for a resource.
* Optionally filters out deleted resources.
*
* @throws ResourceNotFoundException If the ID can not be found
*/
@Nonnull
ResourcePersistentId resolveResourcePersistentIds(@Nonnull RequestPartitionId theRequestPartitionId, String theResourceType, String theId, boolean theExcludeDeleted);
/**
* Returns a mapping of Id -> ResourcePersistentId.
* If any resource is not found, it will throw ResourceNotFound exception
@ -68,6 +77,14 @@ public interface IIdHelperService {
@Nonnull
Map<String, ResourcePersistentId> resolveResourcePersistentIds(@Nonnull RequestPartitionId theRequestPartitionId, String theResourceType, List<String> theIds);
/**
* Returns a mapping of Id -> ResourcePersistentId.
* If any resource is not found, it will throw ResourceNotFound exception (and no map will be returned)
* Optionally filters out deleted resources.
*/
@Nonnull
Map<String, ResourcePersistentId> resolveResourcePersistentIds(@Nonnull RequestPartitionId theRequestPartitionId, String theResourceType, List<String> theIds, boolean theExcludeDeleted);
/**
* Given a persistent ID, returns the associated resource ID
*/
@ -83,6 +100,16 @@ public interface IIdHelperService {
@Nonnull
IResourceLookup resolveResourceIdentity(@Nonnull RequestPartitionId theRequestPartitionId, String theResourceType, String theResourceId) throws ResourceNotFoundException;
/**
* Given a forced ID, convert it to it's Long value. Since you are allowed to use string IDs for resources, we need to
* convert those to the underlying Long values that are stored, for lookup and comparison purposes.
* Optionally filters out deleted resources.
*
* @throws ResourceNotFoundException If the ID can not be found
*/
@Nonnull
IResourceLookup resolveResourceIdentity(@Nonnull RequestPartitionId theRequestPartitionId, String theResourceType, String theResourceId, boolean theExcludeDeleted) throws ResourceNotFoundException;
/**
* Returns true if the given resource ID should be stored in a forced ID. Under default config
* (meaning client ID strategy is {@link ca.uhn.fhir.jpa.api.config.DaoConfig.ClientIdStrategyEnum#ALPHANUMERIC})

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
@ -58,37 +58,37 @@
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-structures-dstu3</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-structures-hl7org-dstu2</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-structures-r4</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-structures-r5</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-validation-resources-dstu2</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-validation-resources-dstu3</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-validation-resources-r4</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<packaging>pom</packaging>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
<name>HAPI-FHIR</name>
<description>An open-source implementation of the FHIR specification in Java.</description>
<url>https://hapifhir.io</url>
@ -2011,7 +2011,7 @@
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-checkstyle</artifactId>
<!-- Remember to bump this when you upgrade the version -->
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
</dependency>
</dependencies>
</plugin>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>6.1.0-PRE8-SNAPSHOT</version>
<version>6.1.0-PRE9-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>