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