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