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