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