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