org.hl7.fhir.r5.renderers.ExampleScenarioRenderer Maven / Gradle / Ivy
package org.hl7.fhir.r5.renderers;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.FHIRFormatError;
import org.hl7.fhir.r5.context.ContextUtilities;
import org.hl7.fhir.r5.model.CanonicalType;
import org.hl7.fhir.r5.model.Enumerations;
import org.hl7.fhir.r5.model.ExampleScenario;
import org.hl7.fhir.r5.model.ExampleScenario.ExampleScenarioActorComponent;
import org.hl7.fhir.r5.model.ExampleScenario.ExampleScenarioInstanceComponent;
import org.hl7.fhir.r5.model.ExampleScenario.ExampleScenarioInstanceContainedInstanceComponent;
import org.hl7.fhir.r5.model.ExampleScenario.ExampleScenarioInstanceVersionComponent;
import org.hl7.fhir.r5.model.ExampleScenario.ExampleScenarioProcessComponent;
import org.hl7.fhir.r5.model.ExampleScenario.ExampleScenarioProcessStepAlternativeComponent;
import org.hl7.fhir.r5.model.ExampleScenario.ExampleScenarioProcessStepComponent;
import org.hl7.fhir.r5.model.ExampleScenario.ExampleScenarioProcessStepOperationComponent;
import org.hl7.fhir.r5.model.Resource;
import org.hl7.fhir.r5.model.StructureDefinition;
import org.hl7.fhir.r5.renderers.utils.RenderingContext;
import org.hl7.fhir.r5.renderers.utils.RenderingContext.KnownLinkType;
import org.hl7.fhir.r5.renderers.utils.ResourceWrapper;
import org.hl7.fhir.r5.utils.EOperationOutcome;
import org.hl7.fhir.utilities.MarkedToMoveToAdjunctPackage;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.xhtml.XhtmlDocument;
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
import net.sourceforge.plantuml.FileFormat;
import net.sourceforge.plantuml.FileFormatOption;
import net.sourceforge.plantuml.SourceStringReader;
@MarkedToMoveToAdjunctPackage
public class ExampleScenarioRenderer extends TerminologyRenderer {
public ExampleScenarioRenderer(RenderingContext context) {
super(context);
}
@Override
public void buildNarrative(RenderingStatus status, XhtmlNode x, ResourceWrapper r) throws FHIRFormatError, DefinitionException, IOException, FHIRException, EOperationOutcome {
if (r.isDirect()) {
renderResourceTechDetails(r, x);
genSummaryTable(status, x, (ExampleScenario) r.getBase());
render(status, x, (ExampleScenario) r.getBase(), r);
} else {
// the intention is to change this in the future
x.para().tx("ExampleScenarioRenderer only renders native resources directly");
}
}
@Override
public String buildSummary(ResourceWrapper r) throws UnsupportedEncodingException, IOException {
return canonicalTitle(r);
}
public void render(RenderingStatus status, XhtmlNode x, ExampleScenario scen, ResourceWrapper res) throws FHIRException {
try {
if (context.getScenarioMode() == null) {
renderActors(status, res, x, scen);
} else {
switch (context.getScenarioMode()) {
case ACTORS:
renderActors(status, res, x, scen);
break;
case INSTANCES:
renderInstances(status, res, x, scen);
break;
case PROCESSES:
renderProcesses(status, x, scen);
break;
default:
throw new FHIRException(context.formatPhrase(RenderingContext.EX_SCEN_UN, context.getScenarioMode()) + " ");
}
}
} catch (Exception e) {
e.printStackTrace();
throw new FHIRException(context.formatPhrase(RenderingContext.EX_SCEN_ERR_REN, scen.getUrl(), e) + " ");
}
}
public String renderDiagram(RenderingStatus status, ResourceWrapper res, ExampleScenario scen) throws IOException {
try {
String plantUml = toPlantUml(status, res, scen);
SourceStringReader reader = new SourceStringReader(plantUml);
final ByteArrayOutputStream os = new ByteArrayOutputStream();
reader.outputImage(os, new FileFormatOption(FileFormat.SVG));
os.close();
final String svg = new String(os.toByteArray(), Charset.forName("UTF-8"));
return svg;
} catch (Exception e) {
return ""+Utilities.escapeXml(e.getMessage())+"
";
}
}
protected String toPlantUml(RenderingStatus status, ResourceWrapper res, ExampleScenario scen) throws IOException {
String plantUml = "@startuml\r\n";
plantUml += "Title " + (scen.hasTitle() ? scen.getTitle() : scen.getName()) + "\r\n\r\n";
Map actorKeys = new HashMap();
for (ExampleScenarioActorComponent actor: scen.getActor()) {
String actorType = actor.getType().equals(Enumerations.ExampleScenarioActorType.PERSON) ? "actor" : "participant";
actorKeys.put(actor.getKey(), escapeKey(actor.getKey()));
plantUml += actorType + " \"" + creolLink(actor.getTitle(), "#a_" + actor.getKey(), actor.getDescription()) + "\" as " + actorKeys.get(actor.getKey()) + "\r\n";
}
plantUml += "\r\n";
int processNum = 1;
for (ExampleScenarioProcessComponent process: scen.getProcess()) {
plantUml += toPlantUml(status, res, process, Integer.toString(processNum), scen, actorKeys);
processNum++;
}
plantUml += "@enduml";
return plantUml;
}
private String escapeKey(String origKey) {
char[] chars = origKey.toCharArray();
for (int i=0; i='A' && c<='Z') || (c>='a' && c<='z') || (c>='0' && c<='9') || c=='@' || c=='.'))
chars[i] = '_';
}
return new String(chars);
}
protected String toPlantUml(RenderingStatus status, ResourceWrapper res, ExampleScenarioProcessComponent process, String prefix, ExampleScenario scen, Map actorKeys) throws IOException {
String plantUml = "group " + process.getTitle() + " " + creolLink("details", "#p_" + prefix, process.getDescription()) + "\r\n";
Map actorsActive = new HashMap();
for (ExampleScenarioActorComponent actor : scen.getActor()) {
actorsActive.put(actor.getKey(), Boolean.FALSE);
}
int stepCount = 1;
for (ExampleScenarioProcessStepComponent step: process.getStep()) {
plantUml += toPlantUml(status, res, step, stepPrefix(prefix, step, stepCount), scen, actorsActive, actorKeys);
if (step.getPause())
plantUml += context.formatPhrase(RenderingContext.EX_SCEN_TIME)+"\n";
stepCount++;
}
plantUml += "end\r\n\r\n";
return plantUml;
}
protected String toPlantUml(RenderingStatus status, ResourceWrapper res, ExampleScenarioProcessStepComponent step, String prefix, ExampleScenario scen, Map actorsActive, Map actorKeys) throws IOException {
String plantUml = "";
if (step.hasWorkflow()) {
XhtmlNode n = new XhtmlDocument();
renderCanonical(status, res, n, Resource.class, step.getWorkflowElement());
XhtmlNode ref = n.getChildNodes().get(0);
plantUml += noteOver(scen.getActor(), context.formatPhrase(RenderingContext.EXAMPLE_SCEN_STEP_SCEN, trimPrefix(prefix), creolLink((ref.getContent()), ref.getAttribute("href"))));
} else if (step.hasProcess())
plantUml += toPlantUml(status, res, step.getProcess(), prefix, scen, actorKeys);
else {
// Operation
plantUml += toPlantUml(step.getOperation(), prefix, scen, actorsActive, actorKeys);
}
return plantUml;
}
protected String toPlantUml(ExampleScenarioProcessStepOperationComponent op, String prefix, ExampleScenario scen, Map actorsActive, Map actorKeys) {
StringBuilder plantUml = new StringBuilder();
plantUml.append(handleActivation(op.getInitiator(), op.getInitiatorActive(), actorsActive, actorKeys));
plantUml.append(handleActivation(op.getReceiver(), op.getReceiverActive(), actorsActive, actorKeys));
plantUml.append(actorKeys.get(op.getInitiator()) + " -> " + actorKeys.get(op.getReceiver()) + ": ");
plantUml.append(creolLink(op.getTitle(), "#s_" + prefix, op.getDescription()));
if (op.hasRequest()) {
plantUml.append(" (" + creolLink("payload", linkForInstance(op.getRequest())) + ")\r\n");
}
if (op.hasResponse()) {
plantUml.append("activate " + actorKeys.get(op.getReceiver()) + "\r\n");
plantUml.append(actorKeys.get(op.getReceiver()) + " --> " + actorKeys.get(op.getInitiator()) + ": ");
plantUml.append(creolLink("response", "#s_" + prefix, op.getDescription()));
plantUml.append(" (" + creolLink("payload", linkForInstance(op.getResponse())) + ")\r\n");
plantUml.append("deactivate " + actorKeys.get(op.getReceiver()) + "\r\n");
}
plantUml.append(handleDeactivation(op.getInitiator(), op.getInitiatorActive(), actorsActive, actorKeys));
plantUml.append(handleDeactivation(op.getReceiver(), op.getReceiverActive(), actorsActive, actorKeys));
return plantUml.toString();
}
private String handleActivation(String actorId, boolean active, Map actorsActive, Map actorKeys) {
String plantUml = "";
Boolean actorWasActive = actorsActive.get(actorId);
if (active && !actorWasActive) {
plantUml += "activate " + actorKeys.get(actorId) + "\r\n";
}
return plantUml;
}
private String handleDeactivation(String actorId, boolean active, Map actorsActive, Map actorKeys) {
String plantUml = "";
Boolean actorWasActive = actorsActive.get(actorId);
if (actorWasActive != null) {
if (!active && actorWasActive) {
plantUml += "deactivate " + actorKeys.get(actorId) + "\r\n";
}
if (active != actorWasActive) {
actorsActive.remove(actorId);
actorsActive.put(actorId, Boolean.valueOf(active));
}
}
return plantUml;
}
private String linkForInstance(ExampleScenarioInstanceContainedInstanceComponent ref) {
String plantUml = "#i_" + ref.getInstanceReference();
if (ref.hasVersionReference())
plantUml += "v_" + ref.getVersionReference();
return plantUml;
}
private String trimPrefix(String prefix){
return prefix.substring(prefix.lastIndexOf(".") + 1);
}
private String noteOver(List actors, String text) {
String plantUml = "Note over ";
List actorKeys = new ArrayList();
for (ExampleScenarioActorComponent actor: actors) {
actorKeys.add(actor.getKey());
}
plantUml += String.join(", ", actorKeys);
plantUml += " " + text;
return plantUml;
}
private String creolLink(String text, String url) {
return creolLink(text, url, null);
}
private String creolLink(String text, String url, String flyover) {
String s = "[[" + url;
if (flyover!=null)
s += "{" + flyover + "}";
s += " " + text + "]]";
return s;
}
public boolean renderActors(RenderingStatus status, ResourceWrapper res, XhtmlNode x, ExampleScenario scen) throws IOException {
XhtmlNode tbl = x.table("table-striped table-bordered", false);
XhtmlNode thead = tbl.tr();
thead.th().addText(context.formatPhrase(RenderingContext.GENERAL_NAME));
thead.th().addText(context.formatPhrase(RenderingContext.GENERAL_TYPE));
thead.th().addText(context.formatPhrase(RenderingContext.GENERAL_DESC));
for (ExampleScenarioActorComponent actor : scen.getActor()) {
XhtmlNode tr = tbl.tr();
XhtmlNode nameCell = tr.td();
nameCell.an(context.prefixAnchor("a_" + actor.getKey()));
nameCell.tx(actor.getTitle());
tr.td().tx(actor.getType().getDisplay());
addMarkdown(tr.td().style("overflow-wrap:break-word"), actor.getDescription());
}
return true;
}
public boolean renderInstances(RenderingStatus status, ResourceWrapper res, XhtmlNode x, ExampleScenario scen) throws IOException {
XhtmlNode tbl = x.table("table-striped table-bordered", false);
XhtmlNode thead = tbl.tr();
thead.th().addText(context.formatPhrase(RenderingContext.GENERAL_NAME));
thead.th().addText(context.formatPhrase(RenderingContext.GENERAL_TYPE));
thead.th().addText(context.formatPhrase(RenderingContext.GENERAL_CONTENT));
thead.th().addText(context.formatPhrase(RenderingContext.GENERAL_DESC));
Map instanceNames = new HashMap();
for (ExampleScenarioInstanceComponent instance : scen.getInstance()) {
instanceNames.put("i_" + instance.getKey(), instance.getTitle());
if (instance.hasVersion()) {
for (ExampleScenarioInstanceVersionComponent version: instance.getVersion()) {
instanceNames.put("i_" + instance.getKey() + "v_" + version.getKey(), version.getTitle());
}
}
}
for (ExampleScenarioInstanceComponent instance : scen.getInstance()) {
XhtmlNode row = tbl.tr();
XhtmlNode nameCell = row.td();
nameCell.an(context.prefixAnchor("i_" + instance.getKey()));
nameCell.tx(instance.getTitle());
XhtmlNode typeCell = row.td();
if (instance.hasVersion())
typeCell.attribute("rowSpan", Integer.toString(instance.getVersion().size()+1));
if (!instance.hasStructureVersion() || instance.getStructureType().getSystem().equals("")) {
if (instance.hasStructureVersion())
typeCell.tx((context.formatPhrase(RenderingContext.EX_SCEN_FVER, instance.getStructureVersion()) + " ") + " ");
if (instance.hasStructureProfileCanonicalType()) {
renderCanonical(status, res, typeCell, StructureDefinition.class, instance.getStructureProfileCanonicalType());
} else if (instance.hasStructureProfileUriType()) {
renderBase(status, typeCell, instance.getStructureProfileUriType());
} else {
CanonicalType ct = new CanonicalType("http://hl7.org/fhir/StructureDefinition/" + instance.getStructureType().getCode());
renderCanonical(status, res, typeCell, StructureDefinition.class, ct);
}
} else {
renderDataType(status, typeCell, wrapWC(res, instance.getStructureVersionElement()));
typeCell.tx(" "+(context.formatPhrase(RenderingContext.GENERAL_VER_LOW, instance.getStructureVersion())+" "));
if (instance.hasStructureProfile()) {
typeCell.tx(" ");
if (instance.hasStructureProfileCanonicalType()) {
renderCanonical(status, res, typeCell, StructureDefinition.class, instance.getStructureProfileCanonicalType());
}
}
}
if (instance.hasContent() && instance.getContent().hasReference()) {
// Force end-user mode to avoid ugly references
RenderingContext.ResourceRendererMode mode = context.getMode();
context.setMode(RenderingContext.ResourceRendererMode.END_USER);
renderReference(status, row.td(), wrapWC(res, instance.getContent().copy().setDisplay("here")));
context.setMode(mode);
} else
row.td();
XhtmlNode descCell = row.td();
addMarkdown(descCell, instance.getDescription());
if (instance.hasContainedInstance()) {
descCell.b().tx(context.formatPhrase(RenderingContext.EX_SCEN_CONTA) + " ");
int containedCount = 1;
for (ExampleScenarioInstanceContainedInstanceComponent contained: instance.getContainedInstance()) {
String key = "i_" + contained.getInstanceReference();
if (contained.hasVersionReference())
key += "v_" + contained.getVersionReference();
String description = instanceNames.get(key);
if (description==null)
throw new FHIRException("Unable to find contained instance " + key + " under " + instance.getKey());
descCell.ah(context.prefixLocalHref("#" + key)).tx(description);
containedCount++;
if (instance.getContainedInstance().size() > containedCount)
descCell.tx(", ");
}
}
for (ExampleScenarioInstanceVersionComponent version: instance.getVersion()) {
row = tbl.tr();
nameCell = row.td().style("padding-left: 10px;");
nameCell.an("i_" + instance.getKey() + "v_" + version.getKey());
XhtmlNode nameItem = nameCell.ul().li();
nameItem.tx(version.getTitle());
if (version.hasContent() && version.getContent().hasReference()) {
// Force end-user mode to avoid ugly references
RenderingContext.ResourceRendererMode mode = context.getMode();
context.setMode(RenderingContext.ResourceRendererMode.END_USER);
renderReference(status, row.td(), wrapWC(res, version.getContent().copy().setDisplay("here")));
context.setMode(mode);
} else
row.td();
descCell = row.td();
addMarkdown(descCell, instance.getDescription());
}
}
return true;
}
public boolean renderProcesses(RenderingStatus status, XhtmlNode x, ExampleScenario scen) throws IOException {
Map actors = new HashMap<>();
for (ExampleScenarioActorComponent actor: scen.getActor()) {
actors.put(actor.getKey(), actor);
}
Map instances = new HashMap<>();
for (ExampleScenarioInstanceComponent instance: scen.getInstance()) {
instances.put(instance.getKey(), instance);
}
int num = 1;
for (ExampleScenarioProcessComponent process : scen.getProcess()) {
renderProcess(status, x, process, Integer.toString(num), actors, instances);
num++;
}
return true;
}
public void renderProcess(RenderingStatus status, XhtmlNode x, ExampleScenarioProcessComponent process, String prefix, Map actors, Map instances) throws IOException {
XhtmlNode div = x.div();
div.an(context.prefixAnchor("p_" + prefix));
div.b().tx(context.formatPhrase(RenderingContext.EX_SCEN_PROC, process.getTitle())+" ");
if (process.hasDescription())
addMarkdown(div, process.getDescription());
if (process.hasPreConditions()) {
div.para().b().i().tx(context.formatPhrase(RenderingContext.EX_SCEN_PRECON));
addMarkdown(div, process.getPreConditions());
}
if (process.hasPostConditions()) {
div.para().b().i().tx(context.formatPhrase(RenderingContext.EX_SCEN_POSTCON));
addMarkdown(div, process.getPostConditions());
}
XhtmlNode tbl = div.table("table-striped table-bordered", false).style("width:100%");
XhtmlNode thead = tbl.tr();
thead.th().addText(context.formatPhrase(RenderingContext.EX_SCEN_STEP));
thead.th().addText(context.formatPhrase(RenderingContext.GENERAL_NAME));
thead.th().addText(context.formatPhrase(RenderingContext.GENERAL_DESC));
thead.th().addText(context.formatPhrase(RenderingContext.EX_SCEN_IN));
thead.th().addText(context.formatPhrase(RenderingContext.EX_SCEN_REC));
thead.th().addText(context.formatPhrase(RenderingContext.GENERAL_REQUEST));
thead.th().addText(context.formatPhrase(RenderingContext.EX_SCEN_RES));
int stepCount = 1;
for (ExampleScenarioProcessStepComponent step: process.getStep()) {
renderStep(status, tbl, step, stepPrefix(prefix, step, stepCount), actors, instances);
stepCount++;
}
// Now go through the steps again and spit out any child processes
stepCount = 1;
for (ExampleScenarioProcessStepComponent step: process.getStep()) {
stepSubProcesses(status, tbl, step, stepPrefix(prefix, step, stepCount), actors, instances);
stepCount++;
}
}
private String stepPrefix(String prefix, ExampleScenarioProcessStepComponent step, int stepCount) {
String num = step.hasNumber() ? step.getNumber() : Integer.toString(stepCount);
return (!prefix.isEmpty() ? prefix + "." : "") + num;
}
private String altStepPrefix(String prefix, ExampleScenarioProcessStepComponent step, int altNum, int stepCount) {
return stepPrefix(prefix + "-Alt" + Integer.toString(altNum) + ".", step, stepCount);
}
private void stepSubProcesses(RenderingStatus status, XhtmlNode x, ExampleScenarioProcessStepComponent step, String prefix, Map actors, Map instances) throws IOException {
if (step.hasProcess())
renderProcess(status, x, step.getProcess(), prefix, actors, instances);
if (step.hasAlternative()) {
int altNum = 1;
for (ExampleScenarioProcessStepAlternativeComponent alt: step.getAlternative()) {
int stepCount = 1;
for (ExampleScenarioProcessStepComponent altStep: alt.getStep()) {
stepSubProcesses(status, x, altStep, altStepPrefix(prefix, altStep, altNum, stepCount), actors, instances);
stepCount++;
}
altNum++;
}
}
}
private boolean renderStep(RenderingStatus status, XhtmlNode tbl, ExampleScenarioProcessStepComponent step, String stepLabel, Map actors, Map instances) throws IOException {
XhtmlNode row = tbl.tr();
XhtmlNode prefixCell = row.td();
prefixCell.an(context.prefixAnchor("s_" + stepLabel));
prefixCell.tx(stepLabel.substring(stepLabel.indexOf(".") + 1));
if (step.hasProcess()) {
XhtmlNode n = row.td().colspan(6);
n.tx(context.formatPhrase(RenderingContext.EX_SCEN_SEE));
n.ah(context.prefixLocalHref("#p_" + stepLabel), step.getProcess().getTitle());
n.tx(" "+ context.formatPhrase(RenderingContext.EX_SCEN_BEL));
} else if (step.hasWorkflow()) {
XhtmlNode n = row.td().colspan(6);
n.tx(context.formatPhrase(RenderingContext.EX_SCEN_OTH));
String link = new ContextUtilities(context.getWorker()).getLinkForUrl(context.getLink(KnownLinkType.SPEC), step.getWorkflow());
n.ah(context.prefixLocalHref(link), step.getProcess().getTitle());
} else {
// Must be an operation
ExampleScenarioProcessStepOperationComponent op = step.getOperation();
XhtmlNode name = row.td();
name.tx(op.getTitle());
if (op.hasType()) {
name.tx(" - ");
renderCoding(status, name, wrapNC(op.getType()));
}
XhtmlNode descCell = row.td();
addMarkdown(descCell, op.getDescription());
addActor(row, op.getInitiator(), actors);
addActor(row, op.getReceiver(), actors);
addInstance(row, op.getRequest(), instances);
addInstance(row, op.getResponse(), instances);
}
int altNum = 1;
for (ExampleScenarioProcessStepAlternativeComponent alt : step.getAlternative()) {
XhtmlNode altHeading = tbl.tr().colspan(7).td();
altHeading.para().i().tx(context.formatPhrase(RenderingContext.EX_SCEN_ALT, alt.getTitle())+" ");
if (alt.hasDescription())
addMarkdown(altHeading, alt.getDescription());
int stepCount = 1;
for (ExampleScenarioProcessStepComponent subStep : alt.getStep()) {
renderStep(status, tbl, subStep, altStepPrefix(stepLabel, step, altNum, stepCount), actors, instances);
stepCount++;
}
altNum++;
}
return true;
}
private void addActor(XhtmlNode row, String actorId, Map actors) throws FHIRException {
XhtmlNode actorCell = row.td();
if (actorId==null)
return;
ExampleScenarioActorComponent actor = actors.get(actorId);
if (actor==null)
throw new FHIRException(context.formatPhrase(RenderingContext.EX_SCEN_UN_ACT, actorId)+" ");
actorCell.ah("#a_" + actor.getKey(), actor.getDescription()).tx(actor.getTitle());
}
private void addInstance(XhtmlNode row, ExampleScenarioInstanceContainedInstanceComponent instanceRef, Map instances) {
XhtmlNode instanceCell = row.td();
if (instanceRef==null || instanceRef.getInstanceReference()==null)
return;
ExampleScenarioInstanceComponent instance = instances.get(instanceRef.getInstanceReference());
if (instance==null) {
instanceCell.b().tx("Bad reference: "+instanceRef.getInstanceReference());
} else if (instanceRef.hasVersionReference()) {
ExampleScenarioInstanceVersionComponent theVersion = null;
for (ExampleScenarioInstanceVersionComponent version: instance.getVersion()) {
if (version.getKey().equals(instanceRef.getVersionReference())) {
theVersion = version;
break;
}
}
if (theVersion==null)
throw new FHIRException("Unable to find referenced version " + instanceRef.getVersionReference() + " within instance " + instanceRef.getInstanceReference());
instanceCell.ah(context.prefixLocalHref("#i_" + instance.getKey() + "v_"+ theVersion.getKey()) , theVersion.getDescription()).tx(theVersion.getTitle());
} else
instanceCell.ah(context.prefixLocalHref("#i_" + instance.getKey()), instance.getDescription()).tx(instance.getTitle());
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy