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