001package org.hl7.fhir.r4.utils; 002 003// remember group resolution 004// trace - account for which wasn't transformed in the source 005 006import java.io.IOException; 007import java.util.ArrayList; 008import java.util.EnumSet; 009import java.util.HashMap; 010import java.util.HashSet; 011import java.util.List; 012import java.util.Map; 013import java.util.Set; 014import java.util.UUID; 015 016import org.hl7.fhir.r4.conformance.ProfileUtilities; 017import org.hl7.fhir.r4.conformance.ProfileUtilities.ProfileKnowledgeProvider; 018import org.hl7.fhir.r4.context.IWorkerContext; 019import org.hl7.fhir.r4.context.IWorkerContext.ValidationResult; 020import org.hl7.fhir.r4.elementmodel.Element; 021import org.hl7.fhir.r4.elementmodel.Property; 022import org.hl7.fhir.r4.model.Base; 023import org.hl7.fhir.r4.model.BooleanType; 024import org.hl7.fhir.r4.model.CanonicalType; 025import org.hl7.fhir.r4.model.CodeType; 026import org.hl7.fhir.r4.model.CodeableConcept; 027import org.hl7.fhir.r4.model.Coding; 028import org.hl7.fhir.r4.model.ConceptMap; 029import org.hl7.fhir.r4.model.ConceptMap.ConceptMapGroupComponent; 030import org.hl7.fhir.r4.model.ConceptMap.ConceptMapGroupUnmappedMode; 031import org.hl7.fhir.r4.model.ConceptMap.SourceElementComponent; 032import org.hl7.fhir.r4.model.ConceptMap.TargetElementComponent; 033import org.hl7.fhir.r4.model.Constants; 034import org.hl7.fhir.r4.model.ContactDetail; 035import org.hl7.fhir.r4.model.ContactPoint; 036import org.hl7.fhir.r4.model.DecimalType; 037import org.hl7.fhir.r4.model.ElementDefinition; 038import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionMappingComponent; 039import org.hl7.fhir.r4.model.ElementDefinition.TypeRefComponent; 040import org.hl7.fhir.r4.model.Enumeration; 041import org.hl7.fhir.r4.model.Enumerations.ConceptMapEquivalence; 042import org.hl7.fhir.r4.model.Enumerations.PublicationStatus; 043import org.hl7.fhir.r4.model.ExpressionNode; 044import org.hl7.fhir.r4.model.ExpressionNode.CollectionStatus; 045import org.hl7.fhir.r4.model.IdType; 046import org.hl7.fhir.r4.model.IntegerType; 047import org.hl7.fhir.r4.model.Narrative.NarrativeStatus; 048import org.hl7.fhir.r4.model.PrimitiveType; 049import org.hl7.fhir.r4.model.Reference; 050import org.hl7.fhir.r4.model.Resource; 051import org.hl7.fhir.r4.model.ResourceFactory; 052import org.hl7.fhir.r4.model.StringType; 053import org.hl7.fhir.r4.model.StructureDefinition; 054import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionMappingComponent; 055import org.hl7.fhir.r4.model.StructureDefinition.TypeDerivationRule; 056import org.hl7.fhir.r4.model.StructureMap; 057import org.hl7.fhir.r4.model.StructureMap.StructureMapContextType; 058import org.hl7.fhir.r4.model.StructureMap.StructureMapGroupComponent; 059import org.hl7.fhir.r4.model.StructureMap.StructureMapGroupInputComponent; 060import org.hl7.fhir.r4.model.StructureMap.StructureMapGroupRuleComponent; 061import org.hl7.fhir.r4.model.StructureMap.StructureMapGroupRuleDependentComponent; 062import org.hl7.fhir.r4.model.StructureMap.StructureMapGroupRuleSourceComponent; 063import org.hl7.fhir.r4.model.StructureMap.StructureMapGroupRuleTargetComponent; 064import org.hl7.fhir.r4.model.StructureMap.StructureMapGroupRuleTargetParameterComponent; 065import org.hl7.fhir.r4.model.StructureMap.StructureMapGroupTypeMode; 066import org.hl7.fhir.r4.model.StructureMap.StructureMapInputMode; 067import org.hl7.fhir.r4.model.StructureMap.StructureMapSourceListMode; 068import org.hl7.fhir.r4.model.StructureMap.StructureMapTargetListMode; 069import org.hl7.fhir.r4.model.StructureMap.StructureMapModelMode; 070import org.hl7.fhir.r4.model.StructureMap.StructureMapStructureComponent; 071import org.hl7.fhir.r4.model.StructureMap.StructureMapTransform; 072import org.hl7.fhir.r4.model.Type; 073import org.hl7.fhir.r4.model.TypeDetails; 074import org.hl7.fhir.r4.model.TypeDetails.ProfiledType; 075import org.hl7.fhir.r4.model.UriType; 076import org.hl7.fhir.r4.model.ValueSet; 077import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent; 078import org.hl7.fhir.r4.terminologies.ValueSetExpander.ValueSetExpansionOutcome; 079import org.hl7.fhir.r4.utils.FHIRLexer.FHIRLexerException; 080import org.hl7.fhir.r4.utils.FHIRPathEngine.IEvaluationContext; 081import org.hl7.fhir.exceptions.DefinitionException; 082import org.hl7.fhir.exceptions.FHIRException; 083import org.hl7.fhir.exceptions.FHIRFormatError; 084import org.hl7.fhir.exceptions.PathEngineException; 085import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; 086import org.hl7.fhir.utilities.TextFile; 087import org.hl7.fhir.utilities.Utilities; 088import org.hl7.fhir.utilities.xhtml.NodeType; 089import org.hl7.fhir.utilities.xhtml.XhtmlNode; 090 091/** 092 * Services in this class: 093 * 094 * string render(map) - take a structure and convert it to text 095 * map parse(text) - take a text representation and parse it 096 * getTargetType(map) - return the definition for the type to create to hand in 097 * transform(appInfo, source, map, target) - transform from source to target following the map 098 * analyse(appInfo, map) - generate profiles and other analysis artifacts for the targets of the transform 099 * map generateMapFromMappings(StructureDefinition) - build a mapping from a structure definition with loigcal mappings 100 * 101 * @author Grahame Grieve 102 * 103 */ 104public class StructureMapUtilities { 105 106 public class ResolvedGroup { 107 public StructureMapGroupComponent target; 108 public StructureMap targetMap; 109 } 110 public static final String MAP_WHERE_CHECK = "map.where.check"; 111 public static final String MAP_WHERE_EXPRESSION = "map.where.expression"; 112 public static final String MAP_SEARCH_EXPRESSION = "map.search.expression"; 113 public static final String MAP_EXPRESSION = "map.transform.expression"; 114 private static final boolean RENDER_MULTIPLE_TARGETS_ONELINE = true; 115 private static final String AUTO_VAR_NAME = "vvv"; 116 117 public interface ITransformerServices { 118 // public boolean validateByValueSet(Coding code, String valuesetId); 119 public void log(String message); // log internal progress 120 public Base createType(Object appInfo, String name) throws FHIRException; 121 public Base createResource(Object appInfo, Base res, boolean atRootofTransform); // an already created resource is provided; this is to identify/store it 122 public Coding translate(Object appInfo, Coding source, String conceptMapUrl) throws FHIRException; 123 // public Coding translate(Coding code) 124 // ValueSet validation operation 125 // Translation operation 126 // Lookup another tree of data 127 // Create an instance tree 128 // Return the correct string format to refer to a tree (input or output) 129 public Base resolveReference(Object appContext, String url) throws FHIRException; 130 public List<Base> performSearch(Object appContext, String url) throws FHIRException; 131 } 132 133 private class FFHIRPathHostServices implements IEvaluationContext{ 134 135 public Base resolveConstant(Object appContext, String name) throws PathEngineException { 136 Variables vars = (Variables) appContext; 137 Base res = vars.get(VariableMode.INPUT, name); 138 if (res == null) 139 res = vars.get(VariableMode.OUTPUT, name); 140 return res; 141 } 142 143 @Override 144 public TypeDetails resolveConstantType(Object appContext, String name) throws PathEngineException { 145 if (!(appContext instanceof VariablesForProfiling)) 146 throw new Error("Internal Logic Error (wrong type '"+appContext.getClass().getName()+"' in resolveConstantType)"); 147 VariablesForProfiling vars = (VariablesForProfiling) appContext; 148 VariableForProfiling v = vars.get(null, name); 149 if (v == null) 150 throw new PathEngineException("Unknown variable '"+name+"' from variables "+vars.summary()); 151 return v.property.types; 152 } 153 154 @Override 155 public boolean log(String argument, List<Base> focus) { 156 throw new Error("Not Implemented Yet"); 157 } 158 159 @Override 160 public FunctionDetails resolveFunction(String functionName) { 161 return null; // throw new Error("Not Implemented Yet"); 162 } 163 164 @Override 165 public TypeDetails checkFunction(Object appContext, String functionName, List<TypeDetails> parameters) throws PathEngineException { 166 throw new Error("Not Implemented Yet"); 167 } 168 169 @Override 170 public List<Base> executeFunction(Object appContext, String functionName, List<List<Base>> parameters) { 171 throw new Error("Not Implemented Yet"); 172 } 173 174 @Override 175 public Base resolveReference(Object appContext, String url) throws FHIRException { 176 if (services == null) 177 return null; 178 return services.resolveReference(appContext, url); 179 } 180 181 } 182 private IWorkerContext worker; 183 private FHIRPathEngine fpe; 184 private ITransformerServices services; 185 private ProfileKnowledgeProvider pkp; 186 private Map<String, Integer> ids = new HashMap<String, Integer>(); 187 188 public StructureMapUtilities(IWorkerContext worker, ITransformerServices services, ProfileKnowledgeProvider pkp) { 189 super(); 190 this.worker = worker; 191 this.services = services; 192 this.pkp = pkp; 193 fpe = new FHIRPathEngine(worker); 194 fpe.setHostServices(new FFHIRPathHostServices()); 195 } 196 197 public StructureMapUtilities(IWorkerContext worker, ITransformerServices services) { 198 super(); 199 this.worker = worker; 200 this.services = services; 201 fpe = new FHIRPathEngine(worker); 202 fpe.setHostServices(new FFHIRPathHostServices()); 203 } 204 205 public StructureMapUtilities(IWorkerContext worker) { 206 super(); 207 this.worker = worker; 208 fpe = new FHIRPathEngine(worker); 209 fpe.setHostServices(new FFHIRPathHostServices()); 210 } 211 212 public static String render(StructureMap map) { 213 StringBuilder b = new StringBuilder(); 214 b.append("map \""); 215 b.append(map.getUrl()); 216 b.append("\" = \""); 217 b.append(Utilities.escapeJava(map.getName())); 218 b.append("\"\r\n\r\n"); 219 220 renderConceptMaps(b, map); 221 renderUses(b, map); 222 renderImports(b, map); 223 for (StructureMapGroupComponent g : map.getGroup()) 224 renderGroup(b, g); 225 return b.toString(); 226 } 227 228 private static void renderConceptMaps(StringBuilder b, StructureMap map) { 229 for (Resource r : map.getContained()) { 230 if (r instanceof ConceptMap) { 231 produceConceptMap(b, (ConceptMap) r); 232 } 233 } 234 } 235 236 private static void produceConceptMap(StringBuilder b, ConceptMap cm) { 237 b.append("conceptmap \""); 238 b.append(cm.getId()); 239 b.append("\" {\r\n"); 240 Map<String, String> prefixesSrc = new HashMap<String, String>(); 241 Map<String, String> prefixesTgt = new HashMap<String, String>(); 242 char prefix = 's'; 243 for (ConceptMapGroupComponent cg : cm.getGroup()) { 244 if (!prefixesSrc.containsKey(cg.getSource())) { 245 prefixesSrc.put(cg.getSource(), String.valueOf(prefix)); 246 b.append(" prefix "); 247 b.append(prefix); 248 b.append(" = \""); 249 b.append(cg.getSource()); 250 b.append("\"\r\n"); 251 prefix++; 252 } 253 if (!prefixesTgt.containsKey(cg.getTarget())) { 254 prefixesTgt.put(cg.getTarget(), String.valueOf(prefix)); 255 b.append(" prefix "); 256 b.append(prefix); 257 b.append(" = \""); 258 b.append(cg.getTarget()); 259 b.append("\"\r\n"); 260 prefix++; 261 } 262 } 263 b.append("\r\n"); 264 for (ConceptMapGroupComponent cg : cm.getGroup()) { 265 if (cg.hasUnmapped()) { 266 b.append(" unmapped for "); 267 b.append(prefix); 268 b.append(" = "); 269 b.append(cg.getUnmapped().getMode()); 270 b.append("\r\n"); 271 } 272 } 273 274 for (ConceptMapGroupComponent cg : cm.getGroup()) { 275 for (SourceElementComponent ce : cg.getElement()) { 276 b.append(" "); 277 b.append(prefixesSrc.get(cg.getSource())); 278 b.append(":"); 279 b.append(ce.getCode()); 280 b.append(" "); 281 b.append(getChar(ce.getTargetFirstRep().getEquivalence())); 282 b.append(" "); 283 b.append(prefixesTgt.get(cg.getTarget())); 284 b.append(":"); 285 b.append(ce.getTargetFirstRep().getCode()); 286 b.append("\r\n"); 287 } 288 } 289 b.append("}\r\n\r\n"); 290 } 291 292 private static Object getChar(ConceptMapEquivalence equivalence) { 293 switch (equivalence) { 294 case RELATEDTO: return "-"; 295 case EQUAL: return "="; 296 case EQUIVALENT: return "=="; 297 case DISJOINT: return "!="; 298 case UNMATCHED: return "--"; 299 case WIDER: return "<="; 300 case SUBSUMES: return "<-"; 301 case NARROWER: return ">="; 302 case SPECIALIZES: return ">-"; 303 case INEXACT: return "~"; 304 default: return "??"; 305 } 306 } 307 308 private static void renderUses(StringBuilder b, StructureMap map) { 309 for (StructureMapStructureComponent s : map.getStructure()) { 310 b.append("uses \""); 311 b.append(s.getUrl()); 312 b.append("\" "); 313 if (s.hasAlias()) { 314 b.append("alias "); 315 b.append(s.getAlias()); 316 b.append(" "); 317 } 318 b.append("as "); 319 b.append(s.getMode().toCode()); 320 b.append("\r\n"); 321 renderDoco(b, s.getDocumentation()); 322 } 323 if (map.hasStructure()) 324 b.append("\r\n"); 325 } 326 327 private static void renderImports(StringBuilder b, StructureMap map) { 328 for (UriType s : map.getImport()) { 329 b.append("imports \""); 330 b.append(s.getValue()); 331 b.append("\"\r\n"); 332 } 333 if (map.hasImport()) 334 b.append("\r\n"); 335 } 336 337 public static String groupToString(StructureMapGroupComponent g) { 338 StringBuilder b = new StringBuilder(); 339 renderGroup(b, g); 340 return b.toString(); 341 } 342 343 private static void renderGroup(StringBuilder b, StructureMapGroupComponent g) { 344 b.append("group "); 345 switch (g.getTypeMode()) { 346 case TYPES: 347 b.append("for types"); 348 break; 349 case TYPEANDTYPES: 350 b.append("for type+types "); 351 break; 352 default: // NONE, NULL 353 } 354 b.append(g.getName()); 355 if (g.hasExtends()) { 356 b.append(" extends "); 357 b.append(g.getExtends()); 358 } 359 if (g.hasDocumentation()) 360 renderDoco(b, g.getDocumentation()); 361 b.append("\r\n"); 362 for (StructureMapGroupInputComponent gi : g.getInput()) { 363 b.append(" input "); 364 b.append(gi.getName()); 365 if (gi.hasType()) { 366 b.append(" : "); 367 b.append(gi.getType()); 368 } 369 b.append(" as "); 370 b.append(gi.getMode().toCode()); 371 b.append("\r\n"); 372 } 373 if (g.hasInput()) 374 b.append("\r\n"); 375 for (StructureMapGroupRuleComponent r : g.getRule()) { 376 renderRule(b, r, 2); 377 } 378 b.append("\r\nendgroup\r\n"); 379 } 380 381 public static String ruleToString(StructureMapGroupRuleComponent r) { 382 StringBuilder b = new StringBuilder(); 383 renderRule(b, r, 0); 384 return b.toString(); 385 } 386 387 private static void renderRule(StringBuilder b, StructureMapGroupRuleComponent r, int indent) { 388 for (int i = 0; i < indent; i++) 389 b.append(' '); 390 b.append(r.getName()); 391 b.append(" : for "); 392 boolean canBeAbbreviated = checkisSimple(r); 393 394 boolean first = true; 395 for (StructureMapGroupRuleSourceComponent rs : r.getSource()) { 396 if (first) 397 first = false; 398 else 399 b.append(", "); 400 renderSource(b, rs, canBeAbbreviated); 401 } 402 if (r.getTarget().size() > 1) { 403 b.append(" make "); 404 first = true; 405 for (StructureMapGroupRuleTargetComponent rt : r.getTarget()) { 406 if (first) 407 first = false; 408 else 409 b.append(", "); 410 if (RENDER_MULTIPLE_TARGETS_ONELINE) 411 b.append(' '); 412 else { 413 b.append("\r\n"); 414 for (int i = 0; i < indent+4; i++) 415 b.append(' '); 416 } 417 renderTarget(b, rt, false); 418 } 419 } else if (r.hasTarget()) { 420 b.append(" make "); 421 renderTarget(b, r.getTarget().get(0), canBeAbbreviated); 422 } 423 if (!canBeAbbreviated) { 424 if (r.hasRule()) { 425 b.append(" then {\r\n"); 426 renderDoco(b, r.getDocumentation()); 427 for (StructureMapGroupRuleComponent ir : r.getRule()) { 428 renderRule(b, ir, indent+2); 429 } 430 for (int i = 0; i < indent; i++) 431 b.append(' '); 432 b.append("}\r\n"); 433 } else { 434 if (r.hasDependent()) { 435 b.append(" then "); 436 first = true; 437 for (StructureMapGroupRuleDependentComponent rd : r.getDependent()) { 438 if (first) 439 first = false; 440 else 441 b.append(", "); 442 b.append(rd.getName()); 443 b.append("("); 444 boolean ifirst = true; 445 for (StringType rdp : rd.getVariable()) { 446 if (ifirst) 447 ifirst = false; 448 else 449 b.append(", "); 450 b.append(rdp.asStringValue()); 451 } 452 b.append(")"); 453 } 454 } 455 } 456 } 457 renderDoco(b, r.getDocumentation()); 458 b.append("\r\n"); 459 } 460 461 private static boolean checkisSimple(StructureMapGroupRuleComponent r) { 462 return 463 (r.getSource().size() == 1 && r.getSourceFirstRep().hasElement() && r.getSourceFirstRep().hasVariable()) && 464 (r.getTarget().size() == 1 && r.getTargetFirstRep().hasVariable() && (r.getTargetFirstRep().getTransform() == null || r.getTargetFirstRep().getTransform() == StructureMapTransform.CREATE) && r.getTargetFirstRep().getParameter().size() == 0) && 465 (r.getDependent().size() == 0); 466 } 467 468 public static String sourceToString(StructureMapGroupRuleSourceComponent r) { 469 StringBuilder b = new StringBuilder(); 470 renderSource(b, r, false); 471 return b.toString(); 472 } 473 474 private static void renderSource(StringBuilder b, StructureMapGroupRuleSourceComponent rs, boolean abbreviate) { 475 b.append(rs.getContext()); 476 if (rs.getContext().equals("@search")) { 477 b.append('('); 478 b.append(rs.getElement()); 479 b.append(')'); 480 } else if (rs.hasElement()) { 481 b.append('.'); 482 b.append(rs.getElement()); 483 } 484 if (rs.hasType()) { 485 b.append(" : "); 486 b.append(rs.getType()); 487 if (rs.hasMin()) { 488 b.append(" "); 489 b.append(rs.getMin()); 490 b.append(".."); 491 b.append(rs.getMax()); 492 } 493 } 494 495 if (rs.hasListMode()) { 496 b.append(" "); 497 b.append(rs.getListMode().toCode()); 498 } 499 if (rs.hasDefaultValue()) { 500 b.append(" default "); 501 assert rs.getDefaultValue() instanceof StringType; 502 b.append("\""+Utilities.escapeJson(((StringType) rs.getDefaultValue()).asStringValue())+"\""); 503 } 504 if (!abbreviate && rs.hasVariable()) { 505 b.append(" as "); 506 b.append(rs.getVariable()); 507 } 508 if (rs.hasCondition()) { 509 b.append(" where "); 510 b.append(rs.getCondition()); 511 } 512 if (rs.hasCheck()) { 513 b.append(" check "); 514 b.append(rs.getCheck()); 515 } 516 } 517 518 public static String targetToString(StructureMapGroupRuleTargetComponent rt) { 519 StringBuilder b = new StringBuilder(); 520 renderTarget(b, rt, false); 521 return b.toString(); 522 } 523 524 private static void renderTarget(StringBuilder b, StructureMapGroupRuleTargetComponent rt, boolean abbreviate) { 525 if (rt.hasContext()) { 526 if (rt.getContextType() == StructureMapContextType.TYPE) 527 b.append("@"); 528 b.append(rt.getContext()); 529 if (rt.hasElement()) { 530 b.append('.'); 531 b.append(rt.getElement()); 532 } 533 } 534 if (!abbreviate && rt.hasTransform()) { 535 if (rt.hasContext()) 536 b.append(" = "); 537 if (rt.getTransform() == StructureMapTransform.COPY && rt.getParameter().size() == 1) { 538 renderTransformParam(b, rt.getParameter().get(0)); 539 } else if (rt.getTransform() == StructureMapTransform.EVALUATE && rt.getParameter().size() == 1) { 540 b.append("("); 541 b.append("\""+((StringType) rt.getParameter().get(0).getValue()).asStringValue()+"\""); 542 b.append(")"); 543 } else if (rt.getTransform() == StructureMapTransform.EVALUATE && rt.getParameter().size() == 2) { 544 b.append(rt.getTransform().toCode()); 545 b.append("("); 546 b.append(((IdType) rt.getParameter().get(0).getValue()).asStringValue()); 547 b.append("\""+((StringType) rt.getParameter().get(1).getValue()).asStringValue()+"\""); 548 b.append(")"); 549 } else { 550 b.append(rt.getTransform().toCode()); 551 b.append("("); 552 boolean first = true; 553 for (StructureMapGroupRuleTargetParameterComponent rtp : rt.getParameter()) { 554 if (first) 555 first = false; 556 else 557 b.append(", "); 558 renderTransformParam(b, rtp); 559 } 560 b.append(")"); 561 } 562 } 563 if (!abbreviate && rt.hasVariable()) { 564 b.append(" as "); 565 b.append(rt.getVariable()); 566 } 567 for (Enumeration<StructureMapTargetListMode> lm : rt.getListMode()) { 568 b.append(" "); 569 b.append(lm.getValue().toCode()); 570 if (lm.getValue() == StructureMapTargetListMode.SHARE) { 571 b.append(" "); 572 b.append(rt.getListRuleId()); 573 } 574 } 575 } 576 577 public static String paramToString(StructureMapGroupRuleTargetParameterComponent rtp) { 578 StringBuilder b = new StringBuilder(); 579 renderTransformParam(b, rtp); 580 return b.toString(); 581 } 582 583 private static void renderTransformParam(StringBuilder b, StructureMapGroupRuleTargetParameterComponent rtp) { 584 try { 585 if (rtp.hasValueBooleanType()) 586 b.append(rtp.getValueBooleanType().asStringValue()); 587 else if (rtp.hasValueDecimalType()) 588 b.append(rtp.getValueDecimalType().asStringValue()); 589 else if (rtp.hasValueIdType()) 590 b.append(rtp.getValueIdType().asStringValue()); 591 else if (rtp.hasValueDecimalType()) 592 b.append(rtp.getValueDecimalType().asStringValue()); 593 else if (rtp.hasValueIntegerType()) 594 b.append(rtp.getValueIntegerType().asStringValue()); 595 else 596 b.append("\""+Utilities.escapeJava(rtp.getValueStringType().asStringValue())+"\""); 597 } catch (FHIRException e) { 598 e.printStackTrace(); 599 b.append("error!"); 600 } 601 } 602 603 private static void renderDoco(StringBuilder b, String doco) { 604 if (Utilities.noString(doco)) 605 return; 606 b.append(" // "); 607 b.append(doco.replace("\r\n", " ").replace("\r", " ").replace("\n", " ")); 608 } 609 610 public StructureMap parse(String text) throws FHIRException { 611 FHIRLexer lexer = new FHIRLexer(text); 612 if (lexer.done()) 613 throw lexer.error("Map Input cannot be empty"); 614 lexer.skipComments(); 615 lexer.token("map"); 616 StructureMap result = new StructureMap(); 617 result.setUrl(lexer.readConstant("url")); 618 lexer.token("="); 619 result.setName(lexer.readConstant("name")); 620 lexer.skipComments(); 621 622 while (lexer.hasToken("conceptmap")) 623 parseConceptMap(result, lexer); 624 625 while (lexer.hasToken("uses")) 626 parseUses(result, lexer); 627 while (lexer.hasToken("imports")) 628 parseImports(result, lexer); 629 630 parseGroup(result, lexer); 631 632 while (!lexer.done()) { 633 parseGroup(result, lexer); 634 } 635 636 return result; 637 } 638 639 private void parseConceptMap(StructureMap result, FHIRLexer lexer) throws FHIRLexerException { 640 lexer.token("conceptmap"); 641 ConceptMap map = new ConceptMap(); 642 String id = lexer.readConstant("map id"); 643 if (!id.startsWith("#")) 644 lexer.error("Concept Map identifier must start with #"); 645 map.setId(id); 646 map.setStatus(PublicationStatus.DRAFT); // todo: how to add this to the text format 647 result.getContained().add(map); 648 lexer.token("{"); 649 lexer.skipComments(); 650 // lexer.token("source"); 651 // map.setSource(new UriType(lexer.readConstant("source"))); 652 // lexer.token("target"); 653 // map.setSource(new UriType(lexer.readConstant("target"))); 654 Map<String, String> prefixes = new HashMap<String, String>(); 655 while (lexer.hasToken("prefix")) { 656 lexer.token("prefix"); 657 String n = lexer.take(); 658 lexer.token("="); 659 String v = lexer.readConstant("prefix url"); 660 prefixes.put(n, v); 661 } 662 while (lexer.hasToken("unmapped")) { 663 lexer.token("unmapped"); 664 lexer.token("for"); 665 String n = readPrefix(prefixes, lexer); 666 ConceptMapGroupComponent g = getGroup(map, n, null); 667 lexer.token("="); 668 String v = lexer.take(); 669 if (v.equals("provided")) { 670 g.getUnmapped().setMode(ConceptMapGroupUnmappedMode.PROVIDED); 671 } else 672 lexer.error("Only unmapped mode PROVIDED is supported at this time"); 673 } 674 while (!lexer.hasToken("}")) { 675 String srcs = readPrefix(prefixes, lexer); 676 lexer.token(":"); 677 String sc = lexer.getCurrent().startsWith("\"") ? lexer.readConstant("code") : lexer.take(); 678 ConceptMapEquivalence eq = readEquivalence(lexer); 679 String tgts = (eq != ConceptMapEquivalence.UNMATCHED) ? readPrefix(prefixes, lexer) : ""; 680 ConceptMapGroupComponent g = getGroup(map, srcs, tgts); 681 SourceElementComponent e = g.addElement(); 682 e.setCode(sc); 683 if (e.getCode().startsWith("\"")) 684 e.setCode(lexer.processConstant(e.getCode())); 685 TargetElementComponent tgt = e.addTarget(); 686 if (eq != ConceptMapEquivalence.EQUIVALENT) 687 tgt.setEquivalence(eq); 688 if (tgt.getEquivalence() != ConceptMapEquivalence.UNMATCHED) { 689 lexer.token(":"); 690 tgt.setCode(lexer.take()); 691 if (tgt.getCode().startsWith("\"")) 692 tgt.setCode(lexer.processConstant(tgt.getCode())); 693 } 694 if (lexer.hasComment()) 695 tgt.setComment(lexer.take().substring(2).trim()); 696 } 697 lexer.token("}"); 698 } 699 700 701 private ConceptMapGroupComponent getGroup(ConceptMap map, String srcs, String tgts) { 702 for (ConceptMapGroupComponent grp : map.getGroup()) { 703 if (grp.getSource().equals(srcs)) 704 if ((tgts == null && !grp.hasTarget()) || (tgts != null && tgts.equals(grp.getTarget()))) 705 return grp; 706 } 707 ConceptMapGroupComponent grp = map.addGroup(); 708 grp.setSource(srcs); 709 grp.setTarget(tgts); 710 return grp; 711 } 712 713 714 private String readPrefix(Map<String, String> prefixes, FHIRLexer lexer) throws FHIRLexerException { 715 String prefix = lexer.take(); 716 if (!prefixes.containsKey(prefix)) 717 throw lexer.error("Unknown prefix '"+prefix+"'"); 718 return prefixes.get(prefix); 719 } 720 721 722 private ConceptMapEquivalence readEquivalence(FHIRLexer lexer) throws FHIRLexerException { 723 String token = lexer.take(); 724 if (token.equals("-")) 725 return ConceptMapEquivalence.RELATEDTO; 726 if (token.equals("=")) 727 return ConceptMapEquivalence.EQUAL; 728 if (token.equals("==")) 729 return ConceptMapEquivalence.EQUIVALENT; 730 if (token.equals("!=")) 731 return ConceptMapEquivalence.DISJOINT; 732 if (token.equals("--")) 733 return ConceptMapEquivalence.UNMATCHED; 734 if (token.equals("<=")) 735 return ConceptMapEquivalence.WIDER; 736 if (token.equals("<-")) 737 return ConceptMapEquivalence.SUBSUMES; 738 if (token.equals(">=")) 739 return ConceptMapEquivalence.NARROWER; 740 if (token.equals(">-")) 741 return ConceptMapEquivalence.SPECIALIZES; 742 if (token.equals("~")) 743 return ConceptMapEquivalence.INEXACT; 744 throw lexer.error("Unknown equivalence token '"+token+"'"); 745 } 746 747 748 private void parseUses(StructureMap result, FHIRLexer lexer) throws FHIRException { 749 lexer.token("uses"); 750 StructureMapStructureComponent st = result.addStructure(); 751 st.setUrl(lexer.readConstant("url")); 752 if (lexer.hasToken("alias")) { 753 lexer.token("alias"); 754 st.setAlias(lexer.take()); 755 } 756 lexer.token("as"); 757 st.setMode(StructureMapModelMode.fromCode(lexer.take())); 758 lexer.skipToken(";"); 759 if (lexer.hasComment()) { 760 st.setDocumentation(lexer.take().substring(2).trim()); 761 } 762 lexer.skipComments(); 763 } 764 765 private void parseImports(StructureMap result, FHIRLexer lexer) throws FHIRException { 766 lexer.token("imports"); 767 result.addImport(lexer.readConstant("url")); 768 lexer.skipToken(";"); 769 if (lexer.hasComment()) { 770 lexer.next(); 771 } 772 lexer.skipComments(); 773 } 774 775 private void parseGroup(StructureMap result, FHIRLexer lexer) throws FHIRException { 776 lexer.token("group"); 777 StructureMapGroupComponent group = result.addGroup(); 778 if (lexer.hasToken("for")) { 779 lexer.token("for"); 780 if ("type".equals(lexer.getCurrent())) { 781 lexer.token("type"); 782 lexer.token("+"); 783 lexer.token("types"); 784 group.setTypeMode(StructureMapGroupTypeMode.TYPEANDTYPES); 785 } else { 786 lexer.token("types"); 787 group.setTypeMode(StructureMapGroupTypeMode.TYPES); 788 } 789 } else 790 group.setTypeMode(StructureMapGroupTypeMode.NONE); 791 group.setName(lexer.take()); 792 if (lexer.hasToken("extends")) { 793 lexer.next(); 794 group.setExtends(lexer.take()); 795 } 796 lexer.skipComments(); 797 while (lexer.hasToken("input")) 798 parseInput(group, lexer); 799 while (!lexer.hasToken("endgroup")) { 800 if (lexer.done()) 801 throw lexer.error("premature termination expecting 'endgroup'"); 802 parseRule(result, group.getRule(), lexer); 803 } 804 lexer.next(); 805 lexer.skipComments(); 806 } 807 808 private void parseInput(StructureMapGroupComponent group, FHIRLexer lexer) throws FHIRException { 809 lexer.token("input"); 810 StructureMapGroupInputComponent input = group.addInput(); 811 input.setName(lexer.take()); 812 if (lexer.hasToken(":")) { 813 lexer.token(":"); 814 input.setType(lexer.take()); 815 } 816 lexer.token("as"); 817 input.setMode(StructureMapInputMode.fromCode(lexer.take())); 818 if (lexer.hasComment()) { 819 input.setDocumentation(lexer.take().substring(2).trim()); 820 } 821 lexer.skipToken(";"); 822 lexer.skipComments(); 823 } 824 825 private void parseRule(StructureMap map, List<StructureMapGroupRuleComponent> list, FHIRLexer lexer) throws FHIRException { 826 StructureMapGroupRuleComponent rule = new StructureMapGroupRuleComponent(); 827 list.add(rule); 828 rule.setName(lexer.takeDottedToken()); 829 lexer.token(":"); 830 lexer.token("for"); 831 boolean done = false; 832 while (!done) { 833 parseSource(rule, lexer); 834 done = !lexer.hasToken(","); 835 if (!done) 836 lexer.next(); 837 } 838 if (lexer.hasToken("make")) { 839 lexer.token("make"); 840 done = false; 841 while (!done) { 842 parseTarget(rule, lexer); 843 done = !lexer.hasToken(","); 844 if (!done) 845 lexer.next(); 846 } 847 } 848 if (lexer.hasToken("then")) { 849 lexer.token("then"); 850 if (lexer.hasToken("{")) { 851 lexer.token("{"); 852 if (lexer.hasComment()) { 853 rule.setDocumentation(lexer.take().substring(2).trim()); 854 } 855 lexer.skipComments(); 856 while (!lexer.hasToken("}")) { 857 if (lexer.done()) 858 throw lexer.error("premature termination expecting '}' in nested group"); 859 parseRule(map, rule.getRule(), lexer); 860 } 861 lexer.token("}"); 862 } else { 863 done = false; 864 while (!done) { 865 parseRuleReference(rule, lexer); 866 done = !lexer.hasToken(","); 867 if (!done) 868 lexer.next(); 869 } 870 } 871 } else if (lexer.hasComment()) { 872 rule.setDocumentation(lexer.take().substring(2).trim()); 873 } 874 if (isSimpleSyntax(rule)) { 875 rule.getSourceFirstRep().setVariable(AUTO_VAR_NAME); 876 rule.getTargetFirstRep().setVariable(AUTO_VAR_NAME); 877 rule.getTargetFirstRep().setTransform(StructureMapTransform.CREATE); // with no parameter - e.g. imply what is to be created 878 // no dependencies - imply what is to be done based on types 879 } 880 lexer.skipComments(); 881 } 882 883 private boolean isSimpleSyntax(StructureMapGroupRuleComponent rule) { 884 return 885 (rule.getSource().size() == 1 && rule.getSourceFirstRep().hasContext() && rule.getSourceFirstRep().hasElement() && !rule.getSourceFirstRep().hasVariable()) && 886 (rule.getTarget().size() == 1 && rule.getTargetFirstRep().hasContext() && rule.getTargetFirstRep().hasElement() && !rule.getTargetFirstRep().hasVariable() && !rule.getTargetFirstRep().hasParameter()) && 887 (rule.getDependent().size() == 0 && rule.getRule().size() == 0); 888 } 889 890 private void parseRuleReference(StructureMapGroupRuleComponent rule, FHIRLexer lexer) throws FHIRLexerException { 891 StructureMapGroupRuleDependentComponent ref = rule.addDependent(); 892 ref.setName(lexer.take()); 893 lexer.token("("); 894 boolean done = false; 895 while (!done) { 896 ref.addVariable(lexer.take()); 897 done = !lexer.hasToken(","); 898 if (!done) 899 lexer.next(); 900 } 901 lexer.token(")"); 902 } 903 904 private void parseSource(StructureMapGroupRuleComponent rule, FHIRLexer lexer) throws FHIRException { 905 StructureMapGroupRuleSourceComponent source = rule.addSource(); 906 source.setContext(lexer.take()); 907 if (source.getContext().equals("search") && lexer.hasToken("(")) { 908 source.setContext("@search"); 909 lexer.take(); 910 ExpressionNode node = fpe.parse(lexer); 911 source.setUserData(MAP_SEARCH_EXPRESSION, node); 912 source.setElement(node.toString()); 913 lexer.token(")"); 914 } else if (lexer.hasToken(".")) { 915 lexer.token("."); 916 source.setElement(lexer.take()); 917 } 918 if (lexer.hasToken(":")) { 919 // type and cardinality 920 lexer.token(":"); 921 source.setType(lexer.takeDottedToken()); 922 if (!lexer.hasToken("as", "first", "last", "not_first", "not_last", "only_one", "default")) { 923 source.setMin(lexer.takeInt()); 924 lexer.token(".."); 925 source.setMax(lexer.take()); 926 } 927 } 928 if (lexer.hasToken("default")) { 929 lexer.token("default"); 930 source.setDefaultValue(new StringType(lexer.readConstant("default value"))); 931 } 932 if (Utilities.existsInList(lexer.getCurrent(), "first", "last", "not_first", "not_last", "only_one")) 933 source.setListMode(StructureMapSourceListMode.fromCode(lexer.take())); 934 935 if (lexer.hasToken("as")) { 936 lexer.take(); 937 source.setVariable(lexer.take()); 938 } 939 if (lexer.hasToken("where")) { 940 lexer.take(); 941 ExpressionNode node = fpe.parse(lexer); 942 source.setUserData(MAP_WHERE_EXPRESSION, node); 943 source.setCondition(node.toString()); 944 } 945 if (lexer.hasToken("check")) { 946 lexer.take(); 947 ExpressionNode node = fpe.parse(lexer); 948 source.setUserData(MAP_WHERE_CHECK, node); 949 source.setCheck(node.toString()); 950 } 951 } 952 953 private void parseTarget(StructureMapGroupRuleComponent rule, FHIRLexer lexer) throws FHIRException { 954 StructureMapGroupRuleTargetComponent target = rule.addTarget(); 955 String start = lexer.take(); 956 if (lexer.hasToken(".")) { 957 target.setContext(start); 958 target.setContextType(StructureMapContextType.VARIABLE); 959 start = null; 960 lexer.token("."); 961 target.setElement(lexer.take()); 962 } 963 String name; 964 boolean isConstant = false; 965 if (lexer.hasToken("=")) { 966 if (start != null) 967 target.setContext(start); 968 lexer.token("="); 969 isConstant = lexer.isConstant(true); 970 name = lexer.take(); 971 } else 972 name = start; 973 974 if ("(".equals(name)) { 975 // inline fluentpath expression 976 target.setTransform(StructureMapTransform.EVALUATE); 977 ExpressionNode node = fpe.parse(lexer); 978 target.setUserData(MAP_EXPRESSION, node); 979 target.addParameter().setValue(new StringType(node.toString())); 980 lexer.token(")"); 981 } else if (lexer.hasToken("(")) { 982 target.setTransform(StructureMapTransform.fromCode(name)); 983 lexer.token("("); 984 if (target.getTransform() == StructureMapTransform.EVALUATE) { 985 parseParameter(target, lexer); 986 lexer.token(","); 987 ExpressionNode node = fpe.parse(lexer); 988 target.setUserData(MAP_EXPRESSION, node); 989 target.addParameter().setValue(new StringType(node.toString())); 990 } else { 991 while (!lexer.hasToken(")")) { 992 parseParameter(target, lexer); 993 if (!lexer.hasToken(")")) 994 lexer.token(","); 995 } 996 } 997 lexer.token(")"); 998 } else if (name != null) { 999 target.setTransform(StructureMapTransform.COPY); 1000 if (!isConstant) { 1001 String id = name; 1002 while (lexer.hasToken(".")) { 1003 id = id + lexer.take() + lexer.take(); 1004 } 1005 target.addParameter().setValue(new IdType(id)); 1006 } 1007 else 1008 target.addParameter().setValue(readConstant(name, lexer)); 1009 } 1010 if (lexer.hasToken("as")) { 1011 lexer.take(); 1012 target.setVariable(lexer.take()); 1013 } 1014 while (Utilities.existsInList(lexer.getCurrent(), "first", "last", "share", "collate")) { 1015 if (lexer.getCurrent().equals("share")) { 1016 target.addListMode(StructureMapTargetListMode.SHARE); 1017 lexer.next(); 1018 target.setListRuleId(lexer.take()); 1019 } else if (lexer.getCurrent().equals("first")) 1020 target.addListMode(StructureMapTargetListMode.FIRST); 1021 else 1022 target.addListMode(StructureMapTargetListMode.LAST); 1023 lexer.next(); 1024 } 1025 } 1026 1027 1028 private void parseParameter(StructureMapGroupRuleTargetComponent target, FHIRLexer lexer) throws FHIRLexerException, FHIRFormatError { 1029 if (!lexer.isConstant(true)) { 1030 target.addParameter().setValue(new IdType(lexer.take())); 1031 } else if (lexer.isStringConstant()) 1032 target.addParameter().setValue(new StringType(lexer.readConstant("??"))); 1033 else { 1034 target.addParameter().setValue(readConstant(lexer.take(), lexer)); 1035 } 1036 } 1037 1038 private Type readConstant(String s, FHIRLexer lexer) throws FHIRLexerException { 1039 if (Utilities.isInteger(s)) 1040 return new IntegerType(s); 1041 else if (Utilities.isDecimal(s)) 1042 return new DecimalType(s); 1043 else if (Utilities.existsInList(s, "true", "false")) 1044 return new BooleanType(s.equals("true")); 1045 else 1046 return new StringType(lexer.processConstant(s)); 1047 } 1048 1049 public StructureDefinition getTargetType(StructureMap map) throws FHIRException { 1050 boolean found = false; 1051 StructureDefinition res = null; 1052 for (StructureMapStructureComponent uses : map.getStructure()) { 1053 if (uses.getMode() == StructureMapModelMode.TARGET) { 1054 if (found) 1055 throw new FHIRException("Multiple targets found in map "+map.getUrl()); 1056 found = true; 1057 res = worker.fetchResource(StructureDefinition.class, uses.getUrl()); 1058 if (res == null) 1059 throw new FHIRException("Unable to find "+uses.getUrl()+" referenced from map "+map.getUrl()); 1060 } 1061 } 1062 if (res == null) 1063 throw new FHIRException("No targets found in map "+map.getUrl()); 1064 return res; 1065 } 1066 1067 public enum VariableMode { 1068 INPUT, OUTPUT 1069 } 1070 1071 public class Variable { 1072 private VariableMode mode; 1073 private String name; 1074 private Base object; 1075 public Variable(VariableMode mode, String name, Base object) { 1076 super(); 1077 this.mode = mode; 1078 this.name = name; 1079 this.object = object; 1080 } 1081 public VariableMode getMode() { 1082 return mode; 1083 } 1084 public String getName() { 1085 return name; 1086 } 1087 public Base getObject() { 1088 return object; 1089 } 1090 public String summary() { 1091 return name+": "+object.fhirType(); 1092 } 1093 } 1094 1095 public class Variables { 1096 private List<Variable> list = new ArrayList<Variable>(); 1097 1098 public void add(VariableMode mode, String name, Base object) { 1099 Variable vv = null; 1100 for (Variable v : list) 1101 if ((v.mode == mode) && v.getName().equals(name)) 1102 vv = v; 1103 if (vv != null) 1104 list.remove(vv); 1105 list.add(new Variable(mode, name, object)); 1106 } 1107 1108 public Variables copy() { 1109 Variables result = new Variables(); 1110 result.list.addAll(list); 1111 return result; 1112 } 1113 1114 public Base get(VariableMode mode, String name) { 1115 for (Variable v : list) 1116 if ((v.mode == mode) && v.getName().equals(name)) 1117 return v.getObject(); 1118 return null; 1119 } 1120 1121 public String summary() { 1122 CommaSeparatedStringBuilder s = new CommaSeparatedStringBuilder(); 1123 CommaSeparatedStringBuilder t = new CommaSeparatedStringBuilder(); 1124 for (Variable v : list) 1125 if (v.mode == VariableMode.INPUT) 1126 s.append(v.summary()); 1127 else 1128 t.append(v.summary()); 1129 return "source variables ["+s.toString()+"], target variables ["+t.toString()+"]"; 1130 } 1131 } 1132 1133 public class TransformContext { 1134 private Object appInfo; 1135 1136 public TransformContext(Object appInfo) { 1137 super(); 1138 this.appInfo = appInfo; 1139 } 1140 1141 public Object getAppInfo() { 1142 return appInfo; 1143 } 1144 1145 } 1146 1147 private void log(String cnt) { 1148 if (services != null) 1149 services.log(cnt); 1150 } 1151 1152 /** 1153 * Given an item, return all the children that conform to the pattern described in name 1154 * 1155 * Possible patterns: 1156 * - a simple name (which may be the base of a name with [] e.g. value[x]) 1157 * - a name with a type replacement e.g. valueCodeableConcept 1158 * - * which means all children 1159 * - ** which means all descendents 1160 * 1161 * @param item 1162 * @param name 1163 * @param result 1164 * @throws FHIRException 1165 */ 1166 protected void getChildrenByName(Base item, String name, List<Base> result) throws FHIRException { 1167 for (Base v : item.listChildrenByName(name, true)) 1168 if (v != null) 1169 result.add(v); 1170 } 1171 1172 public void transform(Object appInfo, Base source, StructureMap map, Base target) throws FHIRException { 1173 TransformContext context = new TransformContext(appInfo); 1174 log("Start Transform "+map.getUrl()); 1175 StructureMapGroupComponent g = map.getGroup().get(0); 1176 1177 Variables vars = new Variables(); 1178 vars.add(VariableMode.INPUT, getInputName(g, StructureMapInputMode.SOURCE, "source"), source); 1179 vars.add(VariableMode.OUTPUT, getInputName(g, StructureMapInputMode.TARGET, "target"), target); 1180 1181 executeGroup("", context, map, vars, g, true); 1182 if (target instanceof Element) 1183 ((Element) target).sort(); 1184 } 1185 1186 private String getInputName(StructureMapGroupComponent g, StructureMapInputMode mode, String def) throws DefinitionException { 1187 String name = null; 1188 for (StructureMapGroupInputComponent inp : g.getInput()) { 1189 if (inp.getMode() == mode) 1190 if (name != null) 1191 throw new DefinitionException("This engine does not support multiple source inputs"); 1192 else 1193 name = inp.getName(); 1194 } 1195 return name == null ? def : name; 1196 } 1197 1198 private void executeGroup(String indent, TransformContext context, StructureMap map, Variables vars, StructureMapGroupComponent group, boolean atRoot) throws FHIRException { 1199 log(indent+"Group : "+group.getName()); 1200 // todo: check inputs 1201 if (group.hasExtends()) { 1202 ResolvedGroup rg = resolveGroupReference(map, group, group.getExtends()); 1203 executeGroup(indent+" ", context, rg.targetMap, vars, rg.target, false); 1204 } 1205 1206 for (StructureMapGroupRuleComponent r : group.getRule()) { 1207 executeRule(indent+" ", context, map, vars, group, r, atRoot); 1208 } 1209 } 1210 1211 private void executeRule(String indent, TransformContext context, StructureMap map, Variables vars, StructureMapGroupComponent group, StructureMapGroupRuleComponent rule, boolean atRoot) throws FHIRException { 1212 log(indent+"rule : "+rule.getName()); 1213 if (rule.getName().contains("CarePlan.participant-unlink")) 1214 System.out.println("debug"); 1215 Variables srcVars = vars.copy(); 1216 if (rule.getSource().size() != 1) 1217 throw new FHIRException("Rule \""+rule.getName()+"\": not handled yet"); 1218 List<Variables> source = processSource(rule.getName(), context, srcVars, rule.getSource().get(0), map.getUrl()); 1219 if (source != null) { 1220 for (Variables v : source) { 1221 for (StructureMapGroupRuleTargetComponent t : rule.getTarget()) { 1222 processTarget(rule.getName(), context, v, map, group, t, rule.getSource().size() == 1 ? rule.getSourceFirstRep().getVariable() : null, atRoot); 1223 } 1224 if (rule.hasRule()) { 1225 for (StructureMapGroupRuleComponent childrule : rule.getRule()) { 1226 executeRule(indent +" ", context, map, v, group, childrule, false); 1227 } 1228 } else if (rule.hasDependent()) { 1229 for (StructureMapGroupRuleDependentComponent dependent : rule.getDependent()) { 1230 executeDependency(indent+" ", context, map, v, group, dependent); 1231 } 1232 } else if (rule.getSource().size() == 1 && rule.getSourceFirstRep().hasVariable() && rule.getTarget().size() == 1 && rule.getTargetFirstRep().hasVariable() && rule.getTargetFirstRep().getTransform() == StructureMapTransform.CREATE && !rule.getTargetFirstRep().hasParameter()) { 1233 // simple inferred, map by type 1234 Base src = v.get(VariableMode.INPUT, rule.getSourceFirstRep().getVariable()); 1235 Base tgt = v.get(VariableMode.OUTPUT, rule.getTargetFirstRep().getVariable()); 1236 String srcType = src.fhirType(); 1237 String tgtType = tgt.fhirType(); 1238 ResolvedGroup defGroup = resolveGroupByTypes(map, rule.getName(), group, srcType, tgtType); 1239 Variables vdef = new Variables(); 1240 vdef.add(VariableMode.INPUT, defGroup.target.getInput().get(0).getName(), src); 1241 vdef.add(VariableMode.OUTPUT, defGroup.target.getInput().get(1).getName(), tgt); 1242 executeGroup(indent+" ", context, defGroup.targetMap, vdef, defGroup.target, false); 1243 } 1244 } 1245 } 1246 } 1247 1248 private void executeDependency(String indent, TransformContext context, StructureMap map, Variables vin, StructureMapGroupComponent group, StructureMapGroupRuleDependentComponent dependent) throws FHIRException { 1249 ResolvedGroup rg = resolveGroupReference(map, group, dependent.getName()); 1250 1251 if (rg.target.getInput().size() != dependent.getVariable().size()) { 1252 throw new FHIRException("Rule '"+dependent.getName()+"' has "+Integer.toString(rg.target.getInput().size())+" but the invocation has "+Integer.toString(dependent.getVariable().size())+" variables"); 1253 } 1254 Variables v = new Variables(); 1255 for (int i = 0; i < rg.target.getInput().size(); i++) { 1256 StructureMapGroupInputComponent input = rg.target.getInput().get(i); 1257 StringType rdp = dependent.getVariable().get(i); 1258 String var = rdp.asStringValue(); 1259 VariableMode mode = input.getMode() == StructureMapInputMode.SOURCE ? VariableMode.INPUT : VariableMode.OUTPUT; 1260 Base vv = vin.get(mode, var); 1261 if (vv == null && mode == VariableMode.INPUT) //* once source, always source. but target can be treated as source at user convenient 1262 vv = vin.get(VariableMode.OUTPUT, var); 1263 if (vv == null) 1264 throw new FHIRException("Rule '"+dependent.getName()+"' "+mode.toString()+" variable '"+input.getName()+"' named as '"+var+"' has no value"); 1265 v.add(mode, input.getName(), vv); 1266 } 1267 executeGroup(indent+" ", context, rg.targetMap, v, rg.target, false); 1268 } 1269 1270 private String determineTypeFromSourceType(StructureMap map, StructureMapGroupComponent source, Base base, String[] types) throws FHIRException { 1271 String type = base.fhirType(); 1272 String kn = "type^"+type; 1273 if (source.hasUserData(kn)) 1274 return source.getUserString(kn); 1275 1276 ResolvedGroup res = new ResolvedGroup(); 1277 res.targetMap = null; 1278 res.target = null; 1279 for (StructureMapGroupComponent grp : map.getGroup()) { 1280 if (matchesByType(map, grp, type)) { 1281 if (res.targetMap == null) { 1282 res.targetMap = map; 1283 res.target = grp; 1284 } else 1285 throw new FHIRException("Multiple possible matches looking for default rule for '"+type+"'"); 1286 } 1287 } 1288 if (res.targetMap != null) { 1289 String result = getActualType(res.targetMap, res.target.getInput().get(1).getType()); 1290 source.setUserData(kn, result); 1291 return result; 1292 } 1293 1294 for (UriType imp : map.getImport()) { 1295 List<StructureMap> impMapList = findMatchingMaps(imp.getValue()); 1296 if (impMapList.size() == 0) 1297 throw new FHIRException("Unable to find map(s) for "+imp.getValue()); 1298 for (StructureMap impMap : impMapList) { 1299 if (!impMap.getUrl().equals(map.getUrl())) { 1300 for (StructureMapGroupComponent grp : impMap.getGroup()) { 1301 if (matchesByType(impMap, grp, type)) { 1302 if (res.targetMap == null) { 1303 res.targetMap = impMap; 1304 res.target = grp; 1305 } else 1306 throw new FHIRException("Multiple possible matches for default rule for '"+type+"' in "+res.targetMap.getUrl()+" ("+res.target.getName()+") and "+impMap.getUrl()+" ("+grp.getName()+")"); 1307 } 1308 } 1309 } 1310 } 1311 } 1312 if (res.target == null) 1313 throw new FHIRException("No matches found for default rule for '"+type+"' from "+map.getUrl()); 1314 String result = getActualType(res.targetMap, res.target.getInput().get(1).getType()); // should be .getType, but R2... 1315 source.setUserData(kn, result); 1316 return result; 1317 } 1318 1319 private List<StructureMap> findMatchingMaps(String value) { 1320 List<StructureMap> res = new ArrayList<StructureMap>(); 1321 if (value.contains("*")) { 1322 for (StructureMap sm : worker.listTransforms()) { 1323 if (urlMatches(value, sm.getUrl())) { 1324 res.add(sm); 1325 } 1326 } 1327 } else { 1328 StructureMap sm = worker.getTransform(value); 1329 if (sm != null) 1330 res.add(sm); 1331 } 1332 Set<String> check = new HashSet<String>(); 1333 for (StructureMap sm : res) { 1334 if (check.contains(sm.getUrl())) 1335 throw new Error("duplicate"); 1336 else 1337 check.add(sm.getUrl()); 1338 } 1339 return res; 1340 } 1341 1342 private boolean urlMatches(String mask, String url) { 1343 return url.length() > mask.length() && url.startsWith(mask.substring(0, mask.indexOf("*"))) && url.endsWith(mask.substring(mask.indexOf("*")+1)) ; 1344 } 1345 1346 private ResolvedGroup resolveGroupByTypes(StructureMap map, String ruleid, StructureMapGroupComponent source, String srcType, String tgtType) throws FHIRException { 1347 String kn = "types^"+srcType+":"+tgtType; 1348 if (source.hasUserData(kn)) 1349 return (ResolvedGroup) source.getUserData(kn); 1350 1351 ResolvedGroup res = new ResolvedGroup(); 1352 res.targetMap = null; 1353 res.target = null; 1354 for (StructureMapGroupComponent grp : map.getGroup()) { 1355 if (matchesByType(map, grp, srcType, tgtType)) { 1356 if (res.targetMap == null) { 1357 res.targetMap = map; 1358 res.target = grp; 1359 } else 1360 throw new FHIRException("Multiple possible matches looking for rule for '"+srcType+"/"+tgtType+"', from rule '"+ruleid+"'"); 1361 } 1362 } 1363 if (res.targetMap != null) { 1364 source.setUserData(kn, res); 1365 return res; 1366 } 1367 1368 for (UriType imp : map.getImport()) { 1369 List<StructureMap> impMapList = findMatchingMaps(imp.getValue()); 1370 if (impMapList.size() == 0) 1371 throw new FHIRException("Unable to find map(s) for "+imp.getValue()); 1372 for (StructureMap impMap : impMapList) { 1373 if (!impMap.getUrl().equals(map.getUrl())) { 1374 for (StructureMapGroupComponent grp : impMap.getGroup()) { 1375 if (matchesByType(impMap, grp, srcType, tgtType)) { 1376 if (res.targetMap == null) { 1377 res.targetMap = impMap; 1378 res.target = grp; 1379 } else 1380 throw new FHIRException("Multiple possible matches for rule for '"+srcType+"/"+tgtType+"' in "+res.targetMap.getUrl()+" and "+impMap.getUrl()+", from rule '"+ruleid+"'"); 1381 } 1382 } 1383 } 1384 } 1385 } 1386 if (res.target == null) 1387 throw new FHIRException("No matches found for rule for '"+srcType+" to "+tgtType+"' from "+map.getUrl()+", from rule '"+ruleid+"'"); 1388 source.setUserData(kn, res); 1389 return res; 1390 } 1391 1392 1393 private boolean matchesByType(StructureMap map, StructureMapGroupComponent grp, String type) throws FHIRException { 1394 if (grp.getTypeMode() != StructureMapGroupTypeMode.TYPEANDTYPES) 1395 return false; 1396 if (grp.getInput().size() != 2 || grp.getInput().get(0).getMode() != StructureMapInputMode.SOURCE || grp.getInput().get(1).getMode() != StructureMapInputMode.TARGET) 1397 return false; 1398 return matchesType(map, type, grp.getInput().get(0).getType()); 1399 } 1400 1401 private boolean matchesByType(StructureMap map, StructureMapGroupComponent grp, String srcType, String tgtType) throws FHIRException { 1402 if (grp.getTypeMode() == StructureMapGroupTypeMode.NONE) 1403 return false; 1404 if (grp.getInput().size() != 2 || grp.getInput().get(0).getMode() != StructureMapInputMode.SOURCE || grp.getInput().get(1).getMode() != StructureMapInputMode.TARGET) 1405 return false; 1406 if (!grp.getInput().get(0).hasType() || !grp.getInput().get(1).hasType()) 1407 return false; 1408 return matchesType(map, srcType, grp.getInput().get(0).getType()) && matchesType(map, tgtType, grp.getInput().get(1).getType()); 1409 } 1410 1411 private boolean matchesType(StructureMap map, String actualType, String statedType) throws FHIRException { 1412 // check the aliases 1413 for (StructureMapStructureComponent imp : map.getStructure()) { 1414 if (imp.hasAlias() && statedType.equals(imp.getAlias())) { 1415 StructureDefinition sd = worker.fetchResource(StructureDefinition.class, imp.getUrl()); 1416 if (sd != null) 1417 statedType = sd.getType(); 1418 break; 1419 } 1420 } 1421 1422 if (Utilities.isAbsoluteUrl(actualType)) { 1423 StructureDefinition sd = worker.fetchResource(StructureDefinition.class, actualType); 1424 if (sd != null) 1425 actualType = sd.getType(); 1426 } 1427 if (Utilities.isAbsoluteUrl(statedType)) { 1428 StructureDefinition sd = worker.fetchResource(StructureDefinition.class, statedType); 1429 if (sd != null) 1430 statedType = sd.getType(); 1431 } 1432 return actualType.equals(statedType); 1433 } 1434 1435 private String getActualType(StructureMap map, String statedType) throws FHIRException { 1436 // check the aliases 1437 for (StructureMapStructureComponent imp : map.getStructure()) { 1438 if (imp.hasAlias() && statedType.equals(imp.getAlias())) { 1439 StructureDefinition sd = worker.fetchResource(StructureDefinition.class, imp.getUrl()); 1440 if (sd == null) 1441 throw new FHIRException("Unable to resolve structure "+imp.getUrl()); 1442 return sd.getId(); // should be sd.getType(), but R2... 1443 } 1444 } 1445 return statedType; 1446 } 1447 1448 1449 private ResolvedGroup resolveGroupReference(StructureMap map, StructureMapGroupComponent source, String name) throws FHIRException { 1450 String kn = "ref^"+name; 1451 if (source.hasUserData(kn)) 1452 return (ResolvedGroup) source.getUserData(kn); 1453 1454 ResolvedGroup res = new ResolvedGroup(); 1455 res.targetMap = null; 1456 res.target = null; 1457 for (StructureMapGroupComponent grp : map.getGroup()) { 1458 if (grp.getName().equals(name)) { 1459 if (res.targetMap == null) { 1460 res.targetMap = map; 1461 res.target = grp; 1462 } else 1463 throw new FHIRException("Multiple possible matches for rule '"+name+"'"); 1464 } 1465 } 1466 if (res.targetMap != null) { 1467 source.setUserData(kn, res); 1468 return res; 1469 } 1470 1471 for (UriType imp : map.getImport()) { 1472 List<StructureMap> impMapList = findMatchingMaps(imp.getValue()); 1473 if (impMapList.size() == 0) 1474 throw new FHIRException("Unable to find map(s) for "+imp.getValue()); 1475 for (StructureMap impMap : impMapList) { 1476 if (!impMap.getUrl().equals(map.getUrl())) { 1477 for (StructureMapGroupComponent grp : impMap.getGroup()) { 1478 if (grp.getName().equals(name)) { 1479 if (res.targetMap == null) { 1480 res.targetMap = impMap; 1481 res.target = grp; 1482 } else 1483 throw new FHIRException("Multiple possible matches for rule '"+name+"' in "+res.targetMap.getUrl()+" and "+impMap.getUrl()); 1484 } 1485 } 1486 } 1487 } 1488 } 1489 if (res.target == null) 1490 throw new FHIRException("No matches found for rule '"+name+"'. Reference found in "+map.getUrl()); 1491 source.setUserData(kn, res); 1492 return res; 1493 } 1494 1495 private List<Variables> processSource(String ruleId, TransformContext context, Variables vars, StructureMapGroupRuleSourceComponent src, String pathForErrors) throws FHIRException { 1496 List<Base> items; 1497 if (src.getContext().equals("@search")) { 1498 ExpressionNode expr = (ExpressionNode) src.getUserData(MAP_SEARCH_EXPRESSION); 1499 if (expr == null) { 1500 expr = fpe.parse(src.getElement()); 1501 src.setUserData(MAP_SEARCH_EXPRESSION, expr); 1502 } 1503 String search = fpe.evaluateToString(vars, null, new StringType(), expr); // string is a holder of nothing to ensure that variables are processed correctly 1504 items = services.performSearch(context.appInfo, search); 1505 } else { 1506 items = new ArrayList<Base>(); 1507 Base b = vars.get(VariableMode.INPUT, src.getContext()); 1508 if (b == null) 1509 throw new FHIRException("Unknown input variable "+src.getContext()+" in "+pathForErrors+" rule "+ruleId+" (vars = "+vars.summary()+")"); 1510 1511 if (!src.hasElement()) 1512 items.add(b); 1513 else { 1514 getChildrenByName(b, src.getElement(), items); 1515 if (items.size() == 0 && src.hasDefaultValue()) 1516 items.add(src.getDefaultValue()); 1517 } 1518 } 1519 1520 if (src.hasType()) { 1521 List<Base> remove = new ArrayList<Base>(); 1522 for (Base item : items) { 1523 if (item != null && !isType(item, src.getType())) { 1524 remove.add(item); 1525 } 1526 } 1527 items.removeAll(remove); 1528 } 1529 1530 if (src.hasCondition()) { 1531 ExpressionNode expr = (ExpressionNode) src.getUserData(MAP_WHERE_EXPRESSION); 1532 if (expr == null) { 1533 expr = fpe.parse(src.getCondition()); 1534 // fpe.check(context.appInfo, ??, ??, expr) 1535 src.setUserData(MAP_WHERE_EXPRESSION, expr); 1536 } 1537 List<Base> remove = new ArrayList<Base>(); 1538 for (Base item : items) { 1539 if (!fpe.evaluateToBoolean(vars, null, item, expr)) 1540 remove.add(item); 1541 } 1542 items.removeAll(remove); 1543 } 1544 1545 if (src.hasCheck()) { 1546 ExpressionNode expr = (ExpressionNode) src.getUserData(MAP_WHERE_CHECK); 1547 if (expr == null) { 1548 expr = fpe.parse(src.getCheck()); 1549 // fpe.check(context.appInfo, ??, ??, expr) 1550 src.setUserData(MAP_WHERE_CHECK, expr); 1551 } 1552 List<Base> remove = new ArrayList<Base>(); 1553 for (Base item : items) { 1554 if (!fpe.evaluateToBoolean(vars, null, item, expr)) 1555 throw new FHIRException("Rule \""+ruleId+"\": Check condition failed"); 1556 } 1557 } 1558 1559 1560 if (src.hasListMode() && !items.isEmpty()) { 1561 switch (src.getListMode()) { 1562 case FIRST: 1563 Base bt = items.get(0); 1564 items.clear(); 1565 items.add(bt); 1566 break; 1567 case NOTFIRST: 1568 if (items.size() > 0) 1569 items.remove(0); 1570 break; 1571 case LAST: 1572 bt = items.get(items.size()-1); 1573 items.clear(); 1574 items.add(bt); 1575 break; 1576 case NOTLAST: 1577 if (items.size() > 0) 1578 items.remove(items.size()-1); 1579 break; 1580 case ONLYONE: 1581 if (items.size() > 1) 1582 throw new FHIRException("Rule \""+ruleId+"\": Check condition failed: the collection has more than one item"); 1583 break; 1584 case NULL: 1585 } 1586 } 1587 List<Variables> result = new ArrayList<Variables>(); 1588 for (Base r : items) { 1589 Variables v = vars.copy(); 1590 if (src.hasVariable()) 1591 v.add(VariableMode.INPUT, src.getVariable(), r); 1592 result.add(v); 1593 } 1594 return result; 1595 } 1596 1597 1598 private boolean isType(Base item, String type) { 1599 if (type.equals(item.fhirType())) 1600 return true; 1601 return false; 1602 } 1603 1604 private void processTarget(String ruleId, TransformContext context, Variables vars, StructureMap map, StructureMapGroupComponent group, StructureMapGroupRuleTargetComponent tgt, String srcVar, boolean atRoot) throws FHIRException { 1605 Base dest = null; 1606 if (tgt.hasContext()) { 1607 dest = vars.get(VariableMode.OUTPUT, tgt.getContext()); 1608 if (dest == null) 1609 throw new FHIRException("Rule \""+ruleId+"\": target context not known: "+tgt.getContext()); 1610 if (!tgt.hasElement()) 1611 throw new FHIRException("Rule \""+ruleId+"\": Not supported yet"); 1612 } 1613 Base v = null; 1614 if (tgt.hasTransform()) { 1615 v = runTransform(ruleId, context, map, group, tgt, vars, dest, tgt.getElement(), srcVar, atRoot); 1616 if (v != null && dest != null) 1617 v = dest.setProperty(tgt.getElement().hashCode(), tgt.getElement(), v); // reset v because some implementations may have to rewrite v when setting the value 1618 } else if (dest != null) 1619 v = dest.makeProperty(tgt.getElement().hashCode(), tgt.getElement()); 1620 if (tgt.hasVariable() && v != null) 1621 vars.add(VariableMode.OUTPUT, tgt.getVariable(), v); 1622 } 1623 1624 private Base runTransform(String ruleId, TransformContext context, StructureMap map, StructureMapGroupComponent group, StructureMapGroupRuleTargetComponent tgt, Variables vars, Base dest, String element, String srcVar, boolean root) throws FHIRException { 1625 try { 1626 switch (tgt.getTransform()) { 1627 case CREATE : 1628 String tn; 1629 if (tgt.getParameter().isEmpty()) { 1630 // 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 1631 String[] types = dest.getTypesForProperty(element.hashCode(), element); 1632 if (types.length == 1 && !"*".equals(types[0]) && !types[0].equals("Resource")) 1633 tn = types[0]; 1634 else if (srcVar != null) { 1635 tn = determineTypeFromSourceType(map, group, vars.get(VariableMode.INPUT, srcVar), types); 1636 } else 1637 throw new Error("Cannot determine type implicitly because there is no single input variable"); 1638 } else 1639 tn = getParamStringNoNull(vars, tgt.getParameter().get(0), tgt.toString()); 1640 Base res = services != null ? services.createType(context.getAppInfo(), tn) : ResourceFactory.createResourceOrType(tn); 1641 if (res.isResource() && !res.fhirType().equals("Parameters")) { 1642// res.setIdBase(tgt.getParameter().size() > 1 ? getParamString(vars, tgt.getParameter().get(0)) : UUID.randomUUID().toString().toLowerCase()); 1643 if (services != null) 1644 res = services.createResource(context.getAppInfo(), res, root); 1645 } 1646 if (tgt.hasUserData("profile")) 1647 res.setUserData("profile", tgt.getUserData("profile")); 1648 return res; 1649 case COPY : 1650 return getParam(vars, tgt.getParameter().get(0)); 1651 case EVALUATE : 1652 ExpressionNode expr = (ExpressionNode) tgt.getUserData(MAP_EXPRESSION); 1653 if (expr == null) { 1654 expr = fpe.parse(getParamStringNoNull(vars, tgt.getParameter().get(1), tgt.toString())); 1655 tgt.setUserData(MAP_WHERE_EXPRESSION, expr); 1656 } 1657 List<Base> v = fpe.evaluate(vars, null, tgt.getParameter().size() == 2 ? getParam(vars, tgt.getParameter().get(0)) : new BooleanType(false), expr); 1658 if (v.size() == 0) 1659 return null; 1660 else if (v.size() != 1) 1661 throw new FHIRException("Rule \""+ruleId+"\": Evaluation of "+expr.toString()+" returned "+Integer.toString(v.size())+" objects"); 1662 else 1663 return v.get(0); 1664 1665 case TRUNCATE : 1666 String src = getParamString(vars, tgt.getParameter().get(0)); 1667 String len = getParamStringNoNull(vars, tgt.getParameter().get(1), tgt.toString()); 1668 if (Utilities.isInteger(len)) { 1669 int l = Integer.parseInt(len); 1670 if (src.length() > l) 1671 src = src.substring(0, l); 1672 } 1673 return new StringType(src); 1674 case ESCAPE : 1675 throw new Error("Rule \""+ruleId+"\": Transform "+tgt.getTransform().toCode()+" not supported yet"); 1676 case CAST : 1677 src = getParamString(vars, tgt.getParameter().get(0)); 1678 if (tgt.getParameter().size() == 1) 1679 throw new FHIRException("Implicit type parameters on cast not yet supported"); 1680 String t = getParamString(vars, tgt.getParameter().get(1)); 1681 if (t.equals("string")) 1682 return new StringType(src); 1683 else 1684 throw new FHIRException("cast to "+t+" not yet supported"); 1685 case APPEND : 1686 StringBuilder sb = new StringBuilder(getParamString(vars, tgt.getParameter().get(0))); 1687 for (int i = 1; i < tgt.getParameter().size(); i++) 1688 sb.append(getParamString(vars, tgt.getParameter().get(1))); 1689 return new StringType(sb.toString()); 1690 case TRANSLATE : 1691 return translate(context, map, vars, tgt.getParameter()); 1692 case REFERENCE : 1693 Base b = getParam(vars, tgt.getParameter().get(0)); 1694 if (b == null) 1695 throw new FHIRException("Rule \""+ruleId+"\": Unable to find parameter "+((IdType) tgt.getParameter().get(0).getValue()).asStringValue()); 1696 if (!b.isResource()) 1697 throw new FHIRException("Rule \""+ruleId+"\": Transform engine cannot point at an element of type "+b.fhirType()); 1698 else { 1699 String id = b.getIdBase(); 1700 if (id == null) { 1701 id = UUID.randomUUID().toString().toLowerCase(); 1702 b.setIdBase(id); 1703 } 1704 return new Reference().setReference(b.fhirType()+"/"+id); 1705 } 1706 case DATEOP : 1707 throw new Error("Rule \""+ruleId+"\": Transform "+tgt.getTransform().toCode()+" not supported yet"); 1708 case UUID : 1709 return new IdType(UUID.randomUUID().toString()); 1710 case POINTER : 1711 b = getParam(vars, tgt.getParameter().get(0)); 1712 if (b instanceof Resource) 1713 return new UriType("urn:uuid:"+((Resource) b).getId()); 1714 else 1715 throw new FHIRException("Rule \""+ruleId+"\": Transform engine cannot point at an element of type "+b.fhirType()); 1716 case CC: 1717 CodeableConcept cc = new CodeableConcept(); 1718 cc.addCoding(buildCoding(getParamStringNoNull(vars, tgt.getParameter().get(0), tgt.toString()), getParamStringNoNull(vars, tgt.getParameter().get(1), tgt.toString()))); 1719 return cc; 1720 case C: 1721 Coding c = buildCoding(getParamStringNoNull(vars, tgt.getParameter().get(0), tgt.toString()), getParamStringNoNull(vars, tgt.getParameter().get(1), tgt.toString())); 1722 return c; 1723 default: 1724 throw new Error("Rule \""+ruleId+"\": Transform Unknown: "+tgt.getTransform().toCode()); 1725 } 1726 } catch (Exception e) { 1727 throw new FHIRException("Exception executing transform "+tgt.toString()+" on Rule \""+ruleId+"\": "+e.getMessage(), e); 1728 } 1729 } 1730 1731 1732 private Coding buildCoding(String uri, String code) throws FHIRException { 1733 // if we can get this as a valueSet, we will 1734 String system = null; 1735 String display = null; 1736 ValueSet vs = Utilities.noString(uri) ? null : worker.fetchResourceWithException(ValueSet.class, uri); 1737 if (vs != null) { 1738 ValueSetExpansionOutcome vse = worker.expandVS(vs, true, false); 1739 if (vse.getError() != null) 1740 throw new FHIRException(vse.getError()); 1741 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 1742 for (ValueSetExpansionContainsComponent t : vse.getValueset().getExpansion().getContains()) { 1743 if (t.hasCode()) 1744 b.append(t.getCode()); 1745 if (code.equals(t.getCode()) && t.hasSystem()) { 1746 system = t.getSystem(); 1747 display = t.getDisplay(); 1748 break; 1749 } 1750 if (code.equalsIgnoreCase(t.getDisplay()) && t.hasSystem()) { 1751 system = t.getSystem(); 1752 display = t.getDisplay(); 1753 break; 1754 } 1755 } 1756 if (system == null) 1757 throw new FHIRException("The code '"+code+"' is not in the value set '"+uri+"' (valid codes: "+b.toString()+"; also checked displays)"); 1758 } else 1759 system = uri; 1760 ValidationResult vr = worker.validateCode(system, code, null); 1761 if (vr != null && vr.getDisplay() != null) 1762 display = vr.getDisplay(); 1763 return new Coding().setSystem(system).setCode(code).setDisplay(display); 1764 } 1765 1766 1767 private String getParamStringNoNull(Variables vars, StructureMapGroupRuleTargetParameterComponent parameter, String message) throws FHIRException { 1768 Base b = getParam(vars, parameter); 1769 if (b == null) 1770 throw new FHIRException("Unable to find a value for "+parameter.toString()+". Context: "+message); 1771 if (!b.hasPrimitiveValue()) 1772 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); 1773 return b.primitiveValue(); 1774 } 1775 1776 private String getParamString(Variables vars, StructureMapGroupRuleTargetParameterComponent parameter) throws DefinitionException { 1777 Base b = getParam(vars, parameter); 1778 if (b == null || !b.hasPrimitiveValue()) 1779 return null; 1780 return b.primitiveValue(); 1781 } 1782 1783 1784 private Base getParam(Variables vars, StructureMapGroupRuleTargetParameterComponent parameter) throws DefinitionException { 1785 Type p = parameter.getValue(); 1786 if (!(p instanceof IdType)) 1787 return p; 1788 else { 1789 String n = ((IdType) p).asStringValue(); 1790 Base b = vars.get(VariableMode.INPUT, n); 1791 if (b == null) 1792 b = vars.get(VariableMode.OUTPUT, n); 1793 if (b == null) 1794 throw new DefinitionException("Variable "+n+" not found ("+vars.summary()+")"); 1795 return b; 1796 } 1797 } 1798 1799 1800 private Base translate(TransformContext context, StructureMap map, Variables vars, List<StructureMapGroupRuleTargetParameterComponent> parameter) throws FHIRException { 1801 Base src = getParam(vars, parameter.get(0)); 1802 String id = getParamString(vars, parameter.get(1)); 1803 String fld = parameter.size() > 2 ? getParamString(vars, parameter.get(2)) : null; 1804 return translate(context, map, src, id, fld); 1805 } 1806 1807 private class SourceElementComponentWrapper { 1808 private ConceptMapGroupComponent group; 1809 private SourceElementComponent comp; 1810 public SourceElementComponentWrapper(ConceptMapGroupComponent group, SourceElementComponent comp) { 1811 super(); 1812 this.group = group; 1813 this.comp = comp; 1814 } 1815 } 1816 public Base translate(TransformContext context, StructureMap map, Base source, String conceptMapUrl, String fieldToReturn) throws FHIRException { 1817 Coding src = new Coding(); 1818 if (source.isPrimitive()) { 1819 src.setCode(source.primitiveValue()); 1820 } else if ("Coding".equals(source.fhirType())) { 1821 Base[] b = source.getProperty("system".hashCode(), "system", true); 1822 if (b.length == 1) 1823 src.setSystem(b[0].primitiveValue()); 1824 b = source.getProperty("code".hashCode(), "code", true); 1825 if (b.length == 1) 1826 src.setCode(b[0].primitiveValue()); 1827 } else if ("CE".equals(source.fhirType())) { 1828 Base[] b = source.getProperty("codeSystem".hashCode(), "codeSystem", true); 1829 if (b.length == 1) 1830 src.setSystem(b[0].primitiveValue()); 1831 b = source.getProperty("code".hashCode(), "code", true); 1832 if (b.length == 1) 1833 src.setCode(b[0].primitiveValue()); 1834 } else 1835 throw new FHIRException("Unable to translate source "+source.fhirType()); 1836 1837 String su = conceptMapUrl; 1838 if (conceptMapUrl.equals("http://hl7.org/fhir/ConceptMap/special-oid2uri")) { 1839 String uri = worker.oid2Uri(src.getCode()); 1840 if (uri == null) 1841 uri = "urn:oid:"+src.getCode(); 1842 if ("uri".equals(fieldToReturn)) 1843 return new UriType(uri); 1844 else 1845 throw new FHIRException("Error in return code"); 1846 } else { 1847 ConceptMap cmap = null; 1848 if (conceptMapUrl.startsWith("#")) { 1849 for (Resource r : map.getContained()) { 1850 if (r instanceof ConceptMap && ((ConceptMap) r).getId().equals(conceptMapUrl.substring(1))) { 1851 cmap = (ConceptMap) r; 1852 su = map.getUrl()+conceptMapUrl; 1853 } 1854 } 1855 if (cmap == null) 1856 throw new FHIRException("Unable to translate - cannot find map "+conceptMapUrl); 1857 } else 1858 cmap = worker.fetchResource(ConceptMap.class, conceptMapUrl); 1859 Coding outcome = null; 1860 boolean done = false; 1861 String message = null; 1862 if (cmap == null) { 1863 if (services == null) 1864 message = "No map found for "+conceptMapUrl; 1865 else { 1866 outcome = services.translate(context.appInfo, src, conceptMapUrl); 1867 done = true; 1868 } 1869 } else { 1870 List<SourceElementComponentWrapper> list = new ArrayList<SourceElementComponentWrapper>(); 1871 for (ConceptMapGroupComponent g : cmap.getGroup()) { 1872 for (SourceElementComponent e : g.getElement()) { 1873 if (!src.hasSystem() && src.getCode().equals(e.getCode())) 1874 list.add(new SourceElementComponentWrapper(g, e)); 1875 else if (src.hasSystem() && src.getSystem().equals(g.getSource()) && src.getCode().equals(e.getCode())) 1876 list.add(new SourceElementComponentWrapper(g, e)); 1877 } 1878 } 1879 if (list.size() == 0) 1880 done = true; 1881 else if (list.get(0).comp.getTarget().size() == 0) 1882 message = "Concept map "+su+" found no translation for "+src.getCode(); 1883 else { 1884 for (TargetElementComponent tgt : list.get(0).comp.getTarget()) { 1885 if (tgt.getEquivalence() == null || EnumSet.of( ConceptMapEquivalence.EQUAL , ConceptMapEquivalence.RELATEDTO , ConceptMapEquivalence.EQUIVALENT, ConceptMapEquivalence.WIDER).contains(tgt.getEquivalence())) { 1886 if (done) { 1887 message = "Concept map "+su+" found multiple matches for "+src.getCode(); 1888 done = false; 1889 } else { 1890 done = true; 1891 outcome = new Coding().setCode(tgt.getCode()).setSystem(list.get(0).group.getTarget()); 1892 } 1893 } else if (tgt.getEquivalence() == ConceptMapEquivalence.UNMATCHED) { 1894 done = true; 1895 } 1896 } 1897 if (!done) 1898 message = "Concept map "+su+" found no usable translation for "+src.getCode(); 1899 } 1900 } 1901 if (!done) 1902 throw new FHIRException(message); 1903 if (outcome == null) 1904 return null; 1905 if ("code".equals(fieldToReturn)) 1906 return new CodeType(outcome.getCode()); 1907 else 1908 return outcome; 1909 } 1910 } 1911 1912 1913 public class PropertyWithType { 1914 private String path; 1915 private Property baseProperty; 1916 private Property profileProperty; 1917 private TypeDetails types; 1918 public PropertyWithType(String path, Property baseProperty, Property profileProperty, TypeDetails types) { 1919 super(); 1920 this.baseProperty = baseProperty; 1921 this.profileProperty = profileProperty; 1922 this.path = path; 1923 this.types = types; 1924 } 1925 1926 public TypeDetails getTypes() { 1927 return types; 1928 } 1929 public String getPath() { 1930 return path; 1931 } 1932 1933 public Property getBaseProperty() { 1934 return baseProperty; 1935 } 1936 1937 public void setBaseProperty(Property baseProperty) { 1938 this.baseProperty = baseProperty; 1939 } 1940 1941 public Property getProfileProperty() { 1942 return profileProperty; 1943 } 1944 1945 public void setProfileProperty(Property profileProperty) { 1946 this.profileProperty = profileProperty; 1947 } 1948 1949 public String summary() { 1950 return path; 1951 } 1952 1953 } 1954 1955 public class VariableForProfiling { 1956 private VariableMode mode; 1957 private String name; 1958 private PropertyWithType property; 1959 1960 public VariableForProfiling(VariableMode mode, String name, PropertyWithType property) { 1961 super(); 1962 this.mode = mode; 1963 this.name = name; 1964 this.property = property; 1965 } 1966 public VariableMode getMode() { 1967 return mode; 1968 } 1969 public String getName() { 1970 return name; 1971 } 1972 public PropertyWithType getProperty() { 1973 return property; 1974 } 1975 public String summary() { 1976 return name+": "+property.summary(); 1977 } 1978 } 1979 1980 public class VariablesForProfiling { 1981 private List<VariableForProfiling> list = new ArrayList<VariableForProfiling>(); 1982 private boolean optional; 1983 private boolean repeating; 1984 1985 public VariablesForProfiling(boolean optional, boolean repeating) { 1986 this.optional = optional; 1987 this.repeating = repeating; 1988 } 1989 1990 public void add(VariableMode mode, String name, String path, Property property, TypeDetails types) { 1991 add(mode, name, new PropertyWithType(path, property, null, types)); 1992 } 1993 1994 public void add(VariableMode mode, String name, String path, Property baseProperty, Property profileProperty, TypeDetails types) { 1995 add(mode, name, new PropertyWithType(path, baseProperty, profileProperty, types)); 1996 } 1997 1998 public void add(VariableMode mode, String name, PropertyWithType property) { 1999 VariableForProfiling vv = null; 2000 for (VariableForProfiling v : list) 2001 if ((v.mode == mode) && v.getName().equals(name)) 2002 vv = v; 2003 if (vv != null) 2004 list.remove(vv); 2005 list.add(new VariableForProfiling(mode, name, property)); 2006 } 2007 2008 public VariablesForProfiling copy(boolean optional, boolean repeating) { 2009 VariablesForProfiling result = new VariablesForProfiling(optional, repeating); 2010 result.list.addAll(list); 2011 return result; 2012 } 2013 2014 public VariablesForProfiling copy() { 2015 VariablesForProfiling result = new VariablesForProfiling(optional, repeating); 2016 result.list.addAll(list); 2017 return result; 2018 } 2019 2020 public VariableForProfiling get(VariableMode mode, String name) { 2021 if (mode == null) { 2022 for (VariableForProfiling v : list) 2023 if ((v.mode == VariableMode.OUTPUT) && v.getName().equals(name)) 2024 return v; 2025 for (VariableForProfiling v : list) 2026 if ((v.mode == VariableMode.INPUT) && v.getName().equals(name)) 2027 return v; 2028 } 2029 for (VariableForProfiling v : list) 2030 if ((v.mode == mode) && v.getName().equals(name)) 2031 return v; 2032 return null; 2033 } 2034 2035 public String summary() { 2036 CommaSeparatedStringBuilder s = new CommaSeparatedStringBuilder(); 2037 CommaSeparatedStringBuilder t = new CommaSeparatedStringBuilder(); 2038 for (VariableForProfiling v : list) 2039 if (v.mode == VariableMode.INPUT) 2040 s.append(v.summary()); 2041 else 2042 t.append(v.summary()); 2043 return "source variables ["+s.toString()+"], target variables ["+t.toString()+"]"; 2044 } 2045 } 2046 2047 public class StructureMapAnalysis { 2048 private List<StructureDefinition> profiles = new ArrayList<StructureDefinition>(); 2049 private XhtmlNode summary; 2050 public List<StructureDefinition> getProfiles() { 2051 return profiles; 2052 } 2053 public XhtmlNode getSummary() { 2054 return summary; 2055 } 2056 2057 } 2058 2059 /** 2060 * Given a structure map, return a set of analyses on it. 2061 * 2062 * Returned: 2063 * - a list or profiles for what it will create. First profile is the target 2064 * - a table with a summary (in xhtml) for easy human undertanding of the mapping 2065 * 2066 * 2067 * @param appInfo 2068 * @param map 2069 * @return 2070 * @throws Exception 2071 */ 2072 public StructureMapAnalysis analyse(Object appInfo, StructureMap map) throws Exception { 2073 ids.clear(); 2074 StructureMapAnalysis result = new StructureMapAnalysis(); 2075 TransformContext context = new TransformContext(appInfo); 2076 VariablesForProfiling vars = new VariablesForProfiling(false, false); 2077 StructureMapGroupComponent start = map.getGroup().get(0); 2078 for (StructureMapGroupInputComponent t : start.getInput()) { 2079 PropertyWithType ti = resolveType(map, t.getType(), t.getMode()); 2080 if (t.getMode() == StructureMapInputMode.SOURCE) 2081 vars.add(VariableMode.INPUT, t.getName(), ti); 2082 else 2083 vars.add(VariableMode.OUTPUT, t.getName(), createProfile(map, result.profiles, ti, start.getName(), start)); 2084 } 2085 2086 result.summary = new XhtmlNode(NodeType.Element, "table").setAttribute("class", "grid"); 2087 XhtmlNode tr = result.summary.addTag("tr"); 2088 tr.addTag("td").addTag("b").addText("Source"); 2089 tr.addTag("td").addTag("b").addText("Target"); 2090 2091 log("Start Profiling Transform "+map.getUrl()); 2092 analyseGroup("", context, map, vars, start, result); 2093 ProfileUtilities pu = new ProfileUtilities(worker, null, pkp); 2094 for (StructureDefinition sd : result.getProfiles()) 2095 pu.cleanUpDifferential(sd); 2096 return result; 2097 } 2098 2099 2100 private void analyseGroup(String indent, TransformContext context, StructureMap map, VariablesForProfiling vars, StructureMapGroupComponent group, StructureMapAnalysis result) throws Exception { 2101 log(indent+"Analyse Group : "+group.getName()); 2102 // todo: extends 2103 // todo: check inputs 2104 XhtmlNode tr = result.summary.addTag("tr").setAttribute("class", "diff-title"); 2105 XhtmlNode xs = tr.addTag("td"); 2106 XhtmlNode xt = tr.addTag("td"); 2107 for (StructureMapGroupInputComponent inp : group.getInput()) { 2108 if (inp.getMode() == StructureMapInputMode.SOURCE) 2109 noteInput(vars, inp, VariableMode.INPUT, xs); 2110 if (inp.getMode() == StructureMapInputMode.TARGET) 2111 noteInput(vars, inp, VariableMode.OUTPUT, xt); 2112 } 2113 for (StructureMapGroupRuleComponent r : group.getRule()) { 2114 analyseRule(indent+" ", context, map, vars, group, r, result); 2115 } 2116 } 2117 2118 2119 private void noteInput(VariablesForProfiling vars, StructureMapGroupInputComponent inp, VariableMode mode, XhtmlNode xs) { 2120 VariableForProfiling v = vars.get(mode, inp.getName()); 2121 if (v != null) 2122 xs.addText("Input: "+v.property.getPath()); 2123 } 2124 2125 private void analyseRule(String indent, TransformContext context, StructureMap map, VariablesForProfiling vars, StructureMapGroupComponent group, StructureMapGroupRuleComponent rule, StructureMapAnalysis result) throws Exception { 2126 log(indent+"Analyse rule : "+rule.getName()); 2127 XhtmlNode tr = result.summary.addTag("tr"); 2128 XhtmlNode xs = tr.addTag("td"); 2129 XhtmlNode xt = tr.addTag("td"); 2130 2131 VariablesForProfiling srcVars = vars.copy(); 2132 if (rule.getSource().size() != 1) 2133 throw new Exception("Rule \""+rule.getName()+"\": not handled yet"); 2134 VariablesForProfiling source = analyseSource(rule.getName(), context, srcVars, rule.getSourceFirstRep(), xs); 2135 2136 TargetWriter tw = new TargetWriter(); 2137 for (StructureMapGroupRuleTargetComponent t : rule.getTarget()) { 2138 analyseTarget(rule.getName(), context, source, map, t, rule.getSourceFirstRep().getVariable(), tw, result.profiles, rule.getName()); 2139 } 2140 tw.commit(xt); 2141 2142 for (StructureMapGroupRuleComponent childrule : rule.getRule()) { 2143 analyseRule(indent+" ", context, map, source, group, childrule, result); 2144 } 2145// for (StructureMapGroupRuleDependentComponent dependent : rule.getDependent()) { 2146// executeDependency(indent+" ", context, map, v, group, dependent); // do we need group here? 2147// } 2148 } 2149 2150 public class StringPair { 2151 private String var; 2152 private String desc; 2153 public StringPair(String var, String desc) { 2154 super(); 2155 this.var = var; 2156 this.desc = desc; 2157 } 2158 public String getVar() { 2159 return var; 2160 } 2161 public String getDesc() { 2162 return desc; 2163 } 2164 } 2165 public class TargetWriter { 2166 private Map<String, String> newResources = new HashMap<String, String>(); 2167 private List<StringPair> assignments = new ArrayList<StringPair>(); 2168 private List<StringPair> keyProps = new ArrayList<StringPair>(); 2169 private CommaSeparatedStringBuilder txt = new CommaSeparatedStringBuilder(); 2170 2171 public void newResource(String var, String name) { 2172 newResources.put(var, name); 2173 txt.append("new "+name); 2174 } 2175 2176 public void valueAssignment(String context, String desc) { 2177 assignments.add(new StringPair(context, desc)); 2178 txt.append(desc); 2179 } 2180 2181 public void keyAssignment(String context, String desc) { 2182 keyProps.add(new StringPair(context, desc)); 2183 txt.append(desc); 2184 } 2185 public void commit(XhtmlNode xt) { 2186 if (newResources.size() == 1 && assignments.size() == 1 && newResources.containsKey(assignments.get(0).getVar()) && keyProps.size() == 1 && newResources.containsKey(keyProps.get(0).getVar()) ) { 2187 xt.addText("new "+assignments.get(0).desc+" ("+keyProps.get(0).desc.substring(keyProps.get(0).desc.indexOf(".")+1)+")"); 2188 } else if (newResources.size() == 1 && assignments.size() == 1 && newResources.containsKey(assignments.get(0).getVar()) && keyProps.size() == 0) { 2189 xt.addText("new "+assignments.get(0).desc); 2190 } else { 2191 xt.addText(txt.toString()); 2192 } 2193 } 2194 } 2195 2196 private VariablesForProfiling analyseSource(String ruleId, TransformContext context, VariablesForProfiling vars, StructureMapGroupRuleSourceComponent src, XhtmlNode td) throws Exception { 2197 VariableForProfiling var = vars.get(VariableMode.INPUT, src.getContext()); 2198 if (var == null) 2199 throw new FHIRException("Rule \""+ruleId+"\": Unknown input variable "+src.getContext()); 2200 PropertyWithType prop = var.getProperty(); 2201 2202 boolean optional = false; 2203 boolean repeating = false; 2204 2205 if (src.hasCondition()) { 2206 optional = true; 2207 } 2208 2209 if (src.hasElement()) { 2210 Property element = prop.getBaseProperty().getChild(prop.types.getType(), src.getElement()); 2211 if (element == null) 2212 throw new Exception("Rule \""+ruleId+"\": Unknown element name "+src.getElement()); 2213 if (element.getDefinition().getMin() == 0) 2214 optional = true; 2215 if (element.getDefinition().getMax().equals("*")) 2216 repeating = true; 2217 VariablesForProfiling result = vars.copy(optional, repeating); 2218 TypeDetails type = new TypeDetails(CollectionStatus.SINGLETON); 2219 for (TypeRefComponent tr : element.getDefinition().getType()) { 2220 if (!tr.hasCode()) 2221 throw new Error("Rule \""+ruleId+"\": Element has no type"); 2222 ProfiledType pt = new ProfiledType(tr.getCode()); 2223 if (tr.hasProfile()) 2224 pt.addProfiles(tr.getProfile()); 2225 if (element.getDefinition().hasBinding()) 2226 pt.addBinding(element.getDefinition().getBinding()); 2227 type.addType(pt); 2228 } 2229 td.addText(prop.getPath()+"."+src.getElement()); 2230 if (src.hasVariable()) 2231 result.add(VariableMode.INPUT, src.getVariable(), new PropertyWithType(prop.getPath()+"."+src.getElement(), element, null, type)); 2232 return result; 2233 } else { 2234 td.addText(prop.getPath()); // ditto! 2235 return vars.copy(optional, repeating); 2236 } 2237 } 2238 2239 2240 private void analyseTarget(String ruleId, TransformContext context, VariablesForProfiling vars, StructureMap map, StructureMapGroupRuleTargetComponent tgt, String tv, TargetWriter tw, List<StructureDefinition> profiles, String sliceName) throws Exception { 2241 VariableForProfiling var = null; 2242 if (tgt.hasContext()) { 2243 var = vars.get(VariableMode.OUTPUT, tgt.getContext()); 2244 if (var == null) 2245 throw new Exception("Rule \""+ruleId+"\": target context not known: "+tgt.getContext()); 2246 if (!tgt.hasElement()) 2247 throw new Exception("Rule \""+ruleId+"\": Not supported yet"); 2248 } 2249 2250 2251 TypeDetails type = null; 2252 if (tgt.hasTransform()) { 2253 type = analyseTransform(context, map, tgt, var, vars); 2254 // profiling: dest.setProperty(tgt.getElement().hashCode(), tgt.getElement(), v); 2255 } else { 2256 Property vp = var.property.baseProperty.getChild(tgt.getElement(), tgt.getElement()); 2257 if (vp == null) 2258 throw new Exception("Unknown Property "+tgt.getElement()+" on "+var.property.path); 2259 2260 type = new TypeDetails(CollectionStatus.SINGLETON, vp.getType(tgt.getElement())); 2261 } 2262 2263 if (tgt.getTransform() == StructureMapTransform.CREATE) { 2264 String s = getParamString(vars, tgt.getParameter().get(0)); 2265 if (worker.getResourceNames().contains(s)) 2266 tw.newResource(tgt.getVariable(), s); 2267 } else { 2268 boolean mapsSrc = false; 2269 for (StructureMapGroupRuleTargetParameterComponent p : tgt.getParameter()) { 2270 Type pr = p.getValue(); 2271 if (pr instanceof IdType && ((IdType) pr).asStringValue().equals(tv)) 2272 mapsSrc = true; 2273 } 2274 if (mapsSrc) { 2275 if (var == null) 2276 throw new Error("Rule \""+ruleId+"\": Attempt to assign with no context"); 2277 tw.valueAssignment(tgt.getContext(), var.property.getPath()+"."+tgt.getElement()+getTransformSuffix(tgt.getTransform())); 2278 } else if (tgt.hasContext()) { 2279 if (isSignificantElement(var.property, tgt.getElement())) { 2280 String td = describeTransform(tgt); 2281 if (td != null) 2282 tw.keyAssignment(tgt.getContext(), var.property.getPath()+"."+tgt.getElement()+" = "+td); 2283 } 2284 } 2285 } 2286 Type fixed = generateFixedValue(tgt); 2287 2288 PropertyWithType prop = updateProfile(var, tgt.getElement(), type, map, profiles, sliceName, fixed, tgt); 2289 if (tgt.hasVariable()) 2290 if (tgt.hasElement()) 2291 vars.add(VariableMode.OUTPUT, tgt.getVariable(), prop); 2292 else 2293 vars.add(VariableMode.OUTPUT, tgt.getVariable(), prop); 2294 } 2295 2296 private Type generateFixedValue(StructureMapGroupRuleTargetComponent tgt) { 2297 if (!allParametersFixed(tgt)) 2298 return null; 2299 if (!tgt.hasTransform()) 2300 return null; 2301 switch (tgt.getTransform()) { 2302 case COPY: return tgt.getParameter().get(0).getValue(); 2303 case TRUNCATE: return null; 2304 //case ESCAPE: 2305 //case CAST: 2306 //case APPEND: 2307 case TRANSLATE: return null; 2308 //case DATEOP, 2309 //case UUID, 2310 //case POINTER, 2311 //case EVALUATE, 2312 case CC: 2313 CodeableConcept cc = new CodeableConcept(); 2314 cc.addCoding(buildCoding(tgt.getParameter().get(0).getValue(), tgt.getParameter().get(1).getValue())); 2315 return cc; 2316 case C: 2317 return buildCoding(tgt.getParameter().get(0).getValue(), tgt.getParameter().get(1).getValue()); 2318 case QTY: return null; 2319 //case ID, 2320 //case CP, 2321 default: 2322 return null; 2323 } 2324 } 2325 2326 @SuppressWarnings("rawtypes") 2327 private Coding buildCoding(Type value1, Type value2) { 2328 return new Coding().setSystem(((PrimitiveType) value1).asStringValue()).setCode(((PrimitiveType) value2).asStringValue()) ; 2329 } 2330 2331 private boolean allParametersFixed(StructureMapGroupRuleTargetComponent tgt) { 2332 for (StructureMapGroupRuleTargetParameterComponent p : tgt.getParameter()) { 2333 Type pr = p.getValue(); 2334 if (pr instanceof IdType) 2335 return false; 2336 } 2337 return true; 2338 } 2339 2340 private String describeTransform(StructureMapGroupRuleTargetComponent tgt) throws FHIRException { 2341 switch (tgt.getTransform()) { 2342 case COPY: return null; 2343 case TRUNCATE: return null; 2344 //case ESCAPE: 2345 //case CAST: 2346 //case APPEND: 2347 case TRANSLATE: return null; 2348 //case DATEOP, 2349 //case UUID, 2350 //case POINTER, 2351 //case EVALUATE, 2352 case CC: return describeTransformCCorC(tgt); 2353 case C: return describeTransformCCorC(tgt); 2354 case QTY: return null; 2355 //case ID, 2356 //case CP, 2357 default: 2358 return null; 2359 } 2360 } 2361 2362 @SuppressWarnings("rawtypes") 2363 private String describeTransformCCorC(StructureMapGroupRuleTargetComponent tgt) throws FHIRException { 2364 if (tgt.getParameter().size() < 2) 2365 return null; 2366 Type p1 = tgt.getParameter().get(0).getValue(); 2367 Type p2 = tgt.getParameter().get(1).getValue(); 2368 if (p1 instanceof IdType || p2 instanceof IdType) 2369 return null; 2370 if (!(p1 instanceof PrimitiveType) || !(p2 instanceof PrimitiveType)) 2371 return null; 2372 String uri = ((PrimitiveType) p1).asStringValue(); 2373 String code = ((PrimitiveType) p2).asStringValue(); 2374 if (Utilities.noString(uri)) 2375 throw new FHIRException("Describe Transform, but the uri is blank"); 2376 if (Utilities.noString(code)) 2377 throw new FHIRException("Describe Transform, but the code is blank"); 2378 Coding c = buildCoding(uri, code); 2379 return NarrativeGenerator.describeSystem(c.getSystem())+"#"+c.getCode()+(c.hasDisplay() ? "("+c.getDisplay()+")" : ""); 2380 } 2381 2382 2383 private boolean isSignificantElement(PropertyWithType property, String element) { 2384 if ("Observation".equals(property.getPath())) 2385 return "code".equals(element); 2386 else if ("Bundle".equals(property.getPath())) 2387 return "type".equals(element); 2388 else 2389 return false; 2390 } 2391 2392 private String getTransformSuffix(StructureMapTransform transform) { 2393 switch (transform) { 2394 case COPY: return ""; 2395 case TRUNCATE: return " (truncated)"; 2396 //case ESCAPE: 2397 //case CAST: 2398 //case APPEND: 2399 case TRANSLATE: return " (translated)"; 2400 //case DATEOP, 2401 //case UUID, 2402 //case POINTER, 2403 //case EVALUATE, 2404 case CC: return " (--> CodeableConcept)"; 2405 case C: return " (--> Coding)"; 2406 case QTY: return " (--> Quantity)"; 2407 //case ID, 2408 //case CP, 2409 default: 2410 return " {??)"; 2411 } 2412 } 2413 2414 private PropertyWithType updateProfile(VariableForProfiling var, String element, TypeDetails type, StructureMap map, List<StructureDefinition> profiles, String sliceName, Type fixed, StructureMapGroupRuleTargetComponent tgt) throws FHIRException { 2415 if (var == null) { 2416 assert (Utilities.noString(element)); 2417 // 1. start the new structure definition 2418 StructureDefinition sdn = worker.fetchResource(StructureDefinition.class, type.getType()); 2419 if (sdn == null) 2420 throw new FHIRException("Unable to find definition for "+type.getType()); 2421 ElementDefinition edn = sdn.getSnapshot().getElementFirstRep(); 2422 PropertyWithType pn = createProfile(map, profiles, new PropertyWithType(sdn.getId(), new Property(worker, edn, sdn), null, type), sliceName, tgt); 2423 2424// // 2. hook it into the base bundle 2425// if (type.getType().startsWith("http://hl7.org/fhir/StructureDefinition/") && worker.getResourceNames().contains(type.getType().substring(40))) { 2426// StructureDefinition sd = var.getProperty().profileProperty.getStructure(); 2427// ElementDefinition ed = sd.getDifferential().addElement(); 2428// ed.setPath("Bundle.entry"); 2429// ed.setName(sliceName); 2430// ed.setMax("1"); // well, it is for now... 2431// ed = sd.getDifferential().addElement(); 2432// ed.setPath("Bundle.entry.fullUrl"); 2433// ed.setMin(1); 2434// ed = sd.getDifferential().addElement(); 2435// ed.setPath("Bundle.entry.resource"); 2436// ed.setMin(1); 2437// ed.addType().setCode(pn.getProfileProperty().getStructure().getType()).setProfile(pn.getProfileProperty().getStructure().getUrl()); 2438// } 2439 return pn; 2440 } else { 2441 assert (!Utilities.noString(element)); 2442 Property pvb = var.getProperty().getBaseProperty(); 2443 Property pvd = var.getProperty().getProfileProperty(); 2444 Property pc = pvb.getChild(element, var.property.types); 2445 if (pc == null) 2446 throw new DefinitionException("Unable to find a definition for "+pvb.getDefinition().getPath()+"."+element); 2447 2448 // the profile structure definition (derived) 2449 StructureDefinition sd = var.getProperty().profileProperty.getStructure(); 2450 ElementDefinition ednew = sd.getDifferential().addElement(); 2451 ednew.setPath(var.getProperty().profileProperty.getDefinition().getPath()+"."+pc.getName()); 2452 ednew.setUserData("slice-name", sliceName); 2453 ednew.setFixed(fixed); 2454 for (ProfiledType pt : type.getProfiledTypes()) { 2455 if (pt.hasBindings()) 2456 ednew.setBinding(pt.getBindings().get(0)); 2457 if (pt.getUri().startsWith("http://hl7.org/fhir/StructureDefinition/")) { 2458 String t = pt.getUri().substring(40); 2459 t = checkType(t, pc, pt.getProfiles()); 2460 if (t != null) { 2461 if (pt.hasProfiles()) { 2462 for (String p : pt.getProfiles()) 2463 if (t.equals("Reference")) 2464 ednew.getType(t).addTargetProfile(p); 2465 else 2466 ednew.getType(t).addProfile(p); 2467 } else 2468 ednew.getType(t); 2469 } 2470 } 2471 } 2472 2473 return new PropertyWithType(var.property.path+"."+element, pc, new Property(worker, ednew, sd), type); 2474 } 2475 } 2476 2477 2478 2479 private String checkType(String t, Property pvb, List<String> profiles) throws FHIRException { 2480 if (pvb.getDefinition().getType().size() == 1 && isCompatibleType(t, pvb.getDefinition().getType().get(0).getCode()) && profilesMatch(profiles, pvb.getDefinition().getType().get(0).getProfile())) 2481 return null; 2482 for (TypeRefComponent tr : pvb.getDefinition().getType()) { 2483 if (isCompatibleType(t, tr.getCode())) 2484 return tr.getCode(); // note what is returned - the base type, not the inferred mapping type 2485 } 2486 throw new FHIRException("The type "+t+" is not compatible with the allowed types for "+pvb.getDefinition().getPath()); 2487 } 2488 2489 private boolean profilesMatch(List<String> profiles, List<CanonicalType> profile) { 2490 return profiles == null || profiles.size() == 0 || profile.size() == 0 || (profiles.size() == 1 && profiles.get(0).equals(profile.get(0).getValue())); 2491 } 2492 2493 private boolean isCompatibleType(String t, String code) { 2494 if (t.equals(code)) 2495 return true; 2496 if (t.equals("string")) { 2497 StructureDefinition sd = worker.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/"+code); 2498 if (sd != null && sd.getBaseDefinition().equals("http://hl7.org/fhir/StructureDefinition/string")) 2499 return true; 2500 } 2501 return false; 2502 } 2503 2504 private TypeDetails analyseTransform(TransformContext context, StructureMap map, StructureMapGroupRuleTargetComponent tgt, VariableForProfiling var, VariablesForProfiling vars) throws FHIRException { 2505 switch (tgt.getTransform()) { 2506 case CREATE : 2507 String p = getParamString(vars, tgt.getParameter().get(0)); 2508 return new TypeDetails(CollectionStatus.SINGLETON, p); 2509 case COPY : 2510 return getParam(vars, tgt.getParameter().get(0)); 2511 case EVALUATE : 2512 ExpressionNode expr = (ExpressionNode) tgt.getUserData(MAP_EXPRESSION); 2513 if (expr == null) { 2514 expr = fpe.parse(getParamString(vars, tgt.getParameter().get(tgt.getParameter().size()-1))); 2515 tgt.setUserData(MAP_WHERE_EXPRESSION, expr); 2516 } 2517 return fpe.check(vars, null, expr); 2518 2519////case TRUNCATE : 2520//// String src = getParamString(vars, tgt.getParameter().get(0)); 2521//// String len = getParamString(vars, tgt.getParameter().get(1)); 2522//// if (Utilities.isInteger(len)) { 2523//// int l = Integer.parseInt(len); 2524//// if (src.length() > l) 2525//// src = src.substring(0, l); 2526//// } 2527//// return new StringType(src); 2528////case ESCAPE : 2529//// throw new Error("Transform "+tgt.getTransform().toCode()+" not supported yet"); 2530////case CAST : 2531//// throw new Error("Transform "+tgt.getTransform().toCode()+" not supported yet"); 2532////case APPEND : 2533//// throw new Error("Transform "+tgt.getTransform().toCode()+" not supported yet"); 2534 case TRANSLATE : 2535 return new TypeDetails(CollectionStatus.SINGLETON, "CodeableConcept"); 2536 case CC: 2537 ProfiledType res = new ProfiledType("CodeableConcept"); 2538 if (tgt.getParameter().size() >= 2 && isParamId(vars, tgt.getParameter().get(1))) { 2539 TypeDetails td = vars.get(null, getParamId(vars, tgt.getParameter().get(1))).property.types; 2540 if (td != null && td.hasBinding()) 2541 // todo: do we need to check that there's no implicit translation her? I don't think we do... 2542 res.addBinding(td.getBinding()); 2543 } 2544 return new TypeDetails(CollectionStatus.SINGLETON, res); 2545 case C: 2546 return new TypeDetails(CollectionStatus.SINGLETON, "Coding"); 2547 case QTY: 2548 return new TypeDetails(CollectionStatus.SINGLETON, "Quantity"); 2549 case REFERENCE : 2550 VariableForProfiling vrs = vars.get(VariableMode.OUTPUT, getParamId(vars, tgt.getParameterFirstRep())); 2551 if (vrs == null) 2552 throw new FHIRException("Unable to resolve variable \""+getParamId(vars, tgt.getParameterFirstRep())+"\""); 2553 String profile = vrs.property.getProfileProperty().getStructure().getUrl(); 2554 TypeDetails td = new TypeDetails(CollectionStatus.SINGLETON); 2555 td.addType("Reference", profile); 2556 return td; 2557////case DATEOP : 2558//// throw new Error("Transform "+tgt.getTransform().toCode()+" not supported yet"); 2559////case UUID : 2560//// return new IdType(UUID.randomUUID().toString()); 2561////case POINTER : 2562//// Base b = getParam(vars, tgt.getParameter().get(0)); 2563//// if (b instanceof Resource) 2564//// return new UriType("urn:uuid:"+((Resource) b).getId()); 2565//// else 2566//// throw new FHIRException("Transform engine cannot point at an element of type "+b.fhirType()); 2567 default: 2568 throw new Error("Transform Unknown or not handled yet: "+tgt.getTransform().toCode()); 2569 } 2570 } 2571 private String getParamString(VariablesForProfiling vars, StructureMapGroupRuleTargetParameterComponent parameter) { 2572 Type p = parameter.getValue(); 2573 if (p == null || p instanceof IdType) 2574 return null; 2575 if (!p.hasPrimitiveValue()) 2576 return null; 2577 return p.primitiveValue(); 2578 } 2579 2580 private String getParamId(VariablesForProfiling vars, StructureMapGroupRuleTargetParameterComponent parameter) { 2581 Type p = parameter.getValue(); 2582 if (p == null || !(p instanceof IdType)) 2583 return null; 2584 return p.primitiveValue(); 2585 } 2586 2587 private boolean isParamId(VariablesForProfiling vars, StructureMapGroupRuleTargetParameterComponent parameter) { 2588 Type p = parameter.getValue(); 2589 if (p == null || !(p instanceof IdType)) 2590 return false; 2591 return vars.get(null, p.primitiveValue()) != null; 2592 } 2593 2594 private TypeDetails getParam(VariablesForProfiling vars, StructureMapGroupRuleTargetParameterComponent parameter) throws DefinitionException { 2595 Type p = parameter.getValue(); 2596 if (!(p instanceof IdType)) 2597 return new TypeDetails(CollectionStatus.SINGLETON, ProfileUtilities.sdNs(p.fhirType())); 2598 else { 2599 String n = ((IdType) p).asStringValue(); 2600 VariableForProfiling b = vars.get(VariableMode.INPUT, n); 2601 if (b == null) 2602 b = vars.get(VariableMode.OUTPUT, n); 2603 if (b == null) 2604 throw new DefinitionException("Variable "+n+" not found ("+vars.summary()+")"); 2605 return b.getProperty().getTypes(); 2606 } 2607 } 2608 2609 private PropertyWithType createProfile(StructureMap map, List<StructureDefinition> profiles, PropertyWithType prop, String sliceName, Base ctxt) throws DefinitionException { 2610 if (prop.getBaseProperty().getDefinition().getPath().contains(".")) 2611 throw new DefinitionException("Unable to process entry point"); 2612 2613 String type = prop.getBaseProperty().getDefinition().getPath(); 2614 String suffix = ""; 2615 if (ids.containsKey(type)) { 2616 int id = ids.get(type); 2617 id++; 2618 ids.put(type, id); 2619 suffix = "-"+Integer.toString(id); 2620 } else 2621 ids.put(type, 0); 2622 2623 StructureDefinition profile = new StructureDefinition(); 2624 profiles.add(profile); 2625 profile.setDerivation(TypeDerivationRule.CONSTRAINT); 2626 profile.setType(type); 2627 profile.setBaseDefinition(prop.getBaseProperty().getStructure().getUrl()); 2628 profile.setName("Profile for "+profile.getType()+" for "+sliceName); 2629 profile.setUrl(map.getUrl().replace("StructureMap", "StructureDefinition")+"-"+profile.getType()+suffix); 2630 ctxt.setUserData("profile", profile.getUrl()); // then we can easily assign this profile url for validation later when we actually transform 2631 profile.setId(map.getId()+"-"+profile.getType()+suffix); 2632 profile.setStatus(map.getStatus()); 2633 profile.setExperimental(map.getExperimental()); 2634 profile.setDescription("Generated automatically from the mapping by the Java Reference Implementation"); 2635 for (ContactDetail c : map.getContact()) { 2636 ContactDetail p = profile.addContact(); 2637 p.setName(c.getName()); 2638 for (ContactPoint cc : c.getTelecom()) 2639 p.addTelecom(cc); 2640 } 2641 profile.setDate(map.getDate()); 2642 profile.setCopyright(map.getCopyright()); 2643 profile.setFhirVersion(Constants.VERSION); 2644 profile.setKind(prop.getBaseProperty().getStructure().getKind()); 2645 profile.setAbstract(false); 2646 ElementDefinition ed = profile.getDifferential().addElement(); 2647 ed.setPath(profile.getType()); 2648 prop.profileProperty = new Property(worker, ed, profile); 2649 return prop; 2650 } 2651 2652 private PropertyWithType resolveType(StructureMap map, String type, StructureMapInputMode mode) throws Exception { 2653 for (StructureMapStructureComponent imp : map.getStructure()) { 2654 if ((imp.getMode() == StructureMapModelMode.SOURCE && mode == StructureMapInputMode.SOURCE) || 2655 (imp.getMode() == StructureMapModelMode.TARGET && mode == StructureMapInputMode.TARGET)) { 2656 StructureDefinition sd = worker.fetchResource(StructureDefinition.class, imp.getUrl()); 2657 if (sd == null) 2658 throw new Exception("Import "+imp.getUrl()+" cannot be resolved"); 2659 if (sd.getId().equals(type)) { 2660 return new PropertyWithType(sd.getType(), new Property(worker, sd.getSnapshot().getElement().get(0), sd), null, new TypeDetails(CollectionStatus.SINGLETON, sd.getUrl())); 2661 } 2662 } 2663 } 2664 throw new Exception("Unable to find structure definition for "+type+" in imports"); 2665 } 2666 2667 2668 public StructureMap generateMapFromMappings(StructureDefinition sd) throws IOException, FHIRException { 2669 String id = getLogicalMappingId(sd); 2670 if (id == null) 2671 return null; 2672 String prefix = ToolingExtensions.readStringExtension(sd, ToolingExtensions.EXT_MAPPING_PREFIX); 2673 String suffix = ToolingExtensions.readStringExtension(sd, ToolingExtensions.EXT_MAPPING_SUFFIX); 2674 if (prefix == null || suffix == null) 2675 return null; 2676 // we build this by text. Any element that has a mapping, we put it's mappings inside it.... 2677 StringBuilder b = new StringBuilder(); 2678 b.append(prefix); 2679 2680 ElementDefinition root = sd.getSnapshot().getElementFirstRep(); 2681 String m = getMapping(root, id); 2682 if (m != null) 2683 b.append(m+"\r\n"); 2684 addChildMappings(b, id, "", sd, root, false); 2685 b.append("\r\n"); 2686 b.append(suffix); 2687 b.append("\r\n"); 2688 StructureMap map = parse(b.toString()); 2689 map.setId(tail(map.getUrl())); 2690 if (!map.hasStatus()) 2691 map.setStatus(PublicationStatus.DRAFT); 2692 map.getText().setStatus(NarrativeStatus.GENERATED); 2693 map.getText().setDiv(new XhtmlNode(NodeType.Element, "div")); 2694 map.getText().getDiv().addTag("pre").addText(render(map)); 2695 return map; 2696 } 2697 2698 2699 private String tail(String url) { 2700 return url.substring(url.lastIndexOf("/")+1); 2701 } 2702 2703 2704 private void addChildMappings(StringBuilder b, String id, String indent, StructureDefinition sd, ElementDefinition ed, boolean inner) throws DefinitionException { 2705 boolean first = true; 2706 List<ElementDefinition> children = ProfileUtilities.getChildMap(sd, ed); 2707 for (ElementDefinition child : children) { 2708 if (first && inner) { 2709 b.append(" then {\r\n"); 2710 first = false; 2711 } 2712 String map = getMapping(child, id); 2713 if (map != null) { 2714 b.append(indent+" "+child.getPath()+": "+map); 2715 addChildMappings(b, id, indent+" ", sd, child, true); 2716 b.append("\r\n"); 2717 } 2718 } 2719 if (!first && inner) 2720 b.append(indent+"}"); 2721 2722 } 2723 2724 2725 private String getMapping(ElementDefinition ed, String id) { 2726 for (ElementDefinitionMappingComponent map : ed.getMapping()) 2727 if (id.equals(map.getIdentity())) 2728 return map.getMap(); 2729 return null; 2730 } 2731 2732 2733 private String getLogicalMappingId(StructureDefinition sd) { 2734 String id = null; 2735 for (StructureDefinitionMappingComponent map : sd.getMapping()) { 2736 if ("http://hl7.org/fhir/logical".equals(map.getUri())) 2737 return map.getIdentity(); 2738 } 2739 return null; 2740 } 2741 2742}