diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/JpaConfig.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/JpaConfig.java index 8583f80b789..b3066877a15 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/JpaConfig.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/JpaConfig.java @@ -12,6 +12,7 @@ import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.api.dao.DaoRegistry; import ca.uhn.fhir.jpa.api.model.ExpungeOptions; import ca.uhn.fhir.jpa.api.svc.IIdHelperService; +import ca.uhn.fhir.jpa.api.svc.IResourceSearchUrlSvc; import ca.uhn.fhir.jpa.binary.interceptor.BinaryStorageInterceptor; import ca.uhn.fhir.jpa.binary.provider.BinaryAccessProvider; import ca.uhn.fhir.jpa.bulk.export.api.IBulkDataExportJobSchedulingHelper; @@ -124,6 +125,7 @@ import ca.uhn.fhir.jpa.searchparam.nickname.NicknameInterceptor; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamProvider; import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc; import ca.uhn.fhir.jpa.sp.SearchParamPresenceSvcImpl; +import ca.uhn.fhir.jpa.search.ResourceSearchUrlSvcImpl; import ca.uhn.fhir.jpa.term.TermCodeSystemStorageSvcImpl; import ca.uhn.fhir.jpa.term.TermConceptMappingSvcImpl; import ca.uhn.fhir.jpa.term.TermReadSvcImpl; @@ -792,4 +794,9 @@ public class JpaConfig { @Bean IMdmLinkImplFactory mdmLinkImplFactory() {return new JpaMdmLinkImplFactory();} + + @Bean + public IResourceSearchUrlSvc resourceSearchUrlSvc(){ + return new ResourceSearchUrlSvcImpl(); + } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java index 2c1d92e7d35..aa7e249dfec 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java @@ -405,6 +405,7 @@ public abstract class BaseHapiFhirResourceDao extends B // Pre-cache the match URL if (theMatchUrl != null) { + myMatchResourceUrlService.storeMatchUrlForResource(getResourceName(), theMatchUrl, jpaPid); myMatchResourceUrlService.matchUrlResolved(theTransactionDetails, getResourceName(), theMatchUrl, jpaPid); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceSearchUrlDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceSearchUrlDao.java new file mode 100644 index 00000000000..1bd664be221 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceSearchUrlDao.java @@ -0,0 +1,8 @@ +package ca.uhn.fhir.jpa.dao.data; + + +import ca.uhn.fhir.jpa.model.entity.ResourceSearchUrlEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface IResourceSearchUrlDao extends JpaRepository, IHapiFhirJpaRepository{ +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java index d79bac1db1f..68a116902aa 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java @@ -88,6 +88,27 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks { init620(); init630(); init640(); + init642(); + } + + + protected void init642() { + Builder version = forVersion(VersionEnum.V6_4_2); + + Builder.BuilderWithTableName resSearchUrlTable = version.onTable("HFJ_RES_SEARCH_URL"); + + resSearchUrlTable.addColumn("20230227.1","PID").nonNullable().type(ColumnTypeEnum.LONG); + resSearchUrlTable.addColumn("20230227.2", "RES_ID").nonNullable().type(ColumnTypeEnum.LONG); + + resSearchUrlTable.addColumn("20230227.3", "RES_SEARC_URL").nonNullable().type(ColumnTypeEnum.STRING); + resSearchUrlTable.addColumn("20230227.4", "RES_HASH").nonNullable().type(ColumnTypeEnum.STRING, 128); + resSearchUrlTable.addColumn("20230227.5", "RES_SEARCH_URL").nonNullable().type(ColumnTypeEnum.STRING); + resSearchUrlTable.addColumn("20230227.6", "CREATED_TIME").nonNullable().type(ColumnTypeEnum.DATE_TIMESTAMP); + + resSearchUrlTable.addForeignKey("20230227.7", "FK_RESSEARCHURL_RESID").toColumn("RES_ID").references("HFJ_RESOURCE", "RES_ID"); + resSearchUrlTable.addIndex("20230227.8", "IDX_RES_HASH").unique(true).withColumns("RES_HASH"); + version.addIdGenerator("20230227.9", "SEQ_RESSEARCHURL_ID"); + } protected void init640() { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/ResourceSearchUrlSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/ResourceSearchUrlSvcImpl.java new file mode 100644 index 00000000000..e4b988e3459 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/ResourceSearchUrlSvcImpl.java @@ -0,0 +1,22 @@ +package ca.uhn.fhir.jpa.search; + +import ca.uhn.fhir.jpa.api.svc.IResourceSearchUrlSvc; +import ca.uhn.fhir.jpa.dao.data.IResourceSearchUrlDao; +import ca.uhn.fhir.jpa.model.entity.ResourceSearchUrlEntity; +import ca.uhn.fhir.rest.api.server.storage.IResourcePersistentId; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class ResourceSearchUrlSvcImpl implements IResourceSearchUrlSvc { + + @Autowired + private IResourceSearchUrlDao myResourceSearchUrlDao; + + @Override + public void saveResourceSearchUrl(String theCanonicalizedUrlForStorage, IResourcePersistentId theResourcePersistentId) { + ResourceSearchUrlEntity searchUrlEntity = ResourceSearchUrlEntity.from(theCanonicalizedUrlForStorage, (Long)theResourcePersistentId.getId()); + myResourceSearchUrlDao.save(searchUrlEntity); + + } +} diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceSearchUrlEntity.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceSearchUrlEntity.java new file mode 100644 index 00000000000..0a6852e9964 --- /dev/null +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceSearchUrlEntity.java @@ -0,0 +1,108 @@ +package ca.uhn.fhir.jpa.model.entity; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.ForeignKey; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToOne; +import javax.persistence.SequenceGenerator; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; +import javax.persistence.UniqueConstraint; +import java.util.Date; + +@Entity +@Table(name = "HFJ_RES_SEARCH_URL", uniqueConstraints = {@UniqueConstraint(name = "IDX_RES_HASH", columnNames = "RES_HASH") +}) +public class ResourceSearchUrlEntity { + + @Id + @SequenceGenerator(name = "SEQ_RESSEARCHURL_ID", sequenceName = "SEQ_RESSEARCHURL_ID") + @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_RESSEARCHURL_ID") + @Column(name = "PID") + private Long myId; + + @OneToOne() + @JoinColumn(name = "RES_ID", referencedColumnName = "RES_ID", nullable = false, updatable = false, foreignKey = @ForeignKey(name = "FK_RESSEARCHURL_RESID")) + private ResourceTable myResource; + @Column(name = "RES_ID", insertable = false, updatable = false, nullable = false) + private Long myResourcePid; + + @Column(name = "RES_HASH", length = 128, nullable = false) + private String myHash; + + @Column(name = "RES_SEARCH_URL", nullable = false) + private String mySearchUrl; + + @Column(name = "CREATED_TIME", nullable = false) + @Temporal(TemporalType.TIMESTAMP) + private Date myCreatedTime; + + public static ResourceSearchUrlEntity from(String theUrl, Long theId) { + return new ResourceSearchUrlEntity() + .setResourcePid(theId) + .setSearchUrl(theUrl) + .setCreatedTime(new Date()); + } + + public Long getId() { + return myId; + } + + public ResourceSearchUrlEntity setId(Long theId) { + myId = theId; + return this; + } + + public ResourceTable getResource() { + return myResource; + } + + public ResourceSearchUrlEntity setResource(ResourceTable theResource) { + myResource = theResource; + return this; + } + + public Long getResourcePid() { + return myResourcePid; + } + + public ResourceSearchUrlEntity setResourcePid(Long theResourcePid) { + myResourcePid = theResourcePid; + return this; + } + + public Date getCreatedTime() { + return myCreatedTime; + } + + public ResourceSearchUrlEntity setCreatedTime(Date theCreatedTime) { + myCreatedTime = theCreatedTime; + return this; + } + + public String getHash() { + return myHash; + } + + public ResourceSearchUrlEntity setHash(String theHash) { + myHash = theHash; + return this; + } + + public String getSearchUrl() { + return mySearchUrl; + } + + public ResourceSearchUrlEntity setSearchUrl(String theSearchUrl) { + mySearchUrl = theSearchUrl; + return this; + } +} + + diff --git a/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/api/svc/IResourceSearchUrlSvc.java b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/api/svc/IResourceSearchUrlSvc.java new file mode 100644 index 00000000000..8eb5368535e --- /dev/null +++ b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/api/svc/IResourceSearchUrlSvc.java @@ -0,0 +1,7 @@ +package ca.uhn.fhir.jpa.api.svc; + +import ca.uhn.fhir.rest.api.server.storage.IResourcePersistentId; + +public interface IResourceSearchUrlSvc { + void saveResourceSearchUrl(String theCanonicalizedUrlForStorage, IResourcePersistentId theResourcePersistentId); +} diff --git a/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/dao/MatchResourceUrlService.java b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/dao/MatchResourceUrlService.java index e87dfeb814a..d31660147d9 100644 --- a/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/dao/MatchResourceUrlService.java +++ b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/dao/MatchResourceUrlService.java @@ -58,6 +58,7 @@ import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; +import ca.uhn.fhir.jpa.api.svc.IResourceSearchUrlSvc; @Service public class MatchResourceUrlService { @@ -76,6 +77,8 @@ public class MatchResourceUrlService { private IInterceptorBroadcaster myInterceptorBroadcaster; @Autowired private MemoryCacheService myMemoryCacheService; + @Autowired + private IResourceSearchUrlSvc myResourceSearchUrlSvc; /** * Note that this will only return a maximum of 2 results!! @@ -109,8 +112,7 @@ public class MatchResourceUrlService { } if (retVal == null) { - RuntimeResourceDefinition resourceDef = myContext.getResourceDefinition(theResourceType); - SearchParameterMap paramMap = myMatchUrlService.translateMatchUrl(matchUrl, resourceDef); + SearchParameterMap paramMap = deriveSearchParameterMap(matchUrl, myContext.getResourceType(theResourceType)); if (paramMap.isEmpty() && paramMap.getLastUpdated() == null) { throw new InvalidRequestException(Msg.code(518) + "Invalid match URL[" + matchUrl + "] - URL has no search parameters"); } @@ -162,6 +164,11 @@ public class MatchResourceUrlService { return retVal; } + private SearchParameterMap deriveSearchParameterMap(String theMatchUrl, String theResourceType) { + RuntimeResourceDefinition resourceDef = myContext.getResourceDefinition(theResourceType); + return myMatchUrlService.translateMatchUrl(theMatchUrl, resourceDef); + } + private IFhirResourceDao getResourceDao(Class theResourceType) { IFhirResourceDao dao = myDaoRegistry.getResourceDao(theResourceType); if (dao == null) { @@ -223,4 +230,11 @@ public class MatchResourceUrlService { } } + public void storeMatchUrlForResource(String theResourceName, String theMatchUrl, T theResourcePersistentId) { + SearchParameterMap matchUrlSearchParameterMap = deriveSearchParameterMap(theMatchUrl, theResourceName); + String canonicalizedMatchUrl = matchUrlSearchParameterMap.toNormalizedQueryString(myContext); + + String canonicalizedUrlForStorage = massageForStorage(theResourceName, canonicalizedMatchUrl); + myResourceSearchUrlSvc.saveResourceSearchUrl(canonicalizedUrlForStorage, theResourcePersistentId); + } }