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