diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincHandler.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincHandler.java
index 09dfe3372ab..97fd4da07b6 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincHandler.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/loinc/LoincHandler.java
@@ -113,6 +113,7 @@ public class LoincHandler implements IRecordHandler {
ourLog.warn("Unable to find part code with TYPE[{}] and NAME[{}]", key.getPartType(), key.getPartName());
}
break;
+ case DECIMAL:
case CODE:
case INTEGER:
case BOOLEAN:
diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/FhirTesterConfig.java b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/FhirTesterConfig.java
index 240c22eaad7..daee8943c41 100644
--- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/FhirTesterConfig.java
+++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/FhirTesterConfig.java
@@ -87,8 +87,8 @@ public class FhirTesterConfig {
.addServer()
.withId("spark2")
.withFhirVersion(FhirVersionEnum.DSTU2)
- .withBaseUrl("http://spark-dstu2.furore.com/fhir")
- .withName("Spark - Furore (DSTU2 FHIR)");
+ .withBaseUrl("http://vonk.furore.com/")
+ .withName("Vonk - Furore (STU3 FHIR)");
return retVal;
}
diff --git a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/utils/StructureMapUtilities.java b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/utils/StructureMapUtilities.java
index 7316f1bd6f4..55501fbf9d1 100644
--- a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/utils/StructureMapUtilities.java
+++ b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/utils/StructureMapUtilities.java
@@ -1,2746 +1,2752 @@
-package org.hl7.fhir.dstu3.utils;
-
-// remember group resolution
-// trace - account for which wasn't transformed in the source
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.EnumSet;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.UUID;
-
-import org.hl7.fhir.dstu3.conformance.ProfileUtilities;
-import org.hl7.fhir.dstu3.conformance.ProfileUtilities.ProfileKnowledgeProvider;
-import org.hl7.fhir.dstu3.context.IWorkerContext;
-import org.hl7.fhir.dstu3.context.IWorkerContext.ValidationResult;
-import org.hl7.fhir.dstu3.elementmodel.Element;
-import org.hl7.fhir.dstu3.elementmodel.Property;
-import org.hl7.fhir.dstu3.model.Base;
-import org.hl7.fhir.dstu3.model.BooleanType;
-import org.hl7.fhir.dstu3.model.CodeType;
-import org.hl7.fhir.dstu3.model.CodeableConcept;
-import org.hl7.fhir.dstu3.model.Coding;
-import org.hl7.fhir.dstu3.model.ConceptMap;
-import org.hl7.fhir.dstu3.model.ConceptMap.ConceptMapGroupComponent;
-import org.hl7.fhir.dstu3.model.ConceptMap.ConceptMapGroupUnmappedMode;
-import org.hl7.fhir.dstu3.model.ConceptMap.SourceElementComponent;
-import org.hl7.fhir.dstu3.model.ConceptMap.TargetElementComponent;
-import org.hl7.fhir.dstu3.model.Constants;
-import org.hl7.fhir.dstu3.model.ContactDetail;
-import org.hl7.fhir.dstu3.model.ContactPoint;
-import org.hl7.fhir.dstu3.model.DecimalType;
-import org.hl7.fhir.dstu3.model.ElementDefinition;
-import org.hl7.fhir.dstu3.model.ElementDefinition.ElementDefinitionMappingComponent;
-import org.hl7.fhir.dstu3.model.ElementDefinition.TypeRefComponent;
-import org.hl7.fhir.dstu3.model.Enumeration;
-import org.hl7.fhir.dstu3.model.Enumerations.ConceptMapEquivalence;
-import org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus;
-import org.hl7.fhir.dstu3.model.ExpressionNode;
-import org.hl7.fhir.dstu3.model.ExpressionNode.CollectionStatus;
-import org.hl7.fhir.dstu3.model.IdType;
-import org.hl7.fhir.dstu3.model.IntegerType;
-import org.hl7.fhir.dstu3.model.Narrative.NarrativeStatus;
-import org.hl7.fhir.dstu3.model.PrimitiveType;
-import org.hl7.fhir.dstu3.model.Reference;
-import org.hl7.fhir.dstu3.model.Resource;
-import org.hl7.fhir.dstu3.model.ResourceFactory;
-import org.hl7.fhir.dstu3.model.StringType;
-import org.hl7.fhir.dstu3.model.StructureDefinition;
-import org.hl7.fhir.dstu3.model.StructureDefinition.StructureDefinitionMappingComponent;
-import org.hl7.fhir.dstu3.model.StructureDefinition.TypeDerivationRule;
-import org.hl7.fhir.dstu3.model.StructureMap;
-import org.hl7.fhir.dstu3.model.StructureMap.StructureMapContextType;
-import org.hl7.fhir.dstu3.model.StructureMap.StructureMapGroupComponent;
-import org.hl7.fhir.dstu3.model.StructureMap.StructureMapGroupInputComponent;
-import org.hl7.fhir.dstu3.model.StructureMap.StructureMapGroupRuleComponent;
-import org.hl7.fhir.dstu3.model.StructureMap.StructureMapGroupRuleDependentComponent;
-import org.hl7.fhir.dstu3.model.StructureMap.StructureMapGroupRuleSourceComponent;
-import org.hl7.fhir.dstu3.model.StructureMap.StructureMapGroupRuleTargetComponent;
-import org.hl7.fhir.dstu3.model.StructureMap.StructureMapGroupRuleTargetParameterComponent;
-import org.hl7.fhir.dstu3.model.StructureMap.StructureMapGroupTypeMode;
-import org.hl7.fhir.dstu3.model.StructureMap.StructureMapInputMode;
-import org.hl7.fhir.dstu3.model.StructureMap.StructureMapSourceListMode;
-import org.hl7.fhir.dstu3.model.StructureMap.StructureMapTargetListMode;
-import org.hl7.fhir.dstu3.model.StructureMap.StructureMapModelMode;
-import org.hl7.fhir.dstu3.model.StructureMap.StructureMapStructureComponent;
-import org.hl7.fhir.dstu3.model.StructureMap.StructureMapTransform;
-import org.hl7.fhir.dstu3.model.Type;
-import org.hl7.fhir.dstu3.model.TypeDetails;
-import org.hl7.fhir.dstu3.model.TypeDetails.ProfiledType;
-import org.hl7.fhir.dstu3.model.UriType;
-import org.hl7.fhir.dstu3.model.ValueSet;
-import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionContainsComponent;
-import org.hl7.fhir.dstu3.terminologies.ValueSetExpander.ValueSetExpansionOutcome;
-import org.hl7.fhir.dstu3.utils.FHIRLexer.FHIRLexerException;
-import org.hl7.fhir.dstu3.utils.FHIRPathEngine.IEvaluationContext;
-import org.hl7.fhir.exceptions.DefinitionException;
-import org.hl7.fhir.exceptions.FHIRException;
-import org.hl7.fhir.exceptions.FHIRFormatError;
-import org.hl7.fhir.exceptions.PathEngineException;
-import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
-import org.hl7.fhir.utilities.TextFile;
-import org.hl7.fhir.utilities.Utilities;
-import org.hl7.fhir.utilities.xhtml.NodeType;
-import org.hl7.fhir.utilities.xhtml.XhtmlNode;
-
-/**
- * Services in this class:
- *
- * string render(map) - take a structure and convert it to text
- * map parse(text) - take a text representation and parse it
- * getTargetType(map) - return the definition for the type to create to hand in
- * transform(appInfo, source, map, target) - transform from source to target following the map
- * analyse(appInfo, map) - generate profiles and other analysis artifacts for the targets of the transform
- * map generateMapFromMappings(StructureDefinition) - build a mapping from a structure definition with loigcal mappings
- *
- * @author Grahame Grieve
- *
- */
-public class StructureMapUtilities {
-
- public class ResolvedGroup {
- public StructureMapGroupComponent target;
- public StructureMap targetMap;
- }
- public static final String MAP_WHERE_CHECK = "map.where.check";
- public static final String MAP_WHERE_EXPRESSION = "map.where.expression";
- public static final String MAP_SEARCH_EXPRESSION = "map.search.expression";
- public static final String MAP_EXPRESSION = "map.transform.expression";
- private static final boolean RENDER_MULTIPLE_TARGETS_ONELINE = true;
- private static final String AUTO_VAR_NAME = "vvv";
-
- public interface ITransformerServices {
- // public boolean validateByValueSet(Coding code, String valuesetId);
- public void log(String message); // log internal progress
- public Base createType(Object appInfo, String name) throws FHIRException;
- public Base createResource(Object appInfo, Base res); // an already created resource is provided; this is to identify/store it
- public Coding translate(Object appInfo, Coding source, String conceptMapUrl) throws FHIRException;
- // public Coding translate(Coding code)
- // ValueSet validation operation
- // Translation operation
- // Lookup another tree of data
- // Create an instance tree
- // Return the correct string format to refer to a tree (input or output)
- public Base resolveReference(Object appContext, String url);
- public List performSearch(Object appContext, String url);
- }
-
- private class FFHIRPathHostServices implements IEvaluationContext{
-
- public Base resolveConstant(Object appContext, String name) throws PathEngineException {
- Variables vars = (Variables) appContext;
- Base res = vars.get(VariableMode.INPUT, name);
- if (res == null)
- res = vars.get(VariableMode.OUTPUT, name);
- return res;
- }
-
- @Override
- public TypeDetails resolveConstantType(Object appContext, String name) throws PathEngineException {
- if (!(appContext instanceof VariablesForProfiling))
- throw new Error("Internal Logic Error (wrong type '"+appContext.getClass().getName()+"' in resolveConstantType)");
- VariablesForProfiling vars = (VariablesForProfiling) appContext;
- VariableForProfiling v = vars.get(null, name);
- if (v == null)
- throw new PathEngineException("Unknown variable '"+name+"' from variables "+vars.summary());
- return v.property.types;
- }
-
- @Override
- public boolean log(String argument, List focus) {
- throw new Error("Not Implemented Yet");
- }
-
- @Override
- public FunctionDetails resolveFunction(String functionName) {
- return null; // throw new Error("Not Implemented Yet");
- }
-
- @Override
- public TypeDetails checkFunction(Object appContext, String functionName, List parameters) throws PathEngineException {
- throw new Error("Not Implemented Yet");
- }
-
- @Override
- public List executeFunction(Object appContext, String functionName, List> parameters) {
- throw new Error("Not Implemented Yet");
- }
-
- @Override
- public Base resolveReference(Object appContext, String url) {
- if (services == null)
- return null;
- return services.resolveReference(appContext, url);
- }
-
- }
- private IWorkerContext worker;
- private FHIRPathEngine fpe;
- private Map library;
- private ITransformerServices services;
- private ProfileKnowledgeProvider pkp;
- private Map ids = new HashMap();
-
- public StructureMapUtilities(IWorkerContext worker, Map library, ITransformerServices services, ProfileKnowledgeProvider pkp) {
- super();
- this.worker = worker;
- this.library = library;
- this.services = services;
- this.pkp = pkp;
- fpe = new FHIRPathEngine(worker);
- fpe.setHostServices(new FFHIRPathHostServices());
- }
-
- public StructureMapUtilities(IWorkerContext worker, Map library, ITransformerServices services) {
- super();
- this.worker = worker;
- this.library = library;
- this.services = services;
- fpe = new FHIRPathEngine(worker);
- fpe.setHostServices(new FFHIRPathHostServices());
- }
-
- public StructureMapUtilities(IWorkerContext worker, Map library) {
- super();
- this.worker = worker;
- this.library = library;
- fpe = new FHIRPathEngine(worker);
- fpe.setHostServices(new FFHIRPathHostServices());
- }
-
- public StructureMapUtilities(IWorkerContext worker) {
- super();
- this.worker = worker;
- fpe = new FHIRPathEngine(worker);
- fpe.setHostServices(new FFHIRPathHostServices());
- }
-
- public StructureMapUtilities(IWorkerContext worker, ITransformerServices services) {
- super();
- this.worker = worker;
- this.library = new HashMap();
- for (org.hl7.fhir.dstu3.model.MetadataResource bc : worker.allConformanceResources()) {
- if (bc instanceof StructureMap)
- library.put(bc.getUrl(), (StructureMap) bc);
- }
- this.services = services;
- fpe = new FHIRPathEngine(worker);
- fpe.setHostServices(new FFHIRPathHostServices());
- }
-
- public static String render(StructureMap map) {
- StringBuilder b = new StringBuilder();
- b.append("map \"");
- b.append(map.getUrl());
- b.append("\" = \"");
- b.append(Utilities.escapeJava(map.getName()));
- b.append("\"\r\n\r\n");
-
- renderConceptMaps(b, map);
- renderUses(b, map);
- renderImports(b, map);
- for (StructureMapGroupComponent g : map.getGroup())
- renderGroup(b, g);
- return b.toString();
- }
-
- private static void renderConceptMaps(StringBuilder b, StructureMap map) {
- for (Resource r : map.getContained()) {
- if (r instanceof ConceptMap) {
- produceConceptMap(b, (ConceptMap) r);
- }
- }
- }
-
- private static void produceConceptMap(StringBuilder b, ConceptMap cm) {
- b.append("conceptmap \"");
- b.append(cm.getId());
- b.append("\" {\r\n");
- Map prefixesSrc = new HashMap();
- Map prefixesTgt = new HashMap();
- char prefix = 's';
- for (ConceptMapGroupComponent cg : cm.getGroup()) {
- if (!prefixesSrc.containsKey(cg.getSource())) {
- prefixesSrc.put(cg.getSource(), String.valueOf(prefix));
- b.append(" prefix ");
- b.append(prefix);
- b.append(" = \"");
- b.append(cg.getSource());
- b.append("\"\r\n");
- prefix++;
- }
- if (!prefixesTgt.containsKey(cg.getTarget())) {
- prefixesTgt.put(cg.getTarget(), String.valueOf(prefix));
- b.append(" prefix ");
- b.append(prefix);
- b.append(" = \"");
- b.append(cg.getTarget());
- b.append("\"\r\n");
- prefix++;
- }
- }
- b.append("\r\n");
- for (ConceptMapGroupComponent cg : cm.getGroup()) {
- if (cg.hasUnmapped()) {
- b.append(" unmapped for ");
- b.append(prefix);
- b.append(" = ");
- b.append(cg.getUnmapped().getMode());
- b.append("\r\n");
- }
- }
-
- for (ConceptMapGroupComponent cg : cm.getGroup()) {
- for (SourceElementComponent ce : cg.getElement()) {
- b.append(" ");
- b.append(prefixesSrc.get(cg.getSource()));
- b.append(":");
- b.append(ce.getCode());
- b.append(" ");
- b.append(getChar(ce.getTargetFirstRep().getEquivalence()));
- b.append(" ");
- b.append(prefixesTgt.get(cg.getTarget()));
- b.append(":");
- b.append(ce.getTargetFirstRep().getCode());
- b.append("\r\n");
- }
- }
- b.append("}\r\n\r\n");
- }
-
- private static Object getChar(ConceptMapEquivalence equivalence) {
- switch (equivalence) {
- case RELATEDTO: return "-";
- case EQUAL: return "=";
- case EQUIVALENT: return "==";
- case DISJOINT: return "!=";
- case UNMATCHED: return "--";
- case WIDER: return "<=";
- case SUBSUMES: return "<-";
- case NARROWER: return ">=";
- case SPECIALIZES: return ">-";
- case INEXACT: return "~";
- default: return "??";
- }
- }
-
- private static void renderUses(StringBuilder b, StructureMap map) {
- for (StructureMapStructureComponent s : map.getStructure()) {
- b.append("uses \"");
- b.append(s.getUrl());
- b.append("\" ");
- if (s.hasAlias()) {
- b.append("alias ");
- b.append(s.getAlias());
- b.append(" ");
- }
- b.append("as ");
- b.append(s.getMode().toCode());
- b.append("\r\n");
- renderDoco(b, s.getDocumentation());
- }
- if (map.hasStructure())
- b.append("\r\n");
- }
-
- private static void renderImports(StringBuilder b, StructureMap map) {
- for (UriType s : map.getImport()) {
- b.append("imports \"");
- b.append(s.getValue());
- b.append("\"\r\n");
- }
- if (map.hasImport())
- b.append("\r\n");
- }
-
- public static String groupToString(StructureMapGroupComponent g) {
- StringBuilder b = new StringBuilder();
- renderGroup(b, g);
- return b.toString();
- }
-
- private static void renderGroup(StringBuilder b, StructureMapGroupComponent g) {
- b.append("group ");
- switch (g.getTypeMode()) {
- case TYPES: b.append("for types");
- case TYPEANDTYPES: b.append("for type+types ");
- default: // NONE, NULL
- }
- b.append("for types ");
- b.append(g.getName());
- if (g.hasExtends()) {
- b.append(" extends ");
- b.append(g.getExtends());
- }
- if (g.hasDocumentation())
- renderDoco(b, g.getDocumentation());
- b.append("\r\n");
- for (StructureMapGroupInputComponent gi : g.getInput()) {
- b.append(" input ");
- b.append(gi.getName());
- if (gi.hasType()) {
- b.append(" : ");
- b.append(gi.getType());
- }
- b.append(" as ");
- b.append(gi.getMode().toCode());
- b.append("\r\n");
- }
- if (g.hasInput())
- b.append("\r\n");
- for (StructureMapGroupRuleComponent r : g.getRule()) {
- renderRule(b, r, 2);
- }
- b.append("\r\nendgroup\r\n");
- }
-
- public static String ruleToString(StructureMapGroupRuleComponent r) {
- StringBuilder b = new StringBuilder();
- renderRule(b, r, 0);
- return b.toString();
- }
-
- private static void renderRule(StringBuilder b, StructureMapGroupRuleComponent r, int indent) {
- for (int i = 0; i < indent; i++)
- b.append(' ');
- b.append(r.getName());
- b.append(" : for ");
- boolean canBeAbbreviated = checkisSimple(r);
-
- boolean first = true;
- for (StructureMapGroupRuleSourceComponent rs : r.getSource()) {
- if (first)
- first = false;
- else
- b.append(", ");
- renderSource(b, rs, canBeAbbreviated);
- }
- if (r.getTarget().size() > 1) {
- b.append(" make ");
- first = true;
- for (StructureMapGroupRuleTargetComponent rt : r.getTarget()) {
- if (first)
- first = false;
- else
- b.append(", ");
- if (RENDER_MULTIPLE_TARGETS_ONELINE)
- b.append(' ');
- else {
- b.append("\r\n");
- for (int i = 0; i < indent+4; i++)
- b.append(' ');
- }
- renderTarget(b, rt, false);
- }
- } else if (r.hasTarget()) {
- b.append(" make ");
- renderTarget(b, r.getTarget().get(0), canBeAbbreviated);
- }
- if (!canBeAbbreviated) {
- if (r.hasRule()) {
- b.append(" then {\r\n");
- renderDoco(b, r.getDocumentation());
- for (StructureMapGroupRuleComponent ir : r.getRule()) {
- renderRule(b, ir, indent+2);
- }
- for (int i = 0; i < indent; i++)
- b.append(' ');
- b.append("}\r\n");
- } else {
- if (r.hasDependent()) {
- b.append(" then ");
- first = true;
- for (StructureMapGroupRuleDependentComponent rd : r.getDependent()) {
- if (first)
- first = false;
- else
- b.append(", ");
- b.append(rd.getName());
- b.append("(");
- boolean ifirst = true;
- for (StringType rdp : rd.getVariable()) {
- if (ifirst)
- ifirst = false;
- else
- b.append(", ");
- b.append(rdp.asStringValue());
- }
- b.append(")");
- }
- }
- }
- }
- renderDoco(b, r.getDocumentation());
- b.append("\r\n");
- }
-
- private static boolean checkisSimple(StructureMapGroupRuleComponent r) {
- return
- (r.getSource().size() == 1 && r.getSourceFirstRep().hasElement() && r.getSourceFirstRep().hasVariable()) &&
- (r.getTarget().size() == 1 && r.getTargetFirstRep().hasVariable() && (r.getTargetFirstRep().getTransform() == null || r.getTargetFirstRep().getTransform() == StructureMapTransform.CREATE) && r.getTargetFirstRep().getParameter().size() == 0) &&
- (r.getDependent().size() == 0);
- }
-
- public static String sourceToString(StructureMapGroupRuleSourceComponent r) {
- StringBuilder b = new StringBuilder();
- renderSource(b, r, false);
- return b.toString();
- }
-
- private static void renderSource(StringBuilder b, StructureMapGroupRuleSourceComponent rs, boolean abbreviate) {
- b.append(rs.getContext());
- if (rs.getContext().equals("@search")) {
- b.append('(');
- b.append(rs.getElement());
- b.append(')');
- } else if (rs.hasElement()) {
- b.append('.');
- b.append(rs.getElement());
- }
- if (rs.hasType()) {
- b.append(" : ");
- b.append(rs.getType());
- if (rs.hasMin()) {
- b.append(" ");
- b.append(rs.getMin());
- b.append("..");
- b.append(rs.getMax());
- }
- }
-
- if (rs.hasListMode()) {
- b.append(" ");
- b.append(rs.getListMode().toCode());
- }
- if (rs.hasDefaultValue()) {
- b.append(" default ");
- assert rs.getDefaultValue() instanceof StringType;
- b.append("\""+Utilities.escapeJson(((StringType) rs.getDefaultValue()).asStringValue())+"\"");
- }
- if (!abbreviate && rs.hasVariable()) {
- b.append(" as ");
- b.append(rs.getVariable());
- }
- if (rs.hasCondition()) {
- b.append(" where ");
- b.append(rs.getCondition());
- }
- if (rs.hasCheck()) {
- b.append(" check ");
- b.append(rs.getCheck());
- }
- }
-
- public static String targetToString(StructureMapGroupRuleTargetComponent rt) {
- StringBuilder b = new StringBuilder();
- renderTarget(b, rt, false);
- return b.toString();
- }
-
- private static void renderTarget(StringBuilder b, StructureMapGroupRuleTargetComponent rt, boolean abbreviate) {
- if (rt.hasContext()) {
- if (rt.getContextType() == StructureMapContextType.TYPE)
- b.append("@");
- b.append(rt.getContext());
- if (rt.hasElement()) {
- b.append('.');
- b.append(rt.getElement());
- }
- }
- if (!abbreviate && rt.hasTransform()) {
- if (rt.hasContext())
- b.append(" = ");
- if (rt.getTransform() == StructureMapTransform.COPY && rt.getParameter().size() == 1) {
- renderTransformParam(b, rt.getParameter().get(0));
- } else if (rt.getTransform() == StructureMapTransform.EVALUATE && rt.getParameter().size() == 1) {
- b.append("(");
- b.append("\""+((StringType) rt.getParameter().get(0).getValue()).asStringValue()+"\"");
- b.append(")");
- } else if (rt.getTransform() == StructureMapTransform.EVALUATE && rt.getParameter().size() == 2) {
- b.append(rt.getTransform().toCode());
- b.append("(");
- b.append(((IdType) rt.getParameter().get(0).getValue()).asStringValue());
- b.append("\""+((StringType) rt.getParameter().get(1).getValue()).asStringValue()+"\"");
- b.append(")");
- } else {
- b.append(rt.getTransform().toCode());
- b.append("(");
- boolean first = true;
- for (StructureMapGroupRuleTargetParameterComponent rtp : rt.getParameter()) {
- if (first)
- first = false;
- else
- b.append(", ");
- renderTransformParam(b, rtp);
- }
- b.append(")");
- }
- }
- if (!abbreviate && rt.hasVariable()) {
- b.append(" as ");
- b.append(rt.getVariable());
- }
- for (Enumeration lm : rt.getListMode()) {
- b.append(" ");
- b.append(lm.getValue().toCode());
- if (lm.getValue() == StructureMapTargetListMode.SHARE) {
- b.append(" ");
- b.append(rt.getListRuleId());
- }
- }
- }
-
- public static String paramToString(StructureMapGroupRuleTargetParameterComponent rtp) {
- StringBuilder b = new StringBuilder();
- renderTransformParam(b, rtp);
- return b.toString();
- }
-
- private static void renderTransformParam(StringBuilder b, StructureMapGroupRuleTargetParameterComponent rtp) {
- try {
- if (rtp.hasValueBooleanType())
- b.append(rtp.getValueBooleanType().asStringValue());
- else if (rtp.hasValueDecimalType())
- b.append(rtp.getValueDecimalType().asStringValue());
- else if (rtp.hasValueIdType())
- b.append(rtp.getValueIdType().asStringValue());
- else if (rtp.hasValueDecimalType())
- b.append(rtp.getValueDecimalType().asStringValue());
- else if (rtp.hasValueIntegerType())
- b.append(rtp.getValueIntegerType().asStringValue());
- else
- b.append("\""+Utilities.escapeJava(rtp.getValueStringType().asStringValue())+"\"");
- } catch (FHIRException e) {
- e.printStackTrace();
- b.append("error!");
- }
- }
-
- private static void renderDoco(StringBuilder b, String doco) {
- if (Utilities.noString(doco))
- return;
- b.append(" // ");
- b.append(doco.replace("\r\n", " ").replace("\r", " ").replace("\n", " "));
- }
-
- public StructureMap parse(String text) throws FHIRException {
- FHIRLexer lexer = new FHIRLexer(text);
- if (lexer.done())
- throw lexer.error("Map Input cannot be empty");
- lexer.skipComments();
- lexer.token("map");
- StructureMap result = new StructureMap();
- result.setUrl(lexer.readConstant("url"));
- lexer.token("=");
- result.setName(lexer.readConstant("name"));
- lexer.skipComments();
-
- while (lexer.hasToken("conceptmap"))
- parseConceptMap(result, lexer);
-
- while (lexer.hasToken("uses"))
- parseUses(result, lexer);
- while (lexer.hasToken("imports"))
- parseImports(result, lexer);
-
- parseGroup(result, lexer);
-
- while (!lexer.done()) {
- parseGroup(result, lexer);
- }
-
- return result;
- }
-
- private void parseConceptMap(StructureMap result, FHIRLexer lexer) throws FHIRLexerException {
- lexer.token("conceptmap");
- ConceptMap map = new ConceptMap();
- String id = lexer.readConstant("map id");
- if (!id.startsWith("#"))
- lexer.error("Concept Map identifier must start with #");
- map.setId(id);
- map.setStatus(PublicationStatus.DRAFT); // todo: how to add this to the text format
- result.getContained().add(map);
- lexer.token("{");
- lexer.skipComments();
- // lexer.token("source");
- // map.setSource(new UriType(lexer.readConstant("source")));
- // lexer.token("target");
- // map.setSource(new UriType(lexer.readConstant("target")));
- Map prefixes = new HashMap();
- while (lexer.hasToken("prefix")) {
- lexer.token("prefix");
- String n = lexer.take();
- lexer.token("=");
- String v = lexer.readConstant("prefix url");
- prefixes.put(n, v);
- }
- while (lexer.hasToken("unmapped")) {
- lexer.token("unmapped");
- lexer.token("for");
- String n = readPrefix(prefixes, lexer);
- ConceptMapGroupComponent g = getGroup(map, n, null);
- lexer.token("=");
- String v = lexer.take();
- if (v.equals("provided")) {
- g.getUnmapped().setMode(ConceptMapGroupUnmappedMode.PROVIDED);
- } else
- lexer.error("Only unmapped mode PROVIDED is supported at this time");
- }
- while (!lexer.hasToken("}")) {
- String srcs = readPrefix(prefixes, lexer);
- lexer.token(":");
- String sc = lexer.getCurrent().startsWith("\"") ? lexer.readConstant("code") : lexer.take();
- ConceptMapEquivalence eq = readEquivalence(lexer);
- String tgts = (eq != ConceptMapEquivalence.UNMATCHED) ? readPrefix(prefixes, lexer) : "";
- ConceptMapGroupComponent g = getGroup(map, srcs, tgts);
- SourceElementComponent e = g.addElement();
- e.setCode(sc);
- if (e.getCode().startsWith("\""))
- e.setCode(lexer.processConstant(e.getCode()));
- TargetElementComponent tgt = e.addTarget();
- if (eq != ConceptMapEquivalence.EQUIVALENT)
- tgt.setEquivalence(eq);
- if (tgt.getEquivalence() != ConceptMapEquivalence.UNMATCHED) {
- lexer.token(":");
- tgt.setCode(lexer.take());
- if (tgt.getCode().startsWith("\""))
- tgt.setCode(lexer.processConstant(tgt.getCode()));
- }
- if (lexer.hasComment())
- tgt.setComment(lexer.take().substring(2).trim());
- }
- lexer.token("}");
- }
-
-
- private ConceptMapGroupComponent getGroup(ConceptMap map, String srcs, String tgts) {
- for (ConceptMapGroupComponent grp : map.getGroup()) {
- if (grp.getSource().equals(srcs))
- if ((tgts == null && !grp.hasTarget()) || (tgts != null && tgts.equals(grp.getTarget())))
- return grp;
- }
- ConceptMapGroupComponent grp = map.addGroup();
- grp.setSource(srcs);
- grp.setTarget(tgts);
- return grp;
- }
-
-
- private String readPrefix(Map prefixes, FHIRLexer lexer) throws FHIRLexerException {
- String prefix = lexer.take();
- if (!prefixes.containsKey(prefix))
- throw lexer.error("Unknown prefix '"+prefix+"'");
- return prefixes.get(prefix);
- }
-
-
- private ConceptMapEquivalence readEquivalence(FHIRLexer lexer) throws FHIRLexerException {
- String token = lexer.take();
- if (token.equals("-"))
- return ConceptMapEquivalence.RELATEDTO;
- if (token.equals("="))
- return ConceptMapEquivalence.EQUAL;
- if (token.equals("=="))
- return ConceptMapEquivalence.EQUIVALENT;
- if (token.equals("!="))
- return ConceptMapEquivalence.DISJOINT;
- if (token.equals("--"))
- return ConceptMapEquivalence.UNMATCHED;
- if (token.equals("<="))
- return ConceptMapEquivalence.WIDER;
- if (token.equals("<-"))
- return ConceptMapEquivalence.SUBSUMES;
- if (token.equals(">="))
- return ConceptMapEquivalence.NARROWER;
- if (token.equals(">-"))
- return ConceptMapEquivalence.SPECIALIZES;
- if (token.equals("~"))
- return ConceptMapEquivalence.INEXACT;
- throw lexer.error("Unknown equivalence token '"+token+"'");
- }
-
-
- private void parseUses(StructureMap result, FHIRLexer lexer) throws FHIRException {
- lexer.token("uses");
- StructureMapStructureComponent st = result.addStructure();
- st.setUrl(lexer.readConstant("url"));
- if (lexer.hasToken("alias")) {
- lexer.token("alias");
- st.setAlias(lexer.take());
- }
- lexer.token("as");
- st.setMode(StructureMapModelMode.fromCode(lexer.take()));
- lexer.skipToken(";");
- if (lexer.hasComment()) {
- st.setDocumentation(lexer.take().substring(2).trim());
- }
- lexer.skipComments();
- }
-
- private void parseImports(StructureMap result, FHIRLexer lexer) throws FHIRException {
- lexer.token("imports");
- result.addImport(lexer.readConstant("url"));
- lexer.skipToken(";");
- if (lexer.hasComment()) {
- lexer.next();
- }
- lexer.skipComments();
- }
-
- private void parseGroup(StructureMap result, FHIRLexer lexer) throws FHIRException {
- lexer.token("group");
- StructureMapGroupComponent group = result.addGroup();
- if (lexer.hasToken("for")) {
- lexer.token("for");
- if ("type".equals(lexer.getCurrent())) {
- lexer.token("type");
- lexer.token("+");
- lexer.token("types");
- group.setTypeMode(StructureMapGroupTypeMode.TYPEANDTYPES);
- } else {
- lexer.token("types");
- group.setTypeMode(StructureMapGroupTypeMode.TYPES);
- }
- } else
- group.setTypeMode(StructureMapGroupTypeMode.NONE);
- group.setName(lexer.take());
- if (lexer.hasToken("extends")) {
- lexer.next();
- group.setExtends(lexer.take());
- }
- lexer.skipComments();
- while (lexer.hasToken("input"))
- parseInput(group, lexer);
- while (!lexer.hasToken("endgroup")) {
- if (lexer.done())
- throw lexer.error("premature termination expecting 'endgroup'");
- parseRule(result, group.getRule(), lexer);
- }
- lexer.next();
- lexer.skipComments();
- }
-
- private void parseInput(StructureMapGroupComponent group, FHIRLexer lexer) throws FHIRException {
- lexer.token("input");
- StructureMapGroupInputComponent input = group.addInput();
- input.setName(lexer.take());
- if (lexer.hasToken(":")) {
- lexer.token(":");
- input.setType(lexer.take());
- }
- lexer.token("as");
- input.setMode(StructureMapInputMode.fromCode(lexer.take()));
- if (lexer.hasComment()) {
- input.setDocumentation(lexer.take().substring(2).trim());
- }
- lexer.skipToken(";");
- lexer.skipComments();
- }
-
- private void parseRule(StructureMap map, List list, FHIRLexer lexer) throws FHIRException {
- StructureMapGroupRuleComponent rule = new StructureMapGroupRuleComponent();
- list.add(rule);
- rule.setName(lexer.takeDottedToken());
- lexer.token(":");
- lexer.token("for");
- boolean done = false;
- while (!done) {
- parseSource(rule, lexer);
- done = !lexer.hasToken(",");
- if (!done)
- lexer.next();
- }
- if (lexer.hasToken("make")) {
- lexer.token("make");
- done = false;
- while (!done) {
- parseTarget(rule, lexer);
- done = !lexer.hasToken(",");
- if (!done)
- lexer.next();
- }
- }
- if (lexer.hasToken("then")) {
- lexer.token("then");
- if (lexer.hasToken("{")) {
- lexer.token("{");
- if (lexer.hasComment()) {
- rule.setDocumentation(lexer.take().substring(2).trim());
- }
- lexer.skipComments();
- while (!lexer.hasToken("}")) {
- if (lexer.done())
- throw lexer.error("premature termination expecting '}' in nested group");
- parseRule(map, rule.getRule(), lexer);
- }
- lexer.token("}");
- } else {
- done = false;
- while (!done) {
- parseRuleReference(rule, lexer);
- done = !lexer.hasToken(",");
- if (!done)
- lexer.next();
- }
- }
- } else if (lexer.hasComment()) {
- rule.setDocumentation(lexer.take().substring(2).trim());
- }
- if (isSimpleSyntax(rule)) {
- rule.getSourceFirstRep().setVariable(AUTO_VAR_NAME);
- rule.getTargetFirstRep().setVariable(AUTO_VAR_NAME);
- rule.getTargetFirstRep().setTransform(StructureMapTransform.CREATE); // with no parameter - e.g. imply what is to be created
- // no dependencies - imply what is to be done based on types
- }
- lexer.skipComments();
- }
-
- private boolean isSimpleSyntax(StructureMapGroupRuleComponent rule) {
- return
- (rule.getSource().size() == 1 && rule.getSourceFirstRep().hasContext() && rule.getSourceFirstRep().hasElement() && !rule.getSourceFirstRep().hasVariable()) &&
- (rule.getTarget().size() == 1 && rule.getTargetFirstRep().hasContext() && rule.getTargetFirstRep().hasElement() && !rule.getTargetFirstRep().hasVariable() && !rule.getTargetFirstRep().hasParameter()) &&
- (rule.getDependent().size() == 0 && rule.getRule().size() == 0);
- }
-
- private void parseRuleReference(StructureMapGroupRuleComponent rule, FHIRLexer lexer) throws FHIRLexerException {
- StructureMapGroupRuleDependentComponent ref = rule.addDependent();
- ref.setName(lexer.take());
- lexer.token("(");
- boolean done = false;
- while (!done) {
- ref.addVariable(lexer.take());
- done = !lexer.hasToken(",");
- if (!done)
- lexer.next();
- }
- lexer.token(")");
- }
-
- private void parseSource(StructureMapGroupRuleComponent rule, FHIRLexer lexer) throws FHIRException {
- StructureMapGroupRuleSourceComponent source = rule.addSource();
- source.setContext(lexer.take());
- if (source.getContext().equals("search") && lexer.hasToken("(")) {
- source.setContext("@search");
- lexer.take();
- ExpressionNode node = fpe.parse(lexer);
- source.setUserData(MAP_SEARCH_EXPRESSION, node);
- source.setElement(node.toString());
- lexer.token(")");
- } else if (lexer.hasToken(".")) {
- lexer.token(".");
- source.setElement(lexer.take());
- }
- if (lexer.hasToken(":")) {
- // type and cardinality
- lexer.token(":");
- source.setType(lexer.takeDottedToken());
- if (!lexer.hasToken("as", "first", "last", "not_first", "not_last", "only_one", "default")) {
- source.setMin(lexer.takeInt());
- lexer.token("..");
- source.setMax(lexer.take());
- }
- }
- if (lexer.hasToken("default")) {
- lexer.token("default");
- source.setDefaultValue(new StringType(lexer.readConstant("default value")));
- }
- if (Utilities.existsInList(lexer.getCurrent(), "first", "last", "not_first", "not_last", "only_one"))
- source.setListMode(StructureMapSourceListMode.fromCode(lexer.take()));
-
- if (lexer.hasToken("as")) {
- lexer.take();
- source.setVariable(lexer.take());
- }
- if (lexer.hasToken("where")) {
- lexer.take();
- ExpressionNode node = fpe.parse(lexer);
- source.setUserData(MAP_WHERE_EXPRESSION, node);
- source.setCondition(node.toString());
- }
- if (lexer.hasToken("check")) {
- lexer.take();
- ExpressionNode node = fpe.parse(lexer);
- source.setUserData(MAP_WHERE_CHECK, node);
- source.setCheck(node.toString());
- }
- }
-
- private void parseTarget(StructureMapGroupRuleComponent rule, FHIRLexer lexer) throws FHIRException {
- StructureMapGroupRuleTargetComponent target = rule.addTarget();
- String start = lexer.take();
- if (lexer.hasToken(".")) {
- target.setContext(start);
- target.setContextType(StructureMapContextType.VARIABLE);
- start = null;
- lexer.token(".");
- target.setElement(lexer.take());
- }
- String name;
- boolean isConstant = false;
- if (lexer.hasToken("=")) {
- if (start != null)
- target.setContext(start);
- lexer.token("=");
- isConstant = lexer.isConstant(true);
- name = lexer.take();
- } else
- name = start;
-
- if ("(".equals(name)) {
- // inline fluentpath expression
- target.setTransform(StructureMapTransform.EVALUATE);
- ExpressionNode node = fpe.parse(lexer);
- target.setUserData(MAP_EXPRESSION, node);
- target.addParameter().setValue(new StringType(node.toString()));
- lexer.token(")");
- } else if (lexer.hasToken("(")) {
- target.setTransform(StructureMapTransform.fromCode(name));
- lexer.token("(");
- if (target.getTransform() == StructureMapTransform.EVALUATE) {
- parseParameter(target, lexer);
- lexer.token(",");
- ExpressionNode node = fpe.parse(lexer);
- target.setUserData(MAP_EXPRESSION, node);
- target.addParameter().setValue(new StringType(node.toString()));
- } else {
- while (!lexer.hasToken(")")) {
- parseParameter(target, lexer);
- if (!lexer.hasToken(")"))
- lexer.token(",");
- }
- }
- lexer.token(")");
- } else if (name != null) {
- target.setTransform(StructureMapTransform.COPY);
- if (!isConstant) {
- String id = name;
- while (lexer.hasToken(".")) {
- id = id + lexer.take() + lexer.take();
- }
- target.addParameter().setValue(new IdType(id));
- }
- else
- target.addParameter().setValue(readConstant(name, lexer));
- }
- if (lexer.hasToken("as")) {
- lexer.take();
- target.setVariable(lexer.take());
- }
- while (Utilities.existsInList(lexer.getCurrent(), "first", "last", "share", "collate")) {
- if (lexer.getCurrent().equals("share")) {
- target.addListMode(StructureMapTargetListMode.SHARE);
- lexer.next();
- target.setListRuleId(lexer.take());
- } else if (lexer.getCurrent().equals("first"))
- target.addListMode(StructureMapTargetListMode.FIRST);
- else
- target.addListMode(StructureMapTargetListMode.LAST);
- lexer.next();
- }
- }
-
-
- private void parseParameter(StructureMapGroupRuleTargetComponent target, FHIRLexer lexer) throws FHIRLexerException, FHIRFormatError {
- if (!lexer.isConstant(true)) {
- target.addParameter().setValue(new IdType(lexer.take()));
- } else if (lexer.isStringConstant())
- target.addParameter().setValue(new StringType(lexer.readConstant("??")));
- else {
- target.addParameter().setValue(readConstant(lexer.take(), lexer));
- }
- }
-
- private Type readConstant(String s, FHIRLexer lexer) throws FHIRLexerException {
- if (Utilities.isInteger(s))
- return new IntegerType(s);
- else if (Utilities.isDecimal(s))
- return new DecimalType(s);
- else if (Utilities.existsInList(s, "true", "false"))
- return new BooleanType(s.equals("true"));
- else
- return new StringType(lexer.processConstant(s));
- }
-
- public StructureDefinition getTargetType(StructureMap map) throws FHIRException {
- boolean found = false;
- StructureDefinition res = null;
- for (StructureMapStructureComponent uses : map.getStructure()) {
- if (uses.getMode() == StructureMapModelMode.TARGET) {
- if (found)
- throw new FHIRException("Multiple targets found in map "+map.getUrl());
- found = true;
- res = worker.fetchResource(StructureDefinition.class, uses.getUrl());
- if (res == null)
- throw new FHIRException("Unable to find "+uses.getUrl()+" referenced from map "+map.getUrl());
- }
- }
- if (res == null)
- throw new FHIRException("No targets found in map "+map.getUrl());
- return res;
- }
-
- public enum VariableMode {
- INPUT, OUTPUT
- }
-
- public class Variable {
- private VariableMode mode;
- private String name;
- private Base object;
- public Variable(VariableMode mode, String name, Base object) {
- super();
- this.mode = mode;
- this.name = name;
- this.object = object;
- }
- public VariableMode getMode() {
- return mode;
- }
- public String getName() {
- return name;
- }
- public Base getObject() {
- return object;
- }
- public String summary() {
- return name+": "+object.fhirType();
- }
- }
-
- public class Variables {
- private List list = new ArrayList();
-
- public void add(VariableMode mode, String name, Base object) {
- Variable vv = null;
- for (Variable v : list)
- if ((v.mode == mode) && v.getName().equals(name))
- vv = v;
- if (vv != null)
- list.remove(vv);
- list.add(new Variable(mode, name, object));
- }
-
- public Variables copy() {
- Variables result = new Variables();
- result.list.addAll(list);
- return result;
- }
-
- public Base get(VariableMode mode, String name) {
- for (Variable v : list)
- if ((v.mode == mode) && v.getName().equals(name))
- return v.getObject();
- return null;
- }
-
- public String summary() {
- CommaSeparatedStringBuilder s = new CommaSeparatedStringBuilder();
- CommaSeparatedStringBuilder t = new CommaSeparatedStringBuilder();
- for (Variable v : list)
- if (v.mode == VariableMode.INPUT)
- s.append(v.summary());
- else
- t.append(v.summary());
- return "source variables ["+s.toString()+"], target variables ["+t.toString()+"]";
- }
- }
-
- public class TransformContext {
- private Object appInfo;
-
- public TransformContext(Object appInfo) {
- super();
- this.appInfo = appInfo;
- }
-
- public Object getAppInfo() {
- return appInfo;
- }
-
- }
-
- private void log(String cnt) {
- if (services != null)
- services.log(cnt);
- }
-
- /**
- * Given an item, return all the children that conform to the pattern described in name
- *
- * Possible patterns:
- * - a simple name (which may be the base of a name with [] e.g. value[x])
- * - a name with a type replacement e.g. valueCodeableConcept
- * - * which means all children
- * - ** which means all descendents
- *
- * @param item
- * @param name
- * @param result
- * @throws FHIRException
- */
- protected void getChildrenByName(Base item, String name, List result) throws FHIRException {
- for (Base v : item.listChildrenByName(name, true))
- if (v != null)
- result.add(v);
- }
-
- public void transform(Object appInfo, Base source, StructureMap map, Base target) throws FHIRException {
- TransformContext context = new TransformContext(appInfo);
- log("Start Transform "+map.getUrl());
- StructureMapGroupComponent g = map.getGroup().get(0);
-
- Variables vars = new Variables();
- vars.add(VariableMode.INPUT, getInputName(g, StructureMapInputMode.SOURCE, "source"), source);
- vars.add(VariableMode.OUTPUT, getInputName(g, StructureMapInputMode.TARGET, "target"), target);
-
- executeGroup("", context, map, vars, g);
- if (target instanceof Element)
- ((Element) target).sort();
- }
-
- private String getInputName(StructureMapGroupComponent g, StructureMapInputMode mode, String def) throws DefinitionException {
- String name = null;
- for (StructureMapGroupInputComponent inp : g.getInput()) {
- if (inp.getMode() == mode)
- if (name != null)
- throw new DefinitionException("This engine does not support multiple source inputs");
- else
- name = inp.getName();
- }
- return name == null ? def : name;
- }
-
- private void executeGroup(String indent, TransformContext context, StructureMap map, Variables vars, StructureMapGroupComponent group) throws FHIRException {
- log(indent+"Group : "+group.getName());
- // todo: check inputs
- if (group.hasExtends()) {
- ResolvedGroup rg = resolveGroupReference(map, group, group.getExtends());
- executeGroup(indent+" ", context, rg.targetMap, vars, rg.target);
- }
-
- for (StructureMapGroupRuleComponent r : group.getRule()) {
- executeRule(indent+" ", context, map, vars, group, r);
- }
- }
-
- private void executeRule(String indent, TransformContext context, StructureMap map, Variables vars, StructureMapGroupComponent group, StructureMapGroupRuleComponent rule) throws FHIRException {
- log(indent+"rule : "+rule.getName());
- if (rule.getName().contains("CarePlan.participant-unlink"))
- System.out.println("debug");
- Variables srcVars = vars.copy();
- if (rule.getSource().size() != 1)
- throw new FHIRException("Rule \""+rule.getName()+"\": not handled yet");
- List source = processSource(rule.getName(), context, srcVars, rule.getSource().get(0));
- if (source != null) {
- for (Variables v : source) {
- for (StructureMapGroupRuleTargetComponent t : rule.getTarget()) {
- processTarget(rule.getName(), context, v, map, group, t, rule.getSource().size() == 1 ? rule.getSourceFirstRep().getVariable() : null);
- }
- if (rule.hasRule()) {
- for (StructureMapGroupRuleComponent childrule : rule.getRule()) {
- executeRule(indent +" ", context, map, v, group, childrule);
- }
- } else if (rule.hasDependent()) {
- for (StructureMapGroupRuleDependentComponent dependent : rule.getDependent()) {
- executeDependency(indent+" ", context, map, v, group, dependent);
- }
- } else if (rule.getSource().size() == 1 && rule.getSourceFirstRep().hasVariable() && rule.getTarget().size() == 1 && rule.getTargetFirstRep().hasVariable() && rule.getTargetFirstRep().getTransform() == StructureMapTransform.CREATE && !rule.getTargetFirstRep().hasParameter()) {
- // simple inferred, map by type
- Base src = v.get(VariableMode.INPUT, rule.getSourceFirstRep().getVariable());
- Base tgt = v.get(VariableMode.OUTPUT, rule.getTargetFirstRep().getVariable());
- String srcType = src.fhirType();
- String tgtType = tgt.fhirType();
- ResolvedGroup defGroup = resolveGroupByTypes(map, rule.getName(), group, srcType, tgtType);
- Variables vdef = new Variables();
- vdef.add(VariableMode.INPUT, defGroup.target.getInput().get(0).getName(), src);
- vdef.add(VariableMode.OUTPUT, defGroup.target.getInput().get(1).getName(), tgt);
- executeGroup(indent+" ", context, defGroup.targetMap, vdef, defGroup.target);
- }
- }
- }
- }
-
- private void executeDependency(String indent, TransformContext context, StructureMap map, Variables vin, StructureMapGroupComponent group, StructureMapGroupRuleDependentComponent dependent) throws FHIRException {
- ResolvedGroup rg = resolveGroupReference(map, group, dependent.getName());
-
- if (rg.target.getInput().size() != dependent.getVariable().size()) {
- throw new FHIRException("Rule '"+dependent.getName()+"' has "+Integer.toString(rg.target.getInput().size())+" but the invocation has "+Integer.toString(dependent.getVariable().size())+" variables");
- }
- Variables v = new Variables();
- for (int i = 0; i < rg.target.getInput().size(); i++) {
- StructureMapGroupInputComponent input = rg.target.getInput().get(i);
- StringType rdp = dependent.getVariable().get(i);
- String var = rdp.asStringValue();
- VariableMode mode = input.getMode() == StructureMapInputMode.SOURCE ? VariableMode.INPUT : VariableMode.OUTPUT;
- Base vv = vin.get(mode, var);
- if (vv == null && mode == VariableMode.INPUT) //* once source, always source. but target can be treated as source at user convenient
- vv = vin.get(VariableMode.OUTPUT, var);
- if (vv == null)
- throw new FHIRException("Rule '"+dependent.getName()+"' "+mode.toString()+" variable '"+input.getName()+"' named as '"+var+"' has no value");
- v.add(mode, input.getName(), vv);
- }
- executeGroup(indent+" ", context, rg.targetMap, v, rg.target);
- }
-
- private String determineTypeFromSourceType(StructureMap map, StructureMapGroupComponent source, Base base, String[] types) throws FHIRException {
- String type = base.fhirType();
- String kn = "type^"+type;
- if (source.hasUserData(kn))
- return source.getUserString(kn);
-
- ResolvedGroup res = new ResolvedGroup();
- res.targetMap = null;
- res.target = null;
- for (StructureMapGroupComponent grp : map.getGroup()) {
- if (matchesByType(map, grp, type)) {
- if (res.targetMap == null) {
- res.targetMap = map;
- res.target = grp;
- } else
- throw new FHIRException("Multiple possible matches looking for default rule for '"+type+"'");
- }
- }
- if (res.targetMap != null) {
- String result = getActualType(res.targetMap, res.target.getInput().get(1).getType());
- source.setUserData(kn, result);
- return result;
- }
-
- for (UriType imp : map.getImport()) {
- List impMapList = findMatchingMaps(imp.getValue());
- if (impMapList.size() == 0)
- throw new FHIRException("Unable to find map(s) for "+imp.getValue());
- for (StructureMap impMap : impMapList) {
- if (!impMap.getUrl().equals(map.getUrl())) {
- for (StructureMapGroupComponent grp : impMap.getGroup()) {
- if (matchesByType(impMap, grp, type)) {
- if (res.targetMap == null) {
- res.targetMap = impMap;
- res.target = grp;
- } else
- throw new FHIRException("Multiple possible matches for default rule for '"+type+"' in "+res.targetMap.getUrl()+" ("+res.target.getName()+") and "+impMap.getUrl()+" ("+grp.getName()+")");
- }
- }
- }
- }
- }
- if (res.target == null)
- throw new FHIRException("No matches found for default rule for '"+type+"' from "+map.getUrl());
- String result = getActualType(res.targetMap, res.target.getInput().get(1).getType()); // should be .getType, but R2...
- source.setUserData(kn, result);
- return result;
- }
-
- private List findMatchingMaps(String value) {
- List res = new ArrayList();
- if (value.contains("*")) {
- for (StructureMap sm : library.values()) {
- if (urlMatches(value, sm.getUrl())) {
- res.add(sm);
- }
- }
- } else {
- StructureMap sm = library.get(value);
- if (sm != null)
- res.add(sm);
- }
- Set check = new HashSet();
- for (StructureMap sm : res) {
- if (check.contains(sm.getUrl()))
- throw new Error("duplicate");
- else
- check.add(sm.getUrl());
- }
- return res;
- }
-
- private boolean urlMatches(String mask, String url) {
- return url.length() > mask.length() && url.startsWith(mask.substring(0, mask.indexOf("*"))) && url.endsWith(mask.substring(mask.indexOf("*")+1)) ;
- }
-
- private ResolvedGroup resolveGroupByTypes(StructureMap map, String ruleid, StructureMapGroupComponent source, String srcType, String tgtType) throws FHIRException {
- String kn = "types^"+srcType+":"+tgtType;
- if (source.hasUserData(kn))
- return (ResolvedGroup) source.getUserData(kn);
-
- ResolvedGroup res = new ResolvedGroup();
- res.targetMap = null;
- res.target = null;
- for (StructureMapGroupComponent grp : map.getGroup()) {
- if (matchesByType(map, grp, srcType, tgtType)) {
- if (res.targetMap == null) {
- res.targetMap = map;
- res.target = grp;
- } else
- throw new FHIRException("Multiple possible matches looking for rule for '"+srcType+"/"+tgtType+"', from rule '"+ruleid+"'");
- }
- }
- if (res.targetMap != null) {
- source.setUserData(kn, res);
- return res;
- }
-
- for (UriType imp : map.getImport()) {
- List impMapList = findMatchingMaps(imp.getValue());
- if (impMapList.size() == 0)
- throw new FHIRException("Unable to find map(s) for "+imp.getValue());
- for (StructureMap impMap : impMapList) {
- if (!impMap.getUrl().equals(map.getUrl())) {
- for (StructureMapGroupComponent grp : impMap.getGroup()) {
- if (matchesByType(impMap, grp, srcType, tgtType)) {
- if (res.targetMap == null) {
- res.targetMap = impMap;
- res.target = grp;
- } else
- throw new FHIRException("Multiple possible matches for rule for '"+srcType+"/"+tgtType+"' in "+res.targetMap.getUrl()+" and "+impMap.getUrl()+", from rule '"+ruleid+"'");
- }
- }
- }
- }
- }
- if (res.target == null)
- throw new FHIRException("No matches found for rule for '"+srcType+"/"+tgtType+"' from "+map.getUrl()+", from rule '"+ruleid+"'");
- source.setUserData(kn, res);
- return res;
- }
-
-
- private boolean matchesByType(StructureMap map, StructureMapGroupComponent grp, String type) throws FHIRException {
- if (grp.getTypeMode() != StructureMapGroupTypeMode.TYPEANDTYPES)
- return false;
- if (grp.getInput().size() != 2 || grp.getInput().get(0).getMode() != StructureMapInputMode.SOURCE || grp.getInput().get(1).getMode() != StructureMapInputMode.TARGET)
- return false;
- return matchesType(map, type, grp.getInput().get(0).getType());
- }
-
- private boolean matchesByType(StructureMap map, StructureMapGroupComponent grp, String srcType, String tgtType) throws FHIRException {
- if (grp.getTypeMode() == StructureMapGroupTypeMode.NONE)
- return false;
- if (grp.getInput().size() != 2 || grp.getInput().get(0).getMode() != StructureMapInputMode.SOURCE || grp.getInput().get(1).getMode() != StructureMapInputMode.TARGET)
- return false;
- if (!grp.getInput().get(0).hasType() || !grp.getInput().get(1).hasType())
- return false;
- return matchesType(map, srcType, grp.getInput().get(0).getType()) && matchesType(map, tgtType, grp.getInput().get(1).getType());
- }
-
- private boolean matchesType(StructureMap map, String actualType, String statedType) throws FHIRException {
- // check the aliases
- for (StructureMapStructureComponent imp : map.getStructure()) {
- if (imp.hasAlias() && statedType.equals(imp.getAlias())) {
- StructureDefinition sd = worker.fetchResource(StructureDefinition.class, imp.getUrl());
- if (sd != null)
- statedType = sd.getType();
- break;
- }
- }
-
- return actualType.equals(statedType);
- }
-
- private String getActualType(StructureMap map, String statedType) throws FHIRException {
- // check the aliases
- for (StructureMapStructureComponent imp : map.getStructure()) {
- if (imp.hasAlias() && statedType.equals(imp.getAlias())) {
- StructureDefinition sd = worker.fetchResource(StructureDefinition.class, imp.getUrl());
- if (sd == null)
- throw new FHIRException("Unable to resolve structure "+imp.getUrl());
- return sd.getId(); // should be sd.getType(), but R2...
- }
- }
- return statedType;
- }
-
-
- private ResolvedGroup resolveGroupReference(StructureMap map, StructureMapGroupComponent source, String name) throws FHIRException {
- String kn = "ref^"+name;
- if (source.hasUserData(kn))
- return (ResolvedGroup) source.getUserData(kn);
-
- ResolvedGroup res = new ResolvedGroup();
- res.targetMap = null;
- res.target = null;
- for (StructureMapGroupComponent grp : map.getGroup()) {
- if (grp.getName().equals(name)) {
- if (res.targetMap == null) {
- res.targetMap = map;
- res.target = grp;
- } else
- throw new FHIRException("Multiple possible matches for rule '"+name+"'");
- }
- }
- if (res.targetMap != null) {
- source.setUserData(kn, res);
- return res;
- }
-
- for (UriType imp : map.getImport()) {
- List impMapList = findMatchingMaps(imp.getValue());
- if (impMapList.size() == 0)
- throw new FHIRException("Unable to find map(s) for "+imp.getValue());
- for (StructureMap impMap : impMapList) {
- if (!impMap.getUrl().equals(map.getUrl())) {
- for (StructureMapGroupComponent grp : impMap.getGroup()) {
- if (grp.getName().equals(name)) {
- if (res.targetMap == null) {
- res.targetMap = impMap;
- res.target = grp;
- } else
- throw new FHIRException("Multiple possible matches for rule '"+name+"' in "+res.targetMap.getUrl()+" and "+impMap.getUrl());
- }
- }
- }
- }
- }
- if (res.target == null)
- throw new FHIRException("No matches found for rule '"+name+"'. Reference found in "+map.getUrl());
- source.setUserData(kn, res);
- return res;
- }
-
- private List processSource(String ruleId, TransformContext context, Variables vars, StructureMapGroupRuleSourceComponent src) throws FHIRException {
- List items;
- if (src.getContext().equals("@search")) {
- ExpressionNode expr = (ExpressionNode) src.getUserData(MAP_SEARCH_EXPRESSION);
- if (expr == null) {
- expr = fpe.parse(src.getElement());
- src.setUserData(MAP_SEARCH_EXPRESSION, expr);
- }
- String search = fpe.evaluateToString(vars, null, new StringType(), expr); // string is a holder of nothing to ensure that variables are processed correctly
- items = services.performSearch(context.appInfo, search);
- } else {
- items = new ArrayList();
- Base b = vars.get(VariableMode.INPUT, src.getContext());
- if (b == null)
- throw new FHIRException("Unknown input variable "+src.getContext());
-
- if (!src.hasElement())
- items.add(b);
- else {
- getChildrenByName(b, src.getElement(), items);
- if (items.size() == 0 && src.hasDefaultValue())
- items.add(src.getDefaultValue());
- }
- }
-
- if (src.hasType()) {
- List remove = new ArrayList();
- for (Base item : items) {
- if (item != null && !isType(item, src.getType())) {
- remove.add(item);
- }
- }
- items.removeAll(remove);
- }
-
- if (src.hasCondition()) {
- ExpressionNode expr = (ExpressionNode) src.getUserData(MAP_WHERE_EXPRESSION);
- if (expr == null) {
- expr = fpe.parse(src.getCondition());
- // fpe.check(context.appInfo, ??, ??, expr)
- src.setUserData(MAP_WHERE_EXPRESSION, expr);
- }
- List remove = new ArrayList();
- for (Base item : items) {
- if (!fpe.evaluateToBoolean(vars, null, item, expr))
- remove.add(item);
- }
- items.removeAll(remove);
- }
-
- if (src.hasCheck()) {
- ExpressionNode expr = (ExpressionNode) src.getUserData(MAP_WHERE_CHECK);
- if (expr == null) {
- expr = fpe.parse(src.getCheck());
- // fpe.check(context.appInfo, ??, ??, expr)
- src.setUserData(MAP_WHERE_CHECK, expr);
- }
- List remove = new ArrayList();
- for (Base item : items) {
- if (!fpe.evaluateToBoolean(vars, null, item, expr))
- throw new FHIRException("Rule \""+ruleId+"\": Check condition failed");
- }
- }
-
-
- if (src.hasListMode() && !items.isEmpty()) {
- switch (src.getListMode()) {
- case FIRST:
- Base bt = items.get(0);
- items.clear();
- items.add(bt);
- break;
- case NOTFIRST:
- if (items.size() > 0)
- items.remove(0);
- break;
- case LAST:
- bt = items.get(items.size()-1);
- items.clear();
- items.add(bt);
- break;
- case NOTLAST:
- if (items.size() > 0)
- items.remove(items.size()-1);
- break;
- case ONLYONE:
- if (items.size() > 1)
- throw new FHIRException("Rule \""+ruleId+"\": Check condition failed: the collection has more than one item");
- break;
- case NULL:
- }
- }
- List result = new ArrayList();
- for (Base r : items) {
- Variables v = vars.copy();
- if (src.hasVariable())
- v.add(VariableMode.INPUT, src.getVariable(), r);
- result.add(v);
- }
- return result;
- }
-
-
- private boolean isType(Base item, String type) {
- if (type.equals(item.fhirType()))
- return true;
- return false;
- }
-
- private void processTarget(String ruleId, TransformContext context, Variables vars, StructureMap map, StructureMapGroupComponent group, StructureMapGroupRuleTargetComponent tgt, String srcVar) throws FHIRException {
- Base dest = null;
- if (tgt.hasContext()) {
- dest = vars.get(VariableMode.OUTPUT, tgt.getContext());
- if (dest == null)
- throw new FHIRException("Rule \""+ruleId+"\": target context not known: "+tgt.getContext());
- if (!tgt.hasElement())
- throw new FHIRException("Rule \""+ruleId+"\": Not supported yet");
- }
- Base v = null;
- if (tgt.hasTransform()) {
- v = runTransform(ruleId, context, map, group, tgt, vars, dest, tgt.getElement(), srcVar);
- if (v != null && dest != null)
- v = dest.setProperty(tgt.getElement().hashCode(), tgt.getElement(), v); // reset v because some implementations may have to rewrite v when setting the value
- } else if (dest != null)
- v = dest.makeProperty(tgt.getElement().hashCode(), tgt.getElement());
- if (tgt.hasVariable() && v != null)
- vars.add(VariableMode.OUTPUT, tgt.getVariable(), v);
- }
-
- private Base runTransform(String ruleId, TransformContext context, StructureMap map, StructureMapGroupComponent group, StructureMapGroupRuleTargetComponent tgt, Variables vars, Base dest, String element, String srcVar) throws FHIRException {
- try {
- switch (tgt.getTransform()) {
- case CREATE :
- String tn;
- if (tgt.getParameter().isEmpty()) {
- // we have to work out the type. First, we see if there is a single type for the target. If there is, we use that
- String[] types = dest.getTypesForProperty(element.hashCode(), element);
- if (types.length == 1 && !"*".equals(types[0]) && !types[0].equals("Resource"))
- tn = types[0];
- else if (srcVar != null) {
- tn = determineTypeFromSourceType(map, group, vars.get(VariableMode.INPUT, srcVar), types);
- } else
- throw new Error("Cannot determine type implicitly because there is no single input variable");
- } else
- tn = getParamStringNoNull(vars, tgt.getParameter().get(0), tgt.toString());
- Base res = services != null ? services.createType(context.getAppInfo(), tn) : ResourceFactory.createResourceOrType(tn);
- if (res.isResource() && !res.fhirType().equals("Parameters")) {
-// res.setIdBase(tgt.getParameter().size() > 1 ? getParamString(vars, tgt.getParameter().get(0)) : UUID.randomUUID().toString().toLowerCase());
- if (services != null)
- res = services.createResource(context.getAppInfo(), res);
- }
- if (tgt.hasUserData("profile"))
- res.setUserData("profile", tgt.getUserData("profile"));
- return res;
- case COPY :
- return getParam(vars, tgt.getParameter().get(0));
- case EVALUATE :
- ExpressionNode expr = (ExpressionNode) tgt.getUserData(MAP_EXPRESSION);
- if (expr == null) {
- expr = fpe.parse(getParamStringNoNull(vars, tgt.getParameter().get(1), tgt.toString()));
- tgt.setUserData(MAP_WHERE_EXPRESSION, expr);
- }
- List v = fpe.evaluate(vars, null, tgt.getParameter().size() == 2 ? getParam(vars, tgt.getParameter().get(0)) : new BooleanType(false), expr);
- if (v.size() == 0)
- return null;
- else if (v.size() != 1)
- throw new FHIRException("Rule \""+ruleId+"\": Evaluation of "+expr.toString()+" returned "+Integer.toString(v.size())+" objects");
- else
- return v.get(0);
-
- case TRUNCATE :
- String src = getParamString(vars, tgt.getParameter().get(0));
- String len = getParamStringNoNull(vars, tgt.getParameter().get(1), tgt.toString());
- if (Utilities.isInteger(len)) {
- int l = Integer.parseInt(len);
- if (src.length() > l)
- src = src.substring(0, l);
- }
- return new StringType(src);
- case ESCAPE :
- throw new Error("Rule \""+ruleId+"\": Transform "+tgt.getTransform().toCode()+" not supported yet");
- case CAST :
- throw new Error("Rule \""+ruleId+"\": Transform "+tgt.getTransform().toCode()+" not supported yet");
- case APPEND :
- throw new Error("Rule \""+ruleId+"\": Transform "+tgt.getTransform().toCode()+" not supported yet");
- case TRANSLATE :
- return translate(context, map, vars, tgt.getParameter());
- case REFERENCE :
- Base b = getParam(vars, tgt.getParameter().get(0));
- if (b == null)
- throw new FHIRException("Rule \""+ruleId+"\": Unable to find parameter "+((IdType) tgt.getParameter().get(0).getValue()).asStringValue());
- if (!b.isResource())
- throw new FHIRException("Rule \""+ruleId+"\": Transform engine cannot point at an element of type "+b.fhirType());
- else {
- String id = b.getIdBase();
- if (id == null) {
- id = UUID.randomUUID().toString().toLowerCase();
- b.setIdBase(id);
- }
- return new Reference().setReference(b.fhirType()+"/"+id);
- }
- case DATEOP :
- throw new Error("Rule \""+ruleId+"\": Transform "+tgt.getTransform().toCode()+" not supported yet");
- case UUID :
- return new IdType(UUID.randomUUID().toString());
- case POINTER :
- b = getParam(vars, tgt.getParameter().get(0));
- if (b instanceof Resource)
- return new UriType("urn:uuid:"+((Resource) b).getId());
- else
- throw new FHIRException("Rule \""+ruleId+"\": Transform engine cannot point at an element of type "+b.fhirType());
- case CC:
- CodeableConcept cc = new CodeableConcept();
- cc.addCoding(buildCoding(getParamStringNoNull(vars, tgt.getParameter().get(0), tgt.toString()), getParamStringNoNull(vars, tgt.getParameter().get(1), tgt.toString())));
- return cc;
- case C:
- Coding c = buildCoding(getParamStringNoNull(vars, tgt.getParameter().get(0), tgt.toString()), getParamStringNoNull(vars, tgt.getParameter().get(1), tgt.toString()));
- return c;
- default:
- throw new Error("Rule \""+ruleId+"\": Transform Unknown: "+tgt.getTransform().toCode());
- }
- } catch (Exception e) {
- throw new FHIRException("Exception executing transform "+tgt.toString()+" on Rule \""+ruleId+"\": "+e.getMessage(), e);
- }
- }
-
-
- private Coding buildCoding(String uri, String code) throws FHIRException {
- // if we can get this as a valueSet, we will
- String system = null;
- String display = null;
- ValueSet vs = Utilities.noString(uri) ? null : worker.fetchResourceWithException(ValueSet.class, uri);
- if (vs != null) {
- ValueSetExpansionOutcome vse = worker.expandVS(vs, true, false);
- if (vse.getError() != null)
- throw new FHIRException(vse.getError());
- CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
- for (ValueSetExpansionContainsComponent t : vse.getValueset().getExpansion().getContains()) {
- if (t.hasCode())
- b.append(t.getCode());
- if (code.equals(t.getCode()) && t.hasSystem()) {
- system = t.getSystem();
- display = t.getDisplay();
- break;
- }
- if (code.equalsIgnoreCase(t.getDisplay()) && t.hasSystem()) {
- system = t.getSystem();
- display = t.getDisplay();
- break;
- }
- }
- if (system == null)
- throw new FHIRException("The code '"+code+"' is not in the value set '"+uri+"' (valid codes: "+b.toString()+"; also checked displays)");
- } else
- system = uri;
- ValidationResult vr = worker.validateCode(system, code, null);
- if (vr != null && vr.getDisplay() != null)
- display = vr.getDisplay();
- return new Coding().setSystem(system).setCode(code).setDisplay(display);
- }
-
-
- private String getParamStringNoNull(Variables vars, StructureMapGroupRuleTargetParameterComponent parameter, String message) throws FHIRException {
- Base b = getParam(vars, parameter);
- if (b == null)
- throw new FHIRException("Unable to find a value for "+parameter.toString()+". Context: "+message);
- if (!b.hasPrimitiveValue())
- throw new FHIRException("Found a value for "+parameter.toString()+", but it has a type of "+b.fhirType()+" and cannot be treated as a string. Context: "+message);
- return b.primitiveValue();
- }
-
- private String getParamString(Variables vars, StructureMapGroupRuleTargetParameterComponent parameter) throws DefinitionException {
- Base b = getParam(vars, parameter);
- if (b == null || !b.hasPrimitiveValue())
- return null;
- return b.primitiveValue();
- }
-
-
- private Base getParam(Variables vars, StructureMapGroupRuleTargetParameterComponent parameter) throws DefinitionException {
- Type p = parameter.getValue();
- if (!(p instanceof IdType))
- return p;
- else {
- String n = ((IdType) p).asStringValue();
- Base b = vars.get(VariableMode.INPUT, n);
- if (b == null)
- b = vars.get(VariableMode.OUTPUT, n);
- if (b == null)
- throw new DefinitionException("Variable "+n+" not found ("+vars.summary()+")");
- return b;
- }
- }
-
-
- private Base translate(TransformContext context, StructureMap map, Variables vars, List parameter) throws FHIRException {
- Base src = getParam(vars, parameter.get(0));
- String id = getParamString(vars, parameter.get(1));
- String fld = parameter.size() > 2 ? getParamString(vars, parameter.get(2)) : null;
- return translate(context, map, src, id, fld);
- }
-
- private class SourceElementComponentWrapper {
- private ConceptMapGroupComponent group;
- private SourceElementComponent comp;
- public SourceElementComponentWrapper(ConceptMapGroupComponent group, SourceElementComponent comp) {
- super();
- this.group = group;
- this.comp = comp;
- }
- }
- public Base translate(TransformContext context, StructureMap map, Base source, String conceptMapUrl, String fieldToReturn) throws FHIRException {
- Coding src = new Coding();
- if (source.isPrimitive()) {
- src.setCode(source.primitiveValue());
- } else if ("Coding".equals(source.fhirType())) {
- Base[] b = source.getProperty("system".hashCode(), "system", true);
- if (b.length == 1)
- src.setSystem(b[0].primitiveValue());
- b = source.getProperty("code".hashCode(), "code", true);
- if (b.length == 1)
- src.setCode(b[0].primitiveValue());
- } else if ("CE".equals(source.fhirType())) {
- Base[] b = source.getProperty("codeSystem".hashCode(), "codeSystem", true);
- if (b.length == 1)
- src.setSystem(b[0].primitiveValue());
- b = source.getProperty("code".hashCode(), "code", true);
- if (b.length == 1)
- src.setCode(b[0].primitiveValue());
- } else
- throw new FHIRException("Unable to translate source "+source.fhirType());
-
- String su = conceptMapUrl;
- if (conceptMapUrl.equals("http://hl7.org/fhir/ConceptMap/special-oid2uri")) {
- String uri = worker.oid2Uri(src.getCode());
- if (uri == null)
- uri = "urn:oid:"+src.getCode();
- if ("uri".equals(fieldToReturn))
- return new UriType(uri);
- else
- throw new FHIRException("Error in return code");
- } else {
- ConceptMap cmap = null;
- if (conceptMapUrl.startsWith("#")) {
- for (Resource r : map.getContained()) {
- if (r instanceof ConceptMap && ((ConceptMap) r).getId().equals(conceptMapUrl.substring(1))) {
- cmap = (ConceptMap) r;
- su = map.getUrl()+conceptMapUrl;
- }
- }
- if (cmap == null)
- throw new FHIRException("Unable to translate - cannot find map "+conceptMapUrl);
- } else
- cmap = worker.fetchResource(ConceptMap.class, conceptMapUrl);
- Coding outcome = null;
- boolean done = false;
- String message = null;
- if (cmap == null) {
- if (services == null)
- message = "No map found for "+conceptMapUrl;
- else {
- outcome = services.translate(context.appInfo, src, conceptMapUrl);
- done = true;
- }
- } else {
- List list = new ArrayList();
- for (ConceptMapGroupComponent g : cmap.getGroup()) {
- for (SourceElementComponent e : g.getElement()) {
- if (!src.hasSystem() && src.getCode().equals(e.getCode()))
- list.add(new SourceElementComponentWrapper(g, e));
- else if (src.hasSystem() && src.getSystem().equals(g.getSource()) && src.getCode().equals(e.getCode()))
- list.add(new SourceElementComponentWrapper(g, e));
- }
- }
- if (list.size() == 0)
- done = true;
- else if (list.get(0).comp.getTarget().size() == 0)
- message = "Concept map "+su+" found no translation for "+src.getCode();
- else {
- for (TargetElementComponent tgt : list.get(0).comp.getTarget()) {
- if (tgt.getEquivalence() == null || EnumSet.of( ConceptMapEquivalence.EQUAL , ConceptMapEquivalence.RELATEDTO , ConceptMapEquivalence.EQUIVALENT, ConceptMapEquivalence.WIDER).contains(tgt.getEquivalence())) {
- if (done) {
- message = "Concept map "+su+" found multiple matches for "+src.getCode();
- done = false;
- } else {
- done = true;
- outcome = new Coding().setCode(tgt.getCode()).setSystem(list.get(0).group.getTarget());
- }
- } else if (tgt.getEquivalence() == ConceptMapEquivalence.UNMATCHED) {
- done = true;
- }
- }
- if (!done)
- message = "Concept map "+su+" found no usable translation for "+src.getCode();
- }
- }
- if (!done)
- throw new FHIRException(message);
- if (outcome == null)
- return null;
- if ("code".equals(fieldToReturn))
- return new CodeType(outcome.getCode());
- else
- return outcome;
- }
- }
-
-
- public Map getLibrary() {
- return library;
- }
-
- public class PropertyWithType {
- private String path;
- private Property baseProperty;
- private Property profileProperty;
- private TypeDetails types;
- public PropertyWithType(String path, Property baseProperty, Property profileProperty, TypeDetails types) {
- super();
- this.baseProperty = baseProperty;
- this.profileProperty = profileProperty;
- this.path = path;
- this.types = types;
- }
-
- public TypeDetails getTypes() {
- return types;
- }
- public String getPath() {
- return path;
- }
-
- public Property getBaseProperty() {
- return baseProperty;
- }
-
- public void setBaseProperty(Property baseProperty) {
- this.baseProperty = baseProperty;
- }
-
- public Property getProfileProperty() {
- return profileProperty;
- }
-
- public void setProfileProperty(Property profileProperty) {
- this.profileProperty = profileProperty;
- }
-
- public String summary() {
- return path;
- }
-
- }
-
- public class VariableForProfiling {
- private VariableMode mode;
- private String name;
- private PropertyWithType property;
-
- public VariableForProfiling(VariableMode mode, String name, PropertyWithType property) {
- super();
- this.mode = mode;
- this.name = name;
- this.property = property;
- }
- public VariableMode getMode() {
- return mode;
- }
- public String getName() {
- return name;
- }
- public PropertyWithType getProperty() {
- return property;
- }
- public String summary() {
- return name+": "+property.summary();
- }
- }
-
- public class VariablesForProfiling {
- private List list = new ArrayList();
- private boolean optional;
- private boolean repeating;
-
- public VariablesForProfiling(boolean optional, boolean repeating) {
- this.optional = optional;
- this.repeating = repeating;
- }
-
- public void add(VariableMode mode, String name, String path, Property property, TypeDetails types) {
- add(mode, name, new PropertyWithType(path, property, null, types));
- }
-
- public void add(VariableMode mode, String name, String path, Property baseProperty, Property profileProperty, TypeDetails types) {
- add(mode, name, new PropertyWithType(path, baseProperty, profileProperty, types));
- }
-
- public void add(VariableMode mode, String name, PropertyWithType property) {
- VariableForProfiling vv = null;
- for (VariableForProfiling v : list)
- if ((v.mode == mode) && v.getName().equals(name))
- vv = v;
- if (vv != null)
- list.remove(vv);
- list.add(new VariableForProfiling(mode, name, property));
- }
-
- public VariablesForProfiling copy(boolean optional, boolean repeating) {
- VariablesForProfiling result = new VariablesForProfiling(optional, repeating);
- result.list.addAll(list);
- return result;
- }
-
- public VariablesForProfiling copy() {
- VariablesForProfiling result = new VariablesForProfiling(optional, repeating);
- result.list.addAll(list);
- return result;
- }
-
- public VariableForProfiling get(VariableMode mode, String name) {
- if (mode == null) {
- for (VariableForProfiling v : list)
- if ((v.mode == VariableMode.OUTPUT) && v.getName().equals(name))
- return v;
- for (VariableForProfiling v : list)
- if ((v.mode == VariableMode.INPUT) && v.getName().equals(name))
- return v;
- }
- for (VariableForProfiling v : list)
- if ((v.mode == mode) && v.getName().equals(name))
- return v;
- return null;
- }
-
- public String summary() {
- CommaSeparatedStringBuilder s = new CommaSeparatedStringBuilder();
- CommaSeparatedStringBuilder t = new CommaSeparatedStringBuilder();
- for (VariableForProfiling v : list)
- if (v.mode == VariableMode.INPUT)
- s.append(v.summary());
- else
- t.append(v.summary());
- return "source variables ["+s.toString()+"], target variables ["+t.toString()+"]";
- }
- }
-
- public class StructureMapAnalysis {
- private List profiles = new ArrayList();
- private XhtmlNode summary;
- public List getProfiles() {
- return profiles;
- }
- public XhtmlNode getSummary() {
- return summary;
- }
-
- }
-
- /**
- * Given a structure map, return a set of analyses on it.
- *
- * Returned:
- * - a list or profiles for what it will create. First profile is the target
- * - a table with a summary (in xhtml) for easy human undertanding of the mapping
- *
- *
- * @param appInfo
- * @param map
- * @return
- * @throws Exception
- */
- public StructureMapAnalysis analyse(Object appInfo, StructureMap map) throws Exception {
- ids.clear();
- StructureMapAnalysis result = new StructureMapAnalysis();
- TransformContext context = new TransformContext(appInfo);
- VariablesForProfiling vars = new VariablesForProfiling(false, false);
- StructureMapGroupComponent start = map.getGroup().get(0);
- for (StructureMapGroupInputComponent t : start.getInput()) {
- PropertyWithType ti = resolveType(map, t.getType(), t.getMode());
- if (t.getMode() == StructureMapInputMode.SOURCE)
- vars.add(VariableMode.INPUT, t.getName(), ti);
- else
- vars.add(VariableMode.OUTPUT, t.getName(), createProfile(map, result.profiles, ti, start.getName(), start));
- }
-
- result.summary = new XhtmlNode(NodeType.Element, "table").setAttribute("class", "grid");
- XhtmlNode tr = result.summary.addTag("tr");
- tr.addTag("td").addTag("b").addText("Source");
- tr.addTag("td").addTag("b").addText("Target");
-
- log("Start Profiling Transform "+map.getUrl());
- analyseGroup("", context, map, vars, start, result);
- ProfileUtilities pu = new ProfileUtilities(worker, null, pkp);
- for (StructureDefinition sd : result.getProfiles())
- pu.cleanUpDifferential(sd);
- return result;
- }
-
-
- private void analyseGroup(String indent, TransformContext context, StructureMap map, VariablesForProfiling vars, StructureMapGroupComponent group, StructureMapAnalysis result) throws Exception {
- log(indent+"Analyse Group : "+group.getName());
- // todo: extends
- // todo: check inputs
- XhtmlNode tr = result.summary.addTag("tr").setAttribute("class", "diff-title");
- XhtmlNode xs = tr.addTag("td");
- XhtmlNode xt = tr.addTag("td");
- for (StructureMapGroupInputComponent inp : group.getInput()) {
- if (inp.getMode() == StructureMapInputMode.SOURCE)
- noteInput(vars, inp, VariableMode.INPUT, xs);
- if (inp.getMode() == StructureMapInputMode.TARGET)
- noteInput(vars, inp, VariableMode.OUTPUT, xt);
- }
- for (StructureMapGroupRuleComponent r : group.getRule()) {
- analyseRule(indent+" ", context, map, vars, group, r, result);
- }
- }
-
-
- private void noteInput(VariablesForProfiling vars, StructureMapGroupInputComponent inp, VariableMode mode, XhtmlNode xs) {
- VariableForProfiling v = vars.get(mode, inp.getName());
- if (v != null)
- xs.addText("Input: "+v.property.getPath());
- }
-
- private void analyseRule(String indent, TransformContext context, StructureMap map, VariablesForProfiling vars, StructureMapGroupComponent group, StructureMapGroupRuleComponent rule, StructureMapAnalysis result) throws Exception {
- log(indent+"Analyse rule : "+rule.getName());
- XhtmlNode tr = result.summary.addTag("tr");
- XhtmlNode xs = tr.addTag("td");
- XhtmlNode xt = tr.addTag("td");
-
- VariablesForProfiling srcVars = vars.copy();
- if (rule.getSource().size() != 1)
- throw new Exception("Rule \""+rule.getName()+"\": not handled yet");
- VariablesForProfiling source = analyseSource(rule.getName(), context, srcVars, rule.getSourceFirstRep(), xs);
-
- TargetWriter tw = new TargetWriter();
- for (StructureMapGroupRuleTargetComponent t : rule.getTarget()) {
- analyseTarget(rule.getName(), context, source, map, t, rule.getSourceFirstRep().getVariable(), tw, result.profiles, rule.getName());
- }
- tw.commit(xt);
-
- for (StructureMapGroupRuleComponent childrule : rule.getRule()) {
- analyseRule(indent+" ", context, map, source, group, childrule, result);
- }
-// for (StructureMapGroupRuleDependentComponent dependent : rule.getDependent()) {
-// executeDependency(indent+" ", context, map, v, group, dependent); // do we need group here?
-// }
- }
-
- public class StringPair {
- private String var;
- private String desc;
- public StringPair(String var, String desc) {
- super();
- this.var = var;
- this.desc = desc;
- }
- public String getVar() {
- return var;
- }
- public String getDesc() {
- return desc;
- }
- }
- public class TargetWriter {
- private Map newResources = new HashMap();
- private List assignments = new ArrayList();
- private List keyProps = new ArrayList();
- private CommaSeparatedStringBuilder txt = new CommaSeparatedStringBuilder();
-
- public void newResource(String var, String name) {
- newResources.put(var, name);
- txt.append("new "+name);
- }
-
- public void valueAssignment(String context, String desc) {
- assignments.add(new StringPair(context, desc));
- txt.append(desc);
- }
-
- public void keyAssignment(String context, String desc) {
- keyProps.add(new StringPair(context, desc));
- txt.append(desc);
- }
- public void commit(XhtmlNode xt) {
- if (newResources.size() == 1 && assignments.size() == 1 && newResources.containsKey(assignments.get(0).getVar()) && keyProps.size() == 1 && newResources.containsKey(keyProps.get(0).getVar()) ) {
- xt.addText("new "+assignments.get(0).desc+" ("+keyProps.get(0).desc.substring(keyProps.get(0).desc.indexOf(".")+1)+")");
- } else if (newResources.size() == 1 && assignments.size() == 1 && newResources.containsKey(assignments.get(0).getVar()) && keyProps.size() == 0) {
- xt.addText("new "+assignments.get(0).desc);
- } else {
- xt.addText(txt.toString());
- }
- }
- }
-
- private VariablesForProfiling analyseSource(String ruleId, TransformContext context, VariablesForProfiling vars, StructureMapGroupRuleSourceComponent src, XhtmlNode td) throws Exception {
- VariableForProfiling var = vars.get(VariableMode.INPUT, src.getContext());
- if (var == null)
- throw new FHIRException("Rule \""+ruleId+"\": Unknown input variable "+src.getContext());
- PropertyWithType prop = var.getProperty();
-
- boolean optional = false;
- boolean repeating = false;
-
- if (src.hasCondition()) {
- optional = true;
- }
-
- if (src.hasElement()) {
- Property element = prop.getBaseProperty().getChild(prop.types.getType(), src.getElement());
- if (element == null)
- throw new Exception("Rule \""+ruleId+"\": Unknown element name "+src.getElement());
- if (element.getDefinition().getMin() == 0)
- optional = true;
- if (element.getDefinition().getMax().equals("*"))
- repeating = true;
- VariablesForProfiling result = vars.copy(optional, repeating);
- TypeDetails type = new TypeDetails(CollectionStatus.SINGLETON);
- for (TypeRefComponent tr : element.getDefinition().getType()) {
- if (!tr.hasCode())
- throw new Error("Rule \""+ruleId+"\": Element has no type");
- ProfiledType pt = new ProfiledType(tr.getCode());
- if (tr.hasProfile())
- pt.addProfile(tr.getProfile());
- if (element.getDefinition().hasBinding())
- pt.addBinding(element.getDefinition().getBinding());
- type.addType(pt);
- }
- td.addText(prop.getPath()+"."+src.getElement());
- if (src.hasVariable())
- result.add(VariableMode.INPUT, src.getVariable(), new PropertyWithType(prop.getPath()+"."+src.getElement(), element, null, type));
- return result;
- } else {
- td.addText(prop.getPath()); // ditto!
- return vars.copy(optional, repeating);
- }
- }
-
-
- private void analyseTarget(String ruleId, TransformContext context, VariablesForProfiling vars, StructureMap map, StructureMapGroupRuleTargetComponent tgt, String tv, TargetWriter tw, List profiles, String sliceName) throws Exception {
- VariableForProfiling var = null;
- if (tgt.hasContext()) {
- var = vars.get(VariableMode.OUTPUT, tgt.getContext());
- if (var == null)
- throw new Exception("Rule \""+ruleId+"\": target context not known: "+tgt.getContext());
- if (!tgt.hasElement())
- throw new Exception("Rule \""+ruleId+"\": Not supported yet");
- }
-
-
- TypeDetails type = null;
- if (tgt.hasTransform()) {
- type = analyseTransform(context, map, tgt, var, vars);
- // profiling: dest.setProperty(tgt.getElement().hashCode(), tgt.getElement(), v);
- } else {
- Property vp = var.property.baseProperty.getChild(tgt.getElement(), tgt.getElement());
- if (vp == null)
- throw new Exception("Unknown Property "+tgt.getElement()+" on "+var.property.path);
-
- type = new TypeDetails(CollectionStatus.SINGLETON, vp.getType(tgt.getElement()));
- }
-
- if (tgt.getTransform() == StructureMapTransform.CREATE) {
- String s = getParamString(vars, tgt.getParameter().get(0));
- if (worker.getResourceNames().contains(s))
- tw.newResource(tgt.getVariable(), s);
- } else {
- boolean mapsSrc = false;
- for (StructureMapGroupRuleTargetParameterComponent p : tgt.getParameter()) {
- Type pr = p.getValue();
- if (pr instanceof IdType && ((IdType) pr).asStringValue().equals(tv))
- mapsSrc = true;
- }
- if (mapsSrc) {
- if (var == null)
- throw new Error("Rule \""+ruleId+"\": Attempt to assign with no context");
- tw.valueAssignment(tgt.getContext(), var.property.getPath()+"."+tgt.getElement()+getTransformSuffix(tgt.getTransform()));
- } else if (tgt.hasContext()) {
- if (isSignificantElement(var.property, tgt.getElement())) {
- String td = describeTransform(tgt);
- if (td != null)
- tw.keyAssignment(tgt.getContext(), var.property.getPath()+"."+tgt.getElement()+" = "+td);
- }
- }
- }
- Type fixed = generateFixedValue(tgt);
-
- PropertyWithType prop = updateProfile(var, tgt.getElement(), type, map, profiles, sliceName, fixed, tgt);
- if (tgt.hasVariable())
- if (tgt.hasElement())
- vars.add(VariableMode.OUTPUT, tgt.getVariable(), prop);
- else
- vars.add(VariableMode.OUTPUT, tgt.getVariable(), prop);
- }
-
- private Type generateFixedValue(StructureMapGroupRuleTargetComponent tgt) {
- if (!allParametersFixed(tgt))
- return null;
- if (!tgt.hasTransform())
- return null;
- switch (tgt.getTransform()) {
- case COPY: return tgt.getParameter().get(0).getValue();
- case TRUNCATE: return null;
- //case ESCAPE:
- //case CAST:
- //case APPEND:
- case TRANSLATE: return null;
- //case DATEOP,
- //case UUID,
- //case POINTER,
- //case EVALUATE,
- case CC:
- CodeableConcept cc = new CodeableConcept();
- cc.addCoding(buildCoding(tgt.getParameter().get(0).getValue(), tgt.getParameter().get(1).getValue()));
- return cc;
- case C:
- return buildCoding(tgt.getParameter().get(0).getValue(), tgt.getParameter().get(1).getValue());
- case QTY: return null;
- //case ID,
- //case CP,
- default:
- return null;
- }
- }
-
- @SuppressWarnings("rawtypes")
- private Coding buildCoding(Type value1, Type value2) {
- return new Coding().setSystem(((PrimitiveType) value1).asStringValue()).setCode(((PrimitiveType) value2).asStringValue()) ;
- }
-
- private boolean allParametersFixed(StructureMapGroupRuleTargetComponent tgt) {
- for (StructureMapGroupRuleTargetParameterComponent p : tgt.getParameter()) {
- Type pr = p.getValue();
- if (pr instanceof IdType)
- return false;
- }
- return true;
- }
-
- private String describeTransform(StructureMapGroupRuleTargetComponent tgt) throws FHIRException {
- switch (tgt.getTransform()) {
- case COPY: return null;
- case TRUNCATE: return null;
- //case ESCAPE:
- //case CAST:
- //case APPEND:
- case TRANSLATE: return null;
- //case DATEOP,
- //case UUID,
- //case POINTER,
- //case EVALUATE,
- case CC: return describeTransformCCorC(tgt);
- case C: return describeTransformCCorC(tgt);
- case QTY: return null;
- //case ID,
- //case CP,
- default:
- return null;
- }
- }
-
- @SuppressWarnings("rawtypes")
- private String describeTransformCCorC(StructureMapGroupRuleTargetComponent tgt) throws FHIRException {
- if (tgt.getParameter().size() < 2)
- return null;
- Type p1 = tgt.getParameter().get(0).getValue();
- Type p2 = tgt.getParameter().get(1).getValue();
- if (p1 instanceof IdType || p2 instanceof IdType)
- return null;
- if (!(p1 instanceof PrimitiveType) || !(p2 instanceof PrimitiveType))
- return null;
- String uri = ((PrimitiveType) p1).asStringValue();
- String code = ((PrimitiveType) p2).asStringValue();
- if (Utilities.noString(uri))
- throw new FHIRException("Describe Transform, but the uri is blank");
- if (Utilities.noString(code))
- throw new FHIRException("Describe Transform, but the code is blank");
- Coding c = buildCoding(uri, code);
- return NarrativeGenerator.describeSystem(c.getSystem())+"#"+c.getCode()+(c.hasDisplay() ? "("+c.getDisplay()+")" : "");
- }
-
-
- private boolean isSignificantElement(PropertyWithType property, String element) {
- if ("Observation".equals(property.getPath()))
- return "code".equals(element);
- else if ("Bundle".equals(property.getPath()))
- return "type".equals(element);
- else
- return false;
- }
-
- private String getTransformSuffix(StructureMapTransform transform) {
- switch (transform) {
- case COPY: return "";
- case TRUNCATE: return " (truncated)";
- //case ESCAPE:
- //case CAST:
- //case APPEND:
- case TRANSLATE: return " (translated)";
- //case DATEOP,
- //case UUID,
- //case POINTER,
- //case EVALUATE,
- case CC: return " (--> CodeableConcept)";
- case C: return " (--> Coding)";
- case QTY: return " (--> Quantity)";
- //case ID,
- //case CP,
- default:
- return " {??)";
- }
- }
-
- private PropertyWithType updateProfile(VariableForProfiling var, String element, TypeDetails type, StructureMap map, List profiles, String sliceName, Type fixed, StructureMapGroupRuleTargetComponent tgt) throws FHIRException {
- if (var == null) {
- assert (Utilities.noString(element));
- // 1. start the new structure definition
- StructureDefinition sdn = worker.fetchResource(StructureDefinition.class, type.getType());
- if (sdn == null)
- throw new FHIRException("Unable to find definition for "+type.getType());
- ElementDefinition edn = sdn.getSnapshot().getElementFirstRep();
- PropertyWithType pn = createProfile(map, profiles, new PropertyWithType(sdn.getId(), new Property(worker, edn, sdn), null, type), sliceName, tgt);
-
-// // 2. hook it into the base bundle
-// if (type.getType().startsWith("http://hl7.org/fhir/StructureDefinition/") && worker.getResourceNames().contains(type.getType().substring(40))) {
-// StructureDefinition sd = var.getProperty().profileProperty.getStructure();
-// ElementDefinition ed = sd.getDifferential().addElement();
-// ed.setPath("Bundle.entry");
-// ed.setName(sliceName);
-// ed.setMax("1"); // well, it is for now...
-// ed = sd.getDifferential().addElement();
-// ed.setPath("Bundle.entry.fullUrl");
-// ed.setMin(1);
-// ed = sd.getDifferential().addElement();
-// ed.setPath("Bundle.entry.resource");
-// ed.setMin(1);
-// ed.addType().setCode(pn.getProfileProperty().getStructure().getType()).setProfile(pn.getProfileProperty().getStructure().getUrl());
-// }
- return pn;
- } else {
- assert (!Utilities.noString(element));
- Property pvb = var.getProperty().getBaseProperty();
- Property pvd = var.getProperty().getProfileProperty();
- Property pc = pvb.getChild(element, var.property.types);
- if (pc == null)
- throw new DefinitionException("Unable to find a definition for "+pvb.getDefinition().getPath()+"."+element);
-
- // the profile structure definition (derived)
- StructureDefinition sd = var.getProperty().profileProperty.getStructure();
- ElementDefinition ednew = sd.getDifferential().addElement();
- ednew.setPath(var.getProperty().profileProperty.getDefinition().getPath()+"."+pc.getName());
- ednew.setUserData("slice-name", sliceName);
- ednew.setFixed(fixed);
- for (ProfiledType pt : type.getProfiledTypes()) {
- if (pt.hasBindings())
- ednew.setBinding(pt.getBindings().get(0));
- if (pt.getUri().startsWith("http://hl7.org/fhir/StructureDefinition/")) {
- String t = pt.getUri().substring(40);
- t = checkType(t, pc, pt.getProfiles());
- if (t != null) {
- if (pt.hasProfiles()) {
- for (String p : pt.getProfiles())
- if (t.equals("Reference"))
- ednew.addType().setCode(t).setTargetProfile(p);
- else
- ednew.addType().setCode(t).setProfile(p);
- } else
- ednew.addType().setCode(t);
- }
- }
- }
-
- return new PropertyWithType(var.property.path+"."+element, pc, new Property(worker, ednew, sd), type);
- }
- }
-
-
-
- private String checkType(String t, Property pvb, List profiles) throws FHIRException {
- if (pvb.getDefinition().getType().size() == 1 && isCompatibleType(t, pvb.getDefinition().getType().get(0).getCode()) && profilesMatch(profiles, pvb.getDefinition().getType().get(0).getProfile()))
- return null;
- for (TypeRefComponent tr : pvb.getDefinition().getType()) {
- if (isCompatibleType(t, tr.getCode()))
- return tr.getCode(); // note what is returned - the base type, not the inferred mapping type
- }
- throw new FHIRException("The type "+t+" is not compatible with the allowed types for "+pvb.getDefinition().getPath()+" ("+pvb.getDefinition().typeSummary()+")");
- }
-
- private boolean profilesMatch(List profiles, String profile) {
- return profiles == null || profiles.size() == 0 || (profiles.size() == 1 && profiles.get(0).equals(profile));
- }
-
- private boolean isCompatibleType(String t, String code) {
- if (t.equals(code))
- return true;
- if (t.equals("string")) {
- StructureDefinition sd = worker.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/"+code);
- if (sd != null && sd.getBaseDefinition().equals("http://hl7.org/fhir/StructureDefinition/string"))
- return true;
- }
- return false;
- }
-
- private TypeDetails analyseTransform(TransformContext context, StructureMap map, StructureMapGroupRuleTargetComponent tgt, VariableForProfiling var, VariablesForProfiling vars) throws FHIRException {
- switch (tgt.getTransform()) {
- case CREATE :
- String p = getParamString(vars, tgt.getParameter().get(0));
- return new TypeDetails(CollectionStatus.SINGLETON, p);
- case COPY :
- return getParam(vars, tgt.getParameter().get(0));
- case EVALUATE :
- ExpressionNode expr = (ExpressionNode) tgt.getUserData(MAP_EXPRESSION);
- if (expr == null) {
- expr = fpe.parse(getParamString(vars, tgt.getParameter().get(tgt.getParameter().size()-1)));
- tgt.setUserData(MAP_WHERE_EXPRESSION, expr);
- }
- return fpe.check(vars, null, expr);
-
-////case TRUNCATE :
-//// String src = getParamString(vars, tgt.getParameter().get(0));
-//// String len = getParamString(vars, tgt.getParameter().get(1));
-//// if (Utilities.isInteger(len)) {
-//// int l = Integer.parseInt(len);
-//// if (src.length() > l)
-//// src = src.substring(0, l);
-//// }
-//// return new StringType(src);
-////case ESCAPE :
-//// throw new Error("Transform "+tgt.getTransform().toCode()+" not supported yet");
-////case CAST :
-//// throw new Error("Transform "+tgt.getTransform().toCode()+" not supported yet");
-////case APPEND :
-//// throw new Error("Transform "+tgt.getTransform().toCode()+" not supported yet");
- case TRANSLATE :
- return new TypeDetails(CollectionStatus.SINGLETON, "CodeableConcept");
- case CC:
- ProfiledType res = new ProfiledType("CodeableConcept");
- if (tgt.getParameter().size() >= 2 && isParamId(vars, tgt.getParameter().get(1))) {
- TypeDetails td = vars.get(null, getParamId(vars, tgt.getParameter().get(1))).property.types;
- if (td != null && td.hasBinding())
- // todo: do we need to check that there's no implicit translation her? I don't think we do...
- res.addBinding(td.getBinding());
- }
- return new TypeDetails(CollectionStatus.SINGLETON, res);
- case C:
- return new TypeDetails(CollectionStatus.SINGLETON, "Coding");
- case QTY:
- return new TypeDetails(CollectionStatus.SINGLETON, "Quantity");
- case REFERENCE :
- VariableForProfiling vrs = vars.get(VariableMode.OUTPUT, getParamId(vars, tgt.getParameterFirstRep()));
- if (vrs == null)
- throw new FHIRException("Unable to resolve variable \""+getParamId(vars, tgt.getParameterFirstRep())+"\"");
- String profile = vrs.property.getProfileProperty().getStructure().getUrl();
- TypeDetails td = new TypeDetails(CollectionStatus.SINGLETON);
- td.addType("Reference", profile);
- return td;
-////case DATEOP :
-//// throw new Error("Transform "+tgt.getTransform().toCode()+" not supported yet");
-////case UUID :
-//// return new IdType(UUID.randomUUID().toString());
-////case POINTER :
-//// Base b = getParam(vars, tgt.getParameter().get(0));
-//// if (b instanceof Resource)
-//// return new UriType("urn:uuid:"+((Resource) b).getId());
-//// else
-//// throw new FHIRException("Transform engine cannot point at an element of type "+b.fhirType());
- default:
- throw new Error("Transform Unknown or not handled yet: "+tgt.getTransform().toCode());
- }
- }
- private String getParamString(VariablesForProfiling vars, StructureMapGroupRuleTargetParameterComponent parameter) {
- Type p = parameter.getValue();
- if (p == null || p instanceof IdType)
- return null;
- if (!p.hasPrimitiveValue())
- return null;
- return p.primitiveValue();
- }
-
- private String getParamId(VariablesForProfiling vars, StructureMapGroupRuleTargetParameterComponent parameter) {
- Type p = parameter.getValue();
- if (p == null || !(p instanceof IdType))
- return null;
- return p.primitiveValue();
- }
-
- private boolean isParamId(VariablesForProfiling vars, StructureMapGroupRuleTargetParameterComponent parameter) {
- Type p = parameter.getValue();
- if (p == null || !(p instanceof IdType))
- return false;
- return vars.get(null, p.primitiveValue()) != null;
- }
-
- private TypeDetails getParam(VariablesForProfiling vars, StructureMapGroupRuleTargetParameterComponent parameter) throws DefinitionException {
- Type p = parameter.getValue();
- if (!(p instanceof IdType))
- return new TypeDetails(CollectionStatus.SINGLETON, "http://hl7.org/fhir/StructureDefinition/"+p.fhirType());
- else {
- String n = ((IdType) p).asStringValue();
- VariableForProfiling b = vars.get(VariableMode.INPUT, n);
- if (b == null)
- b = vars.get(VariableMode.OUTPUT, n);
- if (b == null)
- throw new DefinitionException("Variable "+n+" not found ("+vars.summary()+")");
- return b.getProperty().getTypes();
- }
- }
-
- private PropertyWithType createProfile(StructureMap map, List profiles, PropertyWithType prop, String sliceName, Base ctxt) throws DefinitionException {
- if (prop.getBaseProperty().getDefinition().getPath().contains("."))
- throw new DefinitionException("Unable to process entry point");
-
- String type = prop.getBaseProperty().getDefinition().getPath();
- String suffix = "";
- if (ids.containsKey(type)) {
- int id = ids.get(type);
- id++;
- ids.put(type, id);
- suffix = "-"+Integer.toString(id);
- } else
- ids.put(type, 0);
-
- StructureDefinition profile = new StructureDefinition();
- profiles.add(profile);
- profile.setDerivation(TypeDerivationRule.CONSTRAINT);
- profile.setType(type);
- profile.setBaseDefinition(prop.getBaseProperty().getStructure().getUrl());
- profile.setName("Profile for "+profile.getType()+" for "+sliceName);
- profile.setUrl(map.getUrl().replace("StructureMap", "StructureDefinition")+"-"+profile.getType()+suffix);
- ctxt.setUserData("profile", profile.getUrl()); // then we can easily assign this profile url for validation later when we actually transform
- profile.setId(map.getId()+"-"+profile.getType()+suffix);
- profile.setStatus(map.getStatus());
- profile.setExperimental(map.getExperimental());
- profile.setDescription("Generated automatically from the mapping by the Java Reference Implementation");
- for (ContactDetail c : map.getContact()) {
- ContactDetail p = profile.addContact();
- p.setName(c.getName());
- for (ContactPoint cc : c.getTelecom())
- p.addTelecom(cc);
- }
- profile.setDate(map.getDate());
- profile.setCopyright(map.getCopyright());
- profile.setFhirVersion(Constants.VERSION);
- profile.setKind(prop.getBaseProperty().getStructure().getKind());
- profile.setAbstract(false);
- ElementDefinition ed = profile.getDifferential().addElement();
- ed.setPath(profile.getType());
- prop.profileProperty = new Property(worker, ed, profile);
- return prop;
- }
-
- private PropertyWithType resolveType(StructureMap map, String type, StructureMapInputMode mode) throws Exception {
- for (StructureMapStructureComponent imp : map.getStructure()) {
- if ((imp.getMode() == StructureMapModelMode.SOURCE && mode == StructureMapInputMode.SOURCE) ||
- (imp.getMode() == StructureMapModelMode.TARGET && mode == StructureMapInputMode.TARGET)) {
- StructureDefinition sd = worker.fetchResource(StructureDefinition.class, imp.getUrl());
- if (sd == null)
- throw new Exception("Import "+imp.getUrl()+" cannot be resolved");
- if (sd.getId().equals(type)) {
- return new PropertyWithType(sd.getType(), new Property(worker, sd.getSnapshot().getElement().get(0), sd), null, new TypeDetails(CollectionStatus.SINGLETON, sd.getUrl()));
- }
- }
- }
- throw new Exception("Unable to find structure definition for "+type+" in imports");
- }
-
-
- public StructureMap generateMapFromMappings(StructureDefinition sd) throws IOException, FHIRException {
- String id = getLogicalMappingId(sd);
- if (id == null)
- return null;
- String prefix = ToolingExtensions.readStringExtension(sd, ToolingExtensions.EXT_MAPPING_PREFIX);
- String suffix = ToolingExtensions.readStringExtension(sd, ToolingExtensions.EXT_MAPPING_SUFFIX);
- if (prefix == null || suffix == null)
- return null;
- // we build this by text. Any element that has a mapping, we put it's mappings inside it....
- StringBuilder b = new StringBuilder();
- b.append(prefix);
-
- ElementDefinition root = sd.getSnapshot().getElementFirstRep();
- String m = getMapping(root, id);
- if (m != null)
- b.append(m+"\r\n");
- addChildMappings(b, id, "", sd, root, false);
- b.append("\r\n");
- b.append(suffix);
- b.append("\r\n");
- StructureMap map = parse(b.toString());
- map.setId(tail(map.getUrl()));
- if (!map.hasStatus())
- map.setStatus(PublicationStatus.DRAFT);
- map.getText().setStatus(NarrativeStatus.GENERATED);
- map.getText().setDiv(new XhtmlNode(NodeType.Element, "div"));
- map.getText().getDiv().addTag("pre").addText(render(map));
- return map;
- }
-
-
- private String tail(String url) {
- return url.substring(url.lastIndexOf("/")+1);
- }
-
-
- private void addChildMappings(StringBuilder b, String id, String indent, StructureDefinition sd, ElementDefinition ed, boolean inner) throws DefinitionException {
- boolean first = true;
- List children = ProfileUtilities.getChildMap(sd, ed);
- for (ElementDefinition child : children) {
- if (first && inner) {
- b.append(" then {\r\n");
- first = false;
- }
- String map = getMapping(child, id);
- if (map != null) {
- b.append(indent+" "+child.getPath()+": "+map);
- addChildMappings(b, id, indent+" ", sd, child, true);
- b.append("\r\n");
- }
- }
- if (!first && inner)
- b.append(indent+"}");
-
- }
-
-
- private String getMapping(ElementDefinition ed, String id) {
- for (ElementDefinitionMappingComponent map : ed.getMapping())
- if (id.equals(map.getIdentity()))
- return map.getMap();
- return null;
- }
-
-
- private String getLogicalMappingId(StructureDefinition sd) {
- String id = null;
- for (StructureDefinitionMappingComponent map : sd.getMapping()) {
- if ("http://hl7.org/fhir/logical".equals(map.getUri()))
- return map.getIdentity();
- }
- return null;
- }
-
-}
+package org.hl7.fhir.dstu3.utils;
+
+// remember group resolution
+// trace - account for which wasn't transformed in the source
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+
+import org.hl7.fhir.dstu3.conformance.ProfileUtilities;
+import org.hl7.fhir.dstu3.conformance.ProfileUtilities.ProfileKnowledgeProvider;
+import org.hl7.fhir.dstu3.context.IWorkerContext;
+import org.hl7.fhir.dstu3.context.IWorkerContext.ValidationResult;
+import org.hl7.fhir.dstu3.elementmodel.Element;
+import org.hl7.fhir.dstu3.elementmodel.Property;
+import org.hl7.fhir.dstu3.model.Base;
+import org.hl7.fhir.dstu3.model.BooleanType;
+import org.hl7.fhir.dstu3.model.CodeType;
+import org.hl7.fhir.dstu3.model.CodeableConcept;
+import org.hl7.fhir.dstu3.model.Coding;
+import org.hl7.fhir.dstu3.model.ConceptMap;
+import org.hl7.fhir.dstu3.model.ConceptMap.ConceptMapGroupComponent;
+import org.hl7.fhir.dstu3.model.ConceptMap.ConceptMapGroupUnmappedMode;
+import org.hl7.fhir.dstu3.model.ConceptMap.SourceElementComponent;
+import org.hl7.fhir.dstu3.model.ConceptMap.TargetElementComponent;
+import org.hl7.fhir.dstu3.model.Constants;
+import org.hl7.fhir.dstu3.model.ContactDetail;
+import org.hl7.fhir.dstu3.model.ContactPoint;
+import org.hl7.fhir.dstu3.model.DecimalType;
+import org.hl7.fhir.dstu3.model.ElementDefinition;
+import org.hl7.fhir.dstu3.model.ElementDefinition.ElementDefinitionMappingComponent;
+import org.hl7.fhir.dstu3.model.ElementDefinition.TypeRefComponent;
+import org.hl7.fhir.dstu3.model.Enumeration;
+import org.hl7.fhir.dstu3.model.Enumerations.ConceptMapEquivalence;
+import org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus;
+import org.hl7.fhir.dstu3.model.ExpressionNode;
+import org.hl7.fhir.dstu3.model.ExpressionNode.CollectionStatus;
+import org.hl7.fhir.dstu3.model.IdType;
+import org.hl7.fhir.dstu3.model.IntegerType;
+import org.hl7.fhir.dstu3.model.Narrative.NarrativeStatus;
+import org.hl7.fhir.dstu3.model.PrimitiveType;
+import org.hl7.fhir.dstu3.model.Reference;
+import org.hl7.fhir.dstu3.model.Resource;
+import org.hl7.fhir.dstu3.model.ResourceFactory;
+import org.hl7.fhir.dstu3.model.StringType;
+import org.hl7.fhir.dstu3.model.StructureDefinition;
+import org.hl7.fhir.dstu3.model.StructureDefinition.StructureDefinitionMappingComponent;
+import org.hl7.fhir.dstu3.model.StructureDefinition.TypeDerivationRule;
+import org.hl7.fhir.dstu3.model.StructureMap;
+import org.hl7.fhir.dstu3.model.StructureMap.StructureMapContextType;
+import org.hl7.fhir.dstu3.model.StructureMap.StructureMapGroupComponent;
+import org.hl7.fhir.dstu3.model.StructureMap.StructureMapGroupInputComponent;
+import org.hl7.fhir.dstu3.model.StructureMap.StructureMapGroupRuleComponent;
+import org.hl7.fhir.dstu3.model.StructureMap.StructureMapGroupRuleDependentComponent;
+import org.hl7.fhir.dstu3.model.StructureMap.StructureMapGroupRuleSourceComponent;
+import org.hl7.fhir.dstu3.model.StructureMap.StructureMapGroupRuleTargetComponent;
+import org.hl7.fhir.dstu3.model.StructureMap.StructureMapGroupRuleTargetParameterComponent;
+import org.hl7.fhir.dstu3.model.StructureMap.StructureMapGroupTypeMode;
+import org.hl7.fhir.dstu3.model.StructureMap.StructureMapInputMode;
+import org.hl7.fhir.dstu3.model.StructureMap.StructureMapSourceListMode;
+import org.hl7.fhir.dstu3.model.StructureMap.StructureMapTargetListMode;
+import org.hl7.fhir.dstu3.model.StructureMap.StructureMapModelMode;
+import org.hl7.fhir.dstu3.model.StructureMap.StructureMapStructureComponent;
+import org.hl7.fhir.dstu3.model.StructureMap.StructureMapTransform;
+import org.hl7.fhir.dstu3.model.Type;
+import org.hl7.fhir.dstu3.model.TypeDetails;
+import org.hl7.fhir.dstu3.model.TypeDetails.ProfiledType;
+import org.hl7.fhir.dstu3.model.UriType;
+import org.hl7.fhir.dstu3.model.ValueSet;
+import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionContainsComponent;
+import org.hl7.fhir.dstu3.terminologies.ValueSetExpander.ValueSetExpansionOutcome;
+import org.hl7.fhir.dstu3.utils.FHIRLexer.FHIRLexerException;
+import org.hl7.fhir.dstu3.utils.FHIRPathEngine.IEvaluationContext;
+import org.hl7.fhir.exceptions.DefinitionException;
+import org.hl7.fhir.exceptions.FHIRException;
+import org.hl7.fhir.exceptions.FHIRFormatError;
+import org.hl7.fhir.exceptions.PathEngineException;
+import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
+import org.hl7.fhir.utilities.TextFile;
+import org.hl7.fhir.utilities.Utilities;
+import org.hl7.fhir.utilities.xhtml.NodeType;
+import org.hl7.fhir.utilities.xhtml.XhtmlNode;
+
+/**
+ * Services in this class:
+ *
+ * string render(map) - take a structure and convert it to text
+ * map parse(text) - take a text representation and parse it
+ * getTargetType(map) - return the definition for the type to create to hand in
+ * transform(appInfo, source, map, target) - transform from source to target following the map
+ * analyse(appInfo, map) - generate profiles and other analysis artifacts for the targets of the transform
+ * map generateMapFromMappings(StructureDefinition) - build a mapping from a structure definition with loigcal mappings
+ *
+ * @author Grahame Grieve
+ *
+ */
+public class StructureMapUtilities {
+
+ public class ResolvedGroup {
+ public StructureMapGroupComponent target;
+ public StructureMap targetMap;
+ }
+ public static final String MAP_WHERE_CHECK = "map.where.check";
+ public static final String MAP_WHERE_EXPRESSION = "map.where.expression";
+ public static final String MAP_SEARCH_EXPRESSION = "map.search.expression";
+ public static final String MAP_EXPRESSION = "map.transform.expression";
+ private static final boolean RENDER_MULTIPLE_TARGETS_ONELINE = true;
+ private static final String AUTO_VAR_NAME = "vvv";
+
+ public interface ITransformerServices {
+ // public boolean validateByValueSet(Coding code, String valuesetId);
+ public void log(String message); // log internal progress
+ public Base createType(Object appInfo, String name) throws FHIRException;
+ public Base createResource(Object appInfo, Base res); // an already created resource is provided; this is to identify/store it
+ public Coding translate(Object appInfo, Coding source, String conceptMapUrl) throws FHIRException;
+ // public Coding translate(Coding code)
+ // ValueSet validation operation
+ // Translation operation
+ // Lookup another tree of data
+ // Create an instance tree
+ // Return the correct string format to refer to a tree (input or output)
+ public Base resolveReference(Object appContext, String url);
+ public List performSearch(Object appContext, String url);
+ }
+
+ private class FFHIRPathHostServices implements IEvaluationContext{
+
+ public Base resolveConstant(Object appContext, String name) throws PathEngineException {
+ Variables vars = (Variables) appContext;
+ Base res = vars.get(VariableMode.INPUT, name);
+ if (res == null)
+ res = vars.get(VariableMode.OUTPUT, name);
+ return res;
+ }
+
+ @Override
+ public TypeDetails resolveConstantType(Object appContext, String name) throws PathEngineException {
+ if (!(appContext instanceof VariablesForProfiling))
+ throw new Error("Internal Logic Error (wrong type '"+appContext.getClass().getName()+"' in resolveConstantType)");
+ VariablesForProfiling vars = (VariablesForProfiling) appContext;
+ VariableForProfiling v = vars.get(null, name);
+ if (v == null)
+ throw new PathEngineException("Unknown variable '"+name+"' from variables "+vars.summary());
+ return v.property.types;
+ }
+
+ @Override
+ public boolean log(String argument, List focus) {
+ throw new Error("Not Implemented Yet");
+ }
+
+ @Override
+ public FunctionDetails resolveFunction(String functionName) {
+ return null; // throw new Error("Not Implemented Yet");
+ }
+
+ @Override
+ public TypeDetails checkFunction(Object appContext, String functionName, List parameters) throws PathEngineException {
+ throw new Error("Not Implemented Yet");
+ }
+
+ @Override
+ public List executeFunction(Object appContext, String functionName, List> parameters) {
+ throw new Error("Not Implemented Yet");
+ }
+
+ @Override
+ public Base resolveReference(Object appContext, String url) {
+ if (services == null)
+ return null;
+ return services.resolveReference(appContext, url);
+ }
+
+ }
+ private IWorkerContext worker;
+ private FHIRPathEngine fpe;
+ private Map library;
+ private ITransformerServices services;
+ private ProfileKnowledgeProvider pkp;
+ private Map ids = new HashMap();
+
+ public StructureMapUtilities(IWorkerContext worker, Map library, ITransformerServices services, ProfileKnowledgeProvider pkp) {
+ super();
+ this.worker = worker;
+ this.library = library;
+ this.services = services;
+ this.pkp = pkp;
+ fpe = new FHIRPathEngine(worker);
+ fpe.setHostServices(new FFHIRPathHostServices());
+ }
+
+ public StructureMapUtilities(IWorkerContext worker, Map library, ITransformerServices services) {
+ super();
+ this.worker = worker;
+ this.library = library;
+ this.services = services;
+ fpe = new FHIRPathEngine(worker);
+ fpe.setHostServices(new FFHIRPathHostServices());
+ }
+
+ public StructureMapUtilities(IWorkerContext worker, Map library) {
+ super();
+ this.worker = worker;
+ this.library = library;
+ fpe = new FHIRPathEngine(worker);
+ fpe.setHostServices(new FFHIRPathHostServices());
+ }
+
+ public StructureMapUtilities(IWorkerContext worker) {
+ super();
+ this.worker = worker;
+ fpe = new FHIRPathEngine(worker);
+ fpe.setHostServices(new FFHIRPathHostServices());
+ }
+
+ public StructureMapUtilities(IWorkerContext worker, ITransformerServices services) {
+ super();
+ this.worker = worker;
+ this.library = new HashMap();
+ for (org.hl7.fhir.dstu3.model.MetadataResource bc : worker.allConformanceResources()) {
+ if (bc instanceof StructureMap)
+ library.put(bc.getUrl(), (StructureMap) bc);
+ }
+ this.services = services;
+ fpe = new FHIRPathEngine(worker);
+ fpe.setHostServices(new FFHIRPathHostServices());
+ }
+
+ public static String render(StructureMap map) {
+ StringBuilder b = new StringBuilder();
+ b.append("map \"");
+ b.append(map.getUrl());
+ b.append("\" = \"");
+ b.append(Utilities.escapeJava(map.getName()));
+ b.append("\"\r\n\r\n");
+
+ renderConceptMaps(b, map);
+ renderUses(b, map);
+ renderImports(b, map);
+ for (StructureMapGroupComponent g : map.getGroup())
+ renderGroup(b, g);
+ return b.toString();
+ }
+
+ private static void renderConceptMaps(StringBuilder b, StructureMap map) {
+ for (Resource r : map.getContained()) {
+ if (r instanceof ConceptMap) {
+ produceConceptMap(b, (ConceptMap) r);
+ }
+ }
+ }
+
+ private static void produceConceptMap(StringBuilder b, ConceptMap cm) {
+ b.append("conceptmap \"");
+ b.append(cm.getId());
+ b.append("\" {\r\n");
+ Map prefixesSrc = new HashMap();
+ Map prefixesTgt = new HashMap();
+ char prefix = 's';
+ for (ConceptMapGroupComponent cg : cm.getGroup()) {
+ if (!prefixesSrc.containsKey(cg.getSource())) {
+ prefixesSrc.put(cg.getSource(), String.valueOf(prefix));
+ b.append(" prefix ");
+ b.append(prefix);
+ b.append(" = \"");
+ b.append(cg.getSource());
+ b.append("\"\r\n");
+ prefix++;
+ }
+ if (!prefixesTgt.containsKey(cg.getTarget())) {
+ prefixesTgt.put(cg.getTarget(), String.valueOf(prefix));
+ b.append(" prefix ");
+ b.append(prefix);
+ b.append(" = \"");
+ b.append(cg.getTarget());
+ b.append("\"\r\n");
+ prefix++;
+ }
+ }
+ b.append("\r\n");
+ for (ConceptMapGroupComponent cg : cm.getGroup()) {
+ if (cg.hasUnmapped()) {
+ b.append(" unmapped for ");
+ b.append(prefix);
+ b.append(" = ");
+ b.append(cg.getUnmapped().getMode());
+ b.append("\r\n");
+ }
+ }
+
+ for (ConceptMapGroupComponent cg : cm.getGroup()) {
+ for (SourceElementComponent ce : cg.getElement()) {
+ b.append(" ");
+ b.append(prefixesSrc.get(cg.getSource()));
+ b.append(":");
+ b.append(ce.getCode());
+ b.append(" ");
+ b.append(getChar(ce.getTargetFirstRep().getEquivalence()));
+ b.append(" ");
+ b.append(prefixesTgt.get(cg.getTarget()));
+ b.append(":");
+ b.append(ce.getTargetFirstRep().getCode());
+ b.append("\r\n");
+ }
+ }
+ b.append("}\r\n\r\n");
+ }
+
+ private static Object getChar(ConceptMapEquivalence equivalence) {
+ switch (equivalence) {
+ case RELATEDTO: return "-";
+ case EQUAL: return "=";
+ case EQUIVALENT: return "==";
+ case DISJOINT: return "!=";
+ case UNMATCHED: return "--";
+ case WIDER: return "<=";
+ case SUBSUMES: return "<-";
+ case NARROWER: return ">=";
+ case SPECIALIZES: return ">-";
+ case INEXACT: return "~";
+ default: return "??";
+ }
+ }
+
+ private static void renderUses(StringBuilder b, StructureMap map) {
+ for (StructureMapStructureComponent s : map.getStructure()) {
+ b.append("uses \"");
+ b.append(s.getUrl());
+ b.append("\" ");
+ if (s.hasAlias()) {
+ b.append("alias ");
+ b.append(s.getAlias());
+ b.append(" ");
+ }
+ b.append("as ");
+ b.append(s.getMode().toCode());
+ b.append("\r\n");
+ renderDoco(b, s.getDocumentation());
+ }
+ if (map.hasStructure())
+ b.append("\r\n");
+ }
+
+ private static void renderImports(StringBuilder b, StructureMap map) {
+ for (UriType s : map.getImport()) {
+ b.append("imports \"");
+ b.append(s.getValue());
+ b.append("\"\r\n");
+ }
+ if (map.hasImport())
+ b.append("\r\n");
+ }
+
+ public static String groupToString(StructureMapGroupComponent g) {
+ StringBuilder b = new StringBuilder();
+ renderGroup(b, g);
+ return b.toString();
+ }
+
+ private static void renderGroup(StringBuilder b, StructureMapGroupComponent g) {
+ b.append("group ");
+ switch (g.getTypeMode()) {
+ case TYPES:
+ b.append("for types");
+ break;
+ case TYPEANDTYPES:
+ b.append("for type+types ");
+ break;
+ case NONE:
+ case NULL:
+ break;
+ }
+ b.append("for types ");
+ b.append(g.getName());
+ if (g.hasExtends()) {
+ b.append(" extends ");
+ b.append(g.getExtends());
+ }
+ if (g.hasDocumentation())
+ renderDoco(b, g.getDocumentation());
+ b.append("\r\n");
+ for (StructureMapGroupInputComponent gi : g.getInput()) {
+ b.append(" input ");
+ b.append(gi.getName());
+ if (gi.hasType()) {
+ b.append(" : ");
+ b.append(gi.getType());
+ }
+ b.append(" as ");
+ b.append(gi.getMode().toCode());
+ b.append("\r\n");
+ }
+ if (g.hasInput())
+ b.append("\r\n");
+ for (StructureMapGroupRuleComponent r : g.getRule()) {
+ renderRule(b, r, 2);
+ }
+ b.append("\r\nendgroup\r\n");
+ }
+
+ public static String ruleToString(StructureMapGroupRuleComponent r) {
+ StringBuilder b = new StringBuilder();
+ renderRule(b, r, 0);
+ return b.toString();
+ }
+
+ private static void renderRule(StringBuilder b, StructureMapGroupRuleComponent r, int indent) {
+ for (int i = 0; i < indent; i++)
+ b.append(' ');
+ b.append(r.getName());
+ b.append(" : for ");
+ boolean canBeAbbreviated = checkisSimple(r);
+
+ boolean first = true;
+ for (StructureMapGroupRuleSourceComponent rs : r.getSource()) {
+ if (first)
+ first = false;
+ else
+ b.append(", ");
+ renderSource(b, rs, canBeAbbreviated);
+ }
+ if (r.getTarget().size() > 1) {
+ b.append(" make ");
+ first = true;
+ for (StructureMapGroupRuleTargetComponent rt : r.getTarget()) {
+ if (first)
+ first = false;
+ else
+ b.append(", ");
+ if (RENDER_MULTIPLE_TARGETS_ONELINE)
+ b.append(' ');
+ else {
+ b.append("\r\n");
+ for (int i = 0; i < indent+4; i++)
+ b.append(' ');
+ }
+ renderTarget(b, rt, false);
+ }
+ } else if (r.hasTarget()) {
+ b.append(" make ");
+ renderTarget(b, r.getTarget().get(0), canBeAbbreviated);
+ }
+ if (!canBeAbbreviated) {
+ if (r.hasRule()) {
+ b.append(" then {\r\n");
+ renderDoco(b, r.getDocumentation());
+ for (StructureMapGroupRuleComponent ir : r.getRule()) {
+ renderRule(b, ir, indent+2);
+ }
+ for (int i = 0; i < indent; i++)
+ b.append(' ');
+ b.append("}\r\n");
+ } else {
+ if (r.hasDependent()) {
+ b.append(" then ");
+ first = true;
+ for (StructureMapGroupRuleDependentComponent rd : r.getDependent()) {
+ if (first)
+ first = false;
+ else
+ b.append(", ");
+ b.append(rd.getName());
+ b.append("(");
+ boolean ifirst = true;
+ for (StringType rdp : rd.getVariable()) {
+ if (ifirst)
+ ifirst = false;
+ else
+ b.append(", ");
+ b.append(rdp.asStringValue());
+ }
+ b.append(")");
+ }
+ }
+ }
+ }
+ renderDoco(b, r.getDocumentation());
+ b.append("\r\n");
+ }
+
+ private static boolean checkisSimple(StructureMapGroupRuleComponent r) {
+ return
+ (r.getSource().size() == 1 && r.getSourceFirstRep().hasElement() && r.getSourceFirstRep().hasVariable()) &&
+ (r.getTarget().size() == 1 && r.getTargetFirstRep().hasVariable() && (r.getTargetFirstRep().getTransform() == null || r.getTargetFirstRep().getTransform() == StructureMapTransform.CREATE) && r.getTargetFirstRep().getParameter().size() == 0) &&
+ (r.getDependent().size() == 0);
+ }
+
+ public static String sourceToString(StructureMapGroupRuleSourceComponent r) {
+ StringBuilder b = new StringBuilder();
+ renderSource(b, r, false);
+ return b.toString();
+ }
+
+ private static void renderSource(StringBuilder b, StructureMapGroupRuleSourceComponent rs, boolean abbreviate) {
+ b.append(rs.getContext());
+ if (rs.getContext().equals("@search")) {
+ b.append('(');
+ b.append(rs.getElement());
+ b.append(')');
+ } else if (rs.hasElement()) {
+ b.append('.');
+ b.append(rs.getElement());
+ }
+ if (rs.hasType()) {
+ b.append(" : ");
+ b.append(rs.getType());
+ if (rs.hasMin()) {
+ b.append(" ");
+ b.append(rs.getMin());
+ b.append("..");
+ b.append(rs.getMax());
+ }
+ }
+
+ if (rs.hasListMode()) {
+ b.append(" ");
+ b.append(rs.getListMode().toCode());
+ }
+ if (rs.hasDefaultValue()) {
+ b.append(" default ");
+ assert rs.getDefaultValue() instanceof StringType;
+ b.append("\""+Utilities.escapeJson(((StringType) rs.getDefaultValue()).asStringValue())+"\"");
+ }
+ if (!abbreviate && rs.hasVariable()) {
+ b.append(" as ");
+ b.append(rs.getVariable());
+ }
+ if (rs.hasCondition()) {
+ b.append(" where ");
+ b.append(rs.getCondition());
+ }
+ if (rs.hasCheck()) {
+ b.append(" check ");
+ b.append(rs.getCheck());
+ }
+ }
+
+ public static String targetToString(StructureMapGroupRuleTargetComponent rt) {
+ StringBuilder b = new StringBuilder();
+ renderTarget(b, rt, false);
+ return b.toString();
+ }
+
+ private static void renderTarget(StringBuilder b, StructureMapGroupRuleTargetComponent rt, boolean abbreviate) {
+ if (rt.hasContext()) {
+ if (rt.getContextType() == StructureMapContextType.TYPE)
+ b.append("@");
+ b.append(rt.getContext());
+ if (rt.hasElement()) {
+ b.append('.');
+ b.append(rt.getElement());
+ }
+ }
+ if (!abbreviate && rt.hasTransform()) {
+ if (rt.hasContext())
+ b.append(" = ");
+ if (rt.getTransform() == StructureMapTransform.COPY && rt.getParameter().size() == 1) {
+ renderTransformParam(b, rt.getParameter().get(0));
+ } else if (rt.getTransform() == StructureMapTransform.EVALUATE && rt.getParameter().size() == 1) {
+ b.append("(");
+ b.append("\""+((StringType) rt.getParameter().get(0).getValue()).asStringValue()+"\"");
+ b.append(")");
+ } else if (rt.getTransform() == StructureMapTransform.EVALUATE && rt.getParameter().size() == 2) {
+ b.append(rt.getTransform().toCode());
+ b.append("(");
+ b.append(((IdType) rt.getParameter().get(0).getValue()).asStringValue());
+ b.append("\""+((StringType) rt.getParameter().get(1).getValue()).asStringValue()+"\"");
+ b.append(")");
+ } else {
+ b.append(rt.getTransform().toCode());
+ b.append("(");
+ boolean first = true;
+ for (StructureMapGroupRuleTargetParameterComponent rtp : rt.getParameter()) {
+ if (first)
+ first = false;
+ else
+ b.append(", ");
+ renderTransformParam(b, rtp);
+ }
+ b.append(")");
+ }
+ }
+ if (!abbreviate && rt.hasVariable()) {
+ b.append(" as ");
+ b.append(rt.getVariable());
+ }
+ for (Enumeration lm : rt.getListMode()) {
+ b.append(" ");
+ b.append(lm.getValue().toCode());
+ if (lm.getValue() == StructureMapTargetListMode.SHARE) {
+ b.append(" ");
+ b.append(rt.getListRuleId());
+ }
+ }
+ }
+
+ public static String paramToString(StructureMapGroupRuleTargetParameterComponent rtp) {
+ StringBuilder b = new StringBuilder();
+ renderTransformParam(b, rtp);
+ return b.toString();
+ }
+
+ private static void renderTransformParam(StringBuilder b, StructureMapGroupRuleTargetParameterComponent rtp) {
+ try {
+ if (rtp.hasValueBooleanType())
+ b.append(rtp.getValueBooleanType().asStringValue());
+ else if (rtp.hasValueDecimalType())
+ b.append(rtp.getValueDecimalType().asStringValue());
+ else if (rtp.hasValueIdType())
+ b.append(rtp.getValueIdType().asStringValue());
+ else if (rtp.hasValueDecimalType())
+ b.append(rtp.getValueDecimalType().asStringValue());
+ else if (rtp.hasValueIntegerType())
+ b.append(rtp.getValueIntegerType().asStringValue());
+ else
+ b.append("\""+Utilities.escapeJava(rtp.getValueStringType().asStringValue())+"\"");
+ } catch (FHIRException e) {
+ e.printStackTrace();
+ b.append("error!");
+ }
+ }
+
+ private static void renderDoco(StringBuilder b, String doco) {
+ if (Utilities.noString(doco))
+ return;
+ b.append(" // ");
+ b.append(doco.replace("\r\n", " ").replace("\r", " ").replace("\n", " "));
+ }
+
+ public StructureMap parse(String text) throws FHIRException {
+ FHIRLexer lexer = new FHIRLexer(text);
+ if (lexer.done())
+ throw lexer.error("Map Input cannot be empty");
+ lexer.skipComments();
+ lexer.token("map");
+ StructureMap result = new StructureMap();
+ result.setUrl(lexer.readConstant("url"));
+ lexer.token("=");
+ result.setName(lexer.readConstant("name"));
+ lexer.skipComments();
+
+ while (lexer.hasToken("conceptmap"))
+ parseConceptMap(result, lexer);
+
+ while (lexer.hasToken("uses"))
+ parseUses(result, lexer);
+ while (lexer.hasToken("imports"))
+ parseImports(result, lexer);
+
+ parseGroup(result, lexer);
+
+ while (!lexer.done()) {
+ parseGroup(result, lexer);
+ }
+
+ return result;
+ }
+
+ private void parseConceptMap(StructureMap result, FHIRLexer lexer) throws FHIRLexerException {
+ lexer.token("conceptmap");
+ ConceptMap map = new ConceptMap();
+ String id = lexer.readConstant("map id");
+ if (!id.startsWith("#"))
+ lexer.error("Concept Map identifier must start with #");
+ map.setId(id);
+ map.setStatus(PublicationStatus.DRAFT); // todo: how to add this to the text format
+ result.getContained().add(map);
+ lexer.token("{");
+ lexer.skipComments();
+ // lexer.token("source");
+ // map.setSource(new UriType(lexer.readConstant("source")));
+ // lexer.token("target");
+ // map.setSource(new UriType(lexer.readConstant("target")));
+ Map prefixes = new HashMap();
+ while (lexer.hasToken("prefix")) {
+ lexer.token("prefix");
+ String n = lexer.take();
+ lexer.token("=");
+ String v = lexer.readConstant("prefix url");
+ prefixes.put(n, v);
+ }
+ while (lexer.hasToken("unmapped")) {
+ lexer.token("unmapped");
+ lexer.token("for");
+ String n = readPrefix(prefixes, lexer);
+ ConceptMapGroupComponent g = getGroup(map, n, null);
+ lexer.token("=");
+ String v = lexer.take();
+ if (v.equals("provided")) {
+ g.getUnmapped().setMode(ConceptMapGroupUnmappedMode.PROVIDED);
+ } else
+ lexer.error("Only unmapped mode PROVIDED is supported at this time");
+ }
+ while (!lexer.hasToken("}")) {
+ String srcs = readPrefix(prefixes, lexer);
+ lexer.token(":");
+ String sc = lexer.getCurrent().startsWith("\"") ? lexer.readConstant("code") : lexer.take();
+ ConceptMapEquivalence eq = readEquivalence(lexer);
+ String tgts = (eq != ConceptMapEquivalence.UNMATCHED) ? readPrefix(prefixes, lexer) : "";
+ ConceptMapGroupComponent g = getGroup(map, srcs, tgts);
+ SourceElementComponent e = g.addElement();
+ e.setCode(sc);
+ if (e.getCode().startsWith("\""))
+ e.setCode(lexer.processConstant(e.getCode()));
+ TargetElementComponent tgt = e.addTarget();
+ if (eq != ConceptMapEquivalence.EQUIVALENT)
+ tgt.setEquivalence(eq);
+ if (tgt.getEquivalence() != ConceptMapEquivalence.UNMATCHED) {
+ lexer.token(":");
+ tgt.setCode(lexer.take());
+ if (tgt.getCode().startsWith("\""))
+ tgt.setCode(lexer.processConstant(tgt.getCode()));
+ }
+ if (lexer.hasComment())
+ tgt.setComment(lexer.take().substring(2).trim());
+ }
+ lexer.token("}");
+ }
+
+
+ private ConceptMapGroupComponent getGroup(ConceptMap map, String srcs, String tgts) {
+ for (ConceptMapGroupComponent grp : map.getGroup()) {
+ if (grp.getSource().equals(srcs))
+ if ((tgts == null && !grp.hasTarget()) || (tgts != null && tgts.equals(grp.getTarget())))
+ return grp;
+ }
+ ConceptMapGroupComponent grp = map.addGroup();
+ grp.setSource(srcs);
+ grp.setTarget(tgts);
+ return grp;
+ }
+
+
+ private String readPrefix(Map prefixes, FHIRLexer lexer) throws FHIRLexerException {
+ String prefix = lexer.take();
+ if (!prefixes.containsKey(prefix))
+ throw lexer.error("Unknown prefix '"+prefix+"'");
+ return prefixes.get(prefix);
+ }
+
+
+ private ConceptMapEquivalence readEquivalence(FHIRLexer lexer) throws FHIRLexerException {
+ String token = lexer.take();
+ if (token.equals("-"))
+ return ConceptMapEquivalence.RELATEDTO;
+ if (token.equals("="))
+ return ConceptMapEquivalence.EQUAL;
+ if (token.equals("=="))
+ return ConceptMapEquivalence.EQUIVALENT;
+ if (token.equals("!="))
+ return ConceptMapEquivalence.DISJOINT;
+ if (token.equals("--"))
+ return ConceptMapEquivalence.UNMATCHED;
+ if (token.equals("<="))
+ return ConceptMapEquivalence.WIDER;
+ if (token.equals("<-"))
+ return ConceptMapEquivalence.SUBSUMES;
+ if (token.equals(">="))
+ return ConceptMapEquivalence.NARROWER;
+ if (token.equals(">-"))
+ return ConceptMapEquivalence.SPECIALIZES;
+ if (token.equals("~"))
+ return ConceptMapEquivalence.INEXACT;
+ throw lexer.error("Unknown equivalence token '"+token+"'");
+ }
+
+
+ private void parseUses(StructureMap result, FHIRLexer lexer) throws FHIRException {
+ lexer.token("uses");
+ StructureMapStructureComponent st = result.addStructure();
+ st.setUrl(lexer.readConstant("url"));
+ if (lexer.hasToken("alias")) {
+ lexer.token("alias");
+ st.setAlias(lexer.take());
+ }
+ lexer.token("as");
+ st.setMode(StructureMapModelMode.fromCode(lexer.take()));
+ lexer.skipToken(";");
+ if (lexer.hasComment()) {
+ st.setDocumentation(lexer.take().substring(2).trim());
+ }
+ lexer.skipComments();
+ }
+
+ private void parseImports(StructureMap result, FHIRLexer lexer) throws FHIRException {
+ lexer.token("imports");
+ result.addImport(lexer.readConstant("url"));
+ lexer.skipToken(";");
+ if (lexer.hasComment()) {
+ lexer.next();
+ }
+ lexer.skipComments();
+ }
+
+ private void parseGroup(StructureMap result, FHIRLexer lexer) throws FHIRException {
+ lexer.token("group");
+ StructureMapGroupComponent group = result.addGroup();
+ if (lexer.hasToken("for")) {
+ lexer.token("for");
+ if ("type".equals(lexer.getCurrent())) {
+ lexer.token("type");
+ lexer.token("+");
+ lexer.token("types");
+ group.setTypeMode(StructureMapGroupTypeMode.TYPEANDTYPES);
+ } else {
+ lexer.token("types");
+ group.setTypeMode(StructureMapGroupTypeMode.TYPES);
+ }
+ } else
+ group.setTypeMode(StructureMapGroupTypeMode.NONE);
+ group.setName(lexer.take());
+ if (lexer.hasToken("extends")) {
+ lexer.next();
+ group.setExtends(lexer.take());
+ }
+ lexer.skipComments();
+ while (lexer.hasToken("input"))
+ parseInput(group, lexer);
+ while (!lexer.hasToken("endgroup")) {
+ if (lexer.done())
+ throw lexer.error("premature termination expecting 'endgroup'");
+ parseRule(result, group.getRule(), lexer);
+ }
+ lexer.next();
+ lexer.skipComments();
+ }
+
+ private void parseInput(StructureMapGroupComponent group, FHIRLexer lexer) throws FHIRException {
+ lexer.token("input");
+ StructureMapGroupInputComponent input = group.addInput();
+ input.setName(lexer.take());
+ if (lexer.hasToken(":")) {
+ lexer.token(":");
+ input.setType(lexer.take());
+ }
+ lexer.token("as");
+ input.setMode(StructureMapInputMode.fromCode(lexer.take()));
+ if (lexer.hasComment()) {
+ input.setDocumentation(lexer.take().substring(2).trim());
+ }
+ lexer.skipToken(";");
+ lexer.skipComments();
+ }
+
+ private void parseRule(StructureMap map, List list, FHIRLexer lexer) throws FHIRException {
+ StructureMapGroupRuleComponent rule = new StructureMapGroupRuleComponent();
+ list.add(rule);
+ rule.setName(lexer.takeDottedToken());
+ lexer.token(":");
+ lexer.token("for");
+ boolean done = false;
+ while (!done) {
+ parseSource(rule, lexer);
+ done = !lexer.hasToken(",");
+ if (!done)
+ lexer.next();
+ }
+ if (lexer.hasToken("make")) {
+ lexer.token("make");
+ done = false;
+ while (!done) {
+ parseTarget(rule, lexer);
+ done = !lexer.hasToken(",");
+ if (!done)
+ lexer.next();
+ }
+ }
+ if (lexer.hasToken("then")) {
+ lexer.token("then");
+ if (lexer.hasToken("{")) {
+ lexer.token("{");
+ if (lexer.hasComment()) {
+ rule.setDocumentation(lexer.take().substring(2).trim());
+ }
+ lexer.skipComments();
+ while (!lexer.hasToken("}")) {
+ if (lexer.done())
+ throw lexer.error("premature termination expecting '}' in nested group");
+ parseRule(map, rule.getRule(), lexer);
+ }
+ lexer.token("}");
+ } else {
+ done = false;
+ while (!done) {
+ parseRuleReference(rule, lexer);
+ done = !lexer.hasToken(",");
+ if (!done)
+ lexer.next();
+ }
+ }
+ } else if (lexer.hasComment()) {
+ rule.setDocumentation(lexer.take().substring(2).trim());
+ }
+ if (isSimpleSyntax(rule)) {
+ rule.getSourceFirstRep().setVariable(AUTO_VAR_NAME);
+ rule.getTargetFirstRep().setVariable(AUTO_VAR_NAME);
+ rule.getTargetFirstRep().setTransform(StructureMapTransform.CREATE); // with no parameter - e.g. imply what is to be created
+ // no dependencies - imply what is to be done based on types
+ }
+ lexer.skipComments();
+ }
+
+ private boolean isSimpleSyntax(StructureMapGroupRuleComponent rule) {
+ return
+ (rule.getSource().size() == 1 && rule.getSourceFirstRep().hasContext() && rule.getSourceFirstRep().hasElement() && !rule.getSourceFirstRep().hasVariable()) &&
+ (rule.getTarget().size() == 1 && rule.getTargetFirstRep().hasContext() && rule.getTargetFirstRep().hasElement() && !rule.getTargetFirstRep().hasVariable() && !rule.getTargetFirstRep().hasParameter()) &&
+ (rule.getDependent().size() == 0 && rule.getRule().size() == 0);
+ }
+
+ private void parseRuleReference(StructureMapGroupRuleComponent rule, FHIRLexer lexer) throws FHIRLexerException {
+ StructureMapGroupRuleDependentComponent ref = rule.addDependent();
+ ref.setName(lexer.take());
+ lexer.token("(");
+ boolean done = false;
+ while (!done) {
+ ref.addVariable(lexer.take());
+ done = !lexer.hasToken(",");
+ if (!done)
+ lexer.next();
+ }
+ lexer.token(")");
+ }
+
+ private void parseSource(StructureMapGroupRuleComponent rule, FHIRLexer lexer) throws FHIRException {
+ StructureMapGroupRuleSourceComponent source = rule.addSource();
+ source.setContext(lexer.take());
+ if (source.getContext().equals("search") && lexer.hasToken("(")) {
+ source.setContext("@search");
+ lexer.take();
+ ExpressionNode node = fpe.parse(lexer);
+ source.setUserData(MAP_SEARCH_EXPRESSION, node);
+ source.setElement(node.toString());
+ lexer.token(")");
+ } else if (lexer.hasToken(".")) {
+ lexer.token(".");
+ source.setElement(lexer.take());
+ }
+ if (lexer.hasToken(":")) {
+ // type and cardinality
+ lexer.token(":");
+ source.setType(lexer.takeDottedToken());
+ if (!lexer.hasToken("as", "first", "last", "not_first", "not_last", "only_one", "default")) {
+ source.setMin(lexer.takeInt());
+ lexer.token("..");
+ source.setMax(lexer.take());
+ }
+ }
+ if (lexer.hasToken("default")) {
+ lexer.token("default");
+ source.setDefaultValue(new StringType(lexer.readConstant("default value")));
+ }
+ if (Utilities.existsInList(lexer.getCurrent(), "first", "last", "not_first", "not_last", "only_one"))
+ source.setListMode(StructureMapSourceListMode.fromCode(lexer.take()));
+
+ if (lexer.hasToken("as")) {
+ lexer.take();
+ source.setVariable(lexer.take());
+ }
+ if (lexer.hasToken("where")) {
+ lexer.take();
+ ExpressionNode node = fpe.parse(lexer);
+ source.setUserData(MAP_WHERE_EXPRESSION, node);
+ source.setCondition(node.toString());
+ }
+ if (lexer.hasToken("check")) {
+ lexer.take();
+ ExpressionNode node = fpe.parse(lexer);
+ source.setUserData(MAP_WHERE_CHECK, node);
+ source.setCheck(node.toString());
+ }
+ }
+
+ private void parseTarget(StructureMapGroupRuleComponent rule, FHIRLexer lexer) throws FHIRException {
+ StructureMapGroupRuleTargetComponent target = rule.addTarget();
+ String start = lexer.take();
+ if (lexer.hasToken(".")) {
+ target.setContext(start);
+ target.setContextType(StructureMapContextType.VARIABLE);
+ start = null;
+ lexer.token(".");
+ target.setElement(lexer.take());
+ }
+ String name;
+ boolean isConstant = false;
+ if (lexer.hasToken("=")) {
+ if (start != null)
+ target.setContext(start);
+ lexer.token("=");
+ isConstant = lexer.isConstant(true);
+ name = lexer.take();
+ } else
+ name = start;
+
+ if ("(".equals(name)) {
+ // inline fluentpath expression
+ target.setTransform(StructureMapTransform.EVALUATE);
+ ExpressionNode node = fpe.parse(lexer);
+ target.setUserData(MAP_EXPRESSION, node);
+ target.addParameter().setValue(new StringType(node.toString()));
+ lexer.token(")");
+ } else if (lexer.hasToken("(")) {
+ target.setTransform(StructureMapTransform.fromCode(name));
+ lexer.token("(");
+ if (target.getTransform() == StructureMapTransform.EVALUATE) {
+ parseParameter(target, lexer);
+ lexer.token(",");
+ ExpressionNode node = fpe.parse(lexer);
+ target.setUserData(MAP_EXPRESSION, node);
+ target.addParameter().setValue(new StringType(node.toString()));
+ } else {
+ while (!lexer.hasToken(")")) {
+ parseParameter(target, lexer);
+ if (!lexer.hasToken(")"))
+ lexer.token(",");
+ }
+ }
+ lexer.token(")");
+ } else if (name != null) {
+ target.setTransform(StructureMapTransform.COPY);
+ if (!isConstant) {
+ String id = name;
+ while (lexer.hasToken(".")) {
+ id = id + lexer.take() + lexer.take();
+ }
+ target.addParameter().setValue(new IdType(id));
+ }
+ else
+ target.addParameter().setValue(readConstant(name, lexer));
+ }
+ if (lexer.hasToken("as")) {
+ lexer.take();
+ target.setVariable(lexer.take());
+ }
+ while (Utilities.existsInList(lexer.getCurrent(), "first", "last", "share", "collate")) {
+ if (lexer.getCurrent().equals("share")) {
+ target.addListMode(StructureMapTargetListMode.SHARE);
+ lexer.next();
+ target.setListRuleId(lexer.take());
+ } else if (lexer.getCurrent().equals("first"))
+ target.addListMode(StructureMapTargetListMode.FIRST);
+ else
+ target.addListMode(StructureMapTargetListMode.LAST);
+ lexer.next();
+ }
+ }
+
+
+ private void parseParameter(StructureMapGroupRuleTargetComponent target, FHIRLexer lexer) throws FHIRLexerException, FHIRFormatError {
+ if (!lexer.isConstant(true)) {
+ target.addParameter().setValue(new IdType(lexer.take()));
+ } else if (lexer.isStringConstant())
+ target.addParameter().setValue(new StringType(lexer.readConstant("??")));
+ else {
+ target.addParameter().setValue(readConstant(lexer.take(), lexer));
+ }
+ }
+
+ private Type readConstant(String s, FHIRLexer lexer) throws FHIRLexerException {
+ if (Utilities.isInteger(s))
+ return new IntegerType(s);
+ else if (Utilities.isDecimal(s))
+ return new DecimalType(s);
+ else if (Utilities.existsInList(s, "true", "false"))
+ return new BooleanType(s.equals("true"));
+ else
+ return new StringType(lexer.processConstant(s));
+ }
+
+ public StructureDefinition getTargetType(StructureMap map) throws FHIRException {
+ boolean found = false;
+ StructureDefinition res = null;
+ for (StructureMapStructureComponent uses : map.getStructure()) {
+ if (uses.getMode() == StructureMapModelMode.TARGET) {
+ if (found)
+ throw new FHIRException("Multiple targets found in map "+map.getUrl());
+ found = true;
+ res = worker.fetchResource(StructureDefinition.class, uses.getUrl());
+ if (res == null)
+ throw new FHIRException("Unable to find "+uses.getUrl()+" referenced from map "+map.getUrl());
+ }
+ }
+ if (res == null)
+ throw new FHIRException("No targets found in map "+map.getUrl());
+ return res;
+ }
+
+ public enum VariableMode {
+ INPUT, OUTPUT
+ }
+
+ public class Variable {
+ private VariableMode mode;
+ private String name;
+ private Base object;
+ public Variable(VariableMode mode, String name, Base object) {
+ super();
+ this.mode = mode;
+ this.name = name;
+ this.object = object;
+ }
+ public VariableMode getMode() {
+ return mode;
+ }
+ public String getName() {
+ return name;
+ }
+ public Base getObject() {
+ return object;
+ }
+ public String summary() {
+ return name+": "+object.fhirType();
+ }
+ }
+
+ public class Variables {
+ private List list = new ArrayList();
+
+ public void add(VariableMode mode, String name, Base object) {
+ Variable vv = null;
+ for (Variable v : list)
+ if ((v.mode == mode) && v.getName().equals(name))
+ vv = v;
+ if (vv != null)
+ list.remove(vv);
+ list.add(new Variable(mode, name, object));
+ }
+
+ public Variables copy() {
+ Variables result = new Variables();
+ result.list.addAll(list);
+ return result;
+ }
+
+ public Base get(VariableMode mode, String name) {
+ for (Variable v : list)
+ if ((v.mode == mode) && v.getName().equals(name))
+ return v.getObject();
+ return null;
+ }
+
+ public String summary() {
+ CommaSeparatedStringBuilder s = new CommaSeparatedStringBuilder();
+ CommaSeparatedStringBuilder t = new CommaSeparatedStringBuilder();
+ for (Variable v : list)
+ if (v.mode == VariableMode.INPUT)
+ s.append(v.summary());
+ else
+ t.append(v.summary());
+ return "source variables ["+s.toString()+"], target variables ["+t.toString()+"]";
+ }
+ }
+
+ public class TransformContext {
+ private Object appInfo;
+
+ public TransformContext(Object appInfo) {
+ super();
+ this.appInfo = appInfo;
+ }
+
+ public Object getAppInfo() {
+ return appInfo;
+ }
+
+ }
+
+ private void log(String cnt) {
+ if (services != null)
+ services.log(cnt);
+ }
+
+ /**
+ * Given an item, return all the children that conform to the pattern described in name
+ *
+ * Possible patterns:
+ * - a simple name (which may be the base of a name with [] e.g. value[x])
+ * - a name with a type replacement e.g. valueCodeableConcept
+ * - * which means all children
+ * - ** which means all descendents
+ *
+ * @param item
+ * @param name
+ * @param result
+ * @throws FHIRException
+ */
+ protected void getChildrenByName(Base item, String name, List result) throws FHIRException {
+ for (Base v : item.listChildrenByName(name, true))
+ if (v != null)
+ result.add(v);
+ }
+
+ public void transform(Object appInfo, Base source, StructureMap map, Base target) throws FHIRException {
+ TransformContext context = new TransformContext(appInfo);
+ log("Start Transform "+map.getUrl());
+ StructureMapGroupComponent g = map.getGroup().get(0);
+
+ Variables vars = new Variables();
+ vars.add(VariableMode.INPUT, getInputName(g, StructureMapInputMode.SOURCE, "source"), source);
+ vars.add(VariableMode.OUTPUT, getInputName(g, StructureMapInputMode.TARGET, "target"), target);
+
+ executeGroup("", context, map, vars, g);
+ if (target instanceof Element)
+ ((Element) target).sort();
+ }
+
+ private String getInputName(StructureMapGroupComponent g, StructureMapInputMode mode, String def) throws DefinitionException {
+ String name = null;
+ for (StructureMapGroupInputComponent inp : g.getInput()) {
+ if (inp.getMode() == mode)
+ if (name != null)
+ throw new DefinitionException("This engine does not support multiple source inputs");
+ else
+ name = inp.getName();
+ }
+ return name == null ? def : name;
+ }
+
+ private void executeGroup(String indent, TransformContext context, StructureMap map, Variables vars, StructureMapGroupComponent group) throws FHIRException {
+ log(indent+"Group : "+group.getName());
+ // todo: check inputs
+ if (group.hasExtends()) {
+ ResolvedGroup rg = resolveGroupReference(map, group, group.getExtends());
+ executeGroup(indent+" ", context, rg.targetMap, vars, rg.target);
+ }
+
+ for (StructureMapGroupRuleComponent r : group.getRule()) {
+ executeRule(indent+" ", context, map, vars, group, r);
+ }
+ }
+
+ private void executeRule(String indent, TransformContext context, StructureMap map, Variables vars, StructureMapGroupComponent group, StructureMapGroupRuleComponent rule) throws FHIRException {
+ log(indent+"rule : "+rule.getName());
+ if (rule.getName().contains("CarePlan.participant-unlink"))
+ System.out.println("debug");
+ Variables srcVars = vars.copy();
+ if (rule.getSource().size() != 1)
+ throw new FHIRException("Rule \""+rule.getName()+"\": not handled yet");
+ List source = processSource(rule.getName(), context, srcVars, rule.getSource().get(0));
+ if (source != null) {
+ for (Variables v : source) {
+ for (StructureMapGroupRuleTargetComponent t : rule.getTarget()) {
+ processTarget(rule.getName(), context, v, map, group, t, rule.getSource().size() == 1 ? rule.getSourceFirstRep().getVariable() : null);
+ }
+ if (rule.hasRule()) {
+ for (StructureMapGroupRuleComponent childrule : rule.getRule()) {
+ executeRule(indent +" ", context, map, v, group, childrule);
+ }
+ } else if (rule.hasDependent()) {
+ for (StructureMapGroupRuleDependentComponent dependent : rule.getDependent()) {
+ executeDependency(indent+" ", context, map, v, group, dependent);
+ }
+ } else if (rule.getSource().size() == 1 && rule.getSourceFirstRep().hasVariable() && rule.getTarget().size() == 1 && rule.getTargetFirstRep().hasVariable() && rule.getTargetFirstRep().getTransform() == StructureMapTransform.CREATE && !rule.getTargetFirstRep().hasParameter()) {
+ // simple inferred, map by type
+ Base src = v.get(VariableMode.INPUT, rule.getSourceFirstRep().getVariable());
+ Base tgt = v.get(VariableMode.OUTPUT, rule.getTargetFirstRep().getVariable());
+ String srcType = src.fhirType();
+ String tgtType = tgt.fhirType();
+ ResolvedGroup defGroup = resolveGroupByTypes(map, rule.getName(), group, srcType, tgtType);
+ Variables vdef = new Variables();
+ vdef.add(VariableMode.INPUT, defGroup.target.getInput().get(0).getName(), src);
+ vdef.add(VariableMode.OUTPUT, defGroup.target.getInput().get(1).getName(), tgt);
+ executeGroup(indent+" ", context, defGroup.targetMap, vdef, defGroup.target);
+ }
+ }
+ }
+ }
+
+ private void executeDependency(String indent, TransformContext context, StructureMap map, Variables vin, StructureMapGroupComponent group, StructureMapGroupRuleDependentComponent dependent) throws FHIRException {
+ ResolvedGroup rg = resolveGroupReference(map, group, dependent.getName());
+
+ if (rg.target.getInput().size() != dependent.getVariable().size()) {
+ throw new FHIRException("Rule '"+dependent.getName()+"' has "+Integer.toString(rg.target.getInput().size())+" but the invocation has "+Integer.toString(dependent.getVariable().size())+" variables");
+ }
+ Variables v = new Variables();
+ for (int i = 0; i < rg.target.getInput().size(); i++) {
+ StructureMapGroupInputComponent input = rg.target.getInput().get(i);
+ StringType rdp = dependent.getVariable().get(i);
+ String var = rdp.asStringValue();
+ VariableMode mode = input.getMode() == StructureMapInputMode.SOURCE ? VariableMode.INPUT : VariableMode.OUTPUT;
+ Base vv = vin.get(mode, var);
+ if (vv == null && mode == VariableMode.INPUT) //* once source, always source. but target can be treated as source at user convenient
+ vv = vin.get(VariableMode.OUTPUT, var);
+ if (vv == null)
+ throw new FHIRException("Rule '"+dependent.getName()+"' "+mode.toString()+" variable '"+input.getName()+"' named as '"+var+"' has no value");
+ v.add(mode, input.getName(), vv);
+ }
+ executeGroup(indent+" ", context, rg.targetMap, v, rg.target);
+ }
+
+ private String determineTypeFromSourceType(StructureMap map, StructureMapGroupComponent source, Base base, String[] types) throws FHIRException {
+ String type = base.fhirType();
+ String kn = "type^"+type;
+ if (source.hasUserData(kn))
+ return source.getUserString(kn);
+
+ ResolvedGroup res = new ResolvedGroup();
+ res.targetMap = null;
+ res.target = null;
+ for (StructureMapGroupComponent grp : map.getGroup()) {
+ if (matchesByType(map, grp, type)) {
+ if (res.targetMap == null) {
+ res.targetMap = map;
+ res.target = grp;
+ } else
+ throw new FHIRException("Multiple possible matches looking for default rule for '"+type+"'");
+ }
+ }
+ if (res.targetMap != null) {
+ String result = getActualType(res.targetMap, res.target.getInput().get(1).getType());
+ source.setUserData(kn, result);
+ return result;
+ }
+
+ for (UriType imp : map.getImport()) {
+ List impMapList = findMatchingMaps(imp.getValue());
+ if (impMapList.size() == 0)
+ throw new FHIRException("Unable to find map(s) for "+imp.getValue());
+ for (StructureMap impMap : impMapList) {
+ if (!impMap.getUrl().equals(map.getUrl())) {
+ for (StructureMapGroupComponent grp : impMap.getGroup()) {
+ if (matchesByType(impMap, grp, type)) {
+ if (res.targetMap == null) {
+ res.targetMap = impMap;
+ res.target = grp;
+ } else
+ throw new FHIRException("Multiple possible matches for default rule for '"+type+"' in "+res.targetMap.getUrl()+" ("+res.target.getName()+") and "+impMap.getUrl()+" ("+grp.getName()+")");
+ }
+ }
+ }
+ }
+ }
+ if (res.target == null)
+ throw new FHIRException("No matches found for default rule for '"+type+"' from "+map.getUrl());
+ String result = getActualType(res.targetMap, res.target.getInput().get(1).getType()); // should be .getType, but R2...
+ source.setUserData(kn, result);
+ return result;
+ }
+
+ private List findMatchingMaps(String value) {
+ List res = new ArrayList();
+ if (value.contains("*")) {
+ for (StructureMap sm : library.values()) {
+ if (urlMatches(value, sm.getUrl())) {
+ res.add(sm);
+ }
+ }
+ } else {
+ StructureMap sm = library.get(value);
+ if (sm != null)
+ res.add(sm);
+ }
+ Set check = new HashSet();
+ for (StructureMap sm : res) {
+ if (check.contains(sm.getUrl()))
+ throw new Error("duplicate");
+ else
+ check.add(sm.getUrl());
+ }
+ return res;
+ }
+
+ private boolean urlMatches(String mask, String url) {
+ return url.length() > mask.length() && url.startsWith(mask.substring(0, mask.indexOf("*"))) && url.endsWith(mask.substring(mask.indexOf("*")+1)) ;
+ }
+
+ private ResolvedGroup resolveGroupByTypes(StructureMap map, String ruleid, StructureMapGroupComponent source, String srcType, String tgtType) throws FHIRException {
+ String kn = "types^"+srcType+":"+tgtType;
+ if (source.hasUserData(kn))
+ return (ResolvedGroup) source.getUserData(kn);
+
+ ResolvedGroup res = new ResolvedGroup();
+ res.targetMap = null;
+ res.target = null;
+ for (StructureMapGroupComponent grp : map.getGroup()) {
+ if (matchesByType(map, grp, srcType, tgtType)) {
+ if (res.targetMap == null) {
+ res.targetMap = map;
+ res.target = grp;
+ } else
+ throw new FHIRException("Multiple possible matches looking for rule for '"+srcType+"/"+tgtType+"', from rule '"+ruleid+"'");
+ }
+ }
+ if (res.targetMap != null) {
+ source.setUserData(kn, res);
+ return res;
+ }
+
+ for (UriType imp : map.getImport()) {
+ List impMapList = findMatchingMaps(imp.getValue());
+ if (impMapList.size() == 0)
+ throw new FHIRException("Unable to find map(s) for "+imp.getValue());
+ for (StructureMap impMap : impMapList) {
+ if (!impMap.getUrl().equals(map.getUrl())) {
+ for (StructureMapGroupComponent grp : impMap.getGroup()) {
+ if (matchesByType(impMap, grp, srcType, tgtType)) {
+ if (res.targetMap == null) {
+ res.targetMap = impMap;
+ res.target = grp;
+ } else
+ throw new FHIRException("Multiple possible matches for rule for '"+srcType+"/"+tgtType+"' in "+res.targetMap.getUrl()+" and "+impMap.getUrl()+", from rule '"+ruleid+"'");
+ }
+ }
+ }
+ }
+ }
+ if (res.target == null)
+ throw new FHIRException("No matches found for rule for '"+srcType+"/"+tgtType+"' from "+map.getUrl()+", from rule '"+ruleid+"'");
+ source.setUserData(kn, res);
+ return res;
+ }
+
+
+ private boolean matchesByType(StructureMap map, StructureMapGroupComponent grp, String type) throws FHIRException {
+ if (grp.getTypeMode() != StructureMapGroupTypeMode.TYPEANDTYPES)
+ return false;
+ if (grp.getInput().size() != 2 || grp.getInput().get(0).getMode() != StructureMapInputMode.SOURCE || grp.getInput().get(1).getMode() != StructureMapInputMode.TARGET)
+ return false;
+ return matchesType(map, type, grp.getInput().get(0).getType());
+ }
+
+ private boolean matchesByType(StructureMap map, StructureMapGroupComponent grp, String srcType, String tgtType) throws FHIRException {
+ if (grp.getTypeMode() == StructureMapGroupTypeMode.NONE)
+ return false;
+ if (grp.getInput().size() != 2 || grp.getInput().get(0).getMode() != StructureMapInputMode.SOURCE || grp.getInput().get(1).getMode() != StructureMapInputMode.TARGET)
+ return false;
+ if (!grp.getInput().get(0).hasType() || !grp.getInput().get(1).hasType())
+ return false;
+ return matchesType(map, srcType, grp.getInput().get(0).getType()) && matchesType(map, tgtType, grp.getInput().get(1).getType());
+ }
+
+ private boolean matchesType(StructureMap map, String actualType, String statedType) throws FHIRException {
+ // check the aliases
+ for (StructureMapStructureComponent imp : map.getStructure()) {
+ if (imp.hasAlias() && statedType.equals(imp.getAlias())) {
+ StructureDefinition sd = worker.fetchResource(StructureDefinition.class, imp.getUrl());
+ if (sd != null)
+ statedType = sd.getType();
+ break;
+ }
+ }
+
+ return actualType.equals(statedType);
+ }
+
+ private String getActualType(StructureMap map, String statedType) throws FHIRException {
+ // check the aliases
+ for (StructureMapStructureComponent imp : map.getStructure()) {
+ if (imp.hasAlias() && statedType.equals(imp.getAlias())) {
+ StructureDefinition sd = worker.fetchResource(StructureDefinition.class, imp.getUrl());
+ if (sd == null)
+ throw new FHIRException("Unable to resolve structure "+imp.getUrl());
+ return sd.getId(); // should be sd.getType(), but R2...
+ }
+ }
+ return statedType;
+ }
+
+
+ private ResolvedGroup resolveGroupReference(StructureMap map, StructureMapGroupComponent source, String name) throws FHIRException {
+ String kn = "ref^"+name;
+ if (source.hasUserData(kn))
+ return (ResolvedGroup) source.getUserData(kn);
+
+ ResolvedGroup res = new ResolvedGroup();
+ res.targetMap = null;
+ res.target = null;
+ for (StructureMapGroupComponent grp : map.getGroup()) {
+ if (grp.getName().equals(name)) {
+ if (res.targetMap == null) {
+ res.targetMap = map;
+ res.target = grp;
+ } else
+ throw new FHIRException("Multiple possible matches for rule '"+name+"'");
+ }
+ }
+ if (res.targetMap != null) {
+ source.setUserData(kn, res);
+ return res;
+ }
+
+ for (UriType imp : map.getImport()) {
+ List impMapList = findMatchingMaps(imp.getValue());
+ if (impMapList.size() == 0)
+ throw new FHIRException("Unable to find map(s) for "+imp.getValue());
+ for (StructureMap impMap : impMapList) {
+ if (!impMap.getUrl().equals(map.getUrl())) {
+ for (StructureMapGroupComponent grp : impMap.getGroup()) {
+ if (grp.getName().equals(name)) {
+ if (res.targetMap == null) {
+ res.targetMap = impMap;
+ res.target = grp;
+ } else
+ throw new FHIRException("Multiple possible matches for rule '"+name+"' in "+res.targetMap.getUrl()+" and "+impMap.getUrl());
+ }
+ }
+ }
+ }
+ }
+ if (res.target == null)
+ throw new FHIRException("No matches found for rule '"+name+"'. Reference found in "+map.getUrl());
+ source.setUserData(kn, res);
+ return res;
+ }
+
+ private List processSource(String ruleId, TransformContext context, Variables vars, StructureMapGroupRuleSourceComponent src) throws FHIRException {
+ List items;
+ if (src.getContext().equals("@search")) {
+ ExpressionNode expr = (ExpressionNode) src.getUserData(MAP_SEARCH_EXPRESSION);
+ if (expr == null) {
+ expr = fpe.parse(src.getElement());
+ src.setUserData(MAP_SEARCH_EXPRESSION, expr);
+ }
+ String search = fpe.evaluateToString(vars, null, new StringType(), expr); // string is a holder of nothing to ensure that variables are processed correctly
+ items = services.performSearch(context.appInfo, search);
+ } else {
+ items = new ArrayList();
+ Base b = vars.get(VariableMode.INPUT, src.getContext());
+ if (b == null)
+ throw new FHIRException("Unknown input variable "+src.getContext());
+
+ if (!src.hasElement())
+ items.add(b);
+ else {
+ getChildrenByName(b, src.getElement(), items);
+ if (items.size() == 0 && src.hasDefaultValue())
+ items.add(src.getDefaultValue());
+ }
+ }
+
+ if (src.hasType()) {
+ List remove = new ArrayList();
+ for (Base item : items) {
+ if (item != null && !isType(item, src.getType())) {
+ remove.add(item);
+ }
+ }
+ items.removeAll(remove);
+ }
+
+ if (src.hasCondition()) {
+ ExpressionNode expr = (ExpressionNode) src.getUserData(MAP_WHERE_EXPRESSION);
+ if (expr == null) {
+ expr = fpe.parse(src.getCondition());
+ // fpe.check(context.appInfo, ??, ??, expr)
+ src.setUserData(MAP_WHERE_EXPRESSION, expr);
+ }
+ List remove = new ArrayList();
+ for (Base item : items) {
+ if (!fpe.evaluateToBoolean(vars, null, item, expr))
+ remove.add(item);
+ }
+ items.removeAll(remove);
+ }
+
+ if (src.hasCheck()) {
+ ExpressionNode expr = (ExpressionNode) src.getUserData(MAP_WHERE_CHECK);
+ if (expr == null) {
+ expr = fpe.parse(src.getCheck());
+ // fpe.check(context.appInfo, ??, ??, expr)
+ src.setUserData(MAP_WHERE_CHECK, expr);
+ }
+ List remove = new ArrayList();
+ for (Base item : items) {
+ if (!fpe.evaluateToBoolean(vars, null, item, expr))
+ throw new FHIRException("Rule \""+ruleId+"\": Check condition failed");
+ }
+ }
+
+
+ if (src.hasListMode() && !items.isEmpty()) {
+ switch (src.getListMode()) {
+ case FIRST:
+ Base bt = items.get(0);
+ items.clear();
+ items.add(bt);
+ break;
+ case NOTFIRST:
+ if (items.size() > 0)
+ items.remove(0);
+ break;
+ case LAST:
+ bt = items.get(items.size()-1);
+ items.clear();
+ items.add(bt);
+ break;
+ case NOTLAST:
+ if (items.size() > 0)
+ items.remove(items.size()-1);
+ break;
+ case ONLYONE:
+ if (items.size() > 1)
+ throw new FHIRException("Rule \""+ruleId+"\": Check condition failed: the collection has more than one item");
+ break;
+ case NULL:
+ }
+ }
+ List result = new ArrayList();
+ for (Base r : items) {
+ Variables v = vars.copy();
+ if (src.hasVariable())
+ v.add(VariableMode.INPUT, src.getVariable(), r);
+ result.add(v);
+ }
+ return result;
+ }
+
+
+ private boolean isType(Base item, String type) {
+ if (type.equals(item.fhirType()))
+ return true;
+ return false;
+ }
+
+ private void processTarget(String ruleId, TransformContext context, Variables vars, StructureMap map, StructureMapGroupComponent group, StructureMapGroupRuleTargetComponent tgt, String srcVar) throws FHIRException {
+ Base dest = null;
+ if (tgt.hasContext()) {
+ dest = vars.get(VariableMode.OUTPUT, tgt.getContext());
+ if (dest == null)
+ throw new FHIRException("Rule \""+ruleId+"\": target context not known: "+tgt.getContext());
+ if (!tgt.hasElement())
+ throw new FHIRException("Rule \""+ruleId+"\": Not supported yet");
+ }
+ Base v = null;
+ if (tgt.hasTransform()) {
+ v = runTransform(ruleId, context, map, group, tgt, vars, dest, tgt.getElement(), srcVar);
+ if (v != null && dest != null)
+ v = dest.setProperty(tgt.getElement().hashCode(), tgt.getElement(), v); // reset v because some implementations may have to rewrite v when setting the value
+ } else if (dest != null)
+ v = dest.makeProperty(tgt.getElement().hashCode(), tgt.getElement());
+ if (tgt.hasVariable() && v != null)
+ vars.add(VariableMode.OUTPUT, tgt.getVariable(), v);
+ }
+
+ private Base runTransform(String ruleId, TransformContext context, StructureMap map, StructureMapGroupComponent group, StructureMapGroupRuleTargetComponent tgt, Variables vars, Base dest, String element, String srcVar) throws FHIRException {
+ try {
+ switch (tgt.getTransform()) {
+ case CREATE :
+ String tn;
+ if (tgt.getParameter().isEmpty()) {
+ // we have to work out the type. First, we see if there is a single type for the target. If there is, we use that
+ String[] types = dest.getTypesForProperty(element.hashCode(), element);
+ if (types.length == 1 && !"*".equals(types[0]) && !types[0].equals("Resource"))
+ tn = types[0];
+ else if (srcVar != null) {
+ tn = determineTypeFromSourceType(map, group, vars.get(VariableMode.INPUT, srcVar), types);
+ } else
+ throw new Error("Cannot determine type implicitly because there is no single input variable");
+ } else
+ tn = getParamStringNoNull(vars, tgt.getParameter().get(0), tgt.toString());
+ Base res = services != null ? services.createType(context.getAppInfo(), tn) : ResourceFactory.createResourceOrType(tn);
+ if (res.isResource() && !res.fhirType().equals("Parameters")) {
+// res.setIdBase(tgt.getParameter().size() > 1 ? getParamString(vars, tgt.getParameter().get(0)) : UUID.randomUUID().toString().toLowerCase());
+ if (services != null)
+ res = services.createResource(context.getAppInfo(), res);
+ }
+ if (tgt.hasUserData("profile"))
+ res.setUserData("profile", tgt.getUserData("profile"));
+ return res;
+ case COPY :
+ return getParam(vars, tgt.getParameter().get(0));
+ case EVALUATE :
+ ExpressionNode expr = (ExpressionNode) tgt.getUserData(MAP_EXPRESSION);
+ if (expr == null) {
+ expr = fpe.parse(getParamStringNoNull(vars, tgt.getParameter().get(1), tgt.toString()));
+ tgt.setUserData(MAP_WHERE_EXPRESSION, expr);
+ }
+ List v = fpe.evaluate(vars, null, tgt.getParameter().size() == 2 ? getParam(vars, tgt.getParameter().get(0)) : new BooleanType(false), expr);
+ if (v.size() == 0)
+ return null;
+ else if (v.size() != 1)
+ throw new FHIRException("Rule \""+ruleId+"\": Evaluation of "+expr.toString()+" returned "+Integer.toString(v.size())+" objects");
+ else
+ return v.get(0);
+
+ case TRUNCATE :
+ String src = getParamString(vars, tgt.getParameter().get(0));
+ String len = getParamStringNoNull(vars, tgt.getParameter().get(1), tgt.toString());
+ if (Utilities.isInteger(len)) {
+ int l = Integer.parseInt(len);
+ if (src.length() > l)
+ src = src.substring(0, l);
+ }
+ return new StringType(src);
+ case ESCAPE :
+ throw new Error("Rule \""+ruleId+"\": Transform "+tgt.getTransform().toCode()+" not supported yet");
+ case CAST :
+ throw new Error("Rule \""+ruleId+"\": Transform "+tgt.getTransform().toCode()+" not supported yet");
+ case APPEND :
+ throw new Error("Rule \""+ruleId+"\": Transform "+tgt.getTransform().toCode()+" not supported yet");
+ case TRANSLATE :
+ return translate(context, map, vars, tgt.getParameter());
+ case REFERENCE :
+ Base b = getParam(vars, tgt.getParameter().get(0));
+ if (b == null)
+ throw new FHIRException("Rule \""+ruleId+"\": Unable to find parameter "+((IdType) tgt.getParameter().get(0).getValue()).asStringValue());
+ if (!b.isResource())
+ throw new FHIRException("Rule \""+ruleId+"\": Transform engine cannot point at an element of type "+b.fhirType());
+ else {
+ String id = b.getIdBase();
+ if (id == null) {
+ id = UUID.randomUUID().toString().toLowerCase();
+ b.setIdBase(id);
+ }
+ return new Reference().setReference(b.fhirType()+"/"+id);
+ }
+ case DATEOP :
+ throw new Error("Rule \""+ruleId+"\": Transform "+tgt.getTransform().toCode()+" not supported yet");
+ case UUID :
+ return new IdType(UUID.randomUUID().toString());
+ case POINTER :
+ b = getParam(vars, tgt.getParameter().get(0));
+ if (b instanceof Resource)
+ return new UriType("urn:uuid:"+((Resource) b).getId());
+ else
+ throw new FHIRException("Rule \""+ruleId+"\": Transform engine cannot point at an element of type "+b.fhirType());
+ case CC:
+ CodeableConcept cc = new CodeableConcept();
+ cc.addCoding(buildCoding(getParamStringNoNull(vars, tgt.getParameter().get(0), tgt.toString()), getParamStringNoNull(vars, tgt.getParameter().get(1), tgt.toString())));
+ return cc;
+ case C:
+ Coding c = buildCoding(getParamStringNoNull(vars, tgt.getParameter().get(0), tgt.toString()), getParamStringNoNull(vars, tgt.getParameter().get(1), tgt.toString()));
+ return c;
+ default:
+ throw new Error("Rule \""+ruleId+"\": Transform Unknown: "+tgt.getTransform().toCode());
+ }
+ } catch (Exception e) {
+ throw new FHIRException("Exception executing transform "+tgt.toString()+" on Rule \""+ruleId+"\": "+e.getMessage(), e);
+ }
+ }
+
+
+ private Coding buildCoding(String uri, String code) throws FHIRException {
+ // if we can get this as a valueSet, we will
+ String system = null;
+ String display = null;
+ ValueSet vs = Utilities.noString(uri) ? null : worker.fetchResourceWithException(ValueSet.class, uri);
+ if (vs != null) {
+ ValueSetExpansionOutcome vse = worker.expandVS(vs, true, false);
+ if (vse.getError() != null)
+ throw new FHIRException(vse.getError());
+ CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
+ for (ValueSetExpansionContainsComponent t : vse.getValueset().getExpansion().getContains()) {
+ if (t.hasCode())
+ b.append(t.getCode());
+ if (code.equals(t.getCode()) && t.hasSystem()) {
+ system = t.getSystem();
+ display = t.getDisplay();
+ break;
+ }
+ if (code.equalsIgnoreCase(t.getDisplay()) && t.hasSystem()) {
+ system = t.getSystem();
+ display = t.getDisplay();
+ break;
+ }
+ }
+ if (system == null)
+ throw new FHIRException("The code '"+code+"' is not in the value set '"+uri+"' (valid codes: "+b.toString()+"; also checked displays)");
+ } else
+ system = uri;
+ ValidationResult vr = worker.validateCode(system, code, null);
+ if (vr != null && vr.getDisplay() != null)
+ display = vr.getDisplay();
+ return new Coding().setSystem(system).setCode(code).setDisplay(display);
+ }
+
+
+ private String getParamStringNoNull(Variables vars, StructureMapGroupRuleTargetParameterComponent parameter, String message) throws FHIRException {
+ Base b = getParam(vars, parameter);
+ if (b == null)
+ throw new FHIRException("Unable to find a value for "+parameter.toString()+". Context: "+message);
+ if (!b.hasPrimitiveValue())
+ throw new FHIRException("Found a value for "+parameter.toString()+", but it has a type of "+b.fhirType()+" and cannot be treated as a string. Context: "+message);
+ return b.primitiveValue();
+ }
+
+ private String getParamString(Variables vars, StructureMapGroupRuleTargetParameterComponent parameter) throws DefinitionException {
+ Base b = getParam(vars, parameter);
+ if (b == null || !b.hasPrimitiveValue())
+ return null;
+ return b.primitiveValue();
+ }
+
+
+ private Base getParam(Variables vars, StructureMapGroupRuleTargetParameterComponent parameter) throws DefinitionException {
+ Type p = parameter.getValue();
+ if (!(p instanceof IdType))
+ return p;
+ else {
+ String n = ((IdType) p).asStringValue();
+ Base b = vars.get(VariableMode.INPUT, n);
+ if (b == null)
+ b = vars.get(VariableMode.OUTPUT, n);
+ if (b == null)
+ throw new DefinitionException("Variable "+n+" not found ("+vars.summary()+")");
+ return b;
+ }
+ }
+
+
+ private Base translate(TransformContext context, StructureMap map, Variables vars, List parameter) throws FHIRException {
+ Base src = getParam(vars, parameter.get(0));
+ String id = getParamString(vars, parameter.get(1));
+ String fld = parameter.size() > 2 ? getParamString(vars, parameter.get(2)) : null;
+ return translate(context, map, src, id, fld);
+ }
+
+ private class SourceElementComponentWrapper {
+ private ConceptMapGroupComponent group;
+ private SourceElementComponent comp;
+ public SourceElementComponentWrapper(ConceptMapGroupComponent group, SourceElementComponent comp) {
+ super();
+ this.group = group;
+ this.comp = comp;
+ }
+ }
+ public Base translate(TransformContext context, StructureMap map, Base source, String conceptMapUrl, String fieldToReturn) throws FHIRException {
+ Coding src = new Coding();
+ if (source.isPrimitive()) {
+ src.setCode(source.primitiveValue());
+ } else if ("Coding".equals(source.fhirType())) {
+ Base[] b = source.getProperty("system".hashCode(), "system", true);
+ if (b.length == 1)
+ src.setSystem(b[0].primitiveValue());
+ b = source.getProperty("code".hashCode(), "code", true);
+ if (b.length == 1)
+ src.setCode(b[0].primitiveValue());
+ } else if ("CE".equals(source.fhirType())) {
+ Base[] b = source.getProperty("codeSystem".hashCode(), "codeSystem", true);
+ if (b.length == 1)
+ src.setSystem(b[0].primitiveValue());
+ b = source.getProperty("code".hashCode(), "code", true);
+ if (b.length == 1)
+ src.setCode(b[0].primitiveValue());
+ } else
+ throw new FHIRException("Unable to translate source "+source.fhirType());
+
+ String su = conceptMapUrl;
+ if (conceptMapUrl.equals("http://hl7.org/fhir/ConceptMap/special-oid2uri")) {
+ String uri = worker.oid2Uri(src.getCode());
+ if (uri == null)
+ uri = "urn:oid:"+src.getCode();
+ if ("uri".equals(fieldToReturn))
+ return new UriType(uri);
+ else
+ throw new FHIRException("Error in return code");
+ } else {
+ ConceptMap cmap = null;
+ if (conceptMapUrl.startsWith("#")) {
+ for (Resource r : map.getContained()) {
+ if (r instanceof ConceptMap && ((ConceptMap) r).getId().equals(conceptMapUrl.substring(1))) {
+ cmap = (ConceptMap) r;
+ su = map.getUrl()+conceptMapUrl;
+ }
+ }
+ if (cmap == null)
+ throw new FHIRException("Unable to translate - cannot find map "+conceptMapUrl);
+ } else
+ cmap = worker.fetchResource(ConceptMap.class, conceptMapUrl);
+ Coding outcome = null;
+ boolean done = false;
+ String message = null;
+ if (cmap == null) {
+ if (services == null)
+ message = "No map found for "+conceptMapUrl;
+ else {
+ outcome = services.translate(context.appInfo, src, conceptMapUrl);
+ done = true;
+ }
+ } else {
+ List list = new ArrayList();
+ for (ConceptMapGroupComponent g : cmap.getGroup()) {
+ for (SourceElementComponent e : g.getElement()) {
+ if (!src.hasSystem() && src.getCode().equals(e.getCode()))
+ list.add(new SourceElementComponentWrapper(g, e));
+ else if (src.hasSystem() && src.getSystem().equals(g.getSource()) && src.getCode().equals(e.getCode()))
+ list.add(new SourceElementComponentWrapper(g, e));
+ }
+ }
+ if (list.size() == 0)
+ done = true;
+ else if (list.get(0).comp.getTarget().size() == 0)
+ message = "Concept map "+su+" found no translation for "+src.getCode();
+ else {
+ for (TargetElementComponent tgt : list.get(0).comp.getTarget()) {
+ if (tgt.getEquivalence() == null || EnumSet.of( ConceptMapEquivalence.EQUAL , ConceptMapEquivalence.RELATEDTO , ConceptMapEquivalence.EQUIVALENT, ConceptMapEquivalence.WIDER).contains(tgt.getEquivalence())) {
+ if (done) {
+ message = "Concept map "+su+" found multiple matches for "+src.getCode();
+ done = false;
+ } else {
+ done = true;
+ outcome = new Coding().setCode(tgt.getCode()).setSystem(list.get(0).group.getTarget());
+ }
+ } else if (tgt.getEquivalence() == ConceptMapEquivalence.UNMATCHED) {
+ done = true;
+ }
+ }
+ if (!done)
+ message = "Concept map "+su+" found no usable translation for "+src.getCode();
+ }
+ }
+ if (!done)
+ throw new FHIRException(message);
+ if (outcome == null)
+ return null;
+ if ("code".equals(fieldToReturn))
+ return new CodeType(outcome.getCode());
+ else
+ return outcome;
+ }
+ }
+
+
+ public Map getLibrary() {
+ return library;
+ }
+
+ public class PropertyWithType {
+ private String path;
+ private Property baseProperty;
+ private Property profileProperty;
+ private TypeDetails types;
+ public PropertyWithType(String path, Property baseProperty, Property profileProperty, TypeDetails types) {
+ super();
+ this.baseProperty = baseProperty;
+ this.profileProperty = profileProperty;
+ this.path = path;
+ this.types = types;
+ }
+
+ public TypeDetails getTypes() {
+ return types;
+ }
+ public String getPath() {
+ return path;
+ }
+
+ public Property getBaseProperty() {
+ return baseProperty;
+ }
+
+ public void setBaseProperty(Property baseProperty) {
+ this.baseProperty = baseProperty;
+ }
+
+ public Property getProfileProperty() {
+ return profileProperty;
+ }
+
+ public void setProfileProperty(Property profileProperty) {
+ this.profileProperty = profileProperty;
+ }
+
+ public String summary() {
+ return path;
+ }
+
+ }
+
+ public class VariableForProfiling {
+ private VariableMode mode;
+ private String name;
+ private PropertyWithType property;
+
+ public VariableForProfiling(VariableMode mode, String name, PropertyWithType property) {
+ super();
+ this.mode = mode;
+ this.name = name;
+ this.property = property;
+ }
+ public VariableMode getMode() {
+ return mode;
+ }
+ public String getName() {
+ return name;
+ }
+ public PropertyWithType getProperty() {
+ return property;
+ }
+ public String summary() {
+ return name+": "+property.summary();
+ }
+ }
+
+ public class VariablesForProfiling {
+ private List list = new ArrayList();
+ private boolean optional;
+ private boolean repeating;
+
+ public VariablesForProfiling(boolean optional, boolean repeating) {
+ this.optional = optional;
+ this.repeating = repeating;
+ }
+
+ public void add(VariableMode mode, String name, String path, Property property, TypeDetails types) {
+ add(mode, name, new PropertyWithType(path, property, null, types));
+ }
+
+ public void add(VariableMode mode, String name, String path, Property baseProperty, Property profileProperty, TypeDetails types) {
+ add(mode, name, new PropertyWithType(path, baseProperty, profileProperty, types));
+ }
+
+ public void add(VariableMode mode, String name, PropertyWithType property) {
+ VariableForProfiling vv = null;
+ for (VariableForProfiling v : list)
+ if ((v.mode == mode) && v.getName().equals(name))
+ vv = v;
+ if (vv != null)
+ list.remove(vv);
+ list.add(new VariableForProfiling(mode, name, property));
+ }
+
+ public VariablesForProfiling copy(boolean optional, boolean repeating) {
+ VariablesForProfiling result = new VariablesForProfiling(optional, repeating);
+ result.list.addAll(list);
+ return result;
+ }
+
+ public VariablesForProfiling copy() {
+ VariablesForProfiling result = new VariablesForProfiling(optional, repeating);
+ result.list.addAll(list);
+ return result;
+ }
+
+ public VariableForProfiling get(VariableMode mode, String name) {
+ if (mode == null) {
+ for (VariableForProfiling v : list)
+ if ((v.mode == VariableMode.OUTPUT) && v.getName().equals(name))
+ return v;
+ for (VariableForProfiling v : list)
+ if ((v.mode == VariableMode.INPUT) && v.getName().equals(name))
+ return v;
+ }
+ for (VariableForProfiling v : list)
+ if ((v.mode == mode) && v.getName().equals(name))
+ return v;
+ return null;
+ }
+
+ public String summary() {
+ CommaSeparatedStringBuilder s = new CommaSeparatedStringBuilder();
+ CommaSeparatedStringBuilder t = new CommaSeparatedStringBuilder();
+ for (VariableForProfiling v : list)
+ if (v.mode == VariableMode.INPUT)
+ s.append(v.summary());
+ else
+ t.append(v.summary());
+ return "source variables ["+s.toString()+"], target variables ["+t.toString()+"]";
+ }
+ }
+
+ public class StructureMapAnalysis {
+ private List profiles = new ArrayList();
+ private XhtmlNode summary;
+ public List getProfiles() {
+ return profiles;
+ }
+ public XhtmlNode getSummary() {
+ return summary;
+ }
+
+ }
+
+ /**
+ * Given a structure map, return a set of analyses on it.
+ *
+ * Returned:
+ * - a list or profiles for what it will create. First profile is the target
+ * - a table with a summary (in xhtml) for easy human undertanding of the mapping
+ *
+ *
+ * @param appInfo
+ * @param map
+ * @return
+ * @throws Exception
+ */
+ public StructureMapAnalysis analyse(Object appInfo, StructureMap map) throws Exception {
+ ids.clear();
+ StructureMapAnalysis result = new StructureMapAnalysis();
+ TransformContext context = new TransformContext(appInfo);
+ VariablesForProfiling vars = new VariablesForProfiling(false, false);
+ StructureMapGroupComponent start = map.getGroup().get(0);
+ for (StructureMapGroupInputComponent t : start.getInput()) {
+ PropertyWithType ti = resolveType(map, t.getType(), t.getMode());
+ if (t.getMode() == StructureMapInputMode.SOURCE)
+ vars.add(VariableMode.INPUT, t.getName(), ti);
+ else
+ vars.add(VariableMode.OUTPUT, t.getName(), createProfile(map, result.profiles, ti, start.getName(), start));
+ }
+
+ result.summary = new XhtmlNode(NodeType.Element, "table").setAttribute("class", "grid");
+ XhtmlNode tr = result.summary.addTag("tr");
+ tr.addTag("td").addTag("b").addText("Source");
+ tr.addTag("td").addTag("b").addText("Target");
+
+ log("Start Profiling Transform "+map.getUrl());
+ analyseGroup("", context, map, vars, start, result);
+ ProfileUtilities pu = new ProfileUtilities(worker, null, pkp);
+ for (StructureDefinition sd : result.getProfiles())
+ pu.cleanUpDifferential(sd);
+ return result;
+ }
+
+
+ private void analyseGroup(String indent, TransformContext context, StructureMap map, VariablesForProfiling vars, StructureMapGroupComponent group, StructureMapAnalysis result) throws Exception {
+ log(indent+"Analyse Group : "+group.getName());
+ // todo: extends
+ // todo: check inputs
+ XhtmlNode tr = result.summary.addTag("tr").setAttribute("class", "diff-title");
+ XhtmlNode xs = tr.addTag("td");
+ XhtmlNode xt = tr.addTag("td");
+ for (StructureMapGroupInputComponent inp : group.getInput()) {
+ if (inp.getMode() == StructureMapInputMode.SOURCE)
+ noteInput(vars, inp, VariableMode.INPUT, xs);
+ if (inp.getMode() == StructureMapInputMode.TARGET)
+ noteInput(vars, inp, VariableMode.OUTPUT, xt);
+ }
+ for (StructureMapGroupRuleComponent r : group.getRule()) {
+ analyseRule(indent+" ", context, map, vars, group, r, result);
+ }
+ }
+
+
+ private void noteInput(VariablesForProfiling vars, StructureMapGroupInputComponent inp, VariableMode mode, XhtmlNode xs) {
+ VariableForProfiling v = vars.get(mode, inp.getName());
+ if (v != null)
+ xs.addText("Input: "+v.property.getPath());
+ }
+
+ private void analyseRule(String indent, TransformContext context, StructureMap map, VariablesForProfiling vars, StructureMapGroupComponent group, StructureMapGroupRuleComponent rule, StructureMapAnalysis result) throws Exception {
+ log(indent+"Analyse rule : "+rule.getName());
+ XhtmlNode tr = result.summary.addTag("tr");
+ XhtmlNode xs = tr.addTag("td");
+ XhtmlNode xt = tr.addTag("td");
+
+ VariablesForProfiling srcVars = vars.copy();
+ if (rule.getSource().size() != 1)
+ throw new Exception("Rule \""+rule.getName()+"\": not handled yet");
+ VariablesForProfiling source = analyseSource(rule.getName(), context, srcVars, rule.getSourceFirstRep(), xs);
+
+ TargetWriter tw = new TargetWriter();
+ for (StructureMapGroupRuleTargetComponent t : rule.getTarget()) {
+ analyseTarget(rule.getName(), context, source, map, t, rule.getSourceFirstRep().getVariable(), tw, result.profiles, rule.getName());
+ }
+ tw.commit(xt);
+
+ for (StructureMapGroupRuleComponent childrule : rule.getRule()) {
+ analyseRule(indent+" ", context, map, source, group, childrule, result);
+ }
+// for (StructureMapGroupRuleDependentComponent dependent : rule.getDependent()) {
+// executeDependency(indent+" ", context, map, v, group, dependent); // do we need group here?
+// }
+ }
+
+ public class StringPair {
+ private String var;
+ private String desc;
+ public StringPair(String var, String desc) {
+ super();
+ this.var = var;
+ this.desc = desc;
+ }
+ public String getVar() {
+ return var;
+ }
+ public String getDesc() {
+ return desc;
+ }
+ }
+ public class TargetWriter {
+ private Map newResources = new HashMap();
+ private List assignments = new ArrayList();
+ private List keyProps = new ArrayList();
+ private CommaSeparatedStringBuilder txt = new CommaSeparatedStringBuilder();
+
+ public void newResource(String var, String name) {
+ newResources.put(var, name);
+ txt.append("new "+name);
+ }
+
+ public void valueAssignment(String context, String desc) {
+ assignments.add(new StringPair(context, desc));
+ txt.append(desc);
+ }
+
+ public void keyAssignment(String context, String desc) {
+ keyProps.add(new StringPair(context, desc));
+ txt.append(desc);
+ }
+ public void commit(XhtmlNode xt) {
+ if (newResources.size() == 1 && assignments.size() == 1 && newResources.containsKey(assignments.get(0).getVar()) && keyProps.size() == 1 && newResources.containsKey(keyProps.get(0).getVar()) ) {
+ xt.addText("new "+assignments.get(0).desc+" ("+keyProps.get(0).desc.substring(keyProps.get(0).desc.indexOf(".")+1)+")");
+ } else if (newResources.size() == 1 && assignments.size() == 1 && newResources.containsKey(assignments.get(0).getVar()) && keyProps.size() == 0) {
+ xt.addText("new "+assignments.get(0).desc);
+ } else {
+ xt.addText(txt.toString());
+ }
+ }
+ }
+
+ private VariablesForProfiling analyseSource(String ruleId, TransformContext context, VariablesForProfiling vars, StructureMapGroupRuleSourceComponent src, XhtmlNode td) throws Exception {
+ VariableForProfiling var = vars.get(VariableMode.INPUT, src.getContext());
+ if (var == null)
+ throw new FHIRException("Rule \""+ruleId+"\": Unknown input variable "+src.getContext());
+ PropertyWithType prop = var.getProperty();
+
+ boolean optional = false;
+ boolean repeating = false;
+
+ if (src.hasCondition()) {
+ optional = true;
+ }
+
+ if (src.hasElement()) {
+ Property element = prop.getBaseProperty().getChild(prop.types.getType(), src.getElement());
+ if (element == null)
+ throw new Exception("Rule \""+ruleId+"\": Unknown element name "+src.getElement());
+ if (element.getDefinition().getMin() == 0)
+ optional = true;
+ if (element.getDefinition().getMax().equals("*"))
+ repeating = true;
+ VariablesForProfiling result = vars.copy(optional, repeating);
+ TypeDetails type = new TypeDetails(CollectionStatus.SINGLETON);
+ for (TypeRefComponent tr : element.getDefinition().getType()) {
+ if (!tr.hasCode())
+ throw new Error("Rule \""+ruleId+"\": Element has no type");
+ ProfiledType pt = new ProfiledType(tr.getCode());
+ if (tr.hasProfile())
+ pt.addProfile(tr.getProfile());
+ if (element.getDefinition().hasBinding())
+ pt.addBinding(element.getDefinition().getBinding());
+ type.addType(pt);
+ }
+ td.addText(prop.getPath()+"."+src.getElement());
+ if (src.hasVariable())
+ result.add(VariableMode.INPUT, src.getVariable(), new PropertyWithType(prop.getPath()+"."+src.getElement(), element, null, type));
+ return result;
+ } else {
+ td.addText(prop.getPath()); // ditto!
+ return vars.copy(optional, repeating);
+ }
+ }
+
+
+ private void analyseTarget(String ruleId, TransformContext context, VariablesForProfiling vars, StructureMap map, StructureMapGroupRuleTargetComponent tgt, String tv, TargetWriter tw, List profiles, String sliceName) throws Exception {
+ VariableForProfiling var = null;
+ if (tgt.hasContext()) {
+ var = vars.get(VariableMode.OUTPUT, tgt.getContext());
+ if (var == null)
+ throw new Exception("Rule \""+ruleId+"\": target context not known: "+tgt.getContext());
+ if (!tgt.hasElement())
+ throw new Exception("Rule \""+ruleId+"\": Not supported yet");
+ }
+
+
+ TypeDetails type = null;
+ if (tgt.hasTransform()) {
+ type = analyseTransform(context, map, tgt, var, vars);
+ // profiling: dest.setProperty(tgt.getElement().hashCode(), tgt.getElement(), v);
+ } else {
+ Property vp = var.property.baseProperty.getChild(tgt.getElement(), tgt.getElement());
+ if (vp == null)
+ throw new Exception("Unknown Property "+tgt.getElement()+" on "+var.property.path);
+
+ type = new TypeDetails(CollectionStatus.SINGLETON, vp.getType(tgt.getElement()));
+ }
+
+ if (tgt.getTransform() == StructureMapTransform.CREATE) {
+ String s = getParamString(vars, tgt.getParameter().get(0));
+ if (worker.getResourceNames().contains(s))
+ tw.newResource(tgt.getVariable(), s);
+ } else {
+ boolean mapsSrc = false;
+ for (StructureMapGroupRuleTargetParameterComponent p : tgt.getParameter()) {
+ Type pr = p.getValue();
+ if (pr instanceof IdType && ((IdType) pr).asStringValue().equals(tv))
+ mapsSrc = true;
+ }
+ if (mapsSrc) {
+ if (var == null)
+ throw new Error("Rule \""+ruleId+"\": Attempt to assign with no context");
+ tw.valueAssignment(tgt.getContext(), var.property.getPath()+"."+tgt.getElement()+getTransformSuffix(tgt.getTransform()));
+ } else if (tgt.hasContext()) {
+ if (isSignificantElement(var.property, tgt.getElement())) {
+ String td = describeTransform(tgt);
+ if (td != null)
+ tw.keyAssignment(tgt.getContext(), var.property.getPath()+"."+tgt.getElement()+" = "+td);
+ }
+ }
+ }
+ Type fixed = generateFixedValue(tgt);
+
+ PropertyWithType prop = updateProfile(var, tgt.getElement(), type, map, profiles, sliceName, fixed, tgt);
+ if (tgt.hasVariable())
+ if (tgt.hasElement())
+ vars.add(VariableMode.OUTPUT, tgt.getVariable(), prop);
+ else
+ vars.add(VariableMode.OUTPUT, tgt.getVariable(), prop);
+ }
+
+ private Type generateFixedValue(StructureMapGroupRuleTargetComponent tgt) {
+ if (!allParametersFixed(tgt))
+ return null;
+ if (!tgt.hasTransform())
+ return null;
+ switch (tgt.getTransform()) {
+ case COPY: return tgt.getParameter().get(0).getValue();
+ case TRUNCATE: return null;
+ //case ESCAPE:
+ //case CAST:
+ //case APPEND:
+ case TRANSLATE: return null;
+ //case DATEOP,
+ //case UUID,
+ //case POINTER,
+ //case EVALUATE,
+ case CC:
+ CodeableConcept cc = new CodeableConcept();
+ cc.addCoding(buildCoding(tgt.getParameter().get(0).getValue(), tgt.getParameter().get(1).getValue()));
+ return cc;
+ case C:
+ return buildCoding(tgt.getParameter().get(0).getValue(), tgt.getParameter().get(1).getValue());
+ case QTY: return null;
+ //case ID,
+ //case CP,
+ default:
+ return null;
+ }
+ }
+
+ @SuppressWarnings("rawtypes")
+ private Coding buildCoding(Type value1, Type value2) {
+ return new Coding().setSystem(((PrimitiveType) value1).asStringValue()).setCode(((PrimitiveType) value2).asStringValue()) ;
+ }
+
+ private boolean allParametersFixed(StructureMapGroupRuleTargetComponent tgt) {
+ for (StructureMapGroupRuleTargetParameterComponent p : tgt.getParameter()) {
+ Type pr = p.getValue();
+ if (pr instanceof IdType)
+ return false;
+ }
+ return true;
+ }
+
+ private String describeTransform(StructureMapGroupRuleTargetComponent tgt) throws FHIRException {
+ switch (tgt.getTransform()) {
+ case COPY: return null;
+ case TRUNCATE: return null;
+ //case ESCAPE:
+ //case CAST:
+ //case APPEND:
+ case TRANSLATE: return null;
+ //case DATEOP,
+ //case UUID,
+ //case POINTER,
+ //case EVALUATE,
+ case CC: return describeTransformCCorC(tgt);
+ case C: return describeTransformCCorC(tgt);
+ case QTY: return null;
+ //case ID,
+ //case CP,
+ default:
+ return null;
+ }
+ }
+
+ @SuppressWarnings("rawtypes")
+ private String describeTransformCCorC(StructureMapGroupRuleTargetComponent tgt) throws FHIRException {
+ if (tgt.getParameter().size() < 2)
+ return null;
+ Type p1 = tgt.getParameter().get(0).getValue();
+ Type p2 = tgt.getParameter().get(1).getValue();
+ if (p1 instanceof IdType || p2 instanceof IdType)
+ return null;
+ if (!(p1 instanceof PrimitiveType) || !(p2 instanceof PrimitiveType))
+ return null;
+ String uri = ((PrimitiveType) p1).asStringValue();
+ String code = ((PrimitiveType) p2).asStringValue();
+ if (Utilities.noString(uri))
+ throw new FHIRException("Describe Transform, but the uri is blank");
+ if (Utilities.noString(code))
+ throw new FHIRException("Describe Transform, but the code is blank");
+ Coding c = buildCoding(uri, code);
+ return NarrativeGenerator.describeSystem(c.getSystem())+"#"+c.getCode()+(c.hasDisplay() ? "("+c.getDisplay()+")" : "");
+ }
+
+
+ private boolean isSignificantElement(PropertyWithType property, String element) {
+ if ("Observation".equals(property.getPath()))
+ return "code".equals(element);
+ else if ("Bundle".equals(property.getPath()))
+ return "type".equals(element);
+ else
+ return false;
+ }
+
+ private String getTransformSuffix(StructureMapTransform transform) {
+ switch (transform) {
+ case COPY: return "";
+ case TRUNCATE: return " (truncated)";
+ //case ESCAPE:
+ //case CAST:
+ //case APPEND:
+ case TRANSLATE: return " (translated)";
+ //case DATEOP,
+ //case UUID,
+ //case POINTER,
+ //case EVALUATE,
+ case CC: return " (--> CodeableConcept)";
+ case C: return " (--> Coding)";
+ case QTY: return " (--> Quantity)";
+ //case ID,
+ //case CP,
+ default:
+ return " {??)";
+ }
+ }
+
+ private PropertyWithType updateProfile(VariableForProfiling var, String element, TypeDetails type, StructureMap map, List profiles, String sliceName, Type fixed, StructureMapGroupRuleTargetComponent tgt) throws FHIRException {
+ if (var == null) {
+ assert (Utilities.noString(element));
+ // 1. start the new structure definition
+ StructureDefinition sdn = worker.fetchResource(StructureDefinition.class, type.getType());
+ if (sdn == null)
+ throw new FHIRException("Unable to find definition for "+type.getType());
+ ElementDefinition edn = sdn.getSnapshot().getElementFirstRep();
+ PropertyWithType pn = createProfile(map, profiles, new PropertyWithType(sdn.getId(), new Property(worker, edn, sdn), null, type), sliceName, tgt);
+
+// // 2. hook it into the base bundle
+// if (type.getType().startsWith("http://hl7.org/fhir/StructureDefinition/") && worker.getResourceNames().contains(type.getType().substring(40))) {
+// StructureDefinition sd = var.getProperty().profileProperty.getStructure();
+// ElementDefinition ed = sd.getDifferential().addElement();
+// ed.setPath("Bundle.entry");
+// ed.setName(sliceName);
+// ed.setMax("1"); // well, it is for now...
+// ed = sd.getDifferential().addElement();
+// ed.setPath("Bundle.entry.fullUrl");
+// ed.setMin(1);
+// ed = sd.getDifferential().addElement();
+// ed.setPath("Bundle.entry.resource");
+// ed.setMin(1);
+// ed.addType().setCode(pn.getProfileProperty().getStructure().getType()).setProfile(pn.getProfileProperty().getStructure().getUrl());
+// }
+ return pn;
+ } else {
+ assert (!Utilities.noString(element));
+ Property pvb = var.getProperty().getBaseProperty();
+ Property pvd = var.getProperty().getProfileProperty();
+ Property pc = pvb.getChild(element, var.property.types);
+ if (pc == null)
+ throw new DefinitionException("Unable to find a definition for "+pvb.getDefinition().getPath()+"."+element);
+
+ // the profile structure definition (derived)
+ StructureDefinition sd = var.getProperty().profileProperty.getStructure();
+ ElementDefinition ednew = sd.getDifferential().addElement();
+ ednew.setPath(var.getProperty().profileProperty.getDefinition().getPath()+"."+pc.getName());
+ ednew.setUserData("slice-name", sliceName);
+ ednew.setFixed(fixed);
+ for (ProfiledType pt : type.getProfiledTypes()) {
+ if (pt.hasBindings())
+ ednew.setBinding(pt.getBindings().get(0));
+ if (pt.getUri().startsWith("http://hl7.org/fhir/StructureDefinition/")) {
+ String t = pt.getUri().substring(40);
+ t = checkType(t, pc, pt.getProfiles());
+ if (t != null) {
+ if (pt.hasProfiles()) {
+ for (String p : pt.getProfiles())
+ if (t.equals("Reference"))
+ ednew.addType().setCode(t).setTargetProfile(p);
+ else
+ ednew.addType().setCode(t).setProfile(p);
+ } else
+ ednew.addType().setCode(t);
+ }
+ }
+ }
+
+ return new PropertyWithType(var.property.path+"."+element, pc, new Property(worker, ednew, sd), type);
+ }
+ }
+
+
+
+ private String checkType(String t, Property pvb, List profiles) throws FHIRException {
+ if (pvb.getDefinition().getType().size() == 1 && isCompatibleType(t, pvb.getDefinition().getType().get(0).getCode()) && profilesMatch(profiles, pvb.getDefinition().getType().get(0).getProfile()))
+ return null;
+ for (TypeRefComponent tr : pvb.getDefinition().getType()) {
+ if (isCompatibleType(t, tr.getCode()))
+ return tr.getCode(); // note what is returned - the base type, not the inferred mapping type
+ }
+ throw new FHIRException("The type "+t+" is not compatible with the allowed types for "+pvb.getDefinition().getPath()+" ("+pvb.getDefinition().typeSummary()+")");
+ }
+
+ private boolean profilesMatch(List profiles, String profile) {
+ return profiles == null || profiles.size() == 0 || (profiles.size() == 1 && profiles.get(0).equals(profile));
+ }
+
+ private boolean isCompatibleType(String t, String code) {
+ if (t.equals(code))
+ return true;
+ if (t.equals("string")) {
+ StructureDefinition sd = worker.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/"+code);
+ if (sd != null && sd.getBaseDefinition().equals("http://hl7.org/fhir/StructureDefinition/string"))
+ return true;
+ }
+ return false;
+ }
+
+ private TypeDetails analyseTransform(TransformContext context, StructureMap map, StructureMapGroupRuleTargetComponent tgt, VariableForProfiling var, VariablesForProfiling vars) throws FHIRException {
+ switch (tgt.getTransform()) {
+ case CREATE :
+ String p = getParamString(vars, tgt.getParameter().get(0));
+ return new TypeDetails(CollectionStatus.SINGLETON, p);
+ case COPY :
+ return getParam(vars, tgt.getParameter().get(0));
+ case EVALUATE :
+ ExpressionNode expr = (ExpressionNode) tgt.getUserData(MAP_EXPRESSION);
+ if (expr == null) {
+ expr = fpe.parse(getParamString(vars, tgt.getParameter().get(tgt.getParameter().size()-1)));
+ tgt.setUserData(MAP_WHERE_EXPRESSION, expr);
+ }
+ return fpe.check(vars, null, expr);
+
+////case TRUNCATE :
+//// String src = getParamString(vars, tgt.getParameter().get(0));
+//// String len = getParamString(vars, tgt.getParameter().get(1));
+//// if (Utilities.isInteger(len)) {
+//// int l = Integer.parseInt(len);
+//// if (src.length() > l)
+//// src = src.substring(0, l);
+//// }
+//// return new StringType(src);
+////case ESCAPE :
+//// throw new Error("Transform "+tgt.getTransform().toCode()+" not supported yet");
+////case CAST :
+//// throw new Error("Transform "+tgt.getTransform().toCode()+" not supported yet");
+////case APPEND :
+//// throw new Error("Transform "+tgt.getTransform().toCode()+" not supported yet");
+ case TRANSLATE :
+ return new TypeDetails(CollectionStatus.SINGLETON, "CodeableConcept");
+ case CC:
+ ProfiledType res = new ProfiledType("CodeableConcept");
+ if (tgt.getParameter().size() >= 2 && isParamId(vars, tgt.getParameter().get(1))) {
+ TypeDetails td = vars.get(null, getParamId(vars, tgt.getParameter().get(1))).property.types;
+ if (td != null && td.hasBinding())
+ // todo: do we need to check that there's no implicit translation her? I don't think we do...
+ res.addBinding(td.getBinding());
+ }
+ return new TypeDetails(CollectionStatus.SINGLETON, res);
+ case C:
+ return new TypeDetails(CollectionStatus.SINGLETON, "Coding");
+ case QTY:
+ return new TypeDetails(CollectionStatus.SINGLETON, "Quantity");
+ case REFERENCE :
+ VariableForProfiling vrs = vars.get(VariableMode.OUTPUT, getParamId(vars, tgt.getParameterFirstRep()));
+ if (vrs == null)
+ throw new FHIRException("Unable to resolve variable \""+getParamId(vars, tgt.getParameterFirstRep())+"\"");
+ String profile = vrs.property.getProfileProperty().getStructure().getUrl();
+ TypeDetails td = new TypeDetails(CollectionStatus.SINGLETON);
+ td.addType("Reference", profile);
+ return td;
+////case DATEOP :
+//// throw new Error("Transform "+tgt.getTransform().toCode()+" not supported yet");
+////case UUID :
+//// return new IdType(UUID.randomUUID().toString());
+////case POINTER :
+//// Base b = getParam(vars, tgt.getParameter().get(0));
+//// if (b instanceof Resource)
+//// return new UriType("urn:uuid:"+((Resource) b).getId());
+//// else
+//// throw new FHIRException("Transform engine cannot point at an element of type "+b.fhirType());
+ default:
+ throw new Error("Transform Unknown or not handled yet: "+tgt.getTransform().toCode());
+ }
+ }
+ private String getParamString(VariablesForProfiling vars, StructureMapGroupRuleTargetParameterComponent parameter) {
+ Type p = parameter.getValue();
+ if (p == null || p instanceof IdType)
+ return null;
+ if (!p.hasPrimitiveValue())
+ return null;
+ return p.primitiveValue();
+ }
+
+ private String getParamId(VariablesForProfiling vars, StructureMapGroupRuleTargetParameterComponent parameter) {
+ Type p = parameter.getValue();
+ if (p == null || !(p instanceof IdType))
+ return null;
+ return p.primitiveValue();
+ }
+
+ private boolean isParamId(VariablesForProfiling vars, StructureMapGroupRuleTargetParameterComponent parameter) {
+ Type p = parameter.getValue();
+ if (p == null || !(p instanceof IdType))
+ return false;
+ return vars.get(null, p.primitiveValue()) != null;
+ }
+
+ private TypeDetails getParam(VariablesForProfiling vars, StructureMapGroupRuleTargetParameterComponent parameter) throws DefinitionException {
+ Type p = parameter.getValue();
+ if (!(p instanceof IdType))
+ return new TypeDetails(CollectionStatus.SINGLETON, "http://hl7.org/fhir/StructureDefinition/"+p.fhirType());
+ else {
+ String n = ((IdType) p).asStringValue();
+ VariableForProfiling b = vars.get(VariableMode.INPUT, n);
+ if (b == null)
+ b = vars.get(VariableMode.OUTPUT, n);
+ if (b == null)
+ throw new DefinitionException("Variable "+n+" not found ("+vars.summary()+")");
+ return b.getProperty().getTypes();
+ }
+ }
+
+ private PropertyWithType createProfile(StructureMap map, List profiles, PropertyWithType prop, String sliceName, Base ctxt) throws DefinitionException {
+ if (prop.getBaseProperty().getDefinition().getPath().contains("."))
+ throw new DefinitionException("Unable to process entry point");
+
+ String type = prop.getBaseProperty().getDefinition().getPath();
+ String suffix = "";
+ if (ids.containsKey(type)) {
+ int id = ids.get(type);
+ id++;
+ ids.put(type, id);
+ suffix = "-"+Integer.toString(id);
+ } else
+ ids.put(type, 0);
+
+ StructureDefinition profile = new StructureDefinition();
+ profiles.add(profile);
+ profile.setDerivation(TypeDerivationRule.CONSTRAINT);
+ profile.setType(type);
+ profile.setBaseDefinition(prop.getBaseProperty().getStructure().getUrl());
+ profile.setName("Profile for "+profile.getType()+" for "+sliceName);
+ profile.setUrl(map.getUrl().replace("StructureMap", "StructureDefinition")+"-"+profile.getType()+suffix);
+ ctxt.setUserData("profile", profile.getUrl()); // then we can easily assign this profile url for validation later when we actually transform
+ profile.setId(map.getId()+"-"+profile.getType()+suffix);
+ profile.setStatus(map.getStatus());
+ profile.setExperimental(map.getExperimental());
+ profile.setDescription("Generated automatically from the mapping by the Java Reference Implementation");
+ for (ContactDetail c : map.getContact()) {
+ ContactDetail p = profile.addContact();
+ p.setName(c.getName());
+ for (ContactPoint cc : c.getTelecom())
+ p.addTelecom(cc);
+ }
+ profile.setDate(map.getDate());
+ profile.setCopyright(map.getCopyright());
+ profile.setFhirVersion(Constants.VERSION);
+ profile.setKind(prop.getBaseProperty().getStructure().getKind());
+ profile.setAbstract(false);
+ ElementDefinition ed = profile.getDifferential().addElement();
+ ed.setPath(profile.getType());
+ prop.profileProperty = new Property(worker, ed, profile);
+ return prop;
+ }
+
+ private PropertyWithType resolveType(StructureMap map, String type, StructureMapInputMode mode) throws Exception {
+ for (StructureMapStructureComponent imp : map.getStructure()) {
+ if ((imp.getMode() == StructureMapModelMode.SOURCE && mode == StructureMapInputMode.SOURCE) ||
+ (imp.getMode() == StructureMapModelMode.TARGET && mode == StructureMapInputMode.TARGET)) {
+ StructureDefinition sd = worker.fetchResource(StructureDefinition.class, imp.getUrl());
+ if (sd == null)
+ throw new Exception("Import "+imp.getUrl()+" cannot be resolved");
+ if (sd.getId().equals(type)) {
+ return new PropertyWithType(sd.getType(), new Property(worker, sd.getSnapshot().getElement().get(0), sd), null, new TypeDetails(CollectionStatus.SINGLETON, sd.getUrl()));
+ }
+ }
+ }
+ throw new Exception("Unable to find structure definition for "+type+" in imports");
+ }
+
+
+ public StructureMap generateMapFromMappings(StructureDefinition sd) throws IOException, FHIRException {
+ String id = getLogicalMappingId(sd);
+ if (id == null)
+ return null;
+ String prefix = ToolingExtensions.readStringExtension(sd, ToolingExtensions.EXT_MAPPING_PREFIX);
+ String suffix = ToolingExtensions.readStringExtension(sd, ToolingExtensions.EXT_MAPPING_SUFFIX);
+ if (prefix == null || suffix == null)
+ return null;
+ // we build this by text. Any element that has a mapping, we put it's mappings inside it....
+ StringBuilder b = new StringBuilder();
+ b.append(prefix);
+
+ ElementDefinition root = sd.getSnapshot().getElementFirstRep();
+ String m = getMapping(root, id);
+ if (m != null)
+ b.append(m+"\r\n");
+ addChildMappings(b, id, "", sd, root, false);
+ b.append("\r\n");
+ b.append(suffix);
+ b.append("\r\n");
+ StructureMap map = parse(b.toString());
+ map.setId(tail(map.getUrl()));
+ if (!map.hasStatus())
+ map.setStatus(PublicationStatus.DRAFT);
+ map.getText().setStatus(NarrativeStatus.GENERATED);
+ map.getText().setDiv(new XhtmlNode(NodeType.Element, "div"));
+ map.getText().getDiv().addTag("pre").addText(render(map));
+ return map;
+ }
+
+
+ private String tail(String url) {
+ return url.substring(url.lastIndexOf("/")+1);
+ }
+
+
+ private void addChildMappings(StringBuilder b, String id, String indent, StructureDefinition sd, ElementDefinition ed, boolean inner) throws DefinitionException {
+ boolean first = true;
+ List children = ProfileUtilities.getChildMap(sd, ed);
+ for (ElementDefinition child : children) {
+ if (first && inner) {
+ b.append(" then {\r\n");
+ first = false;
+ }
+ String map = getMapping(child, id);
+ if (map != null) {
+ b.append(indent+" "+child.getPath()+": "+map);
+ addChildMappings(b, id, indent+" ", sd, child, true);
+ b.append("\r\n");
+ }
+ }
+ if (!first && inner)
+ b.append(indent+"}");
+
+ }
+
+
+ private String getMapping(ElementDefinition ed, String id) {
+ for (ElementDefinitionMappingComponent map : ed.getMapping())
+ if (id.equals(map.getIdentity()))
+ return map.getMap();
+ return null;
+ }
+
+
+ private String getLogicalMappingId(StructureDefinition sd) {
+ String id = null;
+ for (StructureDefinitionMappingComponent map : sd.getMapping()) {
+ if ("http://hl7.org/fhir/logical".equals(map.getUri()))
+ return map.getIdentity();
+ }
+ return null;
+ }
+
+}
diff --git a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/context/BaseWorkerContext.java b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/context/BaseWorkerContext.java
index feee569839c..1ed5df4b552 100644
--- a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/context/BaseWorkerContext.java
+++ b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/context/BaseWorkerContext.java
@@ -6,13 +6,7 @@ import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
import org.apache.commons.codec.Charsets;
import org.apache.commons.lang3.StringUtils;
@@ -250,7 +244,7 @@ public abstract class BaseWorkerContext implements IWorkerContext {
return laterVersion(newParts[i], oldParts[i]);
}
// This should never happen
- throw new Error("Delimited versions have exact match for delimiter '"+delimiter+"' : "+newParts+" vs "+oldParts);
+ throw new Error("Delimited versions have exact match for delimiter '"+delimiter+"' : "+ Arrays.asList(newParts)+" vs "+Arrays.asList(oldParts));
}
protected void seeMetadataResource(T r, Map map, boolean addId) throws FHIRException {
@@ -1045,6 +1039,8 @@ public abstract class BaseWorkerContext implements IWorkerContext {
return (T) maps.get(uri);
if (transforms.containsKey(uri))
return (T) transforms.get(uri);
+ if (questionnaires.containsKey(uri))
+ return (T) questionnaires.get(uri);
return null;
} else if (class_ == StructureDefinition.class) {
return (T) structures.get(uri);
@@ -1052,6 +1048,8 @@ public abstract class BaseWorkerContext implements IWorkerContext {
return (T) valueSets.get(uri);
} else if (class_ == CodeSystem.class) {
return (T) codeSystems.get(uri);
+ } else if (class_ == ConceptMap.class) {
+ return (T) maps.get(uri);
} else if (class_ == OperationDefinition.class) {
OperationDefinition od = operations.get(uri);
return (T) od;
@@ -1069,7 +1067,7 @@ public abstract class BaseWorkerContext implements IWorkerContext {
}
}
if (class_ == Questionnaire.class)
- return null;
+ return (T) questionnaires.get(uri);
if (class_ == null) {
if (uri.matches(Constants.URI_REGEX) && !uri.contains("ValueSet"))
return null;
diff --git a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/utils/FHIRPathEngine.java b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/utils/FHIRPathEngine.java
index ef4f05d3e69..80e2f7400bd 100644
--- a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/utils/FHIRPathEngine.java
+++ b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/utils/FHIRPathEngine.java
@@ -1,10 +1,11 @@
package org.hl7.fhir.r4.utils;
//import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
-
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
import ca.uhn.fhir.util.ElementUtil;
+
import org.apache.commons.lang3.NotImplementedException;
+import org.apache.http.protocol.ExecutionContext;
import org.fhir.ucum.Decimal;
import org.fhir.ucum.Pair;
import org.fhir.ucum.UcumException;
@@ -24,8 +25,13 @@ import org.hl7.fhir.r4.utils.FHIRPathEngine.IEvaluationContext.FunctionDetails;
import org.hl7.fhir.utilities.Utilities;
import java.math.BigDecimal;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
import java.util.*;
+import static org.apache.commons.lang3.StringUtils.length;
+
/**
*
* @author Grahame Grieve
@@ -731,7 +737,7 @@ public class FHIRPathEngine {
String s = lexer.take();
if (s.equals("year") || s.equals("years"))
ucum = "a";
- else if (s.equals("month") || s.equals("month"))
+ else if (s.equals("month") || s.equals("months"))
ucum = "mo";
else if (s.equals("week") || s.equals("weeks"))
ucum = "wk";
@@ -2072,8 +2078,9 @@ public class FHIRPathEngine {
result.add(item);
} else
getChildrenByName(item, exp.getName(), result);
- // todo: GG 1st April 201 - why do this?
if (result.size() == 0 && atEntry && context.appInfo != null) {
+ // well, we didn't get a match on the name - we'll see if the name matches a constant known by the context.
+ // (if the name does match, and the user wants to get the constant value, they'll have to try harder...
Base temp = hostServices.resolveConstant(context.appInfo, exp.getName());
if (temp != null) {
result.add(temp);
@@ -3280,7 +3287,7 @@ public class FHIRPathEngine {
return Quantity.fromUcum(v, s.substring(1, s.length()-1));
if (s.equals("year") || s.equals("years"))
return Quantity.fromUcum(v, "a");
- else if (s.equals("month") || s.equals("month"))
+ else if (s.equals("month") || s.equals("months"))
return Quantity.fromUcum(v, "mo");
else if (s.equals("week") || s.equals("weeks"))
return Quantity.fromUcum(v, "wk");