From 5a3de7c3dd7db2b45dc665b909a8ce3409eef1af Mon Sep 17 00:00:00 2001 From: jamesagnew Date: Tue, 1 Apr 2014 08:12:45 -0400 Subject: [PATCH] Narrative generator docs --- .../ca/uhn/fhir/model/api/ExtensionDt.java | 2 +- .../dstu/composite/ResourceReferenceDt.java | 1 + .../model/dstu/resource/AdverseReaction.java | 6 + .../uhn/fhir/model/dstu/resource/Alert.java | 6 + .../fhir/model/dstu/resource/Conformance.java | 6 + .../uhn/fhir/model/dstu/resource/Device.java | 6 + .../model/dstu/resource/DiagnosticOrder.java | 6 + .../model/dstu/resource/DiagnosticReport.java | 6 + .../fhir/model/dstu/resource/Encounter.java | 6 + .../uhn/fhir/model/dstu/resource/Group.java | 6 + .../model/dstu/resource/ImagingStudy.java | 6 + .../fhir/model/dstu/resource/Location.java | 6 + .../uhn/fhir/model/dstu/resource/Media.java | 6 + .../fhir/model/dstu/resource/Medication.java | 6 + .../fhir/model/dstu/resource/Observation.java | 6 + .../model/dstu/resource/OperationOutcome.java | 6 + .../model/dstu/resource/Organization.java | 6 + .../uhn/fhir/model/dstu/resource/Patient.java | 6 + .../model/dstu/resource/Practitioner.java | 6 + .../uhn/fhir/model/dstu/resource/Profile.java | 6 + .../uhn/fhir/model/dstu/resource/Query.java | 6 + .../model/dstu/resource/Questionnaire.java | 6 + .../model/dstu/resource/RelatedPerson.java | 6 + .../fhir/model/dstu/resource/Specimen.java | 6 + .../fhir/model/dstu/resource/Substance.java | 6 + .../fhir/model/dstu/resource/ValueSet.java | 6 + .../primitive/BoundCodeableConceptDt.java | 18 +- .../fhir/model/primitive/BoundCodingDt__.java | 31 -- .../BaseThymeleafNarrativeGenerator.java | 376 +++++++++++++++++- .../CustomThymeleafNarrativeGenerator.java | 24 ++ .../DefaultThymeleafNarrativeGenerator.java | 347 +--------------- .../ca/uhn/fhir/narrative/AddressDt.html | 8 + .../ca/uhn/fhir/narrative/Patient.html | 19 +- .../uhn/fhir/narrative/narratives.properties | 35 +- .../site/example/java/example/Narrative.java | 2 +- hapi-fhir-base/src/site/site.xml | 3 +- .../src/site/xdoc/doc_narrative.xml | 4 +- .../BaseThymeleafNarrativeGeneratorTest.java | 62 +++ ...faultThymeleafNarrativeGeneratorTest.java} | 55 +-- .../test/resources/narrative/Practitoner.html | 21 + .../narrative/customnarative.properties | 21 + .../src/main/resources/vm/resource.vm | 8 + 42 files changed, 719 insertions(+), 462 deletions(-) delete mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/BoundCodingDt__.java create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/narrative/CustomThymeleafNarrativeGenerator.java create mode 100644 hapi-fhir-base/src/main/resources/ca/uhn/fhir/narrative/AddressDt.html create mode 100644 hapi-fhir-base/src/test/java/ca/uhn/fhir/narrative/BaseThymeleafNarrativeGeneratorTest.java rename hapi-fhir-base/src/test/java/ca/uhn/fhir/narrative/{ThymeleafNarrativeGeneratorTest.java => DefaultThymeleafNarrativeGeneratorTest.java} (69%) create mode 100644 hapi-fhir-base/src/test/resources/narrative/Practitoner.html create mode 100644 hapi-fhir-base/src/test/resources/narrative/customnarative.properties diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/ExtensionDt.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/ExtensionDt.java index f33340a6905..0899a296e6f 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/ExtensionDt.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/ExtensionDt.java @@ -1,6 +1,6 @@ package ca.uhn.fhir.model.api; -import org.thymeleaf.util.Validate; +import org.apache.commons.lang3.Validate; import ca.uhn.fhir.model.api.annotation.Child; import ca.uhn.fhir.model.api.annotation.DatatypeDef; diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/composite/ResourceReferenceDt.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/composite/ResourceReferenceDt.java index 3b9c7b83e2c..c555d1537c3 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/composite/ResourceReferenceDt.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/composite/ResourceReferenceDt.java @@ -120,6 +120,7 @@ public class ResourceReferenceDt * A reference to a location at which the other resource is found. The reference may a relative reference, in which case it is relative to the service base URL, or an absolute URL that resolves to the location where the resource is found. The reference may be version specific or not. If the reference is not to a FHIR RESTful server, then it should be assumed to be version specific. Internal fragment references (start with '#') refer to contained resources *

*/ + @Override public StringDt getReference() { if (myReference == null) { myReference = new StringDt(); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/AdverseReaction.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/AdverseReaction.java index c49e6cf4f20..0cbbaeca412 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/AdverseReaction.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/AdverseReaction.java @@ -56,6 +56,12 @@ import ca.uhn.fhir.model.primitive.DateTimeDt; * Requirements: * Used to track reactions when it is unknown the exact cause but there's a desire to flag/track potential causes. Also used to capture reactions that are significant for inclusion in the health record or as evidence for an allergy or intolerance. *

+ * + *

+ * Profile Definition: + * http://hl7.org/fhir/profiles/AdverseReaction + *

+ * */ @ResourceDef(name="AdverseReaction", profile="http://hl7.org/fhir/profiles/AdverseReaction", id="adversereaction") public class AdverseReaction extends BaseResource implements IResource { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Alert.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Alert.java index b0d05cebd91..9466748f063 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Alert.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Alert.java @@ -48,6 +48,12 @@ import ca.uhn.fhir.model.primitive.StringDt; * Requirements: * *

+ * + *

+ * Profile Definition: + * http://hl7.org/fhir/profiles/Alert + *

+ * */ @ResourceDef(name="Alert", profile="http://hl7.org/fhir/profiles/Alert", id="alert") public class Alert extends BaseResource implements IResource { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Conformance.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Conformance.java index c6929b3d8bd..91fa3943a53 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Conformance.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Conformance.java @@ -69,6 +69,12 @@ import ca.uhn.fhir.model.primitive.UriDt; * Requirements: * *

+ * + *

+ * Profile Definition: + * http://hl7.org/fhir/profiles/Conformance + *

+ * */ @ResourceDef(name="Conformance", profile="http://hl7.org/fhir/profiles/Conformance", id="conformance") public class Conformance extends BaseResource implements IResource { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Device.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Device.java index 6a228a410f0..90f1a82573e 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Device.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Device.java @@ -50,6 +50,12 @@ import ca.uhn.fhir.model.primitive.UriDt; * Requirements: * Allows institutions to track their devices. *

+ * + *

+ * Profile Definition: + * http://hl7.org/fhir/profiles/Device + *

+ * */ @ResourceDef(name="Device", profile="http://hl7.org/fhir/profiles/Device", id="device") public class Device extends BaseResource implements IResource { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/DiagnosticOrder.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/DiagnosticOrder.java index c1ebe512972..fcef04603fc 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/DiagnosticOrder.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/DiagnosticOrder.java @@ -55,6 +55,12 @@ import ca.uhn.fhir.model.primitive.StringDt; * Requirements: * *

+ * + *

+ * Profile Definition: + * http://hl7.org/fhir/profiles/DiagnosticOrder + *

+ * */ @ResourceDef(name="DiagnosticOrder", profile="http://hl7.org/fhir/profiles/DiagnosticOrder", id="diagnosticorder") public class DiagnosticOrder extends BaseResource implements IResource { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/DiagnosticReport.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/DiagnosticReport.java index f153b988c7f..65aff370ea7 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/DiagnosticReport.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/DiagnosticReport.java @@ -57,6 +57,12 @@ import ca.uhn.fhir.model.primitive.StringDt; * Requirements: * To support reporting for any diagnostic report into a clinical data repository. *

+ * + *

+ * Profile Definition: + * http://hl7.org/fhir/profiles/DiagnosticReport + *

+ * */ @ResourceDef(name="DiagnosticReport", profile="http://hl7.org/fhir/profiles/DiagnosticReport", id="diagnosticreport") public class DiagnosticReport extends BaseResource implements IResource { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Encounter.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Encounter.java index 2bff69d41bc..d08608d0c23 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Encounter.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Encounter.java @@ -59,6 +59,12 @@ import ca.uhn.fhir.model.primitive.CodeDt; * Requirements: * *

+ * + *

+ * Profile Definition: + * http://hl7.org/fhir/profiles/Encounter + *

+ * */ @ResourceDef(name="Encounter", profile="http://hl7.org/fhir/profiles/Encounter", id="encounter") public class Encounter extends BaseResource implements IResource { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Group.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Group.java index 47aba66bba1..83ff009fa1c 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Group.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Group.java @@ -56,6 +56,12 @@ import ca.uhn.fhir.model.primitive.StringDt; * Requirements: * *

+ * + *

+ * Profile Definition: + * http://hl7.org/fhir/profiles/Group + *

+ * */ @ResourceDef(name="Group", profile="http://hl7.org/fhir/profiles/Group", id="group") public class Group extends BaseResource implements IResource { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/ImagingStudy.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/ImagingStudy.java index 32fb139d8d7..b4d9d1927cc 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/ImagingStudy.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/ImagingStudy.java @@ -59,6 +59,12 @@ import ca.uhn.fhir.model.primitive.UriDt; * Requirements: * *

+ * + *

+ * Profile Definition: + * http://hl7.org/fhir/profiles/ImagingStudy + *

+ * */ @ResourceDef(name="ImagingStudy", profile="http://hl7.org/fhir/profiles/ImagingStudy", id="imagingstudy") public class ImagingStudy extends BaseResource implements IResource { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Location.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Location.java index 35e5cf888a8..005ef9bef80 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Location.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Location.java @@ -57,6 +57,12 @@ import ca.uhn.fhir.model.primitive.StringDt; * Requirements: * *

+ * + *

+ * Profile Definition: + * http://hl7.org/fhir/profiles/Location + *

+ * */ @ResourceDef(name="Location", profile="http://hl7.org/fhir/profiles/Location", id="location") public class Location extends BaseResource implements IResource { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Media.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Media.java index e643e588c0b..bb06730710d 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Media.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Media.java @@ -53,6 +53,12 @@ import ca.uhn.fhir.model.primitive.StringDt; * Requirements: * *

+ * + *

+ * Profile Definition: + * http://hl7.org/fhir/profiles/Media + *

+ * */ @ResourceDef(name="Media", profile="http://hl7.org/fhir/profiles/Media", id="media") public class Media extends BaseResource implements IResource { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Medication.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Medication.java index f31a05d0edc..644a3caf550 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Medication.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Medication.java @@ -51,6 +51,12 @@ import ca.uhn.fhir.model.primitive.StringDt; * Requirements: * *

+ * + *

+ * Profile Definition: + * http://hl7.org/fhir/profiles/Medication + *

+ * */ @ResourceDef(name="Medication", profile="http://hl7.org/fhir/profiles/Medication", id="medication") public class Medication extends BaseResource implements IResource { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Observation.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Observation.java index 1ec5d2581e1..87be1f7656e 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Observation.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Observation.java @@ -67,6 +67,12 @@ import ca.uhn.fhir.model.primitive.StringDt; * Requirements: * Observations are a key aspect of healthcare. This resource is used to capture those that do not require more sophisticated mechanisms. *

+ * + *

+ * Profile Definition: + * http://hl7.org/fhir/profiles/Observation + *

+ * */ @ResourceDef(name="Observation", profile="http://hl7.org/fhir/profiles/Observation", id="observation") public class Observation extends BaseResource implements IResource { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/OperationOutcome.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/OperationOutcome.java index c34e12ce48a..d157b03b821 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/OperationOutcome.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/OperationOutcome.java @@ -48,6 +48,12 @@ import ca.uhn.fhir.model.primitive.StringDt; * Requirements: * *

+ * + *

+ * Profile Definition: + * http://hl7.org/fhir/profiles/OperationOutcome + *

+ * */ @ResourceDef(name="OperationOutcome", profile="http://hl7.org/fhir/profiles/OperationOutcome", id="operationoutcome") public class OperationOutcome extends BaseResource implements IResource { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Organization.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Organization.java index 8fc8f7dfd71..3b6c82c59e5 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Organization.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Organization.java @@ -55,6 +55,12 @@ import ca.uhn.fhir.model.primitive.StringDt; * Requirements: * *

+ * + *

+ * Profile Definition: + * http://hl7.org/fhir/profiles/Organization + *

+ * */ @ResourceDef(name="Organization", profile="http://hl7.org/fhir/profiles/Organization", id="organization") public class Organization extends BaseResource implements IResource { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Patient.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Patient.java index 057f9fd05da..7eb4b86be78 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Patient.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Patient.java @@ -64,6 +64,12 @@ import ca.uhn.fhir.model.primitive.IntegerDt; * Requirements: * Tracking patient is the center of the healthcare process *

+ * + *

+ * Profile Definition: + * http://hl7.org/fhir/profiles/Patient + *

+ * */ @ResourceDef(name="Patient", profile="http://hl7.org/fhir/profiles/Patient", id="patient") public class Patient extends BaseResource implements IResource { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Practitioner.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Practitioner.java index dbf3c9d5a1f..2cfef555766 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Practitioner.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Practitioner.java @@ -59,6 +59,12 @@ import ca.uhn.fhir.model.primitive.DateTimeDt; * Requirements: * Need to track doctors, staff, locums etc. for both healthcare practitioners, funders, etc. *

+ * + *

+ * Profile Definition: + * http://hl7.org/fhir/profiles/Practitioner + *

+ * */ @ResourceDef(name="Practitioner", profile="http://hl7.org/fhir/profiles/Practitioner", id="practitioner") public class Practitioner extends BaseResource implements IResource { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Profile.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Profile.java index b4fdd0fb33b..94565db32af 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Profile.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Profile.java @@ -67,6 +67,12 @@ import ca.uhn.fhir.model.primitive.UriDt; * Requirements: * *

+ * + *

+ * Profile Definition: + * http://hl7.org/fhir/profiles/Profile + *

+ * */ @ResourceDef(name="Profile", profile="http://hl7.org/fhir/profiles/Profile", id="profile") public class Profile extends BaseResource implements IResource { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Query.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Query.java index e5f3885fc3d..0429fb8e894 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Query.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Query.java @@ -50,6 +50,12 @@ import ca.uhn.fhir.model.primitive.UriDt; * Requirements: * *

+ * + *

+ * Profile Definition: + * http://hl7.org/fhir/profiles/Query + *

+ * */ @ResourceDef(name="Query", profile="http://hl7.org/fhir/profiles/Query", id="query") public class Query extends BaseResource implements IResource { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Questionnaire.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Questionnaire.java index 5aeb8a832e6..91438039570 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Questionnaire.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Questionnaire.java @@ -64,6 +64,12 @@ import ca.uhn.fhir.model.primitive.StringDt; * Requirements: * To support structured, hierarchical registration of data gathered using digital forms and other questionnaires. *

+ * + *

+ * Profile Definition: + * http://hl7.org/fhir/profiles/Questionnaire + *

+ * */ @ResourceDef(name="Questionnaire", profile="http://hl7.org/fhir/profiles/Questionnaire", id="questionnaire") public class Questionnaire extends BaseResource implements IResource { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/RelatedPerson.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/RelatedPerson.java index 4a7dba81c3a..2e135a7b268 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/RelatedPerson.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/RelatedPerson.java @@ -51,6 +51,12 @@ import ca.uhn.fhir.model.primitive.BoundCodeableConceptDt; * Requirements: * Need to track persons related to the patient or the healthcare process. *

+ * + *

+ * Profile Definition: + * http://hl7.org/fhir/profiles/RelatedPerson + *

+ * */ @ResourceDef(name="RelatedPerson", profile="http://hl7.org/fhir/profiles/RelatedPerson", id="relatedperson") public class RelatedPerson extends BaseResource implements IResource { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Specimen.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Specimen.java index 1857ff88abf..6c5a7185b9d 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Specimen.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Specimen.java @@ -61,6 +61,12 @@ import ca.uhn.fhir.model.primitive.StringDt; * Requirements: * *

+ * + *

+ * Profile Definition: + * http://hl7.org/fhir/profiles/Specimen + *

+ * */ @ResourceDef(name="Specimen", profile="http://hl7.org/fhir/profiles/Specimen", id="specimen") public class Specimen extends BaseResource implements IResource { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Substance.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Substance.java index 44a43479f56..eb45d51475e 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Substance.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Substance.java @@ -56,6 +56,12 @@ import ca.uhn.fhir.model.primitive.StringDt; * Requirements: * *

+ * + *

+ * Profile Definition: + * http://hl7.org/fhir/profiles/Substance + *

+ * */ @ResourceDef(name="Substance", profile="http://hl7.org/fhir/profiles/Substance", id="substance") public class Substance extends BaseResource implements IResource { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/ValueSet.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/ValueSet.java index c1194bde529..d32bd9b392a 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/ValueSet.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/ValueSet.java @@ -57,6 +57,12 @@ import ca.uhn.fhir.model.primitive.UriDt; * Requirements: * *

+ * + *

+ * Profile Definition: + * http://hl7.org/fhir/profiles/ValueSet + *

+ * */ @ResourceDef(name="ValueSet", profile="http://hl7.org/fhir/profiles/ValueSet", id="valueset") public class ValueSet extends BaseResource implements IResource { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/BoundCodeableConceptDt.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/BoundCodeableConceptDt.java index c3c51aa7f01..4e502533b1e 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/BoundCodeableConceptDt.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/BoundCodeableConceptDt.java @@ -2,6 +2,7 @@ package ca.uhn.fhir.model.primitive; import static org.apache.commons.lang3.StringUtils.*; +import java.util.Collection; import java.util.HashSet; import java.util.Set; @@ -19,12 +20,17 @@ public class BoundCodeableConceptDt> extends CodeableConceptDt myBinder = theBinder; } - public BoundCodeableConceptDt(IValueSetEnumBinder theBinder, T... theValues) { + public BoundCodeableConceptDt(IValueSetEnumBinder theBinder, T theValue) { + myBinder = theBinder; + setValueAsEnum(theValue); + } + + public BoundCodeableConceptDt(IValueSetEnumBinder theBinder, Collection theValues) { myBinder = theBinder; setValueAsEnum(theValues); } - public void setValueAsEnum(T... theValues) { + public void setValueAsEnum(Collection theValues) { getCoding().clear(); if (theValues == null) { return; @@ -34,6 +40,14 @@ public class BoundCodeableConceptDt> extends CodeableConceptDt } } + public void setValueAsEnum(T theValue) { + getCoding().clear(); + if (theValue == null) { + return; + } + getCoding().add(new CodingDt(myBinder.toSystemString(theValue), myBinder.toCodeString(theValue))); + } + public Set getValueAsEnum() { Set retVal = new HashSet(); for (CodingDt next : getCoding()) { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/BoundCodingDt__.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/BoundCodingDt__.java deleted file mode 100644 index 17d111f2844..00000000000 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/BoundCodingDt__.java +++ /dev/null @@ -1,31 +0,0 @@ -package ca.uhn.fhir.model.primitive; - -import static org.apache.commons.lang3.StringUtils.*; -import ca.uhn.fhir.model.api.IValueSetEnumBinder; -import ca.uhn.fhir.model.api.annotation.DatatypeDef; -import ca.uhn.fhir.model.dstu.composite.CodingDt; - -@DatatypeDef(name = "Coding", isSpecialization=true) -public class BoundCodingDt__> extends CodingDt { - - private IValueSetEnumBinder myBinder; - - public BoundCodingDt__(IValueSetEnumBinder theBinder) { - myBinder = theBinder; - } - - public BoundCodingDt__(IValueSetEnumBinder theBinder, T theValue) { - myBinder = theBinder; - setValueAsEnum(theValue); - } - - public void setValueAsEnum(T theValue) { - setCode(new BoundCodeDt(myBinder, theValue)); - setSystem(myBinder.toSystemString(theValue)); - } - - public T getValueAsEnum() { - return myBinder.fromCodeString(defaultString(getCode().getValue()), defaultString(getSystem().getValueAsString())); - } - -} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/narrative/BaseThymeleafNarrativeGenerator.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/narrative/BaseThymeleafNarrativeGenerator.java index 8a1a35f2451..de140d76d28 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/narrative/BaseThymeleafNarrativeGenerator.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/narrative/BaseThymeleafNarrativeGenerator.java @@ -1,9 +1,383 @@ package ca.uhn.fhir.narrative; -public class BaseThymeleafNarrativeGenerator { +import static org.apache.commons.lang3.StringUtils.*; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.StringReader; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Properties; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.io.input.ReaderInputStream; +import org.thymeleaf.Arguments; +import org.thymeleaf.Configuration; +import org.thymeleaf.TemplateEngine; +import org.thymeleaf.TemplateProcessingParameters; +import org.thymeleaf.context.Context; +import org.thymeleaf.dom.Document; +import org.thymeleaf.dom.Element; +import org.thymeleaf.dom.Node; +import org.thymeleaf.processor.IProcessor; +import org.thymeleaf.processor.ProcessorResult; +import org.thymeleaf.processor.attr.AbstractAttrProcessor; +import org.thymeleaf.resourceresolver.IResourceResolver; +import org.thymeleaf.standard.StandardDialect; +import org.thymeleaf.standard.expression.IStandardExpression; +import org.thymeleaf.standard.expression.IStandardExpressionParser; +import org.thymeleaf.standard.expression.StandardExpressions; +import org.thymeleaf.templateresolver.TemplateResolver; +import org.thymeleaf.util.DOMUtils; + +import ca.uhn.fhir.context.ConfigurationException; +import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.model.dstu.composite.NarrativeDt; +import ca.uhn.fhir.model.dstu.valueset.NarrativeStatusEnum; +import ca.uhn.fhir.model.primitive.XhtmlDt; +import ca.uhn.fhir.parser.DataFormatException; + +public class BaseThymeleafNarrativeGenerator implements INarrativeGenerator { + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseThymeleafNarrativeGenerator.class); + + private boolean myCleanWhitespace = true; + + private HashMap myDatatypeClassNameToNarrativeTemplate; + private TemplateEngine myDatatypeTemplateEngine; + private boolean myIgnoreFailures = true; + + private boolean myIgnoreMissingTemplates = true; + private TemplateEngine myProfileTemplateEngine; + private HashMap myProfileToNarrativeTemplate; + + public BaseThymeleafNarrativeGenerator() throws IOException { + myProfileToNarrativeTemplate = new HashMap(); + myDatatypeClassNameToNarrativeTemplate = new HashMap(); + + String propFileName = getPropertyFile(); + loadProperties(propFileName); + + { + myProfileTemplateEngine = new TemplateEngine(); + TemplateResolver resolver = new TemplateResolver(); + resolver.setResourceResolver(new ProfileResourceResolver()); + myProfileTemplateEngine.setTemplateResolver(resolver); + StandardDialect dialect = new StandardDialect(); + HashSet additionalProcessors = new HashSet(); + additionalProcessors.add(new MyProcessor()); + dialect.setAdditionalProcessors(additionalProcessors); + myProfileTemplateEngine.setDialect(dialect); + myProfileTemplateEngine.initialize(); + } + { + myDatatypeTemplateEngine = new TemplateEngine(); + TemplateResolver resolver = new TemplateResolver(); + resolver.setResourceResolver(new DatatypeResourceResolver()); + myDatatypeTemplateEngine.setTemplateResolver(resolver); + myDatatypeTemplateEngine.setDialect(new StandardDialect()); + myDatatypeTemplateEngine.initialize(); + } + } + + @Override + public NarrativeDt generateNarrative(String theProfile, IResource theResource) { + if (myIgnoreMissingTemplates && !myProfileToNarrativeTemplate.containsKey(theProfile)) { + ourLog.debug("No narrative template available for profile: {}", theProfile); + return new NarrativeDt(new XhtmlDt("
No narrative available
"), NarrativeStatusEnum.EMPTY); + } + + try { + Context context = new Context(); + context.setVariable("resource", theResource); + + String result = myProfileTemplateEngine.process(theProfile, context); + + if (myCleanWhitespace) { + ourLog.trace("Pre-whitespace cleaning: ", result); + result = cleanWhitespace(result); + ourLog.trace("Post-whitespace cleaning: ", result); + } + + XhtmlDt div = new XhtmlDt(result); + return new NarrativeDt(div, NarrativeStatusEnum.GENERATED); + } catch (Exception e) { + if (myIgnoreFailures) { + ourLog.error("Failed to generate narrative", e); + return new NarrativeDt(new XhtmlDt("
No narrative available
"), NarrativeStatusEnum.EMPTY); + } else { + throw new DataFormatException(e); + } + } + } public String getPropertyFile() { return null; } + /** + * If set to true (which is the default), most whitespace will + * be trimmed from the generated narrative before it is returned. + *

+ * Note that in order to preserve formatting, not all whitespace is trimmed. + * Repeated whitespace characters (e.g. "\n \n ") will be + * trimmed to a single space. + *

+ */ + public boolean isCleanWhitespace() { + return myCleanWhitespace; + } + + /** + * If set to true, which is the default, if any failure occurs + * during narrative generation the generator will suppress any generated + * exceptions, and simply return a default narrative indicating that no + * narrative is available. + */ + public boolean isIgnoreFailures() { + return myIgnoreFailures; + } + + /** + * If set to true, will return an empty narrative block for any profiles + * where no template is available + */ + public boolean isIgnoreMissingTemplates() { + return myIgnoreMissingTemplates; + } + + /** + * If set to true (which is the default), most whitespace will + * be trimmed from the generated narrative before it is returned. + *

+ * Note that in order to preserve formatting, not all whitespace is trimmed. + * Repeated whitespace characters (e.g. "\n \n ") will be + * trimmed to a single space. + *

+ */ + public void setCleanWhitespace(boolean theCleanWhitespace) { + myCleanWhitespace = theCleanWhitespace; + } + + /** + * If set to true, which is the default, if any failure occurs + * during narrative generation the generator will suppress any generated + * exceptions, and simply return a default narrative indicating that no + * narrative is available. + */ + public void setIgnoreFailures(boolean theIgnoreFailures) { + myIgnoreFailures = theIgnoreFailures; + } + + /** + * If set to true, will return an empty narrative block for any profiles + * where no template is available + */ + public void setIgnoreMissingTemplates(boolean theIgnoreMissingTemplates) { + myIgnoreMissingTemplates = theIgnoreMissingTemplates; + } + + private void loadProperties(String propFileName) throws IOException { + Properties file = new Properties(); + + InputStream resource = loadResource(propFileName); + file.load(resource); + for (Object nextKeyObj : file.keySet()) { + String nextKey = (String) nextKeyObj; + if (nextKey.endsWith(".profile")) { + String name = nextKey.substring(0, nextKey.indexOf(".profile")); + if (isBlank(name)) { + continue; + } + + String narrativeName = file.getProperty(name + ".narrative"); + if (isBlank(narrativeName)) { + continue; + } + + String narrative = IOUtils.toString(loadResource(narrativeName)); + myProfileToNarrativeTemplate.put(file.getProperty(nextKey), narrative); + } else if (nextKey.endsWith(".dtclass")) { + String name = nextKey.substring(0, nextKey.indexOf(".dtclass")); + if (isBlank(name)) { + continue; + } + + String className = file.getProperty(nextKey); + + Class dtClass; + try { + dtClass = Class.forName(className); + } catch (ClassNotFoundException e) { + ourLog.warn("Unknown datatype class '{}' identified in narrative file {}", name, propFileName); + continue; + } + + String narrativeName = file.getProperty(name + ".dtnarrative"); + if (isBlank(narrativeName)) { + continue; + } + + String narrative = IOUtils.toString(loadResource(narrativeName)); + myDatatypeClassNameToNarrativeTemplate.put(dtClass.getCanonicalName(), narrative); + } + + } + } + + private InputStream loadResource(String name) throws IOException { + if (name.startsWith("classpath:")) { + String cpName = name.substring("classpath:".length()); + InputStream resource = DefaultThymeleafNarrativeGenerator.class.getResourceAsStream(cpName); + if (resource == null) { + resource = DefaultThymeleafNarrativeGenerator.class.getResourceAsStream("/" + cpName); + if (resource == null) { + throw new ConfigurationException("Can not find '" + cpName + "' on classpath"); + } + } + return resource; + } else if (name.startsWith("file:")) { + File file = new File(name.substring("file:".length())); + if (file.exists() == false) { + throw new IOException("Can not read file: " + file.getAbsolutePath()); + } + return new FileInputStream(file); + } else { + throw new IllegalArgumentException("Invalid resource name: '" + name + "'"); + } + } + + static String cleanWhitespace(String theResult) { + StringBuilder b = new StringBuilder(); + boolean inWhitespace = false; + boolean betweenTags = false; + boolean lastNonWhitespaceCharWasTagEnd = false; + for (int i = 0; i < theResult.length(); i++) { + char nextChar = theResult.charAt(i); + if (nextChar == '>') { + b.append(nextChar); + betweenTags = true; + lastNonWhitespaceCharWasTagEnd = true; + continue; + } else if (nextChar == '\n' || nextChar == '\r') { + // if (inWhitespace) { + // b.append(' '); + // inWhitespace = false; + // } + continue; + } + + if (betweenTags) { + if (Character.isWhitespace(nextChar)) { + inWhitespace = true; + } else if (nextChar == '<') { + if (inWhitespace && !lastNonWhitespaceCharWasTagEnd) { + b.append(' '); + } + inWhitespace = false; + b.append(nextChar); + inWhitespace = false; + betweenTags = false; + lastNonWhitespaceCharWasTagEnd = false; + } else { + lastNonWhitespaceCharWasTagEnd = false; + if (inWhitespace) { + b.append(' '); + inWhitespace = false; + } + b.append(nextChar); + } + } else { + b.append(nextChar); + } + } + return b.toString(); + } + + public class MyProcessor extends AbstractAttrProcessor { + + protected MyProcessor() { + super("narrative"); + } + + @Override + public int getPrecedence() { + return 0; + } + + @Override + protected ProcessorResult processAttribute(Arguments theArguments, Element theElement, String theAttributeName) { + final String attributeValue = theElement.getAttributeValue(theAttributeName); + + final Configuration configuration = theArguments.getConfiguration(); + final IStandardExpressionParser expressionParser = StandardExpressions.getExpressionParser(configuration); + + final IStandardExpression expression = expressionParser.parseExpression(configuration, theArguments, attributeValue); + final Object value = expression.execute(configuration, theArguments); + + Context context = new Context(); + context.setVariable("resource", value); + + String result = myDatatypeTemplateEngine.process(value.getClass().getCanonicalName(), context); + Document dom = DOMUtils.getXhtmlDOMFor(new StringReader(result)); + + theElement.removeAttribute(theAttributeName); + theElement.clearChildren(); + + Element firstChild = (Element) dom.getFirstChild(); + for (Node next : firstChild.getChildren()) { + theElement.addChild(next); + } + + return ProcessorResult.ok(); + } + + } + + private final class DatatypeResourceResolver implements IResourceResolver { + @Override + public String getName() { + return getClass().getCanonicalName(); + } + + @Override + public InputStream getResourceAsStream(TemplateProcessingParameters theTemplateProcessingParameters, String theClassName) { + String template = myDatatypeClassNameToNarrativeTemplate.get(theClassName); + if (template == null) { + throw new NullPointerException("No narrative template exists for datatype: " + theClassName); + } + return new ReaderInputStream(new StringReader(template)); + } + } + + // public String generateString(Patient theValue) { + // + // Context context = new Context(); + // context.setVariable("resource", theValue); + // String result = + // myProfileTemplateEngine.process("ca/uhn/fhir/narrative/Patient.html", + // context); + // + // ourLog.info("Result: {}", result); + // + // return result; + // } + + private final class ProfileResourceResolver implements IResourceResolver { + @Override + public String getName() { + return getClass().getCanonicalName(); + } + + @Override + public InputStream getResourceAsStream(TemplateProcessingParameters theTemplateProcessingParameters, String theResourceName) { + String template = myProfileToNarrativeTemplate.get(theResourceName); + if (template == null) { + ourLog.info("No narative template for resource profile: {}", theResourceName); + return new ReaderInputStream(new StringReader("")); + } + return new ReaderInputStream(new StringReader(template)); + } + } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/narrative/CustomThymeleafNarrativeGenerator.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/narrative/CustomThymeleafNarrativeGenerator.java new file mode 100644 index 00000000000..ee184a9f5ea --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/narrative/CustomThymeleafNarrativeGenerator.java @@ -0,0 +1,24 @@ +package ca.uhn.fhir.narrative; + +import java.io.IOException; + +public class CustomThymeleafNarrativeGenerator extends BaseThymeleafNarrativeGenerator { + + /** + * Create a new narrative generator + * + * @param thePropertyFile + * The name of the property file, in one of the following + * formats: + *
    + *
  • file:/path/to/file/file.properties
  • + *
  • classpath:/com/package/file.properties
  • + *
+ * @throws IOException + * If the file can not be found/read + */ + public CustomThymeleafNarrativeGenerator(String thePropertyFile) throws IOException { + super(); + } + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/narrative/DefaultThymeleafNarrativeGenerator.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/narrative/DefaultThymeleafNarrativeGenerator.java index 5f7225e6014..8f3049c3aca 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/narrative/DefaultThymeleafNarrativeGenerator.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/narrative/DefaultThymeleafNarrativeGenerator.java @@ -1,100 +1,11 @@ package ca.uhn.fhir.narrative; -import static org.apache.commons.lang3.StringUtils.*; - import java.io.IOException; -import java.io.InputStream; -import java.io.StringReader; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Properties; - -import org.apache.commons.io.IOUtils; -import org.apache.commons.io.input.ReaderInputStream; -import org.thymeleaf.Arguments; -import org.thymeleaf.Configuration; -import org.thymeleaf.TemplateEngine; -import org.thymeleaf.TemplateProcessingParameters; -import org.thymeleaf.context.Context; -import org.thymeleaf.dom.Document; -import org.thymeleaf.dom.Element; -import org.thymeleaf.dom.Node; -import org.thymeleaf.processor.IProcessor; -import org.thymeleaf.processor.ProcessorResult; -import org.thymeleaf.processor.attr.AbstractAttrProcessor; -import org.thymeleaf.resourceresolver.IResourceResolver; -import org.thymeleaf.standard.StandardDialect; -import org.thymeleaf.standard.expression.IStandardExpression; -import org.thymeleaf.standard.expression.IStandardExpressionParser; -import org.thymeleaf.standard.expression.StandardExpressions; -import org.thymeleaf.templateresolver.TemplateResolver; -import org.thymeleaf.util.DOMUtils; - -import ca.uhn.fhir.context.ConfigurationException; -import ca.uhn.fhir.model.api.IResource; -import ca.uhn.fhir.model.dstu.composite.NarrativeDt; -import ca.uhn.fhir.model.dstu.valueset.NarrativeStatusEnum; -import ca.uhn.fhir.model.primitive.XhtmlDt; -import ca.uhn.fhir.parser.DataFormatException; public class DefaultThymeleafNarrativeGenerator extends BaseThymeleafNarrativeGenerator implements INarrativeGenerator { - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(DefaultThymeleafNarrativeGenerator.class); - - private HashMap myDatatypeClassNameToNarrativeTemplate; - private TemplateEngine myDatatypeTemplateEngine; - - private boolean myIgnoreFailures = true; - - private boolean myIgnoreMissingTemplates = true; - - private TemplateEngine myProfileTemplateEngine; - private HashMap myProfileToNarrativeTemplate; - - private boolean myCleanWhitespace = true; - - /** - * If set to true (which is the default), most whitespace will be trimmed from the generated narrative before it is returned. - *

- * Note that in order to preserve formatting, not all whitespace is trimmed. Repeated whitespace characters (e.g. "\n \n ") will be trimmed to a single space. - *

- */ - public boolean isCleanWhitespace() { - return myCleanWhitespace; - } - - public void setCleanWhitespace(boolean theCleanWhitespace) { - myCleanWhitespace = theCleanWhitespace; - } - public DefaultThymeleafNarrativeGenerator() throws IOException { - myProfileToNarrativeTemplate = new HashMap(); - myDatatypeClassNameToNarrativeTemplate = new HashMap(); - - String propFileName = getPropertyFile(); - loadProperties(propFileName); - - { - myProfileTemplateEngine = new TemplateEngine(); - TemplateResolver resolver = new TemplateResolver(); - resolver.setResourceResolver(new ProfileResourceResolver()); - myProfileTemplateEngine.setTemplateResolver(resolver); - StandardDialect dialect = new StandardDialect(); - HashSet additionalProcessors = new HashSet(); - additionalProcessors.add(new MyProcessor()); - dialect.setAdditionalProcessors(additionalProcessors); - myProfileTemplateEngine.setDialect(dialect); - myProfileTemplateEngine.initialize(); - } - { - myDatatypeTemplateEngine = new TemplateEngine(); - TemplateResolver resolver = new TemplateResolver(); - resolver.setResourceResolver(new DatatypeResourceResolver()); - myDatatypeTemplateEngine.setTemplateResolver(resolver); - myDatatypeTemplateEngine.setDialect(new StandardDialect()); - myDatatypeTemplateEngine.initialize(); - } - + super(); } @Override @@ -102,260 +13,4 @@ public class DefaultThymeleafNarrativeGenerator extends BaseThymeleafNarrativeGe return "classpath:ca/uhn/fhir/narrative/narratives.properties"; } - @Override - public NarrativeDt generateNarrative(String theProfile, IResource theResource) { - if (myIgnoreMissingTemplates && !myProfileToNarrativeTemplate.containsKey(theProfile)) { - ourLog.debug("No narrative template available for profile: {}", theProfile); - return new NarrativeDt(new XhtmlDt("
No narrative available
"), NarrativeStatusEnum.EMPTY); - } - - try { - Context context = new Context(); - context.setVariable("resource", theResource); - - String result = myProfileTemplateEngine.process(theProfile, context); - - if (myCleanWhitespace) { - ourLog.trace("Pre-whitespace cleaning: ", result); - result = cleanWhitespace(result); - ourLog.trace("Post-whitespace cleaning: ", result); - } - - XhtmlDt div = new XhtmlDt(result); - return new NarrativeDt(div, NarrativeStatusEnum.GENERATED); - } catch (Exception e) { - if (myIgnoreFailures) { - ourLog.error("Failed to generate narrative", e); - return new NarrativeDt(new XhtmlDt("
No narrative available
"), NarrativeStatusEnum.EMPTY); - } else { - throw new DataFormatException(e); - } - } - } - - static String cleanWhitespace(String theResult) { - StringBuilder b = new StringBuilder(); - boolean inWhitespace = false; - boolean betweenTags = false; - boolean lastNonWhitespaceCharWasTagEnd = false; - for (int i = 0; i < theResult.length(); i++) { - char nextChar = theResult.charAt(i); - if (nextChar == '>') { - b.append(nextChar); - betweenTags = true; - lastNonWhitespaceCharWasTagEnd = true; - continue; - } else if (nextChar == '\n' || nextChar == '\r') { - // if (inWhitespace) { - // b.append(' '); - // inWhitespace = false; - // } - continue; - } - - if (betweenTags) { - if (Character.isWhitespace(nextChar)) { - inWhitespace = true; - } else if (nextChar == '<') { - if (inWhitespace && !lastNonWhitespaceCharWasTagEnd) { - b.append(' '); - } - inWhitespace = false; - b.append(nextChar); - inWhitespace = false; - betweenTags = false; - lastNonWhitespaceCharWasTagEnd = false; - } else { - lastNonWhitespaceCharWasTagEnd = false; - if (inWhitespace) { - b.append(' '); - inWhitespace = false; - } - b.append(nextChar); - } - } else { - b.append(nextChar); - } - } - return b.toString(); - } - - /** - * If set to true, which is the default, if any failure occurs during narrative generation the generator will suppress any generated exceptions, and simply return a default narrative - * indicating that no narrative is available. - */ - public boolean isIgnoreFailures() { - return myIgnoreFailures; - } - - /** - * If set to true, will return an empty narrative block for any profiles where no template is available - */ - public boolean isIgnoreMissingTemplates() { - return myIgnoreMissingTemplates; - } - - private void loadProperties(String propFileName) throws IOException { - Properties file = new Properties(); - - InputStream resource = loadResource(propFileName); - file.load(resource); - for (Object nextKeyObj : file.keySet()) { - String nextKey = (String) nextKeyObj; - if (nextKey.endsWith(".profile")) { - String name = nextKey.substring(0, nextKey.indexOf(".profile")); - if (isBlank(name)) { - continue; - } - - String narrativeName = file.getProperty(name + ".narrative"); - if (isBlank(narrativeName)) { - continue; - } - - String narrative = IOUtils.toString(loadResource(narrativeName)); - myProfileToNarrativeTemplate.put(file.getProperty(nextKey), narrative); - } else if (nextKey.endsWith(".dtclass")) { - String name = nextKey.substring(0, nextKey.indexOf(".dtclass")); - if (isBlank(name)) { - continue; - } - - String className = file.getProperty(nextKey); - - Class dtClass; - try { - dtClass = Class.forName(className); - } catch (ClassNotFoundException e) { - ourLog.warn("Unknown datatype class '{}' identified in narrative file {}", name, propFileName); - continue; - } - - String narrativeName = file.getProperty(name + ".dtnarrative"); - if (isBlank(narrativeName)) { - continue; - } - - String narrative = IOUtils.toString(loadResource(narrativeName)); - myDatatypeClassNameToNarrativeTemplate.put(dtClass.getCanonicalName(), narrative); - } - - } - } - - private InputStream loadResource(String name) { - if (name.startsWith("classpath:")) { - String cpName = name.substring("classpath:".length()); - InputStream resource = DefaultThymeleafNarrativeGenerator.class.getResourceAsStream(cpName); - if (resource == null) { - resource = DefaultThymeleafNarrativeGenerator.class.getResourceAsStream("/" + cpName); - if (resource == null) { - throw new ConfigurationException("Can not find '" + cpName + "' on classpath"); - } - } - return resource; - } else { - throw new IllegalArgumentException("Invalid resource name: '" + name + "'"); - } - } - - /** - * If set to true, which is the default, if any failure occurs during narrative generation the generator will suppress any generated exceptions, and simply return a default narrative - * indicating that no narrative is available. - */ - public void setIgnoreFailures(boolean theIgnoreFailures) { - myIgnoreFailures = theIgnoreFailures; - } - - // public String generateString(Patient theValue) { - // - // Context context = new Context(); - // context.setVariable("resource", theValue); - // String result = myProfileTemplateEngine.process("ca/uhn/fhir/narrative/Patient.html", context); - // - // ourLog.info("Result: {}", result); - // - // return result; - // } - - /** - * If set to true, will return an empty narrative block for any profiles where no template is available - */ - public void setIgnoreMissingTemplates(boolean theIgnoreMissingTemplates) { - myIgnoreMissingTemplates = theIgnoreMissingTemplates; - } - - private final class DatatypeResourceResolver implements IResourceResolver { - @Override - public String getName() { - return getClass().getCanonicalName(); - } - - @Override - public InputStream getResourceAsStream(TemplateProcessingParameters theTemplateProcessingParameters, String theClassName) { - String template = myDatatypeClassNameToNarrativeTemplate.get(theClassName); - if (template == null) { - throw new NullPointerException("No narrative template exists for datatype: " + theClassName); - } - return new ReaderInputStream(new StringReader(template)); - } - } - - public class MyProcessor extends AbstractAttrProcessor { - - protected MyProcessor() { - super("narrative"); - } - - @Override - public int getPrecedence() { - return 0; - } - - @Override - protected ProcessorResult processAttribute(Arguments theArguments, Element theElement, String theAttributeName) { - final String attributeValue = theElement.getAttributeValue(theAttributeName); - - final Configuration configuration = theArguments.getConfiguration(); - final IStandardExpressionParser expressionParser = StandardExpressions.getExpressionParser(configuration); - - final IStandardExpression expression = expressionParser.parseExpression(configuration, theArguments, attributeValue); - final Object value = expression.execute(configuration, theArguments); - - Context context = new Context(); - context.setVariable("resource", value); - - String result = myDatatypeTemplateEngine.process(value.getClass().getCanonicalName(), context); - Document dom = DOMUtils.getXhtmlDOMFor(new StringReader(result)); - - theElement.removeAttribute(theAttributeName); - theElement.clearChildren(); - - Element firstChild = (Element) dom.getFirstChild(); - for (Node next : firstChild.getChildren()) { - theElement.addChild(next); - } - - return ProcessorResult.ok(); - } - - } - - private final class ProfileResourceResolver implements IResourceResolver { - @Override - public String getName() { - return getClass().getCanonicalName(); - } - - @Override - public InputStream getResourceAsStream(TemplateProcessingParameters theTemplateProcessingParameters, String theResourceName) { - String template = myProfileToNarrativeTemplate.get(theResourceName); - if (template == null) { - ourLog.info("No narative template for resource profile: {}", theResourceName); - return new ReaderInputStream(new StringReader("")); - } - return new ReaderInputStream(new StringReader(template)); - } - } - } diff --git a/hapi-fhir-base/src/main/resources/ca/uhn/fhir/narrative/AddressDt.html b/hapi-fhir-base/src/main/resources/ca/uhn/fhir/narrative/AddressDt.html new file mode 100644 index 00000000000..6f8d62dc38f --- /dev/null +++ b/hapi-fhir-base/src/main/resources/ca/uhn/fhir/narrative/AddressDt.html @@ -0,0 +1,8 @@ +
+ + 123 Fake Street
+
+ Toronto + ON + Canada +
diff --git a/hapi-fhir-base/src/main/resources/ca/uhn/fhir/narrative/Patient.html b/hapi-fhir-base/src/main/resources/ca/uhn/fhir/narrative/Patient.html index e5995af962c..32459aa260c 100644 --- a/hapi-fhir-base/src/main/resources/ca/uhn/fhir/narrative/Patient.html +++ b/hapi-fhir-base/src/main/resources/ca/uhn/fhir/narrative/Patient.html @@ -14,26 +14,17 @@ a browser.
- + - + - + - + - +
Identifier87086608708660
Address - - 123 Fake Street
-
- Toronto - ON - Canada -
Date of birth - 22 March 2012 - 22 March 2012
diff --git a/hapi-fhir-base/src/main/resources/ca/uhn/fhir/narrative/narratives.properties b/hapi-fhir-base/src/main/resources/ca/uhn/fhir/narrative/narratives.properties index 1023b3295e2..d96c1f73e25 100644 --- a/hapi-fhir-base/src/main/resources/ca/uhn/fhir/narrative/narratives.properties +++ b/hapi-fhir-base/src/main/resources/ca/uhn/fhir/narrative/narratives.properties @@ -1,22 +1,37 @@ +################################################ +# Primitive Datatypes +################################################ + +string.dtclass=ca.uhn.fhir.model.primitive.StringDt +string.dtnarrative=classpath:ca/uhn/fhir/narrative/StringDt.html + datetime.dtclass=ca.uhn.fhir.model.primitive.DateTimeDt datetime.dtnarrative=classpath:ca/uhn/fhir/narrative/DateTimeDt.html -diagnosticreport.profile=http://hl7.org/fhir/profiles/DiagnosticReport -diagnosticreport.narrative=classpath:ca/uhn/fhir/narrative/DiagnosticReport.html - -humanname.dtclass=ca.uhn.fhir.model.dstu.composite.HumanNameDt -humanname.dtnarrative=classpath:ca/uhn/fhir/narrative/HumanNameDt.html - # Instant uses DateTime narrative instant.dtclass=ca.uhn.fhir.model.primitive.InstantDt instant.dtnarrative=classpath:ca/uhn/fhir/narrative/DateTimeDt.html -patient.profile=http://hl7.org/fhir/profiles/Patient -patient.narrative=classpath:ca/uhn/fhir/narrative/Patient.html +################################################ +# Composite Datatypes +################################################ + +address.dtclass=ca.uhn.fhir.model.dstu.composite.AddressDt +address.dtnarrative=classpath:ca/uhn/fhir/narrative/AddressDt.html + +humanname.dtclass=ca.uhn.fhir.model.dstu.composite.HumanNameDt +humanname.dtnarrative=classpath:ca/uhn/fhir/narrative/HumanNameDt.html quantity.dtclass=ca.uhn.fhir.model.dstu.composite.QuantityDt quantity.dtnarrative=classpath:ca/uhn/fhir/narrative/QuantityDt.html -string.dtclass=ca.uhn.fhir.model.primitive.StringDt -string.dtnarrative=classpath:ca/uhn/fhir/narrative/StringDt.html \ No newline at end of file +################################################ +# Resources +################################################ + +patient.profile=http://hl7.org/fhir/profiles/Patient +patient.narrative=classpath:ca/uhn/fhir/narrative/Patient.html + +diagnosticreport.profile=http://hl7.org/fhir/profiles/DiagnosticReport +diagnosticreport.narrative=classpath:ca/uhn/fhir/narrative/DiagnosticReport.html \ No newline at end of file diff --git a/hapi-fhir-base/src/site/example/java/example/Narrative.java b/hapi-fhir-base/src/site/example/java/example/Narrative.java index 1c5fa0d9555..7178a984ff8 100644 --- a/hapi-fhir-base/src/site/example/java/example/Narrative.java +++ b/hapi-fhir-base/src/site/example/java/example/Narrative.java @@ -13,7 +13,7 @@ import ca.uhn.fhir.model.dstu.valueset.NarrativeStatusEnum; import ca.uhn.fhir.model.primitive.DateTimeDt; import ca.uhn.fhir.model.primitive.StringDt; import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator; -import ca.uhn.fhir.narrative.ThymeleafNarrativeGeneratorTest; +import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGeneratorTest; import ca.uhn.fhir.parser.DataFormatException; public class Narrative { diff --git a/hapi-fhir-base/src/site/site.xml b/hapi-fhir-base/src/site/site.xml index 3698db2b852..0d310c1886f 100644 --- a/hapi-fhir-base/src/site/site.xml +++ b/hapi-fhir-base/src/site/site.xml @@ -34,7 +34,8 @@ - + + diff --git a/hapi-fhir-base/src/site/xdoc/doc_narrative.xml b/hapi-fhir-base/src/site/xdoc/doc_narrative.xml index 093c4d121d2..97941a6a8a5 100644 --- a/hapi-fhir-base/src/site/xdoc/doc_narrative.xml +++ b/hapi-fhir-base/src/site/xdoc/doc_narrative.xml @@ -50,8 +50,8 @@ HAPI's built-in narrative generation uses the Thymeleaf library for templating narrative texts. Thymeleaf provides a simple - HTML (with extra attributes) syntax which is easy to use and - meshes well with the HAPI data model. + XHTML-based syntax which is easy to use and + meshes well with the HAPI-FHIR model objects.

diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/narrative/BaseThymeleafNarrativeGeneratorTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/narrative/BaseThymeleafNarrativeGeneratorTest.java new file mode 100644 index 00000000000..9a7a877929f --- /dev/null +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/narrative/BaseThymeleafNarrativeGeneratorTest.java @@ -0,0 +1,62 @@ +package ca.uhn.fhir.narrative; + +import static org.junit.Assert.*; + +import org.junit.Test; + +public class BaseThymeleafNarrativeGeneratorTest { + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseThymeleafNarrativeGeneratorTest.class); + + @Test + public void testTrimWhitespace() { + + //@formatter:off + String input = "
\n" + + "
\n" + + " \n" + + " joe \n" + + " john \n" + + " BLOW \n" + + " \n" + + "
\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
Identifier123456
Address\n" + + " \n" + + " 123 Fake Street
\n" + + " \n" + + " \n" + + " Unit 1
\n" + + " \n" + + " Toronto\n" + + " ON\n" + + " Canada\n" + + "
Date of birth\n" + + " 31 March 2014\n" + + "
\n" + + "
"; + //@formatter:on + + String actual = BaseThymeleafNarrativeGenerator.cleanWhitespace(input); + String expected = "
joe john BLOW
Identifier123456
Address123 Fake Street
Unit 1
TorontoONCanada
Date of birth31 March 2014
"; + + ourLog.info(actual); + + assertEquals(expected, actual); + } + + +} diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/narrative/ThymeleafNarrativeGeneratorTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/narrative/DefaultThymeleafNarrativeGeneratorTest.java similarity index 69% rename from hapi-fhir-base/src/test/java/ca/uhn/fhir/narrative/ThymeleafNarrativeGeneratorTest.java rename to hapi-fhir-base/src/test/java/ca/uhn/fhir/narrative/DefaultThymeleafNarrativeGeneratorTest.java index ffa07f500c1..57405618397 100644 --- a/hapi-fhir-base/src/test/java/ca/uhn/fhir/narrative/ThymeleafNarrativeGeneratorTest.java +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/narrative/DefaultThymeleafNarrativeGeneratorTest.java @@ -22,8 +22,8 @@ import ca.uhn.fhir.model.dstu.valueset.ObservationStatusEnum; import ca.uhn.fhir.model.primitive.StringDt; import ca.uhn.fhir.parser.DataFormatException; -public class ThymeleafNarrativeGeneratorTest { - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ThymeleafNarrativeGeneratorTest.class); +public class DefaultThymeleafNarrativeGeneratorTest { + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(DefaultThymeleafNarrativeGeneratorTest.class); private DefaultThymeleafNarrativeGenerator gen; @Before @@ -33,57 +33,6 @@ public class ThymeleafNarrativeGeneratorTest { gen.setIgnoreMissingTemplates(true); } - @Test - public void testTrimWhitespace() { - - //@formatter:off - String input = "
\n" + - "
\n" + - " \n" + - " joe \n" + - " john \n" + - " BLOW \n" + - " \n" + - "
\n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - "
Identifier123456
Address\n" + - " \n" + - " 123 Fake Street
\n" + - " \n" + - " \n" + - " Unit 1
\n" + - " \n" + - " Toronto\n" + - " ON\n" + - " Canada\n" + - "
Date of birth\n" + - " 31 March 2014\n" + - "
\n" + - "
"; - //@formatter:on - - String actual = DefaultThymeleafNarrativeGenerator.cleanWhitespace(input); - String expected = "
joe john BLOW
Identifier123456
Address123 Fake Street
Unit 1
TorontoONCanada
Date of birth31 March 2014
"; - - ourLog.info(actual); - - assertEquals(expected, actual); - } - @Test public void testGeneratePatient() throws DataFormatException { Patient value = new Patient(); diff --git a/hapi-fhir-base/src/test/resources/narrative/Practitoner.html b/hapi-fhir-base/src/test/resources/narrative/Practitoner.html new file mode 100644 index 00000000000..7db5e9ebcf0 --- /dev/null +++ b/hapi-fhir-base/src/test/resources/narrative/Practitoner.html @@ -0,0 +1,21 @@ +
+ +
+ + +
+ + +
+ +
\ No newline at end of file diff --git a/hapi-fhir-base/src/test/resources/narrative/customnarative.properties b/hapi-fhir-base/src/test/resources/narrative/customnarative.properties new file mode 100644 index 00000000000..b3dd17f735d --- /dev/null +++ b/hapi-fhir-base/src/test/resources/narrative/customnarative.properties @@ -0,0 +1,21 @@ + +# Each resource to be defined has a pair or properties. +# +# The first (name.profile) defines the profile for the resource +# to generate a narrative for. This profile URL string can be +# found by examining the JavaDoc for the resource in question. +# +# The second (name.narrative) defines the path/classpath to the +# template file. +# Format is file:/path/foo.html or classpath:/com/classpath/foo.html +# +practitioner.profile=http://hl7.org/fhir/profiles/Practitioner +practitioner.narrative=file:src/test/resource/narrative/Practitioner.html + +# You may also override/define behaviour for datatypes, but the +# format is a bit different. +# +# For these, the first property (name.dtclass) is the classname +# The second property (name.dtnarrative) is the +humanname.dtclass=ca.uhn.fhir.model.dstu.composite.HumanNameDt +humanname.dtnarrative=classpath:ca/uhn/fhir/narrative/HumanNameDt.html diff --git a/hapi-tinder-plugin/src/main/resources/vm/resource.vm b/hapi-tinder-plugin/src/main/resources/vm/resource.vm index adba07046c9..544c22cb54e 100644 --- a/hapi-tinder-plugin/src/main/resources/vm/resource.vm +++ b/hapi-tinder-plugin/src/main/resources/vm/resource.vm @@ -27,6 +27,14 @@ import ${import}; * Requirements: * ${requirements} *

+ * +#if (${profile} != "") + *

+ * Profile Definition: + * ${profile} + *

+ * +#end */ @ResourceDef(name="${className}", profile="${profile}", id="${id}") public class ${className} extends BaseResource implements IResource {