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