From 81b2b1cc504ba7aeb9e90667b2a06c7d5370c65a Mon Sep 17 00:00:00 2001 From: jamesagnew Date: Tue, 1 Apr 2014 18:01:52 -0400 Subject: [PATCH] More narrative work --- .../BaseThymeleafNarrativeGenerator.java | 197 ++++++++++-------- .../uhn/fhir/narrative/narratives.properties | 28 +-- ...CustomThymeleafNarrativeGeneratorTest.java | 2 + ...efaultThymeleafNarrativeGeneratorTest.java | 2 +- .../narrative/customnarative.properties | 17 +- 5 files changed, 131 insertions(+), 115 deletions(-) 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 2f8bb951507..c96bbd8001c 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 @@ -13,6 +13,7 @@ import java.util.Properties; import org.apache.commons.io.IOUtils; import org.apache.commons.io.input.ReaderInputStream; +import org.apache.commons.lang3.StringUtils; import org.thymeleaf.Arguments; import org.thymeleaf.Configuration; import org.thymeleaf.TemplateEngine; @@ -32,6 +33,7 @@ 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; @@ -42,56 +44,44 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener 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; + private HashMap myProfileToNarrativeName; + private HashMap, String> myClassToNarrativeName; + private HashMap myNameToNarrativeTemplate; - 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(); - } - } + private volatile boolean myInitialized; @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); + if (!myInitialized) { + initialize(); + } + + String name = null; + if (StringUtils.isNotBlank(theProfile)) { + name = myProfileToNarrativeName.get(theProfile); + } + if (name == null) { + name = myClassToNarrativeName.get(theResource.getClass()); + } + + if (name == null) { + if (myIgnoreMissingTemplates) { + ourLog.debug("No narrative template available for profile: {}", theProfile); + return new NarrativeDt(new XhtmlDt("
No narrative available
"), NarrativeStatusEnum.EMPTY); + } else { + throw new DataFormatException("No narrative template for class " + theResource.getClass().getCanonicalName()); + } } try { Context context = new Context(); context.setVariable("resource", theResource); - String result = myProfileTemplateEngine.process(theProfile, context); + String result = myProfileTemplateEngine.process(name, context); if (myCleanWhitespace) { ourLog.trace("Pre-whitespace cleaning: ", result); @@ -111,15 +101,47 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener } } + private synchronized void initialize() { + if (myInitialized) { + return; + } + myProfileToNarrativeName = new HashMap(); + myClassToNarrativeName = new HashMap, String>(); + myNameToNarrativeTemplate = new HashMap(); + + String propFileName = getPropertyFile(); + if (isBlank(propFileName)) { + throw new ConfigurationException("Property file name can not be null"); + } + + try { + loadProperties(propFileName); + } catch (IOException e) { + throw new ConfigurationException("Can not load property file " + propFileName, e); + } + + { + 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 NarrativeAttributeProcessor()); + dialect.setAdditionalProcessors(additionalProcessors); + myProfileTemplateEngine.setDialect(dialect); + myProfileTemplateEngine.initialize(); + } + + myInitialized = true; + } + protected abstract String getPropertyFile(); /** - * If set to true (which is the default), most whitespace will - * be trimmed from the generated narrative before it is returned. + * 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. + * 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() { @@ -127,30 +149,24 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener } /** - * 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. + * 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 + * 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. + * 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. + * 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) { @@ -158,18 +174,15 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener } /** - * 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. + * 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 + * 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; @@ -188,15 +201,19 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener continue; } - String narrativeName = file.getProperty(name + ".narrative"); + String narrativePropName = name + ".narrative"; + String narrativeName = file.getProperty(narrativePropName); if (isBlank(narrativeName)) { - continue; + throw new ConfigurationException("Found property '" + nextKey + "' but no corresponding property '" + narrativePropName + "' in file " + propFileName); } 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")); + myProfileToNarrativeName.put(file.getProperty(nextKey), name); + myNameToNarrativeTemplate.put(name, narrative); + + } else if (nextKey.endsWith(".class")) { + + String name = nextKey.substring(0, nextKey.indexOf(".class")); if (isBlank(name)) { continue; } @@ -211,13 +228,20 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener continue; } - String narrativeName = file.getProperty(name + ".dtnarrative"); + String narrativePropName = name + ".narrative"; + String narrativeName = file.getProperty(narrativePropName); if (isBlank(narrativeName)) { - continue; + throw new ConfigurationException("Found property '" + nextKey + "' but no corresponding property '" + narrativePropName + "' in file " + propFileName); } String narrative = IOUtils.toString(loadResource(narrativeName)); - myDatatypeClassNameToNarrativeTemplate.put(dtClass.getCanonicalName(), narrative); + myClassToNarrativeName.put(dtClass, name); + myNameToNarrativeTemplate.put(name, narrative); + + } else if (nextKey.endsWith(".narrative")) { + continue; + } else { + throw new ConfigurationException("Invalid property name: " + nextKey); } } @@ -292,9 +316,9 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener return b.toString(); } - public class MyProcessor extends AbstractAttrProcessor { + public class NarrativeAttributeProcessor extends AbstractAttrProcessor { - protected MyProcessor() { + protected NarrativeAttributeProcessor() { super("narrative"); } @@ -316,7 +340,18 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener Context context = new Context(); context.setVariable("resource", value); - String result = myDatatypeTemplateEngine.process(value.getClass().getCanonicalName(), context); + String name = myClassToNarrativeName.get(value.getClass()); + + if (name == null) { + if (myIgnoreMissingTemplates) { + ourLog.debug("No narrative template available for type: {}", value.getClass()); + return ProcessorResult.ok(); + } else { + throw new DataFormatException("No narrative template for class " + value.getClass()); + } + } + + String result = myProfileTemplateEngine.process(name, context); Document dom = DOMUtils.getXhtmlDOMFor(new StringReader(result)); theElement.removeAttribute(theAttributeName); @@ -332,22 +367,6 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener } - 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(); @@ -368,10 +387,10 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener } @Override - public InputStream getResourceAsStream(TemplateProcessingParameters theTemplateProcessingParameters, String theResourceName) { - String template = myProfileToNarrativeTemplate.get(theResourceName); + public InputStream getResourceAsStream(TemplateProcessingParameters theTemplateProcessingParameters, String theName) { + String template = myNameToNarrativeTemplate.get(theName); if (template == null) { - ourLog.info("No narative template for resource profile: {}", theResourceName); + ourLog.info("No narative template for resource profile: {}", theName); return new ReaderInputStream(new StringReader("")); } return new ReaderInputStream(new StringReader(template)); 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 d96c1f73e25..c28153ec1ea 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 @@ -3,35 +3,35 @@ # Primitive Datatypes ################################################ -string.dtclass=ca.uhn.fhir.model.primitive.StringDt -string.dtnarrative=classpath:ca/uhn/fhir/narrative/StringDt.html +string.class=ca.uhn.fhir.model.primitive.StringDt +string.narrative=classpath:ca/uhn/fhir/narrative/StringDt.html -datetime.dtclass=ca.uhn.fhir.model.primitive.DateTimeDt -datetime.dtnarrative=classpath:ca/uhn/fhir/narrative/DateTimeDt.html +datetime.class=ca.uhn.fhir.model.primitive.DateTimeDt +datetime.narrative=classpath:ca/uhn/fhir/narrative/DateTimeDt.html # Instant uses DateTime narrative -instant.dtclass=ca.uhn.fhir.model.primitive.InstantDt -instant.dtnarrative=classpath:ca/uhn/fhir/narrative/DateTimeDt.html +instant.class=ca.uhn.fhir.model.primitive.InstantDt +instant.narrative=classpath:ca/uhn/fhir/narrative/DateTimeDt.html ################################################ # Composite Datatypes ################################################ -address.dtclass=ca.uhn.fhir.model.dstu.composite.AddressDt -address.dtnarrative=classpath:ca/uhn/fhir/narrative/AddressDt.html +address.class=ca.uhn.fhir.model.dstu.composite.AddressDt +address.narrative=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 +humanname.class=ca.uhn.fhir.model.dstu.composite.HumanNameDt +humanname.narrative=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 +quantity.class=ca.uhn.fhir.model.dstu.composite.QuantityDt +quantity.narrative=classpath:ca/uhn/fhir/narrative/QuantityDt.html ################################################ # Resources ################################################ -patient.profile=http://hl7.org/fhir/profiles/Patient +patient.class=ca.uhn.fhir.model.dstu.resource.Patient patient.narrative=classpath:ca/uhn/fhir/narrative/Patient.html -diagnosticreport.profile=http://hl7.org/fhir/profiles/DiagnosticReport +diagnosticreport.class=ca.uhn.fhir.model.dstu.resource.DiagnosticReport diagnosticreport.narrative=classpath:ca/uhn/fhir/narrative/DiagnosticReport.html \ No newline at end of file diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/narrative/CustomThymeleafNarrativeGeneratorTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/narrative/CustomThymeleafNarrativeGeneratorTest.java index d5b2be1d76e..208727a65ad 100644 --- a/hapi-fhir-base/src/test/java/ca/uhn/fhir/narrative/CustomThymeleafNarrativeGeneratorTest.java +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/narrative/CustomThymeleafNarrativeGeneratorTest.java @@ -11,6 +11,8 @@ public class CustomThymeleafNarrativeGeneratorTest { CustomThymeleafNarrativeGenerator gen = new CustomThymeleafNarrativeGenerator("src/test/resources/narrative/customnarrative.properties"); + + } } diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/narrative/DefaultThymeleafNarrativeGeneratorTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/narrative/DefaultThymeleafNarrativeGeneratorTest.java index 57405618397..eccb400a179 100644 --- a/hapi-fhir-base/src/test/java/ca/uhn/fhir/narrative/DefaultThymeleafNarrativeGeneratorTest.java +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/narrative/DefaultThymeleafNarrativeGeneratorTest.java @@ -30,7 +30,7 @@ public class DefaultThymeleafNarrativeGeneratorTest { public void before() throws IOException { gen = new DefaultThymeleafNarrativeGenerator(); gen.setIgnoreFailures(false); - gen.setIgnoreMissingTemplates(true); + gen.setIgnoreMissingTemplates(false); } @Test diff --git a/hapi-fhir-base/src/test/resources/narrative/customnarative.properties b/hapi-fhir-base/src/test/resources/narrative/customnarative.properties index b3dd17f735d..7d472fcbbbd 100644 --- a/hapi-fhir-base/src/test/resources/narrative/customnarative.properties +++ b/hapi-fhir-base/src/test/resources/narrative/customnarative.properties @@ -1,21 +1,16 @@ # 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 first (name.class) defines the class name of the +# resource to define a template for # # 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.class=ca.uhn.fhir.model.dstu.resource.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 +# You may also override/define behaviour for datatypes +humanname.class=ca.uhn.fhir.model.dstu.composite.HumanNameDt +humanname.narrative=classpath:ca/uhn/fhir/narrative/HumanNameDt.html