diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/BundleBuilder.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/BundleBuilder.java
index 2b030a8953e..d8b7665be3d 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/BundleBuilder.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/BundleBuilder.java
@@ -626,6 +626,16 @@ public class BundleBuilder {
terser.setElement(myBundle, "Bundle.timestamp", theTimestamp.getValueAsString());
}
+ /**
+ * Adds a profile URL to Bundle.meta.profile
+ *
+ * @since 7.4.0
+ */
+ public void addProfile(String theProfile) {
+ FhirTerser terser = myContext.newTerser();
+ terser.addElement(myBundle, "Bundle.meta.profile", theProfile);
+ }
+
public class DeleteBuilder extends BaseOperationBuilder {
// nothing yet
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_4_0/5938-include-bundle-profile-in-ips.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_4_0/5938-include-bundle-profile-in-ips.yaml
new file mode 100644
index 00000000000..c631d878c61
--- /dev/null
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_4_0/5938-include-bundle-profile-in-ips.yaml
@@ -0,0 +1,4 @@
+---
+type: add
+issue: 5938
+title: "Generated IPS documents will now include a bundle profile declaration."
diff --git a/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/api/IIpsGenerationStrategy.java b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/api/IIpsGenerationStrategy.java
index bad1470d646..9be8fe35c2b 100644
--- a/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/api/IIpsGenerationStrategy.java
+++ b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/api/IIpsGenerationStrategy.java
@@ -42,7 +42,10 @@ public interface IIpsGenerationStrategy {
/**
* This method returns the profile associated with the IPS document
- * generated by this strategy.
+ * generated by this strategy. This URL will be added to generated
+ * IPS Bundles in Bundle.meta.profile
, and can also
+ * be used to support the profile
parameter on the
+ * $summary
operation.
*/
String getBundleProfile();
diff --git a/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/generator/IpsGeneratorSvcImpl.java b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/generator/IpsGeneratorSvcImpl.java
index 7562c5daf93..aa2db895631 100644
--- a/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/generator/IpsGeneratorSvcImpl.java
+++ b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/generator/IpsGeneratorSvcImpl.java
@@ -146,6 +146,7 @@ public class IpsGeneratorSvcImpl implements IIpsGeneratorSvc {
bundleBuilder.setType(Bundle.BundleType.DOCUMENT.toCode());
bundleBuilder.setIdentifier("urn:ietf:rfc:4122", UUID.randomUUID().toString());
bundleBuilder.setTimestamp(InstantType.now());
+ bundleBuilder.addProfile(theStrategy.getBundleProfile());
// Add composition to document
bundleBuilder.addDocumentEntry(composition);
diff --git a/hapi-fhir-jpaserver-ips/src/test/java/ca/uhn/fhir/jpa/ips/generator/IpsGenerationR4Test.java b/hapi-fhir-jpaserver-ips/src/test/java/ca/uhn/fhir/jpa/ips/generator/IpsGenerationR4Test.java
index bb9bf90b283..e697ef20b2a 100644
--- a/hapi-fhir-jpaserver-ips/src/test/java/ca/uhn/fhir/jpa/ips/generator/IpsGenerationR4Test.java
+++ b/hapi-fhir-jpaserver-ips/src/test/java/ca/uhn/fhir/jpa/ips/generator/IpsGenerationR4Test.java
@@ -5,7 +5,6 @@ import ca.uhn.fhir.context.support.ConceptValidationOptions;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.ValidationSupportContext;
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
-import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.ips.api.IIpsGenerationStrategy;
import ca.uhn.fhir.jpa.ips.jpa.DefaultJpaIpsGenerationStrategy;
import ca.uhn.fhir.jpa.ips.provider.IpsOperationProvider;
@@ -18,6 +17,7 @@ import ca.uhn.fhir.validation.SingleValidationMessage;
import ca.uhn.fhir.validation.ValidationResult;
import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;
+import org.hl7.fhir.common.hapi.validation.support.NpmPackageValidationSupport;
import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain;
import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator;
import org.hl7.fhir.instance.model.api.IBaseResource;
@@ -31,6 +31,7 @@ import org.hl7.fhir.r4.model.Immunization;
import org.hl7.fhir.r4.model.MedicationStatement;
import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.Patient;
+import org.hl7.fhir.r4.model.PrimitiveType;
import org.hl7.fhir.r4.model.Reference;
import org.hl7.fhir.r4.model.Resource;
import org.junit.jupiter.api.AfterEach;
@@ -41,6 +42,7 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration;
+import java.io.IOException;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@@ -48,10 +50,10 @@ import java.util.stream.Collectors;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.stringContainsInOrder;
+import static org.hibernate.validator.internal.util.Contracts.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
-import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* This test uses a complete R4 JPA server as a backend and wires the
@@ -77,7 +79,7 @@ public class IpsGenerationR4Test extends BaseResourceProviderR4Test {
@Test
- public void testGenerateLargePatientSummary() {
+ public void testGenerateLargePatientSummary() throws IOException {
Bundle sourceData = ClasspathUtil.loadCompressedResource(myFhirContext, Bundle.class, "/large-patient-everything.json.gz");
sourceData.setType(Bundle.BundleType.TRANSACTION);
for (Bundle.BundleEntryComponent nextEntry : sourceData.getEntry()) {
@@ -97,6 +99,9 @@ public class IpsGenerationR4Test extends BaseResourceProviderR4Test {
ourLog.info("Output: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(output));
// Verify
+ assertThat(output.getMeta().getProfile().stream().map(PrimitiveType::getValue).toList(), contains(
+ "http://hl7.org/fhir/uv/ips/StructureDefinition/Bundle-uv-ips"
+ ));
validateDocument(output);
assertEquals(117, output.getEntry().size());
String patientId = findFirstEntryResource(output, Patient.class, 1).getIdElement().toUnqualifiedVersionless().getValue();
@@ -162,7 +167,7 @@ public class IpsGenerationR4Test extends BaseResourceProviderR4Test {
}
@Test
- public void testGenerateTinyPatientSummary() {
+ public void testGenerateTinyPatientSummary() throws IOException {
myStorageSettings.setResourceClientIdStrategy(JpaStorageSettings.ClientIdStrategyEnum.ANY);
Bundle sourceData = ClasspathUtil.loadCompressedResource(myFhirContext, Bundle.class, "/tiny-patient-everything.json.gz");
@@ -251,7 +256,7 @@ public class IpsGenerationR4Test extends BaseResourceProviderR4Test {
@Nonnull
- private static Composition findCompositionSectionByDisplay(Bundle output, String theDisplay) {
+ private static Composition findCompositionSectionByDisplay(Bundle output, @SuppressWarnings("SameParameterValue") String theDisplay) {
Composition composition = (Composition) output.getEntry().get(0).getResource();
Composition.SectionComponent section = composition
.getSection()
@@ -259,6 +264,7 @@ public class IpsGenerationR4Test extends BaseResourceProviderR4Test {
.filter(t -> t.getCode().getCoding().get(0).getDisplay().equals(theDisplay))
.findFirst()
.orElseThrow();
+ assertNotNull(section);
return composition;
}
@@ -266,23 +272,26 @@ public class IpsGenerationR4Test extends BaseResourceProviderR4Test {
@Nonnull
private static List extractSectionTitles(Bundle outcome) {
Composition composition = (Composition) outcome.getEntry().get(0).getResource();
- List sectionTitles = composition
+ return composition
.getSection()
.stream()
.map(Composition.SectionComponent::getTitle)
.toList();
- return sectionTitles;
}
- private void validateDocument(Bundle theOutcome) {
+ private void validateDocument(Bundle theOutcome) throws IOException {
FhirValidator validator = myFhirContext.newValidator();
FhirInstanceValidator instanceValidator = new FhirInstanceValidator(myFhirContext);
- instanceValidator.setValidationSupport(new ValidationSupportChain(new IpsTerminologySvc(), myFhirContext.getValidationSupport()));
+
+ NpmPackageValidationSupport npmSupport = new NpmPackageValidationSupport(myFhirContext);
+ npmSupport.loadPackageFromClasspath("/ips-package-1.1.0.tgz");
+
+ instanceValidator.setValidationSupport(new ValidationSupportChain(npmSupport, new IpsTerminologySvc(), myFhirContext.getValidationSupport()));
validator.registerValidatorModule(instanceValidator);
ValidationResult validation = validator.validateWithResult(theOutcome);
Optional failure = validation.getMessages().stream().filter(t -> t.getSeverity().ordinal() >= ResultSeverityEnum.ERROR.ordinal()).findFirst();
- assertFalse(failure.isPresent(), () -> failure.get().toString());
+ assertFalse(failure.isPresent(), () -> failure.orElseThrow().toString());
}
@Configuration
@@ -294,7 +303,7 @@ public class IpsGenerationR4Test extends BaseResourceProviderR4Test {
}
@Bean
- public IIpsGeneratorSvc ipsGeneratorSvc(FhirContext theFhirContext, IIpsGenerationStrategy theGenerationStrategy, DaoRegistry theDaoRegistry) {
+ public IIpsGeneratorSvc ipsGeneratorSvc(FhirContext theFhirContext, IIpsGenerationStrategy theGenerationStrategy) {
return new IpsGeneratorSvcImpl(theFhirContext, theGenerationStrategy);
}
diff --git a/hapi-fhir-jpaserver-ips/src/test/resources/ips-package-1.1.0.tgz b/hapi-fhir-jpaserver-ips/src/test/resources/ips-package-1.1.0.tgz
new file mode 100644
index 00000000000..d43871cdd48
Binary files /dev/null and b/hapi-fhir-jpaserver-ips/src/test/resources/ips-package-1.1.0.tgz differ