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