Skip to content

Commit

Permalink
IPS API Refactor (#5682)
Browse files Browse the repository at this point in the history
* IPS enhancements

* API design complete

* Work on section registry

* Work on external fetch

* IPS rewrite

* Cleanup

* Work

* IPS refactor

* Add changelog

* Changelog updates

* Spotless

* Compile fix

* Address review comments

* Address review comments

* License header

* Revert narrative builder change

* Address review comments

* Addres review comments

* Cleanup
  • Loading branch information
jamesagnew authored Feb 11, 2024
1 parent d71736b commit 12eb2d6
Show file tree
Hide file tree
Showing 69 changed files with 3,445 additions and 1,474 deletions.
5 changes: 5 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ tab_width = 4
indent_size = 4
charset = utf-8

[*.html]
indent_style = tab
tab_width = 3
indent_size = 3

[*.xml]
indent_style = tab
tab_width = 3
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.narrative2.BaseNarrativeGenerator;
import ca.uhn.fhir.narrative2.INarrativeTemplate;
import ca.uhn.fhir.narrative2.NarrativeGeneratorTemplateUtils;
import ca.uhn.fhir.narrative2.TemplateTypeEnum;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import com.google.common.collect.Sets;
Expand Down Expand Up @@ -109,6 +110,7 @@ protected String applyTemplate(FhirContext theFhirContext, INarrativeTemplate th
Context context = new Context();
context.setVariable("resource", theTargetContext);
context.setVariable("context", theTargetContext);
context.setVariable("narrativeUtil", NarrativeGeneratorTemplateUtils.INSTANCE);
context.setVariable(
"fhirVersion", theFhirContext.getVersion().getVersion().name());

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*-
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2024 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.narrative2;

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.util.BundleUtil;
import org.apache.commons.lang3.tuple.Pair;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IBaseResource;

import java.util.List;
import java.util.Objects;

/**
* An instance of this class is added to the Thymeleaf context as a variable with
* name <code>"narrativeUtil"</code> and can be accessed from narrative templates.
*
* @since 7.0.0
*/
public class NarrativeGeneratorTemplateUtils {

public static final NarrativeGeneratorTemplateUtils INSTANCE = new NarrativeGeneratorTemplateUtils();

/**
* Given a Bundle as input, are any entries present with a given resource type
*/
public boolean bundleHasEntriesWithResourceType(IBaseBundle theBaseBundle, String theResourceType) {
FhirContext ctx = theBaseBundle.getStructureFhirVersionEnum().newContextCached();
List<Pair<String, IBaseResource>> entryResources =
BundleUtil.getBundleEntryUrlsAndResources(ctx, theBaseBundle);
return entryResources.stream()
.map(Pair::getValue)
.filter(Objects::nonNull)
.anyMatch(t -> ctx.getResourceType(t).equals(theResourceType));
}
}
12 changes: 10 additions & 2 deletions hapi-fhir-base/src/main/java/ca/uhn/fhir/util/BundleBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -435,15 +435,15 @@ private void addRequestMethod(IBase theRequest, String theMethod) {
*/
public void addCollectionEntry(IBaseResource theResource) {
setType("collection");
addEntryAndReturnRequest(theResource, theResource.getIdElement().getValue());
addEntryAndReturnRequest(theResource);
}

/**
* Adds an entry for a Document bundle type
*/
public void addDocumentEntry(IBaseResource theResource) {
setType("document");
addEntryAndReturnRequest(theResource, theResource.getIdElement().getValue());
addEntryAndReturnRequest(theResource);
}

/**
Expand Down Expand Up @@ -475,6 +475,14 @@ public IBaseBackboneElement addSearch(IBase entry) {
return (IBaseBackboneElement) searchInstance;
}

private IBase addEntryAndReturnRequest(IBaseResource theResource) {
IIdType id = theResource.getIdElement();
if (id.hasVersionIdPart()) {
id = id.toVersionless();
}
return addEntryAndReturnRequest(theResource, id.getValue());
}

private IBase addEntryAndReturnRequest(IBaseResource theResource, String theFullUrl) {
Validate.notNull(theResource, "theResource must not be null");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;

import static org.apache.commons.lang3.StringUtils.isBlank;
Expand Down Expand Up @@ -82,6 +83,12 @@ public static void isTrueOrThrowInvalidRequest(boolean theSuccess, String theMes
}
}

public static void isTrueOrThrowResourceNotFound(boolean theSuccess, String theMessage, Object... theValues) {
if (!theSuccess) {
throw new ResourceNotFoundException(Msg.code(2494) + String.format(theMessage, theValues));
}
}

public static void exactlyOneNotNullOrThrowInvalidRequestException(Object[] theObjects, String theMessage) {
int count = 0;
for (Object next : theObjects) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import static org.junit.jupiter.api.Assertions.fail;

import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import org.junit.jupiter.api.Test;

Expand All @@ -12,7 +13,7 @@
public class ValidateUtilTest {

@Test
public void testValidate() {
public void testIsTrueOrThrowInvalidRequest() {
ValidateUtil.isTrueOrThrowInvalidRequest(true, "");

try {
Expand All @@ -23,6 +24,18 @@ public void testValidate() {
}
}

@Test
public void testIsTrueOrThrowResourceNotFound() {
ValidateUtil.isTrueOrThrowResourceNotFound(true, "");

try {
ValidateUtil.isTrueOrThrowResourceNotFound(false, "The message");
fail();
} catch (ResourceNotFoundException e) {
assertEquals(Msg.code(2494) + "The message", e.getMessage());
}
}

@Test
public void testIsGreaterThan() {
ValidateUtil.isGreaterThan(2L, 1L, "");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
type: fix
issue: 5682
title: "The BundleBuilder utility class will no longer include the `/_version/xxx` portion of the
resource ID in the `Bundle.entry.fullUrl` it generates, as the FHIR specification states that this
should be omitted."
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
type: change
issue: 5682
title: "The IPS $summary generation API has been overhauled to make it more flexible for
future use cases. Specifically, the section registry has been removed and folded into
the generation strategy, and support has been added for non-JPA sources of data. This is
a breaking change to the API, and implementers will need to update their code. This updated
API incorporates community feedback, and should now be considered a stable API for IPS
generation."
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
type: add
issue: 5682
title: "Several enhancements have been made to the International Patient Summary generator based on
feedback from implementers:
<ul>
<li>
New methods have been added to the <code>IIpsGenerationStrategy</code> allowing resources
for any or all sections to be fetched from a source other than the FHIR repository.
</li>
<li>
The <code>IpsSectionEnum</code> class has been removed and replaced in any user-facing APIs
with references to <code>SectionRegistry.Section</code>. This makes it much easier to
extend or replace the section registry with custom sections not defined in the universal
IPS implementation guide.
</li>
<li>
Captions have been removed from narrative section tables, and replaced with H5 tags
directly above the table. This results in an easier to read display since the table
title will appear above the table instead of below it.
</li>
<li>
The IPS narrative generator built in templates will now omit tables when the template
specified multiple tables and the specific table would have no resources.
</li>
</ul>"
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
type: fix
issue: 5682
title: "The IPS Generator will no longer replace resource IDs with placeholder IDs in the resulting
bundle by default, although this can be overridden in the generation strategy object."
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,17 @@ The IPS Generator uses FHIR resources stored in your repository as its input. Th

# Generation Strategy

A user supplied strategy class is used to determine various properties of the IPS. This class must implement the `IIpsGenerationStrategy` interface. A default implementation called `DefaultIpsGenerationStrategy` is included. You may use this default implementation, use a subclassed version of it that adds additional logic, or use en entirely new implementation.
A user supplied strategy class is used to determine various properties of the IPS. This class must implement the `IIpsGenerationStrategy` interface. A default implementation called `DefaultJpaIpsGenerationStrategy` is included. You may use this default implementation, use a subclassed version of it that adds additional logic, or use en entirely new implementation.

The generation strategy also supplies the [Section Registry](#section-registry) and [Narrative Templates](#narrative-templates) implementations, so it can be considered the central part of your IPS configuration.
The generation strategy also supplies the [Narrative Templates](#narrative-templates) implementations, so it can be considered the central part of your IPS configuration.

* JavaDoc: [IIpsGenerationStrategy](/hapi-fhir/apidocs/hapi-fhir-jpaserver-ips/ca/uhn/fhir/jpa/ips/api/IIpsGenerationStrategy.html)
* Source Code: [IIpsGenerationStrategy.java](https://github.com/hapifhir/hapi-fhir/blob/master/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/api/IIpsGenerationStrategy.java)
* JavaDoc: [DefaultIpsGenerationStrategy](/hapi-fhir/apidocs/hapi-fhir-jpaserver-ips/ca/uhn/fhir/jpa/ips/strategy/DefaultIpsGenerationStrategy.html)
* Source Code: [DefaultIpsGenerationStrategy.java](https://github.com/hapifhir/hapi-fhir/blob/master/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/strategy/DefaultIpsGenerationStrategy.java)

The default generation strategy defines the sections that will be included in your IPS. Out of the box, the standard IPS sections are all included. See the [IG homepage](http://hl7.org/fhir/uv/ips/) for a list of the standard sections.

<a name="section-registry"/>

# Section Registry

The IPS SectionRegistry class defines the sections that will be included in your IPS. Out of the box, the standard IPS sections are all included. See the [IG homepage](http://hl7.org/fhir/uv/ips/) for a list of the standard sections.

* JavaDoc: [SectionRegistry](/hapi-fhir/apidocs/hapi-fhir-jpaserver-ips/ca/uhn/fhir/jpa/ips/api/SectionRegistry.html)
* Source Code: [SectionRegistry.java](https://github.com/hapifhir/hapi-fhir/blob/master/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/api/SectionRegistry.java)
* JavaDoc: [DefaultJpaIpsGenerationStrategy](/hapi-fhir/apidocs/hapi-fhir-jpaserver-ips/ca/uhn/fhir/jpa/ips/jpa/DefaultJpaIpsGenerationStrategy.html)
* Source Code: [DefaultJpaIpsGenerationStrategy.java](https://github.com/hapifhir/hapi-fhir/blob/master/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/DefaultJpaIpsGenerationStrategy.java)


<a name="narrative-templates"/>
Expand All @@ -44,7 +37,7 @@ The IPS generator uses HAPI FHIR [Narrative Generation](/hapi-fhir/docs/model/na

Narrative templates for individual sections will be supplied a Bundle resource containing only the matched resources for the individual section as entries (ie. the Composition itself will not be present and no other resources will be present). So, for example, when generating the _Allergies / Intolerances_ IPS section narrative, the input to the narrative generator will be a _Bundle_ resource containing only _AllergyIntolerance_ resources.

The narrative properties file should contain definitions using the profile URL of the individual section (as defined in the [section registry](#section-registry)) as the `.profile` qualifier. For example:
The narrative properties file should contain definitions using the profile URL of the individual section (as defined in the section definition within the generation strategy) as the `.profile` qualifier. For example:

```properties
ips-allergyintolerance.resourceType=Bundle
Expand Down
Loading

0 comments on commit 12eb2d6

Please sign in to comment.