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