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