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