Address use case where packages include non-conformance resources. Update documentation as well.
This commit is contained in:
parent
d01c775b99
commit
659e83f451
|
@ -139,3 +139,5 @@ None of the limitations listed here are considered permanent. Over time the HAPI
|
|||
* **Cross-partition History Operations are not supported**: It is not possible to perform a `_history` operation that spans all partitions (`_history` does work when applied to a single partition however).
|
||||
|
||||
* **Bulk Operations are not partition aware**: Bulk export operations will export data across all partitions.
|
||||
|
||||
* **Package Operations are not partition aware**: Package operations will only affect resources defined in the default partition.
|
||||
|
|
|
@ -326,7 +326,7 @@ public class JpaPackageCache extends BasePackageCacheManager implements IHapiPac
|
|||
private ResourceTable createResourceBinary(IBaseBinary theResourceBinary) {
|
||||
|
||||
if (myPartitionSettings.isPartitioningEnabled()) {
|
||||
PackageBinaryRequestDetails myRequestDetails = new PackageBinaryRequestDetails();
|
||||
PackageSystemRequestDetails myRequestDetails = new PackageSystemRequestDetails();
|
||||
return (ResourceTable) getBinaryDao().create(theResourceBinary, myRequestDetails).getEntity();
|
||||
} else {
|
||||
return (ResourceTable) getBinaryDao().create(theResourceBinary).getEntity();
|
||||
|
@ -637,7 +637,7 @@ public class JpaPackageCache extends BasePackageCacheManager implements IHapiPac
|
|||
private void deleteAndExpungeResourceBinary(IIdType theResourceBinaryId, ExpungeOptions theOptions) {
|
||||
|
||||
if (myPartitionSettings.isPartitioningEnabled()) {
|
||||
PackageBinaryRequestDetails myRequestDetails = new PackageBinaryRequestDetails();
|
||||
PackageSystemRequestDetails myRequestDetails = new PackageSystemRequestDetails();
|
||||
getBinaryDao().delete(theResourceBinaryId, myRequestDetails).getEntity();
|
||||
getBinaryDao().forceExpungeInExistingTransaction(theResourceBinaryId, theOptions, myRequestDetails);
|
||||
} else {
|
||||
|
|
|
@ -31,6 +31,7 @@ import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
|||
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
|
||||
import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome;
|
||||
import ca.uhn.fhir.jpa.dao.data.INpmPackageVersionDao;
|
||||
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
|
||||
import ca.uhn.fhir.jpa.model.entity.NpmPackageVersionEntity;
|
||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
|
||||
|
@ -50,12 +51,12 @@ import org.hl7.fhir.instance.model.api.IBase;
|
|||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
import org.hl7.fhir.r4.model.Identifier;
|
||||
import org.hl7.fhir.utilities.npm.IPackageCacheManager;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
import org.hl7.fhir.utilities.npm.BasePackageCacheManager;
|
||||
import org.hl7.fhir.utilities.npm.NpmPackage;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
@ -102,7 +103,8 @@ public class PackageInstallerSvcImpl implements IPackageInstallerSvc {
|
|||
private INpmPackageVersionDao myPackageVersionDao;
|
||||
@Autowired
|
||||
private ISearchParamRegistry mySearchParamRegistry;
|
||||
|
||||
@Autowired
|
||||
private PartitionSettings myPartitionSettings;
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
|
@ -316,19 +318,19 @@ public class PackageInstallerSvcImpl implements IPackageInstallerSvc {
|
|||
private void create(IBaseResource theResource, PackageInstallOutcomeJson theOutcome) {
|
||||
IFhirResourceDao dao = myDaoRegistry.getResourceDao(theResource.getClass());
|
||||
SearchParameterMap map = createSearchParameterMapFor(theResource);
|
||||
IBundleProvider searchResult = dao.search(map);
|
||||
IBundleProvider searchResult = searchResource(dao, map);
|
||||
if (validForUpload(theResource)) {
|
||||
if (searchResult.isEmpty()) {
|
||||
|
||||
ourLog.info("Creating new resource matching {}", map.toNormalizedQueryString(myFhirContext));
|
||||
theOutcome.incrementResourcesInstalled(myFhirContext.getResourceType(theResource));
|
||||
dao.create(theResource);
|
||||
createResource(dao, theResource);
|
||||
|
||||
} else {
|
||||
|
||||
ourLog.info("Updating existing resource matching {}", map.toNormalizedQueryString(myFhirContext));
|
||||
theResource.setId(searchResult.getResources(0, 1).get(0).getIdElement().toUnqualifiedVersionless());
|
||||
DaoMethodOutcome outcome = dao.update(theResource);
|
||||
DaoMethodOutcome outcome = updateResource(dao, theResource);
|
||||
if (!outcome.isNop()) {
|
||||
theOutcome.incrementResourcesInstalled(myFhirContext.getResourceType(theResource));
|
||||
}
|
||||
|
@ -337,6 +339,33 @@ public class PackageInstallerSvcImpl implements IPackageInstallerSvc {
|
|||
}
|
||||
}
|
||||
|
||||
private IBundleProvider searchResource(IFhirResourceDao theDao, SearchParameterMap theMap) {
|
||||
if (myPartitionSettings.isPartitioningEnabled()) {
|
||||
PackageSystemRequestDetails myRequestDetails = new PackageSystemRequestDetails();
|
||||
return theDao.search(theMap, myRequestDetails);
|
||||
} else {
|
||||
return theDao.search(theMap);
|
||||
}
|
||||
}
|
||||
|
||||
private void createResource(IFhirResourceDao theDao, IBaseResource theResource) {
|
||||
if (myPartitionSettings.isPartitioningEnabled()) {
|
||||
PackageSystemRequestDetails myRequestDetails = new PackageSystemRequestDetails();
|
||||
theDao.create(theResource, myRequestDetails);
|
||||
} else {
|
||||
theDao.create(theResource);
|
||||
}
|
||||
}
|
||||
|
||||
private DaoMethodOutcome updateResource(IFhirResourceDao theDao, IBaseResource theResource) {
|
||||
if (myPartitionSettings.isPartitioningEnabled()) {
|
||||
PackageSystemRequestDetails myRequestDetails = new PackageSystemRequestDetails();
|
||||
return theDao.update(theResource, myRequestDetails);
|
||||
} else {
|
||||
return theDao.update(theResource);
|
||||
}
|
||||
}
|
||||
|
||||
boolean validForUpload(IBaseResource theResource) {
|
||||
String resourceType = myFhirContext.getResourceType(theResource);
|
||||
if ("SearchParameter".equals(resourceType)) {
|
||||
|
|
|
@ -5,12 +5,12 @@ import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
|
|||
import ca.uhn.fhir.interceptor.api.Pointcut;
|
||||
import ca.uhn.fhir.jpa.partition.SystemRequestDetails;
|
||||
|
||||
public class PackageBinaryRequestDetails extends SystemRequestDetails {
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* A RequestDetails implementation to be used when processing resources defined in a package to ensure
|
||||
* that they are always queried or updated in the DEFAULT partition if partitioning is enabled.
|
||||
*/
|
||||
public PackageBinaryRequestDetails() {
|
||||
public class PackageSystemRequestDetails extends SystemRequestDetails {
|
||||
public PackageSystemRequestDetails() {
|
||||
super(new MyInterceptorBroadcaster());
|
||||
}
|
||||
|
|
@ -28,7 +28,6 @@ import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
|||
import ca.uhn.fhir.jpa.entity.PartitionEntity;
|
||||
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
|
||||
import ca.uhn.fhir.jpa.model.util.JpaConstants;
|
||||
import ca.uhn.fhir.jpa.packages.PackageBinaryRequestDetails;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||
|
|
|
@ -1,9 +1,16 @@
|
|||
package ca.uhn.fhir.jpa.partition;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.api.AddProfileTagEnum;
|
||||
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
|
||||
import ca.uhn.fhir.interceptor.api.IInterceptorService;
|
||||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.server.ETagSupportEnum;
|
||||
import ca.uhn.fhir.rest.server.ElementsSupportEnum;
|
||||
import ca.uhn.fhir.rest.server.IPagingProvider;
|
||||
import ca.uhn.fhir.rest.server.IRestfulServerDefaults;
|
||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
@ -11,6 +18,12 @@ import java.io.Reader;
|
|||
import java.nio.charset.Charset;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A default RequestDetails implementation that can be used for system calls to
|
||||
* Resource DAO methods when partitioning is enabled. Using a SystemRequestDetails
|
||||
* instance for system calls will ensure that any resource queries or updates will
|
||||
* use the DEFAULT partition when partitioninbg is enabled.
|
||||
*/
|
||||
public class SystemRequestDetails extends RequestDetails {
|
||||
public SystemRequestDetails(IInterceptorBroadcaster theInterceptorBroadcaster) {
|
||||
super(theInterceptorBroadcaster);
|
||||
|
@ -63,11 +76,59 @@ public class SystemRequestDetails extends RequestDetails {
|
|||
|
||||
@Override
|
||||
public IRestfulServerDefaults getServer() {
|
||||
return null;
|
||||
return new MyRestfulServerDefaults();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServerBaseForRequest() {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static class MyRestfulServerDefaults implements IRestfulServerDefaults {
|
||||
|
||||
@Override
|
||||
public AddProfileTagEnum getAddProfileTag() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EncodingEnum getDefaultResponseEncoding() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ETagSupportEnum getETagSupport() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ElementsSupportEnum getElementsSupport() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FhirContext getFhirContext() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<IServerInterceptor> getInterceptors_() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IPagingProvider getPagingProvider() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDefaultPrettyPrint() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IInterceptorService getInterceptorService() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@ package ca.uhn.fhir.jpa.packages;
|
|||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.FhirVersionEnum;
|
||||
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
|
||||
import ca.uhn.fhir.interceptor.api.IInterceptorService;
|
||||
import ca.uhn.fhir.jpa.api.config.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.dao.data.INpmPackageDao;
|
||||
import ca.uhn.fhir.jpa.dao.data.INpmPackageVersionDao;
|
||||
|
@ -10,13 +12,18 @@ import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test;
|
|||
import ca.uhn.fhir.jpa.model.entity.NpmPackageEntity;
|
||||
import ca.uhn.fhir.jpa.model.entity.NpmPackageVersionEntity;
|
||||
import ca.uhn.fhir.jpa.model.entity.NpmPackageVersionResourceEntity;
|
||||
import ca.uhn.fhir.jpa.model.util.JpaConstants;
|
||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||
import ca.uhn.fhir.rest.api.Constants;
|
||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||
import ca.uhn.fhir.rest.param.ReferenceParam;
|
||||
import ca.uhn.fhir.rest.param.TokenParam;
|
||||
import ca.uhn.fhir.rest.param.UriParam;
|
||||
import ca.uhn.fhir.rest.server.IRestfulServerDefaults;
|
||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import ca.uhn.fhir.rest.server.interceptor.partition.RequestTenantPartitionInterceptor;
|
||||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||
import ca.uhn.fhir.test.utilities.JettyUtil;
|
||||
import ca.uhn.fhir.test.utilities.ProxyUtil;
|
||||
import ca.uhn.fhir.util.JsonUtil;
|
||||
|
@ -63,6 +70,8 @@ import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
|||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class NpmR4Test extends BaseJpaR4Test {
|
||||
|
||||
|
@ -81,6 +90,10 @@ public class NpmR4Test extends BaseJpaR4Test {
|
|||
@Autowired
|
||||
private INpmPackageVersionResourceDao myPackageVersionResourceDao;
|
||||
private FakeNpmServlet myFakeNpmServlet;
|
||||
@Autowired
|
||||
private IInterceptorService myInterceptorService;
|
||||
@Autowired
|
||||
private RequestTenantPartitionInterceptor myRequestTenantPartitionInterceptor;
|
||||
|
||||
@BeforeEach
|
||||
public void before() throws Exception {
|
||||
|
@ -105,6 +118,8 @@ public class NpmR4Test extends BaseJpaR4Test {
|
|||
public void after() throws Exception {
|
||||
JettyUtil.closeServer(myServer);
|
||||
myDaoConfig.setAllowExternalReferences(new DaoConfig().isAllowExternalReferences());
|
||||
myPartitionSettings.setPartitioningEnabled(false);
|
||||
myInterceptorService.unregisterInterceptor(myRequestTenantPartitionInterceptor);
|
||||
}
|
||||
|
||||
|
||||
|
@ -273,6 +288,47 @@ public class NpmR4Test extends BaseJpaR4Test {
|
|||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInstallR4Package_NonConformanceResources_Partitioned() throws Exception {
|
||||
myPartitionSettings.setPartitioningEnabled(true);
|
||||
myInterceptorService.registerInterceptor(myRequestTenantPartitionInterceptor);
|
||||
myDaoConfig.setAllowExternalReferences(true);
|
||||
|
||||
byte[] bytes = loadClasspathBytes("/packages/test-organizations-package.tgz");
|
||||
myFakeNpmServlet.myResponses.put("/test-organizations/1.0.0", bytes);
|
||||
|
||||
List<String> resourceList = new ArrayList<>();
|
||||
resourceList.add("Organization");
|
||||
PackageInstallationSpec spec = new PackageInstallationSpec().setName("test-organizations").setVersion("1.0.0").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_AND_INSTALL);
|
||||
spec.setInstallResourceTypes(resourceList);
|
||||
PackageInstallOutcomeJson outcome = igInstaller.install(spec);
|
||||
assertEquals(3, outcome.getResourcesInstalled().get("Organization"));
|
||||
|
||||
// Be sure no further communication with the server
|
||||
JettyUtil.closeServer(myServer);
|
||||
|
||||
// Search for the installed resources
|
||||
mySrd = mock(ServletRequestDetails.class);
|
||||
when(mySrd.getTenantId()).thenReturn(JpaConstants.DEFAULT_PARTITION_NAME);
|
||||
when(mySrd.getServer()).thenReturn(mock(RestfulServer.class));
|
||||
when(mySrd.getInterceptorBroadcaster()).thenReturn(mock(IInterceptorBroadcaster.class));
|
||||
runInTransaction(() -> {
|
||||
SearchParameterMap map = SearchParameterMap.newSynchronous();
|
||||
map.add(Organization.SP_IDENTIFIER, new TokenParam("https://github.com/synthetichealth/synthea", "organization1"));
|
||||
IBundleProvider result = myOrganizationDao.search(map, mySrd);
|
||||
assertEquals(1, result.sizeOrThrowNpe());
|
||||
map = SearchParameterMap.newSynchronous();
|
||||
map.add(Organization.SP_IDENTIFIER, new TokenParam("https://github.com/synthetichealth/synthea", "organization2"));
|
||||
result = myOrganizationDao.search(map, mySrd);
|
||||
assertEquals(1, result.sizeOrThrowNpe());
|
||||
map = SearchParameterMap.newSynchronous();
|
||||
map.add(Organization.SP_IDENTIFIER, new TokenParam("https://github.com/synthetichealth/synthea", "organization3"));
|
||||
result = myOrganizationDao.search(map, mySrd);
|
||||
assertEquals(1, result.sizeOrThrowNpe());
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInstallR4Package_NoIdentifierNoUrl() throws Exception {
|
||||
myDaoConfig.setAllowExternalReferences(true);
|
||||
|
|
Loading…
Reference in New Issue