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