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