package ch.elexis.core.findings.fhir.po.service.internal;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import org.apache.commons.io.IOUtils;
import org.hl7.fhir.instance.model.api.IBaseDatatype;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.INarrative;
import org.slf4j.LoggerFactory;
import org.thymeleaf.IEngineConfiguration;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.cache.AlwaysValidCacheEntryValidity;
import org.thymeleaf.cache.ICacheEntryValidity;
import org.thymeleaf.context.Context;
import org.thymeleaf.context.ITemplateContext;
import org.thymeleaf.engine.AttributeName;
import org.thymeleaf.model.IProcessableElementTag;
import org.thymeleaf.processor.IProcessor;
import org.thymeleaf.processor.element.AbstractAttributeTagProcessor;
import org.thymeleaf.processor.element.IElementTagStructureHandler;
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.templatemode.TemplateMode;
import org.thymeleaf.templateresolver.DefaultTemplateResolver;
import org.thymeleaf.templateresource.ITemplateResource;
import org.thymeleaf.templateresource.StringTemplateResource;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.IDatatype;
import ca.uhn.fhir.parser.DataFormatException;
import ch.elexis.core.findings.IFindingsService;
public class CustomNarrativeGenerator implements ca.uhn.fhir.narrative.INarrativeGenerator {
private static final org.slf4j.Logger logger = LoggerFactory.getLogger(CustomNarrativeGenerator.class);
private boolean initialized;
private HashMap<Class<?>, String> classToName;
private HashMap<String, String> nameToNarrativeTemplate;
private TemplateEngine profileTemplateEngine;
@Override
public void generateNarrative(FhirContext theContext, IBaseResource theResource, INarrative theNarrative) {
if (!initialized) {
initialize(theContext);
}
String name = null;
if (name == null) {
name = classToName.get(theResource.getClass());
}
if (name == null) {
name = theContext.getResourceDefinition(theResource).getName().toLowerCase();
}
if (name == null || !nameToNarrativeTemplate.containsKey(name)) {
logger.debug("No narrative template available for resorce: {}", name);
return;
}
try {
Context context = new Context();
context.setVariable("resource", theResource);
context.setVariable("fhirVersion", theContext.getVersion().getVersion().name());
String result = profileTemplateEngine.process(name, context);
if (result == null || result.trim().isEmpty()) {
return;
}
theNarrative.setDivAsString(result);
theNarrative.setStatusAsString("generated");
return;
} catch (Exception e) {
logger.error("Failed to generate narrative", e);
}
}
private synchronized void initialize(final FhirContext theContext) {
if (initialized) {
return;
}
logger.info("Initializing narrative generator");
classToName = new HashMap<Class<?>, String>();
nameToNarrativeTemplate = new HashMap<String, String>();
try {
loadProperties("/rsc/narrative/custom.properties");
} catch (IOException e) {
logger.info("Failed to load property file.", e);
}
profileTemplateEngine = new TemplateEngine();
ProfileResourceResolver resolver = new ProfileResourceResolver();
profileTemplateEngine.setTemplateResolver(resolver);
StandardDialect dialect = new StandardDialect() {
public Set<IProcessor> getProcessors(String theDialectPrefix) {
Set<IProcessor> retVal = super.getProcessors(theDialectPrefix);
retVal.add(new NarrativeAttributeProcessor(theContext, theDialectPrefix));
return retVal;
}
};
profileTemplateEngine.setDialect(dialect);
initialized = true;
}
private InputStream loadResource(String name) {
return IFindingsService.class.getResourceAsStream(name);
}
private void loadProperties(String propFileName) throws IOException {
Properties file = new Properties();
InputStream resource = IFindingsService.class.getResourceAsStream(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 (name == null || name.trim().isEmpty()) {
continue;
}
String narrativePropName = name + ".narrative";
String narrativeName = file.getProperty(narrativePropName);
if (narrativeName != null && !narrativeName.trim().isEmpty()) {
String narrative = IOUtils.toString(loadResource(narrativeName), "UTF-8");
nameToNarrativeTemplate.put(name, narrative);
}
} else if (nextKey.endsWith(".class")) {
String name = nextKey.substring(0, nextKey.indexOf(".class"));
if (name == null || name.trim().isEmpty()) {
continue;
}
String className = file.getProperty(nextKey);
Class<?> clazz;
try {
clazz = Class.forName(className);
} catch (ClassNotFoundException e) {
logger.debug("Unknown datatype class '{}' identified in narrative file {}", name, propFileName);
clazz = null;
}
if (clazz != null) {
classToName.put(clazz, name);
}
} else if (nextKey.endsWith(".narrative")) {
String name = nextKey.substring(0, nextKey.indexOf(".narrative"));
if (name == null || name.trim().isEmpty()) {
continue;
}
String narrativePropName = name + ".narrative";
String narrativeName = file.getProperty(narrativePropName);
if (narrativeName != null && !narrativeName.trim().isEmpty()) {
String narrative = IOUtils.toString(loadResource(narrativeName), "UTF-8");
nameToNarrativeTemplate.put(name, narrative);
}
continue;
} else {
throw new ConfigurationException("Invalid property name: " + nextKey);
}
}
}
public class NarrativeAttributeProcessor extends AbstractAttributeTagProcessor {
private FhirContext fhirContext;
protected NarrativeAttributeProcessor(FhirContext theContext, String theDialectPrefix) {
super(TemplateMode.XML, theDialectPrefix, null, false, "narrative", true, 0, true);
this.fhirContext = theContext;
}
protected void doProcess(ITemplateContext theContext, IProcessableElementTag theTag,
AttributeName theAttributeName, String theAttributeValue,
IElementTagStructureHandler theStructureHandler) {
IEngineConfiguration configuration = theContext.getConfiguration();
IStandardExpressionParser expressionParser = StandardExpressions.getExpressionParser(configuration);
final IStandardExpression expression = expressionParser.parseExpression(theContext, theAttributeValue);
final Object value = expression.execute(theContext);
if (value == null) {
return;
}
Context context = new Context();
context.setVariable("fhirVersion", fhirContext.getVersion().getVersion().name());
context.setVariable("resource", value);
String name = null;
if (value != null) {
Class<? extends Object> nextClass = value.getClass();
do {
name = classToName.get(nextClass);
nextClass = nextClass.getSuperclass();
} while (name == null && nextClass.equals(Object.class) == false);
if (name == null) {
if (value instanceof IBaseResource) {
name = fhirContext.getResourceDefinition((Class<? extends IBaseResource>) value).getName();
} else if (value instanceof IDatatype) {
name = value.getClass().getSimpleName();
name = name.substring(0, name.length() - 2);
} else if (value instanceof IBaseDatatype) {
name = value.getClass().getSimpleName();
if (name.endsWith("Type")) {
name = name.substring(0, name.length() - 4);
}
} else {
throw new DataFormatException("Don't know how to determine name for type: " + value.getClass());
}
name = name.toLowerCase();
if (!nameToNarrativeTemplate.containsKey(name)) {
name = null;
}
}
}
if (name == null) {
logger.debug("No narrative template available for type: {}", value.getClass());
return;
}
String result = profileTemplateEngine.process(name, context);
String trim = result.trim();
theStructureHandler.setBody(trim, true);
}
}
private final class ProfileResourceResolver extends DefaultTemplateResolver {
protected boolean computeResolvable(IEngineConfiguration theConfiguration, String theOwnerTemplate,
String theTemplate, Map<String, Object> theTemplateResolutionAttributes) {
String template = nameToNarrativeTemplate.get(theTemplate);
return template != null;
}
protected TemplateMode computeTemplateMode(IEngineConfiguration theConfiguration, String theOwnerTemplate,
String theTemplate, Map<String, Object> theTemplateResolutionAttributes) {
return TemplateMode.XML;
}
protected ITemplateResource computeTemplateResource(IEngineConfiguration theConfiguration,
String theOwnerTemplate, String theTemplate, Map<String, Object> theTemplateResolutionAttributes) {
String template = nameToNarrativeTemplate.get(theTemplate);
return new StringTemplateResource(template);
}
protected ICacheEntryValidity computeValidity(IEngineConfiguration theConfiguration, String theOwnerTemplate,
String theTemplate, Map<String, Object> theTemplateResolutionAttributes) {
return AlwaysValidCacheEntryValidity.INSTANCE;
}
}
}