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