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