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