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