Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Normalize out resource type names into new hfj_resource_type table #6890

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
d8f95de
[6889] add migration tasks for new table and column updates
YalingPeiS Apr 22, 2025
11c2155
[6889] correct FK column name
YalingPeiS Apr 22, 2025
3afc835
[6889] add explicit index to the FK column
YalingPeiS Apr 22, 2025
f71bc4b
Merge remote-tracking branch 'refs/remotes/origin/master' into 6889-n…
YalingPeiS Apr 23, 2025
d3c9d90
[6889] update entities with the new column
YalingPeiS Apr 24, 2025
2165910
Merge remote-tracking branch 'origin/master' into 6889-normalize-out-…
YalingPeiS Apr 24, 2025
7e20404
[6889] add migration tasks to populate the new resource type table
YalingPeiS Apr 26, 2025
989403b
Merge remote-tracking branch 'origin/master' into 6889-normalize-out-…
YalingPeiS Apr 26, 2025
08149df
[6889] rename variables
YalingPeiS Apr 26, 2025
7bc3eb2
[6889] minor update
YalingPeiS Apr 26, 2025
4594097
[6889] update migration tasks to entities
YalingPeiS Apr 28, 2025
ca5d48f
[6889] load resource types using Fhir Context
YalingPeiS Apr 29, 2025
4555205
[6889] apply spotless
YalingPeiS Apr 29, 2025
07081f5
[6889] apply spotless
YalingPeiS Apr 29, 2025
31a7702
Merge remote-tracking branch 'origin/master' into 6889-normalize-out-…
YalingPeiS Apr 29, 2025
78b4197
[6889] update entity classes
YalingPeiS Apr 30, 2025
fc312d0
[6889] add new classes for resource type cache service and dao
YalingPeiS Apr 30, 2025
9881bf9
Merge remote-tracking branch 'origin/master' into 6889-normalize-out-…
YalingPeiS Apr 30, 2025
5d8bba8
[6889] write resource type id to the newly added resource rows
YalingPeiS May 3, 2025
fbdef68
[6889] add exception handling for unique index constraint
YalingPeiS May 4, 2025
33ea430
[6889] add util class for generating resource types to be used in mul…
YalingPeiS May 4, 2025
c5158a1
[6889] fix the failed unit tests by caused by the extra query to the …
YalingPeiS May 5, 2025
3036145
[6889] Pre-populate the resource type database table to fix failed tests
YalingPeiS May 6, 2025
23c651c
[6889] fix the failed unit tests in R5
YalingPeiS May 6, 2025
7cffffa
[6889] Revert back to not use HapiTransactionServer due to unit test …
YalingPeiS May 6, 2025
c6a330a
[6889] fix more unit tests
YalingPeiS May 6, 2025
23d0933
[6889] fix more unit tests
YalingPeiS May 6, 2025
40327f4
[6889] minor update
YalingPeiS May 6, 2025
83e981c
[6889] add changelog and fix unit test failure in MdmEventIT
YalingPeiS May 6, 2025
ca42b1a
[6889] fix the failed unit tests in Mdm package
YalingPeiS May 7, 2025
d6ae6bd
Merge remote-tracking branch 'origin/master' into 6889-normalize-out-…
YalingPeiS May 7, 2025
822d5f7
[6889] more unit test updates
YalingPeiS May 7, 2025
a7b6b80
[6889] more unit test updates
YalingPeiS May 7, 2025
8d8cf11
Merge remote-tracking branch 'origin/master' into 6889-normalize-out-…
YalingPeiS May 15, 2025
a9c40b9
[6889] address review comment - remove resource type foreign key cons…
YalingPeiS May 15, 2025
1c0d63d
[6889] address review comment - use IHapiTransactionService
YalingPeiS May 16, 2025
6617bff
[6889] update unit tests
YalingPeiS May 17, 2025
ac6a1e0
Merge remote-tracking branch 'origin/master' into 6889-normalize-out-…
YalingPeiS May 17, 2025
1adf94b
[6889] code cleaning
YalingPeiS May 20, 2025
15e67a2
[6889] address review comment - change return value from Short to short
YalingPeiS May 21, 2025
577d472
[6889] apply spotless
YalingPeiS May 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
type: add
issue: 6889
title: "A new table `HFJ_RESOURCE_TYPE` has been introduced. This table stores all known resource types from all
releases. It is pre-populated at server start up. New or custom resource types are added to this table on demand.
A new column `HFJ_RESOURCE_TYPE_ID` has been added to the following tables.
<ul>
<li>HFJ_RESOURCE</li>
<li>HFJ_RE_VER</li>
<li>HFJ_RES_TAG</li>
<li>HFJ_RES_HISTORY_TAG</li>
<li>HFJ_RES_VER_LINK</li>
</ul>
This column is used to store the resource type ID which references the `HFJ_RESOURCE_TYPE` table."

Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2025 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%
*/
package ca.uhn.fhir.jpa.cache;

public interface IResourceTypeCacheSvc {
YalingPeiS marked this conversation as resolved.
Show resolved Hide resolved
/**
* Retrieves the resource type ID for the given resource type.
* If the resource type does not exist, a new custom resource type will be created,
* added to the database and cache, and its ID will be returned.
*
* @param theResType the resource type to retrieve or create
* @return the resource type ID or throws an exception if the resource creation fails
*/
short getResourceTypeId(String theResType);

/**
* Adds the given resource type and its corresponding ID to the cache.
*
* @param theResType the resource type to be added to the cache
* @param theResTypeId the ID of the resource type to be added to the cache
*/
void addToCache(String theResType, Short theResTypeId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2025 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%
*/
package ca.uhn.fhir.jpa.cache;

import ca.uhn.fhir.jpa.config.util.ResourceTypeUtil;
import ca.uhn.fhir.jpa.dao.data.IResourceTypeDao;
import ca.uhn.fhir.jpa.dao.tx.IHapiTransactionService;
import ca.uhn.fhir.jpa.model.entity.ResourceTypeEntity;
import ca.uhn.fhir.jpa.util.MemoryCacheService;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import jakarta.annotation.PostConstruct;
import org.apache.commons.collections4.CollectionUtils;
import org.slf4j.Logger;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;

import java.util.List;

import static org.slf4j.LoggerFactory.getLogger;

@Service
public class ResourceTypeCacheSvcImpl implements IResourceTypeCacheSvc {
private static final Logger ourLog = getLogger(ResourceTypeCacheSvcImpl.class);

private final IHapiTransactionService myTransactionService;
private final IResourceTypeDao myResourceTypeDao;
private final MemoryCacheService myMemoryCacheService;

public ResourceTypeCacheSvcImpl(
IHapiTransactionService theTransactionService,
IResourceTypeDao theResourceTypeDao,
MemoryCacheService theMemoryCacheService) {
myTransactionService = theTransactionService;
myResourceTypeDao = theResourceTypeDao;
myMemoryCacheService = theMemoryCacheService;
}

@PostConstruct
public void start() {
initResourceTypes();
ourLog.info(
"Resource type cache size: {}",
myMemoryCacheService.getEstimatedSize(MemoryCacheService.CacheEnum.RES_TYPE_TO_RES_TYPE_ID));
}

@Override
public short getResourceTypeId(String theResType) {
Short resTypeId = myMemoryCacheService.get(
MemoryCacheService.CacheEnum.RES_TYPE_TO_RES_TYPE_ID, theResType, this::lookupResourceTypeId);

if (resTypeId == null) {
ourLog.info("Creating a new Resource Type [{}]", theResType);
ResourceTypeEntity entity = createResourceType(theResType);
resTypeId = entity.getResourceTypeId();
addToCache(theResType, resTypeId);
}
return resTypeId;
}

@Override
public void addToCache(String theResType, Short theResTypeId) {
myMemoryCacheService.putAfterCommit(
MemoryCacheService.CacheEnum.RES_TYPE_TO_RES_TYPE_ID, theResType, theResTypeId);
}

protected void initResourceTypes() {
myTransactionService
.withSystemRequestOnDefaultPartition()
.withPropagation(Propagation.REQUIRES_NEW)
.execute(() -> {
List<ResourceTypeEntity> resTypeEntities = myResourceTypeDao.findAll();
if (CollectionUtils.isEmpty(resTypeEntities)) {
List<ResourceTypeEntity> newEntities = ResourceTypeUtil.generateResourceTypes().stream()
.map(r -> {
ResourceTypeEntity entity = new ResourceTypeEntity();
entity.setResourceType(r);
return entity;
})
.toList();

myResourceTypeDao.saveAll(newEntities);
ourLog.info("Table HFJ_RESOURCE_TYPE is populated with {} entries.", newEntities.size());
resTypeEntities = newEntities;
}

// Populate the cache
resTypeEntities.forEach(r -> addToCache(r.getResourceType(), r.getResourceTypeId()));
return null;
});
}

protected ResourceTypeEntity createResourceType(String theResourceType) {
return myTransactionService
.withSystemRequestOnDefaultPartition()
.withPropagation(Propagation.REQUIRES_NEW)
.execute(() -> {
ResourceTypeEntity resTypeEntity = new ResourceTypeEntity();
resTypeEntity.setResourceType(theResourceType);
try {
resTypeEntity = myResourceTypeDao.save(resTypeEntity);
myResourceTypeDao.flush();
} catch (DataIntegrityViolationException e) {
if (e.getMessage().contains("Value too long for column")) {
throw new InternalErrorException("Resource type name is too long: " + theResourceType, e);
}
// This can happen if the resource type already exists in the database
ourLog.info("Resource type already exists: {}", theResourceType);
resTypeEntity = myResourceTypeDao.findByResourceType(theResourceType);
}

return resTypeEntity;
});
}

private Short lookupResourceTypeId(String theResourceType) {
return myTransactionService
.withSystemRequestOnDefaultPartition()
.execute(() -> myResourceTypeDao.findResourceIdByType(theResourceType));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,10 @@
import ca.uhn.fhir.jpa.bulk.export.svc.BulkExportHelperService;
import ca.uhn.fhir.jpa.bulk.imprt.api.IBulkDataImportSvc;
import ca.uhn.fhir.jpa.bulk.imprt.svc.BulkDataImportSvcImpl;
import ca.uhn.fhir.jpa.cache.IResourceTypeCacheSvc;
import ca.uhn.fhir.jpa.cache.IResourceVersionSvc;
import ca.uhn.fhir.jpa.cache.ISearchParamIdentityCacheSvc;
import ca.uhn.fhir.jpa.cache.ResourceTypeCacheSvcImpl;
import ca.uhn.fhir.jpa.cache.ResourceVersionSvcDaoImpl;
import ca.uhn.fhir.jpa.dao.CacheTagDefinitionDao;
import ca.uhn.fhir.jpa.dao.DaoSearchParamProvider;
Expand All @@ -62,6 +64,7 @@
import ca.uhn.fhir.jpa.dao.data.IResourceLinkDao;
import ca.uhn.fhir.jpa.dao.data.IResourceModifiedDao;
import ca.uhn.fhir.jpa.dao.data.IResourceSearchUrlDao;
import ca.uhn.fhir.jpa.dao.data.IResourceTypeDao;
import ca.uhn.fhir.jpa.dao.data.ITagDefinitionDao;
import ca.uhn.fhir.jpa.dao.expunge.ExpungeEverythingService;
import ca.uhn.fhir.jpa.dao.expunge.ExpungeOperation;
Expand All @@ -75,6 +78,7 @@
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
import ca.uhn.fhir.jpa.dao.index.SearchParamWithInlineReferencesExtractor;
import ca.uhn.fhir.jpa.dao.tx.HapiTransactionService;
import ca.uhn.fhir.jpa.dao.tx.IHapiTransactionService;
import ca.uhn.fhir.jpa.dao.validation.SearchParameterDaoValidator;
import ca.uhn.fhir.jpa.delete.DeleteConflictFinderService;
import ca.uhn.fhir.jpa.delete.DeleteConflictService;
Expand Down Expand Up @@ -619,6 +623,14 @@ public ISearchParamIdentityCacheSvc searchParamIdentityCacheSvc(
myStorageSettings, theResourceIndexedSearchParamIdentityDao, theTxManager, theMemoryCacheService);
}

@Bean
public IResourceTypeCacheSvc resourceTypeCacheSvc(
@Autowired IHapiTransactionService theHapiTransactionService,
@Autowired IResourceTypeDao theResourceTypeDao,
@Autowired MemoryCacheService theMemoryCacheService) {
return new ResourceTypeCacheSvcImpl(theHapiTransactionService, theResourceTypeDao, theMemoryCacheService);
}

/* **************************************************************** *
* Prototype Beans Below *
* **************************************************************** */
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2025 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%
*/
package ca.uhn.fhir.jpa.config.util;

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public final class ResourceTypeUtil {

public static List<String> generateResourceTypes() {
return Stream.of(FhirVersionEnum.DSTU2, FhirVersionEnum.DSTU3, FhirVersionEnum.R4, FhirVersionEnum.R5)
.map(FhirContext::forVersion)
.flatMap(c -> c.getResourceTypes().stream())
.distinct()
.sorted()
.collect(Collectors.toList());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome;
import ca.uhn.fhir.jpa.api.svc.IIdHelperService;
import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc;
import ca.uhn.fhir.jpa.cache.IResourceTypeCacheSvc;
import ca.uhn.fhir.jpa.dao.data.IResourceHistoryTableDao;
import ca.uhn.fhir.jpa.dao.data.IResourceLinkDao;
import ca.uhn.fhir.jpa.dao.data.IResourceTableDao;
Expand Down Expand Up @@ -106,6 +107,7 @@
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.persistence.PersistenceContextType;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.NotImplementedException;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
Expand Down Expand Up @@ -236,6 +238,9 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
@Autowired
protected CacheTagDefinitionDao cacheTagDefinitionDao;

@Autowired
protected IResourceTypeCacheSvc myResourceTypeCacheSvc;

protected final CodingSpy myCodingSpy = new CodingSpy();

@VisibleForTesting
Expand Down Expand Up @@ -490,6 +495,9 @@ protected EncodedResource populateResourceIntoEntity(
if (theEntity.getResourceType() == null) {
theEntity.setResourceType(toResourceName(theResource));
}
if (theEntity.getResourceTypeId() == null && theResource != null) {
theEntity.setResourceTypeId(myResourceTypeCacheSvc.getResourceTypeId(toResourceName(theResource)));
}

byte[] resourceBinary;
String resourceText;
Expand Down Expand Up @@ -993,6 +1001,10 @@ public ResourceTable updateEntity(
theRequest,
thePerformIndexing);

if (CollectionUtils.isNotEmpty(newParams.myLinks)) {
setTargetResourceTypeIdForResourceLinks(newParams.myLinks);
}

// Actually persist the ResourceTable and ResourceHistoryTable entities
changed = populateResourceIntoEntity(theTransactionDetails, theRequest, theResource, entity, true);

Expand Down Expand Up @@ -1718,6 +1730,11 @@ public void setJpaStorageResourceParserForUnitTest(IJpaStorageResourceParser the
myJpaStorageResourceParser = theJpaStorageResourceParser;
}

@VisibleForTesting
public void setResourceTypeCacheSvc(IResourceTypeCacheSvc theResourceTypeCacheSvc) {
myResourceTypeCacheSvc = theResourceTypeCacheSvc;
}

@SuppressWarnings("unchecked")
public static String parseContentTextIntoWords(FhirContext theContext, IBaseResource theResource) {

Expand Down Expand Up @@ -1806,4 +1823,11 @@ private enum CreateOrUpdateByMatch {
CREATE,
UPDATE
}

private void setTargetResourceTypeIdForResourceLinks(Collection<ResourceLink> resourceLinks) {
resourceLinks.stream()
.filter(link -> link.getTargetResourceType() != null)
.forEach(link -> link.setTargetResourceTypeId(
myResourceTypeCacheSvc.getResourceTypeId(link.getTargetResourceType())));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,7 @@ private DaoMethodOutcome doCreateForPostOrPut(

ResourceTable entity = new ResourceTable();
entity.setResourceType(toResourceName(theResource));
entity.setResourceTypeId(myResourceTypeCacheSvc.getResourceTypeId(toResourceName(theResource)));

RequestPartitionId targetWritePartitionId = myRequestPartitionHelperService.determineCreatePartitionForRequest(
theRequest, theResource, entity.getResourceType());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2025 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%
*/
package ca.uhn.fhir.jpa.dao.data;

import ca.uhn.fhir.jpa.model.entity.ResourceTypeEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

@Repository
public interface IResourceTypeDao extends JpaRepository<ResourceTypeEntity, Integer>, IHapiFhirJpaRepository {

@Query(value = "SELECT t FROM ResourceTypeEntity t WHERE t.myResourceType = :resType")
ResourceTypeEntity findByResourceType(@Param("resType") String theResType);

@Query(value = "SELECT t.myResourceTypeId FROM ResourceTypeEntity t WHERE t.myResourceType = :resType")
Short findResourceIdByType(@Param("resType") String theResType);
}
Loading
Loading
Morty Proxy This is a proxified and sanitized view of the page, visit original site.