
001package ca.uhn.fhir.narrative2; 002 003/*- 004 * #%L 005 * HAPI FHIR - Core Library 006 * %% 007 * Copyright (C) 2014 - 2022 Smile CDR, Inc. 008 * %% 009 * Licensed under the Apache License, Version 2.0 (the "License"); 010 * you may not use this file except in compliance with the License. 011 * You may obtain a copy of the License at 012 * 013 * http://www.apache.org/licenses/LICENSE-2.0 014 * 015 * Unless required by applicable law or agreed to in writing, software 016 * distributed under the License is distributed on an "AS IS" BASIS, 017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 018 * See the License for the specific language governing permissions and 019 * limitations under the License. 020 * #L% 021 */ 022 023import ca.uhn.fhir.context.FhirContext; 024import ca.uhn.fhir.i18n.Msg; 025import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; 026import org.hl7.fhir.instance.model.api.IBase; 027import org.thymeleaf.IEngineConfiguration; 028import org.thymeleaf.TemplateEngine; 029import org.thymeleaf.cache.AlwaysValidCacheEntryValidity; 030import org.thymeleaf.cache.ICacheEntryValidity; 031import org.thymeleaf.context.Context; 032import org.thymeleaf.context.ITemplateContext; 033import org.thymeleaf.engine.AttributeName; 034import org.thymeleaf.messageresolver.IMessageResolver; 035import org.thymeleaf.model.IProcessableElementTag; 036import org.thymeleaf.processor.IProcessor; 037import org.thymeleaf.processor.element.AbstractAttributeTagProcessor; 038import org.thymeleaf.processor.element.AbstractElementTagProcessor; 039import org.thymeleaf.processor.element.IElementTagStructureHandler; 040import org.thymeleaf.standard.StandardDialect; 041import org.thymeleaf.standard.expression.IStandardExpression; 042import org.thymeleaf.standard.expression.IStandardExpressionParser; 043import org.thymeleaf.standard.expression.StandardExpressions; 044import org.thymeleaf.templatemode.TemplateMode; 045import org.thymeleaf.templateresolver.DefaultTemplateResolver; 046import org.thymeleaf.templateresource.ITemplateResource; 047import org.thymeleaf.templateresource.StringTemplateResource; 048 049import java.util.EnumSet; 050import java.util.List; 051import java.util.Map; 052import java.util.Set; 053 054import static org.apache.commons.lang3.StringUtils.isNotBlank; 055 056public class ThymeleafNarrativeGenerator extends BaseNarrativeGenerator { 057 058 private IMessageResolver myMessageResolver; 059 060 /** 061 * Constructor 062 */ 063 public ThymeleafNarrativeGenerator() { 064 super(); 065 } 066 067 private TemplateEngine getTemplateEngine(FhirContext theFhirContext) { 068 TemplateEngine engine = new TemplateEngine(); 069 ProfileResourceResolver resolver = new ProfileResourceResolver(theFhirContext); 070 engine.setTemplateResolver(resolver); 071 if (myMessageResolver != null) { 072 engine.setMessageResolver(myMessageResolver); 073 } 074 StandardDialect dialect = new StandardDialect() { 075 @Override 076 public Set<IProcessor> getProcessors(String theDialectPrefix) { 077 Set<IProcessor> retVal = super.getProcessors(theDialectPrefix); 078 retVal.add(new NarrativeTagProcessor(theFhirContext, theDialectPrefix)); 079 retVal.add(new NarrativeAttributeProcessor(theDialectPrefix, theFhirContext)); 080 return retVal; 081 } 082 083 }; 084 085 engine.setDialect(dialect); 086 return engine; 087 } 088 089 @Override 090 protected String applyTemplate(FhirContext theFhirContext, INarrativeTemplate theTemplate, IBase theTargetContext) { 091 092 Context context = new Context(); 093 context.setVariable("resource", theTargetContext); 094 context.setVariable("context", theTargetContext); 095 context.setVariable("fhirVersion", theFhirContext.getVersion().getVersion().name()); 096 097 return getTemplateEngine(theFhirContext).process(theTemplate.getTemplateName(), context); 098 } 099 100 101 @Override 102 protected EnumSet<TemplateTypeEnum> getStyle() { 103 return EnumSet.of(TemplateTypeEnum.THYMELEAF); 104 } 105 106 private String applyTemplateWithinTag(FhirContext theFhirContext, ITemplateContext theTemplateContext, String theName, String theElement) { 107 IEngineConfiguration configuration = theTemplateContext.getConfiguration(); 108 IStandardExpressionParser expressionParser = StandardExpressions.getExpressionParser(configuration); 109 final IStandardExpression expression = expressionParser.parseExpression(theTemplateContext, theElement); 110 Object elementValueObj = expression.execute(theTemplateContext); 111 final IBase elementValue = (IBase) elementValueObj; 112 if (elementValue == null) { 113 return ""; 114 } 115 116 List<INarrativeTemplate> templateOpt; 117 if (isNotBlank(theName)) { 118 templateOpt = getManifest().getTemplateByName(theFhirContext, getStyle(), theName); 119 if (templateOpt.isEmpty()) { 120 throw new InternalErrorException(Msg.code(1863) + "Unknown template name: " + theName); 121 } 122 } else { 123 templateOpt = getManifest().getTemplateByElement(theFhirContext, getStyle(), elementValue); 124 if (templateOpt.isEmpty()) { 125 throw new InternalErrorException(Msg.code(1864) + "No template for type: " + elementValue.getClass()); 126 } 127 } 128 129 return applyTemplate(theFhirContext, templateOpt.get(0), elementValue); 130 } 131 132 public void setMessageResolver(IMessageResolver theMessageResolver) { 133 myMessageResolver = theMessageResolver; 134 } 135 136 137 private class ProfileResourceResolver extends DefaultTemplateResolver { 138 private final FhirContext myFhirContext; 139 140 private ProfileResourceResolver(FhirContext theFhirContext) { 141 myFhirContext = theFhirContext; 142 } 143 144 @Override 145 protected boolean computeResolvable(IEngineConfiguration theConfiguration, String theOwnerTemplate, String theTemplate, Map<String, Object> theTemplateResolutionAttributes) { 146 return getManifest().getTemplateByName(myFhirContext, getStyle(), theTemplate).size() > 0; 147 } 148 149 @Override 150 protected TemplateMode computeTemplateMode(IEngineConfiguration theConfiguration, String theOwnerTemplate, String theTemplate, Map<String, Object> theTemplateResolutionAttributes) { 151 return TemplateMode.XML; 152 } 153 154 @Override 155 protected ITemplateResource computeTemplateResource(IEngineConfiguration theConfiguration, String theOwnerTemplate, String theTemplate, Map<String, Object> theTemplateResolutionAttributes) { 156 return getManifest() 157 .getTemplateByName(myFhirContext, getStyle(), theTemplate) 158 .stream() 159 .findFirst() 160 .map(t -> new StringTemplateResource(t.getTemplateText())) 161 .orElseThrow(() -> new IllegalArgumentException("Unknown template: " + theTemplate)); 162 } 163 164 @Override 165 protected ICacheEntryValidity computeValidity(IEngineConfiguration theConfiguration, String theOwnerTemplate, String theTemplate, Map<String, Object> theTemplateResolutionAttributes) { 166 return AlwaysValidCacheEntryValidity.INSTANCE; 167 } 168 } 169 170 private class NarrativeTagProcessor extends AbstractElementTagProcessor { 171 172 private final FhirContext myFhirContext; 173 174 NarrativeTagProcessor(FhirContext theFhirContext, String dialectPrefix) { 175 super(TemplateMode.XML, dialectPrefix, "narrative", true, null, true, 0); 176 myFhirContext = theFhirContext; 177 } 178 179 @Override 180 protected void doProcess(ITemplateContext theTemplateContext, IProcessableElementTag theTag, IElementTagStructureHandler theStructureHandler) { 181 String name = theTag.getAttributeValue("th:name"); 182 String element = theTag.getAttributeValue("th:element"); 183 184 String appliedTemplate = applyTemplateWithinTag(myFhirContext, theTemplateContext, name, element); 185 theStructureHandler.replaceWith(appliedTemplate, false); 186 } 187 } 188 189 /** 190 * This is a thymeleaf extension that allows people to do things like 191 * <th:block th:narrative="${result}"/> 192 */ 193 private class NarrativeAttributeProcessor extends AbstractAttributeTagProcessor { 194 195 private final FhirContext myFhirContext; 196 197 NarrativeAttributeProcessor(String theDialectPrefix, FhirContext theFhirContext) { 198 super(TemplateMode.XML, theDialectPrefix, null, false, "narrative", true, 0, true); 199 myFhirContext = theFhirContext; 200 } 201 202 @Override 203 protected void doProcess(ITemplateContext theContext, IProcessableElementTag theTag, AttributeName theAttributeName, String theAttributeValue, IElementTagStructureHandler theStructureHandler) { 204 String text = applyTemplateWithinTag(myFhirContext, theContext, null, theAttributeValue); 205 theStructureHandler.setBody(text, false); 206 } 207 208 } 209}