diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_2_0/2141-enable-non-conformance-resources-in-package-installer.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_2_0/2141-enable-non-conformance-resources-in-package-installer.yaml new file mode 100644 index 00000000000..040775c632d --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_2_0/2141-enable-non-conformance-resources-in-package-installer.yaml @@ -0,0 +1,5 @@ +--- +type: add +issue: 2141 +title: "The Package Installer has been enhanced to allow resources that do not have a url element to be loaded + from a package. Resources without url will instead use identifier element." diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/packages/PackageInstallerSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/packages/PackageInstallerSvcImpl.java index 46256602d0a..0433f4da73e 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/packages/PackageInstallerSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/packages/PackageInstallerSvcImpl.java @@ -20,6 +20,9 @@ package ca.uhn.fhir.jpa.packages; * #L% */ +import ca.uhn.fhir.context.BaseRuntimeChildDefinition; +import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition; +import ca.uhn.fhir.context.BaseRuntimeElementDefinition; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.context.support.IValidationSupport; @@ -46,6 +49,7 @@ import org.apache.commons.lang3.Validate; 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.cache.IPackageCacheManager; import org.hl7.fhir.utilities.cache.NpmPackage; import org.slf4j.Logger; @@ -384,9 +388,12 @@ public class PackageInstallerSvcImpl implements IPackageInstallerSvc { } else if (resource.getClass().getSimpleName().equals("Subscription")) { String id = extractIdFromSubscription(resource); return SearchParameterMap.newSynchronous().add("_id", new TokenParam(id)); - } else { + } else if (resourceHasUrlElement(resource)) { String url = extractUniqueUrlFromMetadataResource(resource); return SearchParameterMap.newSynchronous().add("url", new UriParam(url)); + } else { + TokenParam identifierToken = extractIdentifierFromOtherResourceTypes(resource); + return SearchParameterMap.newSynchronous().add("identifier", identifierToken); } } @@ -412,6 +419,26 @@ public class PackageInstallerSvcImpl implements IPackageInstallerSvc { return (String) asPrimitiveType.getValue(); } + private TokenParam extractIdentifierFromOtherResourceTypes(IBaseResource resource) { + FhirTerser terser = myFhirContext.newTerser(); + Identifier identifier = (Identifier) terser.getSingleValueOrNull(resource, "identifier"); + if (identifier != null) { + return new TokenParam(identifier.getSystem(), identifier.getValue()); + } else { + throw new UnsupportedOperationException("Resources in a package must have a url or identifier to be loaded by the package installer."); + } + } + + private boolean resourceHasUrlElement(IBaseResource resource) { + BaseRuntimeElementDefinition def = myFhirContext.getElementDefinition(resource.getClass()); + if (!(def instanceof BaseRuntimeElementCompositeDefinition)) { + throw new IllegalArgumentException("Resource is not a composite type: " + resource.getClass().getName()); + } + BaseRuntimeElementCompositeDefinition currentDef = (BaseRuntimeElementCompositeDefinition) def; + BaseRuntimeChildDefinition nextDef = currentDef.getChildByName("url"); + return nextDef != null; + } + @VisibleForTesting void setFhirContextForUnitTest(FhirContext theCtx) { myFhirContext = theCtx; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/packages/NpmTestR4.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/packages/NpmTestR4.java index bee6c8ffbec..0c8726f47cb 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/packages/NpmTestR4.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/packages/NpmTestR4.java @@ -11,7 +11,6 @@ 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.searchparam.SearchParameterMap; -import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.param.ReferenceParam; @@ -48,6 +47,7 @@ import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -61,6 +61,7 @@ import static org.hamcrest.Matchers.not; 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; public class NpmTestR4 extends BaseJpaR4Test { @@ -236,6 +237,59 @@ public class NpmTestR4 extends BaseJpaR4Test { }); } + @Test + public void testInstallR4Package_NonConformanceResources() throws Exception { + myDaoConfig.setAllowExternalReferences(true); + + byte[] bytes = loadClasspathBytes("/packages/test-organizations-package.tgz"); + myFakeNpmServlet.myResponses.put("/test-organizations/1.0.0", bytes); + + List 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 + runInTransaction(() -> { + SearchParameterMap map = SearchParameterMap.newSynchronous(); + map.add(Organization.SP_IDENTIFIER, new TokenParam("https://github.com/synthetichealth/synthea", "organization1")); + IBundleProvider result = myOrganizationDao.search(map); + assertEquals(1, result.sizeOrThrowNpe()); + map = SearchParameterMap.newSynchronous(); + map.add(Organization.SP_IDENTIFIER, new TokenParam("https://github.com/synthetichealth/synthea", "organization2")); + result = myOrganizationDao.search(map); + assertEquals(1, result.sizeOrThrowNpe()); + map = SearchParameterMap.newSynchronous(); + map.add(Organization.SP_IDENTIFIER, new TokenParam("https://github.com/synthetichealth/synthea", "organization3")); + result = myOrganizationDao.search(map); + assertEquals(1, result.sizeOrThrowNpe()); + }); + + } + + @Test + public void testInstallR4Package_NoIdentifierNoUrl() throws Exception { + myDaoConfig.setAllowExternalReferences(true); + + byte[] bytes = loadClasspathBytes("/packages/test-missing-identifier-package.tgz"); + myFakeNpmServlet.myResponses.put("/test-organizations/1.0.0", bytes); + + List 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); + try { + PackageInstallOutcomeJson outcome = igInstaller.install(spec); + fail(); + } catch (ImplementationGuideInstallationException theE) { + assertThat(theE.getMessage(), containsString("Resources in a package must have a url or identifier to be loaded by the package installer.")); + } + } @Test public void testInstallR4Package_DraftResourcesNotInstalled() throws Exception { diff --git a/hapi-fhir-jpaserver-base/src/test/resources/packages/test-missing-identifier-package.tgz b/hapi-fhir-jpaserver-base/src/test/resources/packages/test-missing-identifier-package.tgz new file mode 100644 index 00000000000..46025afc0a2 Binary files /dev/null and b/hapi-fhir-jpaserver-base/src/test/resources/packages/test-missing-identifier-package.tgz differ diff --git a/hapi-fhir-jpaserver-base/src/test/resources/packages/test-organizations-package.tgz b/hapi-fhir-jpaserver-base/src/test/resources/packages/test-organizations-package.tgz new file mode 100644 index 00000000000..a3f53110d0c Binary files /dev/null and b/hapi-fhir-jpaserver-base/src/test/resources/packages/test-organizations-package.tgz differ