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