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