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