More narrative work

This commit is contained in:
jamesagnew 2014-04-01 18:01:52 -04:00
parent 57a3a4881e
commit 81b2b1cc50
5 changed files with 131 additions and 115 deletions

View File

@ -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<String, String> myDatatypeClassNameToNarrativeTemplate;
private TemplateEngine myDatatypeTemplateEngine;
private boolean myIgnoreFailures = true;
private boolean myIgnoreMissingTemplates = true;
private TemplateEngine myProfileTemplateEngine;
private HashMap<String, String> myProfileToNarrativeTemplate;
private HashMap<String, String> myProfileToNarrativeName;
private HashMap<Class<?>, String> myClassToNarrativeName;
private HashMap<String, String> myNameToNarrativeTemplate;
public BaseThymeleafNarrativeGenerator() throws IOException {
myProfileToNarrativeTemplate = new HashMap<String, String>();
myDatatypeClassNameToNarrativeTemplate = new HashMap<String, String>();
String propFileName = getPropertyFile();
loadProperties(propFileName);
{
myProfileTemplateEngine = new TemplateEngine();
TemplateResolver resolver = new TemplateResolver();
resolver.setResourceResolver(new ProfileResourceResolver());
myProfileTemplateEngine.setTemplateResolver(resolver);
StandardDialect dialect = new StandardDialect();
HashSet<IProcessor> additionalProcessors = new HashSet<IProcessor>();
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)) {
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("<div>No narrative available</div>"), 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<String, String>();
myClassToNarrativeName = new HashMap<Class<?>, String>();
myNameToNarrativeTemplate = new HashMap<String, String>();
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<IProcessor> additionalProcessors = new HashSet<IProcessor>();
additionalProcessors.add(new NarrativeAttributeProcessor());
dialect.setAdditionalProcessors(additionalProcessors);
myProfileTemplateEngine.setDialect(dialect);
myProfileTemplateEngine.initialize();
}
myInitialized = true;
}
protected abstract String getPropertyFile();
/**
* If set to <code>true</code> (which is the default), most whitespace will
* be trimmed from the generated narrative before it is returned.
* If set to <code>true</code> (which is the default), most whitespace will be trimmed from the generated narrative before it is returned.
* <p>
* 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.
* </p>
*/
public boolean isCleanWhitespace() {
@ -127,30 +149,24 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
}
/**
* If set to <code>true</code>, 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 <code>true</code>, 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 <code>true</code> (which is the default), most whitespace will
* be trimmed from the generated narrative before it is returned.
* If set to <code>true</code> (which is the default), most whitespace will be trimmed from the generated narrative before it is returned.
* <p>
* 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.
* </p>
*/
public void setCleanWhitespace(boolean theCleanWhitespace) {
@ -158,18 +174,15 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
}
/**
* If set to <code>true</code>, 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 <code>true</code>, 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));

View File

@ -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

View File

@ -11,6 +11,8 @@ public class CustomThymeleafNarrativeGeneratorTest {
CustomThymeleafNarrativeGenerator gen = new CustomThymeleafNarrativeGenerator("src/test/resources/narrative/customnarrative.properties");
}
}

View File

@ -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

View File

@ -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