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