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