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