From 3d3bda30ec517d2c0b8887c733647b9197b9fe54 Mon Sep 17 00:00:00 2001 From: Mark Iantorno Date: Thu, 10 Dec 2020 10:56:56 -0500 Subject: [PATCH] Code changes to assist with validator updates in HAPI (#400) * just some cleanup and simple changes * changed folder name the map is currently structured, not in the past --- .../configuration/configuration.ini | 2 +- .../org/hl7/fhir/r5/model/StructureMap.java | 5 +- .../hl7/fhir/r5/utils/MappingSheetParser.java | 2 +- .../fhir/r5/utils/StructureMapUtilities.java | 3010 ----------------- .../structuremap/FFHIRPathHostServices.java | 99 + .../structuremap/ITransformerServices.java | 28 + .../utils/structuremap/PropertyWithType.java | 48 + .../r5/utils/structuremap/ResolvedGroup.java | 8 + .../SourceElementComponentWrapper.java | 22 + .../r5/utils/structuremap/StringPair.java | 20 + .../structuremap/StructureMapAnalysis.java | 20 + .../structuremap/StructureMapUtilities.java | 2552 ++++++++++++++ .../r5/utils/structuremap/TargetWriter.java | 41 + .../utils/structuremap/TransformContext.java | 15 + .../fhir/r5/utils/structuremap/Variable.java | 38 + .../structuremap/VariableForProfiling.java | 30 + .../r5/utils/structuremap/VariableMode.java | 5 + .../fhir/r5/utils/structuremap/Variables.java | 54 + .../structuremap/VariablesForProfiling.java | 77 + .../r5/test/StructureMapUtilitiesTest.java | 24 +- .../java/org/hl7/fhir/validation/Content.java | 8 + .../hl7/fhir/validation/ValidationEngine.java | 1198 +++---- .../hl7/fhir/validation/ValidationRecord.java | 49 + .../cli/services/HTMLOutputGenerator.java | 2 +- .../cli/services/ValidationService.java | 2 +- .../r5/test/FHIRMappingLanguageTests.java | 3 +- 26 files changed, 3694 insertions(+), 3668 deletions(-) delete mode 100644 org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/StructureMapUtilities.java create mode 100644 org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/FFHIRPathHostServices.java create mode 100644 org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/ITransformerServices.java create mode 100644 org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/PropertyWithType.java create mode 100644 org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/ResolvedGroup.java create mode 100644 org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/SourceElementComponentWrapper.java create mode 100644 org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/StringPair.java create mode 100644 org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/StructureMapAnalysis.java create mode 100644 org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/StructureMapUtilities.java create mode 100644 org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/TargetWriter.java create mode 100644 org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/TransformContext.java create mode 100644 org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/Variable.java create mode 100644 org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/VariableForProfiling.java create mode 100644 org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/VariableMode.java create mode 100644 org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/Variables.java create mode 100644 org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/VariablesForProfiling.java create mode 100644 org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/Content.java create mode 100644 org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationRecord.java diff --git a/org.hl7.fhir.core.generator/configuration/configuration.ini b/org.hl7.fhir.core.generator/configuration/configuration.ini index 8872b770b..ee413bede 100644 --- a/org.hl7.fhir.core.generator/configuration/configuration.ini +++ b/org.hl7.fhir.core.generator/configuration/configuration.ini @@ -30,7 +30,7 @@ Resource = org.hl7.fhir.instance.model.api.IAnyResource Period = ca.uhn.fhir.model.api.TemporalPrecisionEnum Extension = org.hl7.fhir.instance.model.api.IBaseExtension, org.hl7.fhir.instance.model.api.IBaseDatatype, org.hl7.fhir.instance.model.api.IBaseHasExtensions HumanName = ca.uhn.fhir.util.DatatypeUtil, org.hl7.fhir.instance.model.api.IPrimitiveType -StructureMap = org.hl7.fhir.r5.utils.StructureMapUtilities +StructureMap = org.hl7.fhir.r5.utils.structuremap.StructureMapUtilities Narrative = org.hl7.fhir.instance.model.api.INarrative Coding = org.hl7.fhir.instance.model.api.IBaseCoding OperationOutcome = org.hl7.fhir.instance.model.api.IBaseOperationOutcome diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/StructureMap.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/StructureMap.java index d0f8da9ef..2393dc34a 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/StructureMap.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/StructureMap.java @@ -38,16 +38,13 @@ import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.r5.model.Enumerations.*; import org.hl7.fhir.instance.model.api.IBaseBackboneElement; import org.hl7.fhir.exceptions.FHIRException; -import org.hl7.fhir.instance.model.api.ICompositeType; import ca.uhn.fhir.model.api.annotation.ResourceDef; import ca.uhn.fhir.model.api.annotation.SearchParamDefinition; -import org.hl7.fhir.instance.model.api.IBaseBackboneElement; import ca.uhn.fhir.model.api.annotation.Child; -import ca.uhn.fhir.model.api.annotation.ChildOrder; import ca.uhn.fhir.model.api.annotation.Description; import ca.uhn.fhir.model.api.annotation.Block; -import org.hl7.fhir.r5.utils.StructureMapUtilities; +import org.hl7.fhir.r5.utils.structuremap.StructureMapUtilities; /** * A Map of relationships between 2 structures that can be used to transform data. */ diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/MappingSheetParser.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/MappingSheetParser.java index d93b0d09f..8810c9941 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/MappingSheetParser.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/MappingSheetParser.java @@ -31,9 +31,9 @@ import org.hl7.fhir.r5.model.StructureMap.StructureMapGroupRuleComponent; import org.hl7.fhir.r5.model.StructureMap.StructureMapGroupRuleDependentComponent; import org.hl7.fhir.r5.model.StructureMap.StructureMapGroupRuleSourceComponent; import org.hl7.fhir.r5.model.StructureMap.StructureMapGroupRuleTargetComponent; -import org.hl7.fhir.r5.model.StructureMap.StructureMapGroupTypeMode; import org.hl7.fhir.r5.model.StructureMap.StructureMapTransform; import org.hl7.fhir.r5.model.UrlType; +import org.hl7.fhir.r5.utils.structuremap.StructureMapUtilities; import org.hl7.fhir.utilities.CSVReader; import org.hl7.fhir.utilities.TextFile; import org.hl7.fhir.utilities.Utilities; diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/StructureMapUtilities.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/StructureMapUtilities.java deleted file mode 100644 index 41040413d..000000000 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/StructureMapUtilities.java +++ /dev/null @@ -1,3010 +0,0 @@ -package org.hl7.fhir.r5.utils; - -/* - Copyright (c) 2011+, HL7, Inc. - All rights reserved. - - Redistribution and use in source and binary forms, with or without modification, - are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - * Neither the name of HL7 nor the names of its contributors may be used to - endorse or promote products derived from this software without specific - prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, - INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - POSSIBILITY OF SUCH DAMAGE. - - */ - - - -// remember group resolution -// trace - account for which wasn't transformed in the source - -import java.io.IOException; -import java.util.ArrayList; -import java.util.EnumSet; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; - -import org.apache.commons.lang3.NotImplementedException; -import org.hl7.fhir.exceptions.DefinitionException; -import org.hl7.fhir.exceptions.FHIRException; -import org.hl7.fhir.exceptions.FHIRFormatError; -import org.hl7.fhir.exceptions.PathEngineException; -import org.hl7.fhir.r5.conformance.ProfileUtilities; -import org.hl7.fhir.r5.conformance.ProfileUtilities.ProfileKnowledgeProvider; -import org.hl7.fhir.r5.context.IWorkerContext; -import org.hl7.fhir.r5.context.IWorkerContext.ValidationResult; -import org.hl7.fhir.r5.elementmodel.Element; -import org.hl7.fhir.r5.elementmodel.Property; -import org.hl7.fhir.r5.model.Base; -import org.hl7.fhir.r5.model.BooleanType; -import org.hl7.fhir.r5.model.CanonicalType; -import org.hl7.fhir.r5.model.CodeType; -import org.hl7.fhir.r5.model.CodeableConcept; -import org.hl7.fhir.r5.model.Coding; -import org.hl7.fhir.r5.model.ConceptMap; -import org.hl7.fhir.r5.model.ConceptMap.ConceptMapGroupComponent; -import org.hl7.fhir.r5.model.ConceptMap.ConceptMapGroupUnmappedMode; -import org.hl7.fhir.r5.model.ConceptMap.SourceElementComponent; -import org.hl7.fhir.r5.model.ConceptMap.TargetElementComponent; -import org.hl7.fhir.r5.model.Constants; -import org.hl7.fhir.r5.model.ContactDetail; -import org.hl7.fhir.r5.model.ContactPoint; -import org.hl7.fhir.r5.model.DataType; -import org.hl7.fhir.r5.model.DecimalType; -import org.hl7.fhir.r5.model.ElementDefinition; -import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionMappingComponent; -import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent; -import org.hl7.fhir.r5.model.Enumeration; -import org.hl7.fhir.r5.model.Enumerations.ConceptMapRelationship; -import org.hl7.fhir.r5.model.Enumerations.FHIRVersion; -import org.hl7.fhir.r5.model.Enumerations.PublicationStatus; -import org.hl7.fhir.r5.model.ExpressionNode; -import org.hl7.fhir.r5.model.ExpressionNode.CollectionStatus; -import org.hl7.fhir.r5.model.IdType; -import org.hl7.fhir.r5.model.IntegerType; -import org.hl7.fhir.r5.model.Narrative.NarrativeStatus; -import org.hl7.fhir.r5.model.PrimitiveType; -import org.hl7.fhir.r5.model.Resource; -import org.hl7.fhir.r5.model.ResourceFactory; -import org.hl7.fhir.r5.model.StringType; -import org.hl7.fhir.r5.model.StructureDefinition; -import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionMappingComponent; -import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule; -import org.hl7.fhir.r5.model.StructureMap; -import org.hl7.fhir.r5.model.StructureMap.StructureMapContextType; -import org.hl7.fhir.r5.model.StructureMap.StructureMapGroupComponent; -import org.hl7.fhir.r5.model.StructureMap.StructureMapGroupInputComponent; -import org.hl7.fhir.r5.model.StructureMap.StructureMapGroupRuleComponent; -import org.hl7.fhir.r5.model.StructureMap.StructureMapGroupRuleDependentComponent; -import org.hl7.fhir.r5.model.StructureMap.StructureMapGroupRuleSourceComponent; -import org.hl7.fhir.r5.model.StructureMap.StructureMapGroupRuleTargetComponent; -import org.hl7.fhir.r5.model.StructureMap.StructureMapGroupRuleTargetParameterComponent; -import org.hl7.fhir.r5.model.StructureMap.StructureMapGroupTypeMode; -import org.hl7.fhir.r5.model.StructureMap.StructureMapInputMode; -import org.hl7.fhir.r5.model.StructureMap.StructureMapModelMode; -import org.hl7.fhir.r5.model.StructureMap.StructureMapSourceListMode; -import org.hl7.fhir.r5.model.StructureMap.StructureMapStructureComponent; -import org.hl7.fhir.r5.model.StructureMap.StructureMapTargetListMode; -import org.hl7.fhir.r5.model.StructureMap.StructureMapTransform; -import org.hl7.fhir.r5.model.TypeDetails; -import org.hl7.fhir.r5.model.TypeDetails.ProfiledType; -import org.hl7.fhir.r5.model.UriType; -import org.hl7.fhir.r5.model.ValueSet; -import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent; -import org.hl7.fhir.r5.renderers.TerminologyRenderer; -import org.hl7.fhir.r5.terminologies.ValueSetExpander.ValueSetExpansionOutcome; -import org.hl7.fhir.r5.utils.FHIRLexer.FHIRLexerException; -import org.hl7.fhir.r5.utils.FHIRPathEngine.IEvaluationContext; -import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; -import org.hl7.fhir.utilities.Utilities; -import org.hl7.fhir.utilities.validation.ValidationMessage; -import org.hl7.fhir.utilities.validation.ValidationOptions; -import org.hl7.fhir.utilities.xhtml.NodeType; -import org.hl7.fhir.utilities.xhtml.XhtmlNode; - -/** - * Services in this class: - * - * string render(map) - take a structure and convert it to text - * map parse(text) - take a text representation and parse it - * getTargetType(map) - return the definition for the type to create to hand in - * transform(appInfo, source, map, target) - transform from source to target following the map - * analyse(appInfo, map) - generate profiles and other analysis artifacts for the targets of the transform - * map generateMapFromMappings(StructureDefinition) - build a mapping from a structure definition with loigcal mappings - * - * @author Grahame Grieve - * - */ -public class StructureMapUtilities { - - public class ResolvedGroup { - public StructureMapGroupComponent target; - public StructureMap targetMap; - } - public static final String MAP_WHERE_CHECK = "map.where.check"; - public static final String MAP_WHERE_LOG = "map.where.log"; - public static final String MAP_WHERE_EXPRESSION = "map.where.expression"; - public static final String MAP_SEARCH_EXPRESSION = "map.search.expression"; - public static final String MAP_EXPRESSION = "map.transform.expression"; - private static final boolean RENDER_MULTIPLE_TARGETS_ONELINE = true; - private static final String AUTO_VAR_NAME = "vvv"; - - public interface ITransformerServices { - // public boolean validateByValueSet(Coding code, String valuesetId); - public void log(String message); // log internal progress - public Base createType(Object appInfo, String name) throws FHIRException; - public Base createResource(Object appInfo, Base res, boolean atRootofTransform); // an already created resource is provided; this is to identify/store it - public Coding translate(Object appInfo, Coding source, String conceptMapUrl) throws FHIRException; - // public Coding translate(Coding code) - // ValueSet validation operation - // Translation operation - // Lookup another tree of data - // Create an instance tree - // Return the correct string format to refer to a tree (input or output) - public Base resolveReference(Object appContext, String url) throws FHIRException; - public List performSearch(Object appContext, String url) throws FHIRException; - } - - private class FFHIRPathHostServices implements IEvaluationContext{ - - public Base resolveConstant(Object appContext, String name, boolean beforeContext) throws PathEngineException { - Variables vars = (Variables) appContext; - Base res = vars.get(VariableMode.INPUT, name); - if (res == null) - res = vars.get(VariableMode.OUTPUT, name); - return res; - } - - @Override - public TypeDetails resolveConstantType(Object appContext, String name) throws PathEngineException { - if (!(appContext instanceof VariablesForProfiling)) - throw new Error("Internal Logic Error (wrong type '"+appContext.getClass().getName()+"' in resolveConstantType)"); - VariablesForProfiling vars = (VariablesForProfiling) appContext; - VariableForProfiling v = vars.get(null, name); - if (v == null) - throw new PathEngineException("Unknown variable '"+name+"' from variables "+vars.summary()); - return v.property.types; - } - - @Override - public boolean log(String argument, List focus) { - throw new Error("Not Implemented Yet"); - } - - @Override - public FunctionDetails resolveFunction(String functionName) { - return null; // throw new Error("Not Implemented Yet"); - } - - @Override - public TypeDetails checkFunction(Object appContext, String functionName, List parameters) throws PathEngineException { - throw new Error("Not Implemented Yet"); - } - - @Override - public List executeFunction(Object appContext, List focus, String functionName, List> parameters) { - throw new Error("Not Implemented Yet"); - } - - @Override - public Base resolveReference(Object appContext, String url, Base refContext) throws FHIRException { - if (services == null) - return null; - return services.resolveReference(appContext, url); - } - - private boolean noErrorValidationMessages(List valerrors) { - boolean ok = true; - for (ValidationMessage v : valerrors) - ok = ok && !v.getLevel().isError(); - return ok; - } - - @Override - public boolean conformsToProfile(Object appContext, Base item, String url) throws FHIRException { - IResourceValidator val = worker.newValidator(); - List valerrors = new ArrayList(); - if (item instanceof Resource) { - val.validate(appContext, valerrors, (Resource) item, url); - return noErrorValidationMessages(valerrors); - } - if (item instanceof Element) { - val.validate(appContext, valerrors, (Element) item, url); - return noErrorValidationMessages(valerrors); - } - throw new NotImplementedException("Not done yet (FFHIRPathHostServices.conformsToProfile), when item is not element or not resource"); - } - - @Override - public ValueSet resolveValueSet(Object appContext, String url) { - throw new Error("Not Implemented Yet"); - } - - } - private IWorkerContext worker; - private FHIRPathEngine fpe; - private ITransformerServices services; - private ProfileKnowledgeProvider pkp; - private Map ids = new HashMap(); - private ValidationOptions terminologyServiceOptions = new ValidationOptions(); - private ProfileUtilities profileUtilities; - - public StructureMapUtilities(IWorkerContext worker, ITransformerServices services, ProfileKnowledgeProvider pkp) { - super(); - this.worker = worker; - this.services = services; - this.pkp = pkp; - fpe = new FHIRPathEngine(worker); - fpe.setHostServices(new FFHIRPathHostServices()); - profileUtilities = new ProfileUtilities(worker, null, null); - } - - public StructureMapUtilities(IWorkerContext worker, ITransformerServices services) { - super(); - this.worker = worker; - this.services = services; - fpe = new FHIRPathEngine(worker); - fpe.setHostServices(new FFHIRPathHostServices()); - profileUtilities = new ProfileUtilities(worker, null, null); - } - - public StructureMapUtilities(IWorkerContext worker) { - super(); - this.worker = worker; - fpe = new FHIRPathEngine(worker); - fpe.setHostServices(new FFHIRPathHostServices()); - profileUtilities = new ProfileUtilities(worker, null, null); - - } - - public static String render(StructureMap map) { - StringBuilder b = new StringBuilder(); - b.append("map \""); - b.append(map.getUrl()); - b.append("\" = \""); - b.append(Utilities.escapeJson(map.getName())); - b.append("\"\r\n\r\n"); - if (map.getDescription()!=null) { - renderMultilineDoco(b, map.getDescription(), 0); - b.append("\r\n"); - } - renderConceptMaps(b, map); - renderUses(b, map); - renderImports(b, map); - for (StructureMapGroupComponent g : map.getGroup()) - renderGroup(b, g); - return b.toString(); - } - - private static void renderConceptMaps(StringBuilder b, StructureMap map) { - for (Resource r : map.getContained()) { - if (r instanceof ConceptMap) { - produceConceptMap(b, (ConceptMap) r); - } - } - } - - private static void produceConceptMap(StringBuilder b, ConceptMap cm) { - b.append("conceptmap \""); - b.append(cm.getId()); - b.append("\" {\r\n"); - Map prefixesSrc = new HashMap(); - Map prefixesTgt = new HashMap(); - char prefix = 's'; - for (ConceptMapGroupComponent cg : cm.getGroup()) { - if (!prefixesSrc.containsKey(cg.getSource())) { - prefixesSrc.put(cg.getSource(), String.valueOf(prefix)); - b.append(" prefix "); - b.append(prefix); - b.append(" = \""); - b.append(cg.getSource()); - b.append("\"\r\n"); - prefix++; - } - if (!prefixesTgt.containsKey(cg.getTarget())) { - prefixesTgt.put(cg.getTarget(), String.valueOf(prefix)); - b.append(" prefix "); - b.append(prefix); - b.append(" = \""); - b.append(cg.getTarget()); - b.append("\"\r\n"); - prefix++; - } - } - b.append("\r\n"); - for (ConceptMapGroupComponent cg : cm.getGroup()) { - if (cg.hasUnmapped()) { - b.append(" unmapped for "); - b.append(prefixesSrc.get(cg.getSource())); - b.append(" = "); - b.append(cg.getUnmapped().getMode().toCode()); - b.append("\r\n"); - } - } - - for (ConceptMapGroupComponent cg : cm.getGroup()) { - for (SourceElementComponent ce : cg.getElement()) { - b.append(" "); - b.append(prefixesSrc.get(cg.getSource())); - b.append(":"); - if (Utilities.isToken(ce.getCode())) { - b.append(ce.getCode()); - } else { - b.append("\""); - b.append(ce.getCode()); - b.append("\""); - } - b.append(" "); - b.append(getChar(ce.getTargetFirstRep().getRelationship())); - b.append(" "); - b.append(prefixesTgt.get(cg.getTarget())); - b.append(":"); - if (Utilities.isToken(ce.getTargetFirstRep().getCode())) { - b.append(ce.getTargetFirstRep().getCode()); - } else { - b.append("\""); - b.append(ce.getTargetFirstRep().getCode()); - b.append("\""); - } - b.append("\r\n"); - } - } - b.append("}\r\n\r\n"); - } - - private static Object getChar(ConceptMapRelationship relationship) { - switch (relationship) { - case RELATEDTO: return "-"; - case EQUIVALENT: return "=="; - case NOTRELATEDTO: return "!="; - case SOURCEISNARROWERTHANTARGET: return "<="; - case SOURCEISBROADERTHANTARGET: return ">="; - default: return "??"; - } - } - - private static void renderUses(StringBuilder b, StructureMap map) { - for (StructureMapStructureComponent s : map.getStructure()) { - b.append("uses \""); - b.append(s.getUrl()); - b.append("\" "); - if (s.hasAlias()) { - b.append("alias "); - b.append(s.getAlias()); - b.append(" "); - } - b.append("as "); - b.append(s.getMode().toCode()); - renderDoco(b, s.getDocumentation()); - b.append("\r\n"); - } - if (map.hasStructure()) - b.append("\r\n"); - } - - private static void renderImports(StringBuilder b, StructureMap map) { - for (UriType s : map.getImport()) { - b.append("imports \""); - b.append(s.getValue()); - b.append("\"\r\n"); - } - if (map.hasImport()) - b.append("\r\n"); - } - - public static String groupToString(StructureMapGroupComponent g) { - StringBuilder b = new StringBuilder(); - renderGroup(b, g); - return b.toString(); - } - - private static void renderGroup(StringBuilder b, StructureMapGroupComponent g) { - if (g.hasDocumentation()) { - renderMultilineDoco(b, g.getDocumentation(), 0); - } - b.append("group "); - b.append(g.getName()); - b.append("("); - boolean first = true; - for (StructureMapGroupInputComponent gi : g.getInput()) { - if (first) - first = false; - else - b.append(", "); - b.append(gi.getMode().toCode()); - b.append(" "); - b.append(gi.getName()); - if (gi.hasType()) { - b.append(" : "); - b.append(gi.getType()); - } - } - b.append(")"); - if (g.hasExtends()) { - b.append(" extends "); - b.append(g.getExtends()); - } - - if (g.hasTypeMode()) { - switch (g.getTypeMode()) { - case TYPES: - b.append(" <>"); - break; - case TYPEANDTYPES: - b.append(" <>"); - break; - default: // NONE, NULL - } - } - b.append(" {\r\n"); - for (StructureMapGroupRuleComponent r : g.getRule()) { - renderRule(b, r, 2); - } - b.append("}\r\n\r\n"); - } - - public static String ruleToString(StructureMapGroupRuleComponent r) { - StringBuilder b = new StringBuilder(); - renderRule(b, r, 0); - return b.toString(); - } - - private static void renderRule(StringBuilder b, StructureMapGroupRuleComponent r, int indent) { - if (r.getDocumentation()!=null) { - renderMultilineDoco(b, r.getDocumentation(),indent); - } - for (int i = 0; i < indent; i++) - b.append(' '); - boolean canBeAbbreviated = checkisSimple(r); - - boolean first = true; - for (StructureMapGroupRuleSourceComponent rs : r.getSource()) { - if (first) - first = false; - else - b.append(", "); - renderSource(b, rs, canBeAbbreviated); - } - if (r.getTarget().size() > 1) { - b.append(" -> "); - first = true; - for (StructureMapGroupRuleTargetComponent rt : r.getTarget()) { - if (first) - first = false; - else - b.append(", "); - if (RENDER_MULTIPLE_TARGETS_ONELINE) - b.append(' '); - else { - b.append("\r\n"); - for (int i = 0; i < indent+4; i++) - b.append(' '); - } - renderTarget(b, rt, false); - } - } else if (r.hasTarget()) { - b.append(" -> "); - renderTarget(b, r.getTarget().get(0), canBeAbbreviated); - } - if (r.hasRule()) { - b.append(" then {\r\n"); - for (StructureMapGroupRuleComponent ir : r.getRule()) { - renderRule(b, ir, indent+2); - } - for (int i = 0; i < indent; i++) - b.append(' '); - b.append("}"); - } else { - if (r.hasDependent()) { - b.append(" then "); - first = true; - for (StructureMapGroupRuleDependentComponent rd : r.getDependent()) { - if (first) - first = false; - else - b.append(", "); - b.append(rd.getName()); - b.append("("); - boolean ifirst = true; - for (StringType rdp : rd.getVariable()) { - if (ifirst) - ifirst = false; - else - b.append(", "); - b.append(rdp.asStringValue()); - } - b.append(")"); - } - } - } - if (r.hasName()) { - String n = ntail(r.getName()); - if (!n.startsWith("\"")) - n = "\""+n+"\""; - if (!matchesName(n, r.getSource())) { - b.append(" "); - b.append(n); - } - } - b.append(";"); - b.append("\r\n"); - } - - private static boolean matchesName(String n, List source) { - if (source.size() != 1) - return false; - if (!source.get(0).hasElement()) - return false; - String s = source.get(0).getElement(); - if (n.equals(s) || n.equals("\""+s+"\"")) - return true; - if (source.get(0).hasType()) { - s = source.get(0).getElement()+"-"+source.get(0).getType(); - if (n.equals(s) || n.equals("\""+s+"\"")) - return true; - } - return false; - } - - private static String ntail(String name) { - if (name == null) - return null; - if (name.startsWith("\"")) { - name = name.substring(1); - name = name.substring(0, name.length()-1); - } - return "\""+ (name.contains(".") ? name.substring(name.lastIndexOf(".")+1) : name) + "\""; - } - - private static boolean checkisSimple(StructureMapGroupRuleComponent r) { - return - (r.getSource().size() == 1 && r.getSourceFirstRep().hasElement() && r.getSourceFirstRep().hasVariable()) && - (r.getTarget().size() == 1 && r.getTargetFirstRep().hasVariable() && (r.getTargetFirstRep().getTransform() == null || r.getTargetFirstRep().getTransform() == StructureMapTransform.CREATE) && r.getTargetFirstRep().getParameter().size() == 0) && - (r.getDependent().size() == 0) && (r.getRule().size() == 0) ; - } - - public static String sourceToString(StructureMapGroupRuleSourceComponent r) { - StringBuilder b = new StringBuilder(); - renderSource(b, r, false); - return b.toString(); - } - - private static void renderSource(StringBuilder b, StructureMapGroupRuleSourceComponent rs, boolean abbreviate) { - b.append(rs.getContext()); - if (rs.getContext().equals("@search")) { - b.append('('); - b.append(rs.getElement()); - b.append(')'); - } else if (rs.hasElement()) { - b.append('.'); - b.append(rs.getElement()); - } - if (rs.hasType()) { - b.append(" : "); - b.append(rs.getType()); - if (rs.hasMin()) { - b.append(" "); - b.append(rs.getMin()); - b.append(".."); - b.append(rs.getMax()); - } - } - - if (rs.hasListMode()) { - b.append(" "); - b.append(rs.getListMode().toCode()); - } - if (rs.hasDefaultValue()) { - b.append(" default "); - assert rs.getDefaultValue() instanceof StringType; - b.append("\""+Utilities.escapeJson(((StringType) rs.getDefaultValue()).asStringValue())+"\""); - } - if (!abbreviate && rs.hasVariable()) { - b.append(" as "); - b.append(rs.getVariable()); - } - if (rs.hasCondition()) { - b.append(" where "); - b.append(rs.getCondition()); - } - if (rs.hasCheck()) { - b.append(" check "); - b.append(rs.getCheck()); - } - if (rs.hasLogMessage()) { - b.append(" log "); - b.append(rs.getLogMessage()); - } - } - - public static String targetToString(StructureMapGroupRuleTargetComponent rt) { - StringBuilder b = new StringBuilder(); - renderTarget(b, rt, false); - return b.toString(); - } - - private static void renderTarget(StringBuilder b, StructureMapGroupRuleTargetComponent rt, boolean abbreviate) { - if (rt.hasContext()) { - if (rt.getContextType() == StructureMapContextType.TYPE) - b.append("@"); - b.append(rt.getContext()); - if (rt.hasElement()) { - b.append('.'); - b.append(rt.getElement()); - } - } - if (!abbreviate && rt.hasTransform()) { - if (rt.hasContext()) - b.append(" = "); - if (rt.getTransform() == StructureMapTransform.COPY && rt.getParameter().size() == 1) { - renderTransformParam(b, rt.getParameter().get(0)); - } else if (rt.getTransform() == StructureMapTransform.EVALUATE && rt.getParameter().size() == 1) { - b.append("("); - b.append(((StringType) rt.getParameter().get(0).getValue()).asStringValue()); - b.append(")"); - } else if (rt.getTransform() == StructureMapTransform.EVALUATE && rt.getParameter().size() == 2) { - b.append(rt.getTransform().toCode()); - b.append("("); - b.append(((IdType) rt.getParameter().get(0).getValue()).asStringValue()); - b.append(((StringType) rt.getParameter().get(1).getValue()).asStringValue()); - b.append(")"); - } else { - b.append(rt.getTransform().toCode()); - b.append("("); - boolean first = true; - for (StructureMapGroupRuleTargetParameterComponent rtp : rt.getParameter()) { - if (first) - first = false; - else - b.append(", "); - renderTransformParam(b, rtp); - } - b.append(")"); - } - } - if (!abbreviate && rt.hasVariable()) { - b.append(" as "); - b.append(rt.getVariable()); - } - for (Enumeration lm : rt.getListMode()) { - b.append(" "); - b.append(lm.getValue().toCode()); - if (lm.getValue() == StructureMapTargetListMode.SHARE) { - b.append(" "); - b.append(rt.getListRuleId()); - } - } - } - - public static String paramToString(StructureMapGroupRuleTargetParameterComponent rtp) { - StringBuilder b = new StringBuilder(); - renderTransformParam(b, rtp); - return b.toString(); - } - - private static void renderTransformParam(StringBuilder b, StructureMapGroupRuleTargetParameterComponent rtp) { - try { - if (rtp.hasValueBooleanType()) - b.append(rtp.getValueBooleanType().asStringValue()); - else if (rtp.hasValueDecimalType()) - b.append(rtp.getValueDecimalType().asStringValue()); - else if (rtp.hasValueIdType()) - b.append(rtp.getValueIdType().asStringValue()); - else if (rtp.hasValueDecimalType()) - b.append(rtp.getValueDecimalType().asStringValue()); - else if (rtp.hasValueIntegerType()) - b.append(rtp.getValueIntegerType().asStringValue()); - else - b.append("'"+Utilities.escapeJava(rtp.getValueStringType().asStringValue())+"'"); - } catch (FHIRException e) { - e.printStackTrace(); - b.append("error!"); - } - } - - private static void renderDoco(StringBuilder b, String doco) { - if (Utilities.noString(doco)) - return; - if (b!=null && b.length()>1 && b.charAt(b.length()-1)!='\n' && b.charAt(b.length()-1)!=' ') { - b.append(" "); - } - b.append("// "); - b.append(doco.replace("\r\n", " ").replace("\r", " ").replace("\n", " ")); - } - - private static void renderMultilineDoco(StringBuilder b, String doco, int indent) { - if (Utilities.noString(doco)) - return; - String[] lines = doco.split("\\r?\\n"); - for(String line: lines) { - for (int i = 0; i < indent; i++) - b.append(' '); - renderDoco(b, line); - b.append("\r\n"); - } - } - - public StructureMap parse(String text, String srcName) throws FHIRException { - FHIRLexer lexer = new FHIRLexer(text, srcName); - if (lexer.done()) - throw lexer.error("Map Input cannot be empty"); - lexer.token("map"); - StructureMap result = new StructureMap(); - result.setUrl(lexer.readConstant("url")); - lexer.token("="); - result.setName(lexer.readConstant("name")); - result.setDescription(lexer.getAllComments()); - while (lexer.hasToken("conceptmap")) - parseConceptMap(result, lexer); - - while (lexer.hasToken("uses")) - parseUses(result, lexer); - while (lexer.hasToken("imports")) - parseImports(result, lexer); - - while (!lexer.done()) { - parseGroup(result, lexer); - } - - return result; - } - - private void parseConceptMap(StructureMap result, FHIRLexer lexer) throws FHIRLexerException { - lexer.token("conceptmap"); - ConceptMap map = new ConceptMap(); - String id = lexer.readConstant("map id"); - if (id.startsWith("#")) - throw lexer.error("Concept Map identifier must start with #"); - map.setId(id); - map.setStatus(PublicationStatus.DRAFT); // todo: how to add this to the text format - result.getContained().add(map); - lexer.token("{"); - // lexer.token("source"); - // map.setSource(new UriType(lexer.readConstant("source"))); - // lexer.token("target"); - // map.setSource(new UriType(lexer.readConstant("target"))); - Map prefixes = new HashMap(); - while (lexer.hasToken("prefix")) { - lexer.token("prefix"); - String n = lexer.take(); - lexer.token("="); - String v = lexer.readConstant("prefix url"); - prefixes.put(n, v); - } - while (lexer.hasToken("unmapped")) { - lexer.token("unmapped"); - lexer.token("for"); - String n = readPrefix(prefixes, lexer); - ConceptMapGroupComponent g = getGroup(map, n, null); - lexer.token("="); - String v = lexer.take(); - if (v.equals("provided")) { - g.getUnmapped().setMode(ConceptMapGroupUnmappedMode.PROVIDED); - } else - throw lexer.error("Only unmapped mode PROVIDED is supported at this time"); - } - while (!lexer.hasToken("}")) { - String srcs = readPrefix(prefixes, lexer); - lexer.token(":"); - String sc = lexer.getCurrent().startsWith("\"") ? lexer.readConstant("code") : lexer.take(); - ConceptMapRelationship rel = readRelationship(lexer); - String tgts = readPrefix(prefixes, lexer); - ConceptMapGroupComponent g = getGroup(map, srcs, tgts); - SourceElementComponent e = g.addElement(); - e.setCode(sc); - if (e.getCode().startsWith("\"")) - e.setCode(lexer.processConstant(e.getCode())); - TargetElementComponent tgt = e.addTarget(); - tgt.setRelationship(rel); - lexer.token(":"); - tgt.setCode(lexer.take()); - if (tgt.getCode().startsWith("\"")) - tgt.setCode(lexer.processConstant(tgt.getCode())); - tgt.setComment(lexer.getFirstComment()); - } - lexer.token("}"); - } - - private ConceptMapGroupComponent getGroup(ConceptMap map, String srcs, String tgts) { - for (ConceptMapGroupComponent grp : map.getGroup()) { - if (grp.getSource().equals(srcs)) - if (!grp.hasTarget() || tgts == null || tgts.equals(grp.getTarget())) { - if (!grp.hasTarget() && tgts != null) - grp.setTarget(tgts); - return grp; - } - } - ConceptMapGroupComponent grp = map.addGroup(); - grp.setSource(srcs); - grp.setTarget(tgts); - return grp; - } - - - private String readPrefix(Map prefixes, FHIRLexer lexer) throws FHIRLexerException { - String prefix = lexer.take(); - if (!prefixes.containsKey(prefix)) - throw lexer.error("Unknown prefix '"+prefix+"'"); - return prefixes.get(prefix); - } - - - private ConceptMapRelationship readRelationship(FHIRLexer lexer) throws FHIRLexerException { - String token = lexer.take(); - if (token.equals("-")) - return ConceptMapRelationship.RELATEDTO; - if (token.equals("==")) - return ConceptMapRelationship.EQUIVALENT; - if (token.equals("!=")) - return ConceptMapRelationship.NOTRELATEDTO; - if (token.equals("<=")) - return ConceptMapRelationship.SOURCEISNARROWERTHANTARGET; - if (token.equals(">=")) - return ConceptMapRelationship.SOURCEISBROADERTHANTARGET; - throw lexer.error("Unknown relationship token '"+token+"'"); - } - - - private void parseUses(StructureMap result, FHIRLexer lexer) throws FHIRException { - lexer.token("uses"); - int currentLine = lexer.getCurrentLocation().getLine(); - StructureMapStructureComponent st = result.addStructure(); - st.setUrl(lexer.readConstant("url")); - if (lexer.hasToken("alias")) { - lexer.token("alias"); - st.setAlias(lexer.take()); - } - lexer.token("as"); - st.setMode(StructureMapModelMode.fromCode(lexer.take())); - lexer.skipToken(";"); - st.setDocumentation(lexer.getFirstComment()); - } - - private void parseImports(StructureMap result, FHIRLexer lexer) throws FHIRException { - lexer.token("imports"); - int currentLine = lexer.getCurrentLocation().getLine(); - result.addImport(lexer.readConstant("url")); - lexer.skipToken(";"); - lexer.getFirstComment(); - } - - private void parseGroup(StructureMap result, FHIRLexer lexer) throws FHIRException { - String comment = lexer.getAllComments(); - lexer.token("group"); - StructureMapGroupComponent group = result.addGroup(); - if (comment != null) { - group.setDocumentation(comment); - } - boolean newFmt = false; - if (lexer.hasToken("for")) { - lexer.token("for"); - if ("type".equals(lexer.getCurrent())) { - lexer.token("type"); - lexer.token("+"); - lexer.token("types"); - group.setTypeMode(StructureMapGroupTypeMode.TYPEANDTYPES); - } else { - lexer.token("types"); - group.setTypeMode(StructureMapGroupTypeMode.TYPES); - } - } - group.setName(lexer.take()); - if (lexer.hasToken("(")) { - newFmt = true; - lexer.take(); - while (!lexer.hasToken(")")) { - parseInput(group, lexer, true); - if (lexer.hasToken(",")) - lexer.token(","); - } - lexer.take(); - } - if (lexer.hasToken("extends")) { - lexer.next(); - group.setExtends(lexer.take()); - } - if (newFmt) { - if (lexer.hasToken("<")) { - lexer.token("<"); - lexer.token("<"); - if (lexer.hasToken("types")) { - group.setTypeMode(StructureMapGroupTypeMode.TYPES); - lexer.token("types"); - } else { - lexer.token("type"); - lexer.token("+"); - group.setTypeMode(StructureMapGroupTypeMode.TYPEANDTYPES); - } - lexer.token(">"); - lexer.token(">"); - } - lexer.token("{"); - } - if (newFmt) { - while (!lexer.hasToken("}")) { - if (lexer.done()) - throw lexer.error("premature termination expecting 'endgroup'"); - parseRule(result, group.getRule(), lexer, true); - } - } else { - while (lexer.hasToken("input")) - parseInput(group, lexer, false); - while (!lexer.hasToken("endgroup")) { - if (lexer.done()) - throw lexer.error("premature termination expecting 'endgroup'"); - parseRule(result, group.getRule(), lexer, false); - } - } - lexer.next(); - if (newFmt && lexer.hasToken(";")) - lexer.next(); - } - - private void parseInput(StructureMapGroupComponent group, FHIRLexer lexer, boolean newFmt) throws FHIRException { - StructureMapGroupInputComponent input = group.addInput(); - if (newFmt) { - input.setMode(StructureMapInputMode.fromCode(lexer.take())); - } else - lexer.token("input"); - input.setName(lexer.take()); - if (lexer.hasToken(":")) { - lexer.token(":"); - input.setType(lexer.take()); - } - if (!newFmt) { - lexer.token("as"); - input.setMode(StructureMapInputMode.fromCode(lexer.take())); - input.setDocumentation(lexer.getFirstComment()); - lexer.skipToken(";"); - } - } - - private void parseRule(StructureMap map, List list, FHIRLexer lexer, boolean newFmt) throws FHIRException { - StructureMapGroupRuleComponent rule = new StructureMapGroupRuleComponent(); - if (!newFmt) { - rule.setName(lexer.takeDottedToken()); - lexer.token(":"); - lexer.token("for"); - } else { - rule.setDocumentation(lexer.getFirstComment()); - } - list.add(rule); - boolean done = false; - while (!done) { - parseSource(rule, lexer); - done = !lexer.hasToken(","); - if (!done) - lexer.next(); - } - if ((newFmt && lexer.hasToken("->")) || (!newFmt && lexer.hasToken("make"))) { - lexer.token(newFmt ? "->" : "make"); - done = false; - while (!done) { - parseTarget(rule, lexer); - done = !lexer.hasToken(","); - if (!done) - lexer.next(); - } - } - if (lexer.hasToken("then")) { - lexer.token("then"); - if (lexer.hasToken("{")) { - lexer.token("{"); - while (!lexer.hasToken("}")) { - if (lexer.done()) - throw lexer.error("premature termination expecting '}' in nested group"); - parseRule(map, rule.getRule(), lexer, newFmt); - } - lexer.token("}"); - } else { - done = false; - while (!done) { - parseRuleReference(rule, lexer); - done = !lexer.hasToken(","); - if (!done) - lexer.next(); - } - } - } - if (!rule.hasDocumentation() && lexer.hasComments()) { - rule.setDocumentation(lexer.getFirstComment()); - } - if (isSimpleSyntax(rule)) { - rule.getSourceFirstRep().setVariable(AUTO_VAR_NAME); - rule.getTargetFirstRep().setVariable(AUTO_VAR_NAME); - rule.getTargetFirstRep().setTransform(StructureMapTransform.CREATE); // with no parameter - e.g. imply what is to be created - // no dependencies - imply what is to be done based on types - } - if (newFmt) { - if (lexer.isConstant()) { - if (lexer.isStringConstant()) { - rule.setName(lexer.readConstant("ruleName")); - } else { - rule.setName(lexer.take()); - } - } else { - if (rule.getSource().size() != 1 || !rule.getSourceFirstRep().hasElement()) - throw lexer.error("Complex rules must have an explicit name"); - if (rule.getSourceFirstRep().hasType()) - rule.setName(rule.getSourceFirstRep().getElement()+"-"+rule.getSourceFirstRep().getType()); - else - rule.setName(rule.getSourceFirstRep().getElement()); - } - lexer.token(";"); - } - } - - private boolean isSimpleSyntax(StructureMapGroupRuleComponent rule) { - return - (rule.getSource().size() == 1 && rule.getSourceFirstRep().hasContext() && rule.getSourceFirstRep().hasElement() && !rule.getSourceFirstRep().hasVariable()) && - (rule.getTarget().size() == 1 && rule.getTargetFirstRep().hasContext() && rule.getTargetFirstRep().hasElement() && !rule.getTargetFirstRep().hasVariable() && !rule.getTargetFirstRep().hasParameter()) && - (rule.getDependent().size() == 0 && rule.getRule().size() == 0); - } - - private void parseRuleReference(StructureMapGroupRuleComponent rule, FHIRLexer lexer) throws FHIRLexerException { - StructureMapGroupRuleDependentComponent ref = rule.addDependent(); - ref.setName(lexer.take()); - lexer.token("("); - boolean done = false; - while (!done) { - ref.addVariable(lexer.take()); - done = !lexer.hasToken(","); - if (!done) - lexer.next(); - } - lexer.token(")"); - } - - private void parseSource(StructureMapGroupRuleComponent rule, FHIRLexer lexer) throws FHIRException { - StructureMapGroupRuleSourceComponent source = rule.addSource(); - source.setContext(lexer.take()); - if (source.getContext().equals("search") && lexer.hasToken("(")) { - source.setContext("@search"); - lexer.take(); - ExpressionNode node = fpe.parse(lexer); - source.setUserData(MAP_SEARCH_EXPRESSION, node); - source.setElement(node.toString()); - lexer.token(")"); - } else if (lexer.hasToken(".")) { - lexer.token("."); - source.setElement(lexer.take()); - } - if (lexer.hasToken(":")) { - // type and cardinality - lexer.token(":"); - source.setType(lexer.takeDottedToken()); - if (!lexer.hasToken("as", "first", "last", "not_first", "not_last", "only_one", "default")) { - source.setMin(lexer.takeInt()); - lexer.token(".."); - source.setMax(lexer.take()); - } - } - if (lexer.hasToken("default")) { - lexer.token("default"); - source.setDefaultValue(new StringType(lexer.readConstant("default value"))); - } - if (Utilities.existsInList(lexer.getCurrent(), "first", "last", "not_first", "not_last", "only_one")) - source.setListMode(StructureMapSourceListMode.fromCode(lexer.take())); - - if (lexer.hasToken("as")) { - lexer.take(); - source.setVariable(lexer.take()); - } - if (lexer.hasToken("where")) { - lexer.take(); - ExpressionNode node = fpe.parse(lexer); - source.setUserData(MAP_WHERE_EXPRESSION, node); - source.setCondition(node.toString()); - } - if (lexer.hasToken("check")) { - lexer.take(); - ExpressionNode node = fpe.parse(lexer); - source.setUserData(MAP_WHERE_CHECK, node); - source.setCheck(node.toString()); - } - if (lexer.hasToken("log")) { - lexer.take(); - ExpressionNode node = fpe.parse(lexer); - source.setUserData(MAP_WHERE_CHECK, node); - source.setLogMessage(node.toString()); - } - } - - private void parseTarget(StructureMapGroupRuleComponent rule, FHIRLexer lexer) throws FHIRException { - StructureMapGroupRuleTargetComponent target = rule.addTarget(); - String start = lexer.take(); - if (lexer.hasToken(".")) { - target.setContext(start); - target.setContextType(StructureMapContextType.VARIABLE); - start = null; - lexer.token("."); - target.setElement(lexer.take()); - } - String name; - boolean isConstant = false; - if (lexer.hasToken("=")) { - if (start != null) - target.setContext(start); - lexer.token("="); - isConstant = lexer.isConstant(); - name = lexer.take(); - } else - name = start; - - if ("(".equals(name)) { - // inline fluentpath expression - target.setTransform(StructureMapTransform.EVALUATE); - ExpressionNode node = fpe.parse(lexer); - target.setUserData(MAP_EXPRESSION, node); - target.addParameter().setValue(new StringType(node.toString())); - lexer.token(")"); - } else if (lexer.hasToken("(")) { - target.setTransform(StructureMapTransform.fromCode(name)); - lexer.token("("); - if (target.getTransform() == StructureMapTransform.EVALUATE) { - parseParameter(target, lexer); - lexer.token(","); - ExpressionNode node = fpe.parse(lexer); - target.setUserData(MAP_EXPRESSION, node); - target.addParameter().setValue(new StringType(node.toString())); - } else { - while (!lexer.hasToken(")")) { - parseParameter(target, lexer); - if (!lexer.hasToken(")")) - lexer.token(","); - } - } - lexer.token(")"); - } else if (name != null) { - target.setTransform(StructureMapTransform.COPY); - if (!isConstant) { - String id = name; - while (lexer.hasToken(".")) { - id = id + lexer.take() + lexer.take(); - } - target.addParameter().setValue(new IdType(id)); - } - else - target.addParameter().setValue(readConstant(name, lexer)); - } - if (lexer.hasToken("as")) { - lexer.take(); - target.setVariable(lexer.take()); - } - while (Utilities.existsInList(lexer.getCurrent(), "first", "last", "share", "collate")) { - if (lexer.getCurrent().equals("share")) { - target.addListMode(StructureMapTargetListMode.SHARE); - lexer.next(); - target.setListRuleId(lexer.take()); - } else { - if (lexer.getCurrent().equals("first")) - target.addListMode(StructureMapTargetListMode.FIRST); - else - target.addListMode(StructureMapTargetListMode.LAST); - lexer.next(); - } - } - } - - - private void parseParameter(StructureMapGroupRuleTargetComponent target, FHIRLexer lexer) throws FHIRLexerException, FHIRFormatError { - if (!lexer.isConstant()) { - target.addParameter().setValue(new IdType(lexer.take())); - } else if (lexer.isStringConstant()) - target.addParameter().setValue(new StringType(lexer.readConstant("??"))); - else { - target.addParameter().setValue(readConstant(lexer.take(), lexer)); - } - } - - private DataType readConstant(String s, FHIRLexer lexer) throws FHIRLexerException { - if (Utilities.isInteger(s)) - return new IntegerType(s); - else if (Utilities.isDecimal(s, false)) - return new DecimalType(s); - else if (Utilities.existsInList(s, "true", "false")) - return new BooleanType(s.equals("true")); - else - return new StringType(lexer.processConstant(s)); - } - - public StructureDefinition getTargetType(StructureMap map) throws FHIRException { - boolean found = false; - StructureDefinition res = null; - for (StructureMapStructureComponent uses : map.getStructure()) { - if (uses.getMode() == StructureMapModelMode.TARGET) { - if (found) - throw new FHIRException("Multiple targets found in map "+map.getUrl()); - found = true; - res = worker.fetchResource(StructureDefinition.class, uses.getUrl()); - if (res == null) - throw new FHIRException("Unable to find "+uses.getUrl()+" referenced from map "+map.getUrl()); - } - } - if (res == null) - throw new FHIRException("No targets found in map "+map.getUrl()); - return res; - } - - public enum VariableMode { - INPUT, OUTPUT, SHARED - } - - public class Variable { - private VariableMode mode; - private String name; - private Base object; - public Variable(VariableMode mode, String name, Base object) { - super(); - this.mode = mode; - this.name = name; - this.object = object; - } - public VariableMode getMode() { - return mode; - } - public String getName() { - return name; - } - public Base getObject() { - return object; - } - public String summary() { - if (object == null) - return null; - else if (object instanceof PrimitiveType) - return name+": \""+((PrimitiveType) object).asStringValue()+'"'; - else - return name+": ("+object.fhirType()+")"; - } - } - - public class Variables { - private List list = new ArrayList(); - - public void add(VariableMode mode, String name, Base object) { - Variable vv = null; - for (Variable v : list) - if ((v.mode == mode) && v.getName().equals(name)) - vv = v; - if (vv != null) - list.remove(vv); - list.add(new Variable(mode, name, object)); - } - - public Variables copy() { - Variables result = new Variables(); - result.list.addAll(list); - return result; - } - - public Base get(VariableMode mode, String name) { - for (Variable v : list) - if ((v.mode == mode) && v.getName().equals(name)) - return v.getObject(); - return null; - } - - public String summary() { - CommaSeparatedStringBuilder s = new CommaSeparatedStringBuilder(); - CommaSeparatedStringBuilder t = new CommaSeparatedStringBuilder(); - CommaSeparatedStringBuilder sh = new CommaSeparatedStringBuilder(); - for (Variable v : list) - switch(v.mode) { - case INPUT: - s.append(v.summary()); - break; - case OUTPUT: - t.append(v.summary()); - break; - case SHARED: - sh.append(v.summary()); - break; - } - return "source variables ["+s.toString()+"], target variables ["+t.toString()+"], shared variables ["+sh.toString()+"]"; - } - - } - - public class TransformContext { - private Object appInfo; - - public TransformContext(Object appInfo) { - super(); - this.appInfo = appInfo; - } - - public Object getAppInfo() { - return appInfo; - } - - } - - private void log(String cnt) { - if (services != null) - services.log(cnt); - else - System.out.println(cnt); - } - - /** - * Given an item, return all the children that conform to the pattern described in name - * - * Possible patterns: - * - a simple name (which may be the base of a name with [] e.g. value[x]) - * - a name with a type replacement e.g. valueCodeableConcept - * - * which means all children - * - ** which means all descendents - * - * @param item - * @param name - * @param result - * @throws FHIRException - */ - protected void getChildrenByName(Base item, String name, List result) throws FHIRException { - for (Base v : item.listChildrenByName(name, true)) - if (v != null) - result.add(v); - } - - public void transform(Object appInfo, Base source, StructureMap map, Base target) throws FHIRException { - TransformContext context = new TransformContext(appInfo); - log("Start Transform "+map.getUrl()); - StructureMapGroupComponent g = map.getGroup().get(0); - - Variables vars = new Variables(); - vars.add(VariableMode.INPUT, getInputName(g, StructureMapInputMode.SOURCE, "source"), source); - if (target != null) - vars.add(VariableMode.OUTPUT, getInputName(g, StructureMapInputMode.TARGET, "target"), target); - else if (getInputName(g, StructureMapInputMode.TARGET, null) != null) { - String type = getInputType(g, StructureMapInputMode.TARGET); - throw new Error("not handled yet: creating a type of "+type); - } - - executeGroup("", context, map, vars, g, true); - if (target instanceof Element) - ((Element) target).sort(); - } - - private String getInputType(StructureMapGroupComponent g, StructureMapInputMode mode) { - String type = null; - for (StructureMapGroupInputComponent inp : g.getInput()) { - if (inp.getMode() == mode) - if (type != null) - throw new DefinitionException("This engine does not support multiple source inputs"); - else - type = inp.getType(); - } - return type; - } - - private String getInputName(StructureMapGroupComponent g, StructureMapInputMode mode, String def) throws DefinitionException { - String name = null; - for (StructureMapGroupInputComponent inp : g.getInput()) { - if (inp.getMode() == mode) - if (name != null) - throw new DefinitionException("This engine does not support multiple source inputs"); - else - name = inp.getName(); - } - return name == null ? def : name; - } - - private void executeGroup(String indent, TransformContext context, StructureMap map, Variables vars, StructureMapGroupComponent group, boolean atRoot) throws FHIRException { - log(indent+"Group : "+group.getName()+"; vars = "+vars.summary()); - // todo: check inputs - if (group.hasExtends()) { - ResolvedGroup rg = resolveGroupReference(map, group, group.getExtends()); - executeGroup(indent+" ", context, rg.targetMap, vars, rg.target, false); - } - - for (StructureMapGroupRuleComponent r : group.getRule()) { - executeRule(indent+" ", context, map, vars, group, r, atRoot); - } - } - - private void executeRule(String indent, TransformContext context, StructureMap map, Variables vars, StructureMapGroupComponent group, StructureMapGroupRuleComponent rule, boolean atRoot) throws FHIRException { - log(indent+"rule : "+rule.getName()+"; vars = "+vars.summary()); - Variables srcVars = vars.copy(); - if (rule.getSource().size() != 1) - throw new FHIRException("Rule \""+rule.getName()+"\": not handled yet"); - List source = processSource(rule.getName(), context, srcVars, rule.getSource().get(0), map.getUrl(), indent); - if (source != null) { - for (Variables v : source) { - for (StructureMapGroupRuleTargetComponent t : rule.getTarget()) { - processTarget(rule.getName(), context, v, map, group, t, rule.getSource().size() == 1 ? rule.getSourceFirstRep().getVariable() : null, atRoot, vars); - } - if (rule.hasRule()) { - for (StructureMapGroupRuleComponent childrule : rule.getRule()) { - executeRule(indent +" ", context, map, v, group, childrule, false); - } - } else if (rule.hasDependent()) { - for (StructureMapGroupRuleDependentComponent dependent : rule.getDependent()) { - executeDependency(indent+" ", context, map, v, group, dependent); - } - } else if (rule.getSource().size() == 1 && rule.getSourceFirstRep().hasVariable() && rule.getTarget().size() == 1 && rule.getTargetFirstRep().hasVariable() && rule.getTargetFirstRep().getTransform() == StructureMapTransform.CREATE && !rule.getTargetFirstRep().hasParameter()) { - // simple inferred, map by type - System.out.println(v.summary()); - Base src = v.get(VariableMode.INPUT, rule.getSourceFirstRep().getVariable()); - Base tgt = v.get(VariableMode.OUTPUT, rule.getTargetFirstRep().getVariable()); - String srcType = src.fhirType(); - String tgtType = tgt.fhirType(); - ResolvedGroup defGroup = resolveGroupByTypes(map, rule.getName(), group, srcType, tgtType); - Variables vdef = new Variables(); - vdef.add(VariableMode.INPUT, defGroup.target.getInput().get(0).getName(), src); - vdef.add(VariableMode.OUTPUT, defGroup.target.getInput().get(1).getName(), tgt); - executeGroup(indent+" ", context, defGroup.targetMap, vdef, defGroup.target, false); - } - } - } - } - - private void executeDependency(String indent, TransformContext context, StructureMap map, Variables vin, StructureMapGroupComponent group, StructureMapGroupRuleDependentComponent dependent) throws FHIRException { - ResolvedGroup rg = resolveGroupReference(map, group, dependent.getName()); - - if (rg.target.getInput().size() != dependent.getVariable().size()) { - throw new FHIRException("Rule '"+dependent.getName()+"' has "+Integer.toString(rg.target.getInput().size())+" but the invocation has "+Integer.toString(dependent.getVariable().size())+" variables"); - } - Variables v = new Variables(); - for (int i = 0; i < rg.target.getInput().size(); i++) { - StructureMapGroupInputComponent input = rg.target.getInput().get(i); - StringType rdp = dependent.getVariable().get(i); - String var = rdp.asStringValue(); - VariableMode mode = input.getMode() == StructureMapInputMode.SOURCE ? VariableMode.INPUT : VariableMode.OUTPUT; - Base vv = vin.get(mode, var); - if (vv == null && mode == VariableMode.INPUT) //* once source, always source. but target can be treated as source at user convenient - vv = vin.get(VariableMode.OUTPUT, var); - if (vv == null) - throw new FHIRException("Rule '"+dependent.getName()+"' "+mode.toString()+" variable '"+input.getName()+"' named as '"+var+"' has no value (vars = "+vin.summary()+")"); - v.add(mode, input.getName(), vv); - } - executeGroup(indent+" ", context, rg.targetMap, v, rg.target, false); - } - - private String determineTypeFromSourceType(StructureMap map, StructureMapGroupComponent source, Base base, String[] types) throws FHIRException { - String type = base.fhirType(); - String kn = "type^"+type; - if (source.hasUserData(kn)) - return source.getUserString(kn); - - ResolvedGroup res = new ResolvedGroup(); - res.targetMap = null; - res.target = null; - for (StructureMapGroupComponent grp : map.getGroup()) { - if (matchesByType(map, grp, type)) { - if (res.targetMap == null) { - res.targetMap = map; - res.target = grp; - } else - throw new FHIRException("Multiple possible matches looking for default rule for '"+type+"'"); - } - } - if (res.targetMap != null) { - String result = getActualType(res.targetMap, res.target.getInput().get(1).getType()); - source.setUserData(kn, result); - return result; - } - - for (UriType imp : map.getImport()) { - List impMapList = findMatchingMaps(imp.getValue()); - if (impMapList.size() == 0) - throw new FHIRException("Unable to find map(s) for "+imp.getValue()); - for (StructureMap impMap : impMapList) { - if (!impMap.getUrl().equals(map.getUrl())) { - for (StructureMapGroupComponent grp : impMap.getGroup()) { - if (matchesByType(impMap, grp, type)) { - if (res.targetMap == null) { - res.targetMap = impMap; - res.target = grp; - } else - throw new FHIRException("Multiple possible matches for default rule for '"+type+"' in "+res.targetMap.getUrl()+" ("+res.target.getName()+") and "+impMap.getUrl()+" ("+grp.getName()+")"); - } - } - } - } - } - if (res.target == null) - throw new FHIRException("No matches found for default rule for '"+type+"' from "+map.getUrl()); - String result = getActualType(res.targetMap, res.target.getInput().get(1).getType()); // should be .getType, but R2... - source.setUserData(kn, result); - return result; - } - - private List findMatchingMaps(String value) { - List res = new ArrayList(); - if (value.contains("*")) { - for (StructureMap sm : worker.listTransforms()) { - if (urlMatches(value, sm.getUrl())) { - res.add(sm); - } - } - } else { - StructureMap sm = worker.getTransform(value); - if (sm != null) - res.add(sm); - } - Set check = new HashSet(); - for (StructureMap sm : res) { - if (check.contains(sm.getUrl())) - throw new Error("duplicate"); - else - check.add(sm.getUrl()); - } - return res; - } - - private boolean urlMatches(String mask, String url) { - return url.length() > mask.length() && url.startsWith(mask.substring(0, mask.indexOf("*"))) && url.endsWith(mask.substring(mask.indexOf("*")+1)) ; - } - - private ResolvedGroup resolveGroupByTypes(StructureMap map, String ruleid, StructureMapGroupComponent source, String srcType, String tgtType) throws FHIRException { - String kn = "types^"+srcType+":"+tgtType; - if (source.hasUserData(kn)) - return (ResolvedGroup) source.getUserData(kn); - - ResolvedGroup res = new ResolvedGroup(); - res.targetMap = null; - res.target = null; - for (StructureMapGroupComponent grp : map.getGroup()) { - if (matchesByType(map, grp, srcType, tgtType)) { - if (res.targetMap == null) { - res.targetMap = map; - res.target = grp; - } else - throw new FHIRException("Multiple possible matches looking for rule for '"+srcType+"/"+tgtType+"', from rule '"+ruleid+"'"); - } - } - if (res.targetMap != null) { - source.setUserData(kn, res); - return res; - } - - for (UriType imp : map.getImport()) { - List impMapList = findMatchingMaps(imp.getValue()); - if (impMapList.size() == 0) - throw new FHIRException("Unable to find map(s) for "+imp.getValue()); - for (StructureMap impMap : impMapList) { - if (!impMap.getUrl().equals(map.getUrl())) { - for (StructureMapGroupComponent grp : impMap.getGroup()) { - if (matchesByType(impMap, grp, srcType, tgtType)) { - if (res.targetMap == null) { - res.targetMap = impMap; - res.target = grp; - } else - throw new FHIRException("Multiple possible matches for rule for '"+srcType+"/"+tgtType+"' in "+res.targetMap.getUrl()+" and "+impMap.getUrl()+", from rule '"+ruleid+"'"); - } - } - } - } - } - if (res.target == null) - throw new FHIRException("No matches found for rule for '"+srcType+" to "+tgtType+"' from "+map.getUrl()+", from rule '"+ruleid+"'"); - source.setUserData(kn, res); - return res; - } - - - private boolean matchesByType(StructureMap map, StructureMapGroupComponent grp, String type) throws FHIRException { - if (grp.getTypeMode() != StructureMapGroupTypeMode.TYPEANDTYPES) - return false; - if (grp.getInput().size() != 2 || grp.getInput().get(0).getMode() != StructureMapInputMode.SOURCE || grp.getInput().get(1).getMode() != StructureMapInputMode.TARGET) - return false; - return matchesType(map, type, grp.getInput().get(0).getType()); - } - - private boolean matchesByType(StructureMap map, StructureMapGroupComponent grp, String srcType, String tgtType) throws FHIRException { - if (!grp.hasTypeMode()) - return false; - if (grp.getInput().size() != 2 || grp.getInput().get(0).getMode() != StructureMapInputMode.SOURCE || grp.getInput().get(1).getMode() != StructureMapInputMode.TARGET) - return false; - if (!grp.getInput().get(0).hasType() || !grp.getInput().get(1).hasType()) - return false; - return matchesType(map, srcType, grp.getInput().get(0).getType()) && matchesType(map, tgtType, grp.getInput().get(1).getType()); - } - - private boolean matchesType(StructureMap map, String actualType, String statedType) throws FHIRException { - // check the aliases - for (StructureMapStructureComponent imp : map.getStructure()) { - if (imp.hasAlias() && statedType.equals(imp.getAlias())) { - StructureDefinition sd = worker.fetchResource(StructureDefinition.class, imp.getUrl()); - if (sd != null) - statedType = sd.getType(); - break; - } - } - - if (Utilities.isAbsoluteUrl(actualType)) { - StructureDefinition sd = worker.fetchResource(StructureDefinition.class, actualType); - if (sd != null) - actualType = sd.getType(); - } - if (Utilities.isAbsoluteUrl(statedType)) { - StructureDefinition sd = worker.fetchResource(StructureDefinition.class, statedType); - if (sd != null) - statedType = sd.getType(); - } - return actualType.equals(statedType); - } - - private String getActualType(StructureMap map, String statedType) throws FHIRException { - // check the aliases - for (StructureMapStructureComponent imp : map.getStructure()) { - if (imp.hasAlias() && statedType.equals(imp.getAlias())) { - StructureDefinition sd = worker.fetchResource(StructureDefinition.class, imp.getUrl()); - if (sd == null) - throw new FHIRException("Unable to resolve structure "+imp.getUrl()); - return sd.getId(); // should be sd.getType(), but R2... - } - } - return statedType; - } - - - private ResolvedGroup resolveGroupReference(StructureMap map, StructureMapGroupComponent source, String name) throws FHIRException { - String kn = "ref^"+name; - if (source.hasUserData(kn)) - return (ResolvedGroup) source.getUserData(kn); - - ResolvedGroup res = new ResolvedGroup(); - res.targetMap = null; - res.target = null; - for (StructureMapGroupComponent grp : map.getGroup()) { - if (grp.getName().equals(name)) { - if (res.targetMap == null) { - res.targetMap = map; - res.target = grp; - } else - throw new FHIRException("Multiple possible matches for rule '"+name+"'"); - } - } - if (res.targetMap != null) { - source.setUserData(kn, res); - return res; - } - - for (UriType imp : map.getImport()) { - List impMapList = findMatchingMaps(imp.getValue()); - if (impMapList.size() == 0) - throw new FHIRException("Unable to find map(s) for "+imp.getValue()); - for (StructureMap impMap : impMapList) { - if (!impMap.getUrl().equals(map.getUrl())) { - for (StructureMapGroupComponent grp : impMap.getGroup()) { - if (grp.getName().equals(name)) { - if (res.targetMap == null) { - res.targetMap = impMap; - res.target = grp; - } else - throw new FHIRException("Multiple possible matches for rule group '"+name+"' in "+ - res.targetMap.getUrl()+"#"+res.target.getName()+" and "+ - impMap.getUrl()+"#"+grp.getName()); - } - } - } - } - } - if (res.target == null) - throw new FHIRException("No matches found for rule '"+name+"'. Reference found in "+map.getUrl()); - source.setUserData(kn, res); - return res; - } - - private List processSource(String ruleId, TransformContext context, Variables vars, StructureMapGroupRuleSourceComponent src, String pathForErrors, String indent) throws FHIRException { - List items; - if (src.getContext().equals("@search")) { - ExpressionNode expr = (ExpressionNode) src.getUserData(MAP_SEARCH_EXPRESSION); - if (expr == null) { - expr = fpe.parse(src.getElement()); - src.setUserData(MAP_SEARCH_EXPRESSION, expr); - } - String search = fpe.evaluateToString(vars, null, null, new StringType(), expr); // string is a holder of nothing to ensure that variables are processed correctly - items = services.performSearch(context.appInfo, search); - } else { - items = new ArrayList(); - Base b = vars.get(VariableMode.INPUT, src.getContext()); - if (b == null) - throw new FHIRException("Unknown input variable "+src.getContext()+" in "+pathForErrors+" rule "+ruleId+" (vars = "+vars.summary()+")"); - - if (!src.hasElement()) - items.add(b); - else { - getChildrenByName(b, src.getElement(), items); - if (items.size() == 0 && src.hasDefaultValue()) - items.add(src.getDefaultValue()); - } - } - - if (src.hasType()) { - List remove = new ArrayList(); - for (Base item : items) { - if (item != null && !isType(item, src.getType())) { - remove.add(item); - } - } - items.removeAll(remove); - } - - if (src.hasCondition()) { - ExpressionNode expr = (ExpressionNode) src.getUserData(MAP_WHERE_EXPRESSION); - if (expr == null) { - expr = fpe.parse(src.getCondition()); - // fpe.check(context.appInfo, ??, ??, expr) - src.setUserData(MAP_WHERE_EXPRESSION, expr); - } - List remove = new ArrayList(); - for (Base item : items) { - if (!fpe.evaluateToBoolean(vars, null, null, item, expr)) { - log(indent+" condition ["+src.getCondition()+"] for "+item.toString()+" : false"); - remove.add(item); - } else - log(indent+" condition ["+src.getCondition()+"] for "+item.toString()+" : true"); - } - items.removeAll(remove); - } - - if (src.hasCheck()) { - ExpressionNode expr = (ExpressionNode) src.getUserData(MAP_WHERE_CHECK); - if (expr == null) { - expr = fpe.parse(src.getCheck()); - // fpe.check(context.appInfo, ??, ??, expr) - src.setUserData(MAP_WHERE_CHECK, expr); - } - List remove = new ArrayList(); - for (Base item : items) { - if (!fpe.evaluateToBoolean(vars, null, null, item, expr)) - throw new FHIRException("Rule \""+ruleId+"\": Check condition failed"); - } - } - - if (src.hasLogMessage()) { - ExpressionNode expr = (ExpressionNode) src.getUserData(MAP_WHERE_LOG); - if (expr == null) { - expr = fpe.parse(src.getLogMessage()); - // fpe.check(context.appInfo, ??, ??, expr) - src.setUserData(MAP_WHERE_LOG, expr); - } - CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); - for (Base item : items) - b.appendIfNotNull(fpe.evaluateToString(vars, null, null, item, expr)); - if (b.length() > 0) - services.log(b.toString()); - } - - - if (src.hasListMode() && !items.isEmpty()) { - switch (src.getListMode()) { - case FIRST: - Base bt = items.get(0); - items.clear(); - items.add(bt); - break; - case NOTFIRST: - if (items.size() > 0) - items.remove(0); - break; - case LAST: - bt = items.get(items.size()-1); - items.clear(); - items.add(bt); - break; - case NOTLAST: - if (items.size() > 0) - items.remove(items.size()-1); - break; - case ONLYONE: - if (items.size() > 1) - throw new FHIRException("Rule \""+ruleId+"\": Check condition failed: the collection has more than one item"); - break; - case NULL: - } - } - List result = new ArrayList(); - for (Base r : items) { - Variables v = vars.copy(); - if (src.hasVariable()) - v.add(VariableMode.INPUT, src.getVariable(), r); - result.add(v); - } - return result; - } - - - private boolean isType(Base item, String type) { - if (type.equals(item.fhirType())) - return true; - return false; - } - - private void processTarget(String ruleId, TransformContext context, Variables vars, StructureMap map, StructureMapGroupComponent group, StructureMapGroupRuleTargetComponent tgt, String srcVar, boolean atRoot, Variables sharedVars) throws FHIRException { - Base dest = null; - if (tgt.hasContext()) { - dest = vars.get(VariableMode.OUTPUT, tgt.getContext()); - if (dest == null) - throw new FHIRException("Rule \""+ruleId+"\": target context not known: "+tgt.getContext()); - if (!tgt.hasElement()) - throw new FHIRException("Rule \""+ruleId+"\": Not supported yet"); - } - Base v = null; - if (tgt.hasTransform()) { - v = runTransform(ruleId, context, map, group, tgt, vars, dest, tgt.getElement(), srcVar, atRoot); - if (v != null && dest != null) - v = dest.setProperty(tgt.getElement().hashCode(), tgt.getElement(), v); // reset v because some implementations may have to rewrite v when setting the value - } else if (dest != null) { - if (tgt.hasListMode(StructureMapTargetListMode.SHARE)) { - v = sharedVars.get(VariableMode.SHARED, tgt.getListRuleId()); - if (v == null) { - v = dest.makeProperty(tgt.getElement().hashCode(), tgt.getElement()); - sharedVars.add(VariableMode.SHARED, tgt.getListRuleId(), v); - } - } else { - v = dest.makeProperty(tgt.getElement().hashCode(), tgt.getElement()); - } - } - if (tgt.hasVariable() && v != null) - vars.add(VariableMode.OUTPUT, tgt.getVariable(), v); - } - - private Base runTransform(String ruleId, TransformContext context, StructureMap map, StructureMapGroupComponent group, StructureMapGroupRuleTargetComponent tgt, Variables vars, Base dest, String element, String srcVar, boolean root) throws FHIRException { - try { - switch (tgt.getTransform()) { - case CREATE : - String tn; - if (tgt.getParameter().isEmpty()) { - // we have to work out the type. First, we see if there is a single type for the target. If there is, we use that - String[] types = dest.getTypesForProperty(element.hashCode(), element); - if (types.length == 1 && !"*".equals(types[0]) && !types[0].equals("Resource")) - tn = types[0]; - else if (srcVar != null) { - tn = determineTypeFromSourceType(map, group, vars.get(VariableMode.INPUT, srcVar), types); - } else - throw new Error("Cannot determine type implicitly because there is no single input variable"); - } else { - tn = getParamStringNoNull(vars, tgt.getParameter().get(0), tgt.toString()); - // ok, now we resolve the type name against the import statements - for (StructureMapStructureComponent uses : map.getStructure()) { - if (uses.getMode() == StructureMapModelMode.TARGET && uses.hasAlias() && tn.equals(uses.getAlias())) { - tn = uses.getUrl(); - break; - } - } - } - Base res = services != null ? services.createType(context.getAppInfo(), tn) : ResourceFactory.createResourceOrType(tn); - if (res.isResource() && !res.fhirType().equals("Parameters")) { -// res.setIdBase(tgt.getParameter().size() > 1 ? getParamString(vars, tgt.getParameter().get(0)) : UUID.randomUUID().toString().toLowerCase()); - if (services != null) - res = services.createResource(context.getAppInfo(), res, root); - } - if (tgt.hasUserData("profile")) - res.setUserData("profile", tgt.getUserData("profile")); - return res; - case COPY : - return getParam(vars, tgt.getParameter().get(0)); - case EVALUATE : - ExpressionNode expr = (ExpressionNode) tgt.getUserData(MAP_EXPRESSION); - if (expr == null) { - expr = fpe.parse(getParamStringNoNull(vars, tgt.getParameter().get(0), tgt.toString())); - tgt.setUserData(MAP_WHERE_EXPRESSION, expr); - } - List v = fpe.evaluate(vars, null, null, tgt.getParameter().size() == 2 ? getParam(vars, tgt.getParameter().get(0)) : new BooleanType(false), expr); - if (v.size() == 0) - return null; - else if (v.size() != 1) - throw new FHIRException("Rule \""+ruleId+"\": Evaluation of "+expr.toString()+" returned "+Integer.toString(v.size())+" objects"); - else - return v.get(0); - - case TRUNCATE : - String src = getParamString(vars, tgt.getParameter().get(0)); - String len = getParamStringNoNull(vars, tgt.getParameter().get(1), tgt.toString()); - if (Utilities.isInteger(len)) { - int l = Integer.parseInt(len); - if (src.length() > l) - src = src.substring(0, l); - } - return new StringType(src); - case ESCAPE : - throw new Error("Rule \""+ruleId+"\": Transform "+tgt.getTransform().toCode()+" not supported yet"); - case CAST : - src = getParamString(vars, tgt.getParameter().get(0)); - if (tgt.getParameter().size() == 1) - throw new FHIRException("Implicit type parameters on cast not yet supported"); - String t = getParamString(vars, tgt.getParameter().get(1)); - if (t.equals("string")) - return new StringType(src); - else - throw new FHIRException("cast to "+t+" not yet supported"); - case APPEND : - StringBuilder sb = new StringBuilder(getParamString(vars, tgt.getParameter().get(0))); - for (int i = 1; i < tgt.getParameter().size(); i++) - sb.append(getParamString(vars, tgt.getParameter().get(i))); - return new StringType(sb.toString()); - case TRANSLATE : - return translate(context, map, vars, tgt.getParameter()); - case REFERENCE : - Base b = getParam(vars, tgt.getParameter().get(0)); - if (b == null) - throw new FHIRException("Rule \""+ruleId+"\": Unable to find parameter "+((IdType) tgt.getParameter().get(0).getValue()).asStringValue()); - if (!b.isResource()) - throw new FHIRException("Rule \""+ruleId+"\": Transform engine cannot point at an element of type "+b.fhirType()); - else { - String id = b.getIdBase(); - if (id == null) { - id = UUID.randomUUID().toString().toLowerCase(); - b.setIdBase(id); - } - return new StringType(b.fhirType()+"/"+id); - } - case DATEOP : - throw new Error("Rule \""+ruleId+"\": Transform "+tgt.getTransform().toCode()+" not supported yet"); - case UUID : - return new IdType(UUID.randomUUID().toString()); - case POINTER : - b = getParam(vars, tgt.getParameter().get(0)); - if (b instanceof Resource) - return new UriType("urn:uuid:"+((Resource) b).getId()); - else - throw new FHIRException("Rule \""+ruleId+"\": Transform engine cannot point at an element of type "+b.fhirType()); - case CC: - CodeableConcept cc = new CodeableConcept(); - cc.addCoding(buildCoding(getParamStringNoNull(vars, tgt.getParameter().get(0), tgt.toString()), getParamStringNoNull(vars, tgt.getParameter().get(1), tgt.toString()))); - return cc; - case C: - Coding c = buildCoding(getParamStringNoNull(vars, tgt.getParameter().get(0), tgt.toString()), getParamStringNoNull(vars, tgt.getParameter().get(1), tgt.toString())); - return c; - default: - throw new Error("Rule \""+ruleId+"\": Transform Unknown: "+tgt.getTransform().toCode()); - } - } catch (Exception e) { - throw new FHIRException("Exception executing transform "+tgt.toString()+" on Rule \""+ruleId+"\": "+e.getMessage(), e); - } - } - - - private Coding buildCoding(String uri, String code) throws FHIRException { - // if we can get this as a valueSet, we will - String system = null; - String display = null; - ValueSet vs = Utilities.noString(uri) ? null : worker.fetchResourceWithException(ValueSet.class, uri); - if (vs != null) { - ValueSetExpansionOutcome vse = worker.expandVS(vs, true, false); - if (vse.getError() != null) - throw new FHIRException(vse.getError()); - CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); - for (ValueSetExpansionContainsComponent t : vse.getValueset().getExpansion().getContains()) { - if (t.hasCode()) - b.append(t.getCode()); - if (code.equals(t.getCode()) && t.hasSystem()) { - system = t.getSystem(); - display = t.getDisplay(); - break; - } - if (code.equalsIgnoreCase(t.getDisplay()) && t.hasSystem()) { - system = t.getSystem(); - display = t.getDisplay(); - break; - } - } - if (system == null) - throw new FHIRException("The code '"+code+"' is not in the value set '"+uri+"' (valid codes: "+b.toString()+"; also checked displays)"); - } else - system = uri; - ValidationResult vr = worker.validateCode(terminologyServiceOptions, system, code, null); - if (vr != null && vr.getDisplay() != null) - display = vr.getDisplay(); - return new Coding().setSystem(system).setCode(code).setDisplay(display); - } - - - private String getParamStringNoNull(Variables vars, StructureMapGroupRuleTargetParameterComponent parameter, String message) throws FHIRException { - Base b = getParam(vars, parameter); - if (b == null) - throw new FHIRException("Unable to find a value for "+parameter.toString()+". Context: "+message); - if (!b.hasPrimitiveValue()) - throw new FHIRException("Found a value for "+parameter.toString()+", but it has a type of "+b.fhirType()+" and cannot be treated as a string. Context: "+message); - return b.primitiveValue(); - } - - private String getParamString(Variables vars, StructureMapGroupRuleTargetParameterComponent parameter) throws DefinitionException { - Base b = getParam(vars, parameter); - if (b == null || !b.hasPrimitiveValue()) - return null; - return b.primitiveValue(); - } - - - private Base getParam(Variables vars, StructureMapGroupRuleTargetParameterComponent parameter) throws DefinitionException { - DataType p = parameter.getValue(); - if (!(p instanceof IdType)) - return p; - else { - String n = ((IdType) p).asStringValue(); - Base b = vars.get(VariableMode.INPUT, n); - if (b == null) - b = vars.get(VariableMode.OUTPUT, n); - if (b == null) - throw new DefinitionException("Variable "+n+" not found ("+vars.summary()+")"); - return b; - } - } - - - private Base translate(TransformContext context, StructureMap map, Variables vars, List parameter) throws FHIRException { - Base src = getParam(vars, parameter.get(0)); - String id = getParamString(vars, parameter.get(1)); - String fld = parameter.size() > 2 ? getParamString(vars, parameter.get(2)) : null; - return translate(context, map, src, id, fld); - } - - private class SourceElementComponentWrapper { - private ConceptMapGroupComponent group; - private SourceElementComponent comp; - public SourceElementComponentWrapper(ConceptMapGroupComponent group, SourceElementComponent comp) { - super(); - this.group = group; - this.comp = comp; - } - } - public Base translate(TransformContext context, StructureMap map, Base source, String conceptMapUrl, String fieldToReturn) throws FHIRException { - Coding src = new Coding(); - if (source.isPrimitive()) { - src.setCode(source.primitiveValue()); - } else if ("Coding".equals(source.fhirType())) { - Base[] b = source.getProperty("system".hashCode(), "system", true); - if (b.length == 1) - src.setSystem(b[0].primitiveValue()); - b = source.getProperty("code".hashCode(), "code", true); - if (b.length == 1) - src.setCode(b[0].primitiveValue()); - } else if ("CE".equals(source.fhirType())) { - Base[] b = source.getProperty("codeSystem".hashCode(), "codeSystem", true); - if (b.length == 1) - src.setSystem(b[0].primitiveValue()); - b = source.getProperty("code".hashCode(), "code", true); - if (b.length == 1) - src.setCode(b[0].primitiveValue()); - } else - throw new FHIRException("Unable to translate source "+source.fhirType()); - - String su = conceptMapUrl; - if (conceptMapUrl.equals("http://hl7.org/fhir/ConceptMap/special-oid2uri")) { - String uri = worker.oid2Uri(src.getCode()); - if (uri == null) - uri = "urn:oid:"+src.getCode(); - if ("uri".equals(fieldToReturn)) - return new UriType(uri); - else - throw new FHIRException("Error in return code"); - } else { - ConceptMap cmap = null; - if (conceptMapUrl.startsWith("#")) { - for (Resource r : map.getContained()) { - if (r instanceof ConceptMap && ((ConceptMap) r).getId().equals(conceptMapUrl.substring(1))) { - cmap = (ConceptMap) r; - su = map.getUrl()+"#"+conceptMapUrl; - } - } - if (cmap == null) - throw new FHIRException("Unable to translate - cannot find map "+conceptMapUrl); - } else { - if (conceptMapUrl.contains("#")) { - String[] p = conceptMapUrl.split("\\#"); - StructureMap mapU = worker.fetchResource(StructureMap.class, p[0]); - for (Resource r : mapU.getContained()) { - if (r instanceof ConceptMap && ((ConceptMap) r).getId().equals(p[1])) { - cmap = (ConceptMap) r; - su = conceptMapUrl; - } - } - } - if (cmap == null) - cmap = worker.fetchResource(ConceptMap.class, conceptMapUrl); - } - Coding outcome = null; - boolean done = false; - String message = null; - if (cmap == null) { - if (services == null) - message = "No map found for "+conceptMapUrl; - else { - outcome = services.translate(context.appInfo, src, conceptMapUrl); - done = true; - } - } else { - List list = new ArrayList(); - for (ConceptMapGroupComponent g : cmap.getGroup()) { - for (SourceElementComponent e : g.getElement()) { - if (!src.hasSystem() && src.getCode().equals(e.getCode())) - list.add(new SourceElementComponentWrapper(g, e)); - else if (src.hasSystem() && src.getSystem().equals(g.getSource()) && src.getCode().equals(e.getCode())) - list.add(new SourceElementComponentWrapper(g, e)); - } - } - if (list.size() == 0) - done = true; - else if (list.get(0).comp.getTarget().size() == 0) - message = "Concept map "+su+" found no translation for "+src.getCode(); - else { - for (TargetElementComponent tgt : list.get(0).comp.getTarget()) { - if (tgt.getRelationship() == null || EnumSet.of( ConceptMapRelationship.RELATEDTO , ConceptMapRelationship.EQUIVALENT, ConceptMapRelationship.SOURCEISNARROWERTHANTARGET).contains(tgt.getRelationship())) { - if (done) { - message = "Concept map "+su+" found multiple matches for "+src.getCode(); - done = false; - } else { - done = true; - outcome = new Coding().setCode(tgt.getCode()).setSystem(list.get(0).group.getTarget()); - } - } - } - if (!done) - message = "Concept map "+su+" found no usable translation for "+src.getCode(); - } - } - if (!done) - throw new FHIRException(message); - if (outcome == null) - return null; - if ("code".equals(fieldToReturn)) - return new CodeType(outcome.getCode()); - else - return outcome; - } - } - - - public class PropertyWithType { - private String path; - private Property baseProperty; - private Property profileProperty; - private TypeDetails types; - public PropertyWithType(String path, Property baseProperty, Property profileProperty, TypeDetails types) { - super(); - this.baseProperty = baseProperty; - this.profileProperty = profileProperty; - this.path = path; - this.types = types; - } - - public TypeDetails getTypes() { - return types; - } - public String getPath() { - return path; - } - - public Property getBaseProperty() { - return baseProperty; - } - - public void setBaseProperty(Property baseProperty) { - this.baseProperty = baseProperty; - } - - public Property getProfileProperty() { - return profileProperty; - } - - public void setProfileProperty(Property profileProperty) { - this.profileProperty = profileProperty; - } - - public String summary() { - return path; - } - - } - - public class VariableForProfiling { - private VariableMode mode; - private String name; - private PropertyWithType property; - - public VariableForProfiling(VariableMode mode, String name, PropertyWithType property) { - super(); - this.mode = mode; - this.name = name; - this.property = property; - } - public VariableMode getMode() { - return mode; - } - public String getName() { - return name; - } - public PropertyWithType getProperty() { - return property; - } - public String summary() { - return name+": "+property.summary(); - } - } - - public class VariablesForProfiling { - private List list = new ArrayList(); - private boolean optional; - private boolean repeating; - - public VariablesForProfiling(boolean optional, boolean repeating) { - this.optional = optional; - this.repeating = repeating; - } - - public void add(VariableMode mode, String name, String path, Property property, TypeDetails types) { - add(mode, name, new PropertyWithType(path, property, null, types)); - } - - public void add(VariableMode mode, String name, String path, Property baseProperty, Property profileProperty, TypeDetails types) { - add(mode, name, new PropertyWithType(path, baseProperty, profileProperty, types)); - } - - public void add(VariableMode mode, String name, PropertyWithType property) { - VariableForProfiling vv = null; - for (VariableForProfiling v : list) - if ((v.mode == mode) && v.getName().equals(name)) - vv = v; - if (vv != null) - list.remove(vv); - list.add(new VariableForProfiling(mode, name, property)); - } - - public VariablesForProfiling copy(boolean optional, boolean repeating) { - VariablesForProfiling result = new VariablesForProfiling(optional, repeating); - result.list.addAll(list); - return result; - } - - public VariablesForProfiling copy() { - VariablesForProfiling result = new VariablesForProfiling(optional, repeating); - result.list.addAll(list); - return result; - } - - public VariableForProfiling get(VariableMode mode, String name) { - if (mode == null) { - for (VariableForProfiling v : list) - if ((v.mode == VariableMode.OUTPUT) && v.getName().equals(name)) - return v; - for (VariableForProfiling v : list) - if ((v.mode == VariableMode.INPUT) && v.getName().equals(name)) - return v; - } - for (VariableForProfiling v : list) - if ((v.mode == mode) && v.getName().equals(name)) - return v; - return null; - } - - public String summary() { - CommaSeparatedStringBuilder s = new CommaSeparatedStringBuilder(); - CommaSeparatedStringBuilder t = new CommaSeparatedStringBuilder(); - for (VariableForProfiling v : list) - if (v.mode == VariableMode.INPUT) - s.append(v.summary()); - else - t.append(v.summary()); - return "source variables ["+s.toString()+"], target variables ["+t.toString()+"]"; - } - } - - public class StructureMapAnalysis { - private List profiles = new ArrayList(); - private XhtmlNode summary; - public List getProfiles() { - return profiles; - } - public XhtmlNode getSummary() { - return summary; - } - - } - - /** - * Given a structure map, return a set of analyses on it. - * - * Returned: - * - a list or profiles for what it will create. First profile is the target - * - a table with a summary (in xhtml) for easy human undertanding of the mapping - * - * - * @param appInfo - * @param map - * @return - * @throws Exception - */ - public StructureMapAnalysis analyse(Object appInfo, StructureMap map) throws FHIRException { - ids.clear(); - StructureMapAnalysis result = new StructureMapAnalysis(); - TransformContext context = new TransformContext(appInfo); - VariablesForProfiling vars = new VariablesForProfiling(false, false); - StructureMapGroupComponent start = map.getGroup().get(0); - for (StructureMapGroupInputComponent t : start.getInput()) { - PropertyWithType ti = resolveType(map, t.getType(), t.getMode()); - if (t.getMode() == StructureMapInputMode.SOURCE) - vars.add(VariableMode.INPUT, t.getName(), ti); - else - vars.add(VariableMode.OUTPUT, t.getName(), createProfile(map, result.profiles, ti, start.getName(), start)); - } - - result.summary = new XhtmlNode(NodeType.Element, "table").setAttribute("class", "grid"); - XhtmlNode tr = result.summary.addTag("tr"); - tr.addTag("td").addTag("b").addText("Source"); - tr.addTag("td").addTag("b").addText("Target"); - - log("Start Profiling Transform "+map.getUrl()); - analyseGroup("", context, map, vars, start, result); - ProfileUtilities pu = new ProfileUtilities(worker, null, pkp); - for (StructureDefinition sd : result.getProfiles()) - pu.cleanUpDifferential(sd); - return result; - } - - - private void analyseGroup(String indent, TransformContext context, StructureMap map, VariablesForProfiling vars, StructureMapGroupComponent group, StructureMapAnalysis result) throws FHIRException { - log(indent+"Analyse Group : "+group.getName()); - // todo: extends - // todo: check inputs - XhtmlNode tr = result.summary.addTag("tr").setAttribute("class", "diff-title"); - XhtmlNode xs = tr.addTag("td"); - XhtmlNode xt = tr.addTag("td"); - for (StructureMapGroupInputComponent inp : group.getInput()) { - if (inp.getMode() == StructureMapInputMode.SOURCE) - noteInput(vars, inp, VariableMode.INPUT, xs); - if (inp.getMode() == StructureMapInputMode.TARGET) - noteInput(vars, inp, VariableMode.OUTPUT, xt); - } - for (StructureMapGroupRuleComponent r : group.getRule()) { - analyseRule(indent+" ", context, map, vars, group, r, result); - } - } - - - private void noteInput(VariablesForProfiling vars, StructureMapGroupInputComponent inp, VariableMode mode, XhtmlNode xs) { - VariableForProfiling v = vars.get(mode, inp.getName()); - if (v != null) - xs.addText("Input: "+v.property.getPath()); - } - - private void analyseRule(String indent, TransformContext context, StructureMap map, VariablesForProfiling vars, StructureMapGroupComponent group, StructureMapGroupRuleComponent rule, StructureMapAnalysis result) throws FHIRException { - log(indent+"Analyse rule : "+rule.getName()); - XhtmlNode tr = result.summary.addTag("tr"); - XhtmlNode xs = tr.addTag("td"); - XhtmlNode xt = tr.addTag("td"); - - VariablesForProfiling srcVars = vars.copy(); - if (rule.getSource().size() != 1) - throw new FHIRException("Rule \""+rule.getName()+"\": not handled yet"); - VariablesForProfiling source = analyseSource(rule.getName(), context, srcVars, rule.getSourceFirstRep(), xs); - - TargetWriter tw = new TargetWriter(); - for (StructureMapGroupRuleTargetComponent t : rule.getTarget()) { - analyseTarget(rule.getName(), context, source, map, t, rule.getSourceFirstRep().getVariable(), tw, result.profiles, rule.getName()); - } - tw.commit(xt); - - for (StructureMapGroupRuleComponent childrule : rule.getRule()) { - analyseRule(indent+" ", context, map, source, group, childrule, result); - } -// for (StructureMapGroupRuleDependentComponent dependent : rule.getDependent()) { -// executeDependency(indent+" ", context, map, v, group, dependent); // do we need group here? -// } - } - - public class StringPair { - private String var; - private String desc; - public StringPair(String var, String desc) { - super(); - this.var = var; - this.desc = desc; - } - public String getVar() { - return var; - } - public String getDesc() { - return desc; - } - } - public class TargetWriter { - private Map newResources = new HashMap(); - private List assignments = new ArrayList(); - private List keyProps = new ArrayList(); - private CommaSeparatedStringBuilder txt = new CommaSeparatedStringBuilder(); - - public void newResource(String var, String name) { - newResources.put(var, name); - txt.append("new "+name); - } - - public void valueAssignment(String context, String desc) { - assignments.add(new StringPair(context, desc)); - txt.append(desc); - } - - public void keyAssignment(String context, String desc) { - keyProps.add(new StringPair(context, desc)); - txt.append(desc); - } - public void commit(XhtmlNode xt) { - if (newResources.size() == 1 && assignments.size() == 1 && newResources.containsKey(assignments.get(0).getVar()) && keyProps.size() == 1 && newResources.containsKey(keyProps.get(0).getVar()) ) { - xt.addText("new "+assignments.get(0).desc+" ("+keyProps.get(0).desc.substring(keyProps.get(0).desc.indexOf(".")+1)+")"); - } else if (newResources.size() == 1 && assignments.size() == 1 && newResources.containsKey(assignments.get(0).getVar()) && keyProps.size() == 0) { - xt.addText("new "+assignments.get(0).desc); - } else { - xt.addText(txt.toString()); - } - } - } - - private VariablesForProfiling analyseSource(String ruleId, TransformContext context, VariablesForProfiling vars, StructureMapGroupRuleSourceComponent src, XhtmlNode td) throws FHIRException { - VariableForProfiling var = vars.get(VariableMode.INPUT, src.getContext()); - if (var == null) - throw new FHIRException("Rule \""+ruleId+"\": Unknown input variable "+src.getContext()); - PropertyWithType prop = var.getProperty(); - - boolean optional = false; - boolean repeating = false; - - if (src.hasCondition()) { - optional = true; - } - - if (src.hasElement()) { - Property element = prop.getBaseProperty().getChild(prop.types.getType(), src.getElement()); - if (element == null) - throw new FHIRException("Rule \""+ruleId+"\": Unknown element name "+src.getElement()); - if (element.getDefinition().getMin() == 0) - optional = true; - if (element.getDefinition().getMax().equals("*")) - repeating = true; - VariablesForProfiling result = vars.copy(optional, repeating); - TypeDetails type = new TypeDetails(CollectionStatus.SINGLETON); - for (TypeRefComponent tr : element.getDefinition().getType()) { - if (!tr.hasCode()) - throw new Error("Rule \""+ruleId+"\": Element has no type"); - ProfiledType pt = new ProfiledType(tr.getWorkingCode()); - if (tr.hasProfile()) - pt.addProfiles(tr.getProfile()); - if (element.getDefinition().hasBinding()) - pt.addBinding(element.getDefinition().getBinding()); - type.addType(pt); - } - td.addText(prop.getPath()+"."+src.getElement()); - if (src.hasVariable()) - result.add(VariableMode.INPUT, src.getVariable(), new PropertyWithType(prop.getPath()+"."+src.getElement(), element, null, type)); - return result; - } else { - td.addText(prop.getPath()); // ditto! - return vars.copy(optional, repeating); - } - } - - - private void analyseTarget(String ruleId, TransformContext context, VariablesForProfiling vars, StructureMap map, StructureMapGroupRuleTargetComponent tgt, String tv, TargetWriter tw, List profiles, String sliceName) throws FHIRException { - VariableForProfiling var = null; - if (tgt.hasContext()) { - var = vars.get(VariableMode.OUTPUT, tgt.getContext()); - if (var == null) - throw new FHIRException("Rule \""+ruleId+"\": target context not known: "+tgt.getContext()); - if (!tgt.hasElement()) - throw new FHIRException("Rule \""+ruleId+"\": Not supported yet"); - } - - - TypeDetails type = null; - if (tgt.hasTransform()) { - type = analyseTransform(context, map, tgt, var, vars); - // profiling: dest.setProperty(tgt.getElement().hashCode(), tgt.getElement(), v); - } else { - Property vp = var.property.baseProperty.getChild(tgt.getElement(), tgt.getElement()); - if (vp == null) - throw new FHIRException("Unknown Property "+tgt.getElement()+" on "+var.property.path); - - type = new TypeDetails(CollectionStatus.SINGLETON, vp.getType(tgt.getElement())); - } - - if (tgt.getTransform() == StructureMapTransform.CREATE) { - String s = getParamString(vars, tgt.getParameter().get(0)); - if (worker.getResourceNames().contains(s)) - tw.newResource(tgt.getVariable(), s); - } else { - boolean mapsSrc = false; - for (StructureMapGroupRuleTargetParameterComponent p : tgt.getParameter()) { - DataType pr = p.getValue(); - if (pr instanceof IdType && ((IdType) pr).asStringValue().equals(tv)) - mapsSrc = true; - } - if (mapsSrc) { - if (var == null) - throw new Error("Rule \""+ruleId+"\": Attempt to assign with no context"); - tw.valueAssignment(tgt.getContext(), var.property.getPath()+"."+tgt.getElement()+getTransformSuffix(tgt.getTransform())); - } else if (tgt.hasContext()) { - if (isSignificantElement(var.property, tgt.getElement())) { - String td = describeTransform(tgt); - if (td != null) - tw.keyAssignment(tgt.getContext(), var.property.getPath()+"."+tgt.getElement()+" = "+td); - } - } - } - DataType fixed = generateFixedValue(tgt); - - PropertyWithType prop = updateProfile(var, tgt.getElement(), type, map, profiles, sliceName, fixed, tgt); - if (tgt.hasVariable()) - if (tgt.hasElement()) - vars.add(VariableMode.OUTPUT, tgt.getVariable(), prop); - else - vars.add(VariableMode.OUTPUT, tgt.getVariable(), prop); - } - - private DataType generateFixedValue(StructureMapGroupRuleTargetComponent tgt) { - if (!allParametersFixed(tgt)) - return null; - if (!tgt.hasTransform()) - return null; - switch (tgt.getTransform()) { - case COPY: return tgt.getParameter().get(0).getValue(); - case TRUNCATE: return null; - //case ESCAPE: - //case CAST: - //case APPEND: - case TRANSLATE: return null; - //case DATEOP, - //case UUID, - //case POINTER, - //case EVALUATE, - case CC: - CodeableConcept cc = new CodeableConcept(); - cc.addCoding(buildCoding(tgt.getParameter().get(0).getValue(), tgt.getParameter().get(1).getValue())); - return cc; - case C: - return buildCoding(tgt.getParameter().get(0).getValue(), tgt.getParameter().get(1).getValue()); - case QTY: return null; - //case ID, - //case CP, - default: - return null; - } - } - - @SuppressWarnings("rawtypes") - private Coding buildCoding(DataType value1, DataType value2) { - return new Coding().setSystem(((PrimitiveType) value1).asStringValue()).setCode(((PrimitiveType) value2).asStringValue()) ; - } - - private boolean allParametersFixed(StructureMapGroupRuleTargetComponent tgt) { - for (StructureMapGroupRuleTargetParameterComponent p : tgt.getParameter()) { - DataType pr = p.getValue(); - if (pr instanceof IdType) - return false; - } - return true; - } - - private String describeTransform(StructureMapGroupRuleTargetComponent tgt) throws FHIRException { - switch (tgt.getTransform()) { - case COPY: return null; - case TRUNCATE: return null; - //case ESCAPE: - //case CAST: - //case APPEND: - case TRANSLATE: return null; - //case DATEOP, - //case UUID, - //case POINTER, - //case EVALUATE, - case CC: return describeTransformCCorC(tgt); - case C: return describeTransformCCorC(tgt); - case QTY: return null; - //case ID, - //case CP, - default: - return null; - } - } - - @SuppressWarnings("rawtypes") - private String describeTransformCCorC(StructureMapGroupRuleTargetComponent tgt) throws FHIRException { - if (tgt.getParameter().size() < 2) - return null; - DataType p1 = tgt.getParameter().get(0).getValue(); - DataType p2 = tgt.getParameter().get(1).getValue(); - if (p1 instanceof IdType || p2 instanceof IdType) - return null; - if (!(p1 instanceof PrimitiveType) || !(p2 instanceof PrimitiveType)) - return null; - String uri = ((PrimitiveType) p1).asStringValue(); - String code = ((PrimitiveType) p2).asStringValue(); - if (Utilities.noString(uri)) - throw new FHIRException("Describe Transform, but the uri is blank"); - if (Utilities.noString(code)) - throw new FHIRException("Describe Transform, but the code is blank"); - Coding c = buildCoding(uri, code); - return TerminologyRenderer.describeSystem(c.getSystem())+"#"+c.getCode()+(c.hasDisplay() ? "("+c.getDisplay()+")" : ""); - } - - - private boolean isSignificantElement(PropertyWithType property, String element) { - if ("Observation".equals(property.getPath())) - return "code".equals(element); - else if ("Bundle".equals(property.getPath())) - return "type".equals(element); - else - return false; - } - - private String getTransformSuffix(StructureMapTransform transform) { - switch (transform) { - case COPY: return ""; - case TRUNCATE: return " (truncated)"; - //case ESCAPE: - //case CAST: - //case APPEND: - case TRANSLATE: return " (translated)"; - //case DATEOP, - //case UUID, - //case POINTER, - //case EVALUATE, - case CC: return " (--> CodeableConcept)"; - case C: return " (--> Coding)"; - case QTY: return " (--> Quantity)"; - //case ID, - //case CP, - default: - return " {??)"; - } - } - - private PropertyWithType updateProfile(VariableForProfiling var, String element, TypeDetails type, StructureMap map, List profiles, String sliceName, DataType fixed, StructureMapGroupRuleTargetComponent tgt) throws FHIRException { - if (var == null) { - assert (Utilities.noString(element)); - // 1. start the new structure definition - StructureDefinition sdn = worker.fetchResource(StructureDefinition.class, type.getType()); - if (sdn == null) - throw new FHIRException("Unable to find definition for "+type.getType()); - ElementDefinition edn = sdn.getSnapshot().getElementFirstRep(); - PropertyWithType pn = createProfile(map, profiles, new PropertyWithType(sdn.getId(), new Property(worker, edn, sdn), null, type), sliceName, tgt); - -// // 2. hook it into the base bundle -// if (type.getType().startsWith("http://hl7.org/fhir/StructureDefinition/") && worker.getResourceNames().contains(type.getType().substring(40))) { -// StructureDefinition sd = var.getProperty().profileProperty.getStructure(); -// ElementDefinition ed = sd.getDifferential().addElement(); -// ed.setPath("Bundle.entry"); -// ed.setName(sliceName); -// ed.setMax("1"); // well, it is for now... -// ed = sd.getDifferential().addElement(); -// ed.setPath("Bundle.entry.fullUrl"); -// ed.setMin(1); -// ed = sd.getDifferential().addElement(); -// ed.setPath("Bundle.entry.resource"); -// ed.setMin(1); -// ed.addType().setCode(pn.getProfileProperty().getStructure().getType()).setProfile(pn.getProfileProperty().getStructure().getUrl()); -// } - return pn; - } else { - assert (!Utilities.noString(element)); - Property pvb = var.getProperty().getBaseProperty(); - Property pvd = var.getProperty().getProfileProperty(); - Property pc = pvb.getChild(element, var.property.types); - if (pc == null) - throw new DefinitionException("Unable to find a definition for "+pvb.getDefinition().getPath()+"."+element); - - // the profile structure definition (derived) - StructureDefinition sd = var.getProperty().profileProperty.getStructure(); - ElementDefinition ednew = sd.getDifferential().addElement(); - ednew.setPath(var.getProperty().profileProperty.getDefinition().getPath()+"."+pc.getName()); - ednew.setUserData("slice-name", sliceName); - ednew.setFixed(fixed); - for (ProfiledType pt : type.getProfiledTypes()) { - if (pt.hasBindings()) - ednew.setBinding(pt.getBindings().get(0)); - if (pt.getUri().startsWith("http://hl7.org/fhir/StructureDefinition/")) { - String t = pt.getUri().substring(40); - t = checkType(t, pc, pt.getProfiles()); - if (t != null) { - if (pt.hasProfiles()) { - for (String p : pt.getProfiles()) - if (t.equals("Reference")) - ednew.getType(t).addTargetProfile(p); - else - ednew.getType(t).addProfile(p); - } else - ednew.getType(t); - } - } - } - - return new PropertyWithType(var.property.path+"."+element, pc, new Property(worker, ednew, sd), type); - } - } - - - - private String checkType(String t, Property pvb, List profiles) throws FHIRException { - if (pvb.getDefinition().getType().size() == 1 && isCompatibleType(t, pvb.getDefinition().getType().get(0).getWorkingCode()) && profilesMatch(profiles, pvb.getDefinition().getType().get(0).getProfile())) - return null; - for (TypeRefComponent tr : pvb.getDefinition().getType()) { - if (isCompatibleType(t, tr.getWorkingCode())) - return tr.getWorkingCode(); // note what is returned - the base type, not the inferred mapping type - } - throw new FHIRException("The type "+t+" is not compatible with the allowed types for "+pvb.getDefinition().getPath()); - } - - private boolean profilesMatch(List profiles, List profile) { - return profiles == null || profiles.size() == 0 || profile.size() == 0 || (profiles.size() == 1 && profiles.get(0).equals(profile.get(0).getValue())); - } - - private boolean isCompatibleType(String t, String code) { - if (t.equals(code)) - return true; - if (t.equals("string")) { - StructureDefinition sd = worker.fetchTypeDefinition(code); - if (sd != null && sd.getBaseDefinition().equals("http://hl7.org/fhir/StructureDefinition/string")) - return true; - } - return false; - } - - private TypeDetails analyseTransform(TransformContext context, StructureMap map, StructureMapGroupRuleTargetComponent tgt, VariableForProfiling var, VariablesForProfiling vars) throws FHIRException { - switch (tgt.getTransform()) { - case CREATE : - String p = getParamString(vars, tgt.getParameter().get(0)); - return new TypeDetails(CollectionStatus.SINGLETON, p); - case COPY : - return getParam(vars, tgt.getParameter().get(0)); - case EVALUATE : - ExpressionNode expr = (ExpressionNode) tgt.getUserData(MAP_EXPRESSION); - if (expr == null) { - expr = fpe.parse(getParamString(vars, tgt.getParameter().get(tgt.getParameter().size()-1))); - tgt.setUserData(MAP_WHERE_EXPRESSION, expr); - } - return fpe.check(vars, null, expr); - -////case TRUNCATE : -//// String src = getParamString(vars, tgt.getParameter().get(0)); -//// String len = getParamString(vars, tgt.getParameter().get(1)); -//// if (Utilities.isInteger(len)) { -//// int l = Integer.parseInt(len); -//// if (src.length() > l) -//// src = src.substring(0, l); -//// } -//// return new StringType(src); -////case ESCAPE : -//// throw new Error("Transform "+tgt.getTransform().toCode()+" not supported yet"); -////case CAST : -//// throw new Error("Transform "+tgt.getTransform().toCode()+" not supported yet"); -////case APPEND : -//// throw new Error("Transform "+tgt.getTransform().toCode()+" not supported yet"); - case TRANSLATE : - return new TypeDetails(CollectionStatus.SINGLETON, "CodeableConcept"); - case CC: - ProfiledType res = new ProfiledType("CodeableConcept"); - if (tgt.getParameter().size() >= 2 && isParamId(vars, tgt.getParameter().get(1))) { - TypeDetails td = vars.get(null, getParamId(vars, tgt.getParameter().get(1))).property.types; - if (td != null && td.hasBinding()) - // todo: do we need to check that there's no implicit translation her? I don't think we do... - res.addBinding(td.getBinding()); - } - return new TypeDetails(CollectionStatus.SINGLETON, res); - case C: - return new TypeDetails(CollectionStatus.SINGLETON, "Coding"); - case QTY: - return new TypeDetails(CollectionStatus.SINGLETON, "Quantity"); - case REFERENCE : - VariableForProfiling vrs = vars.get(VariableMode.OUTPUT, getParamId(vars, tgt.getParameterFirstRep())); - if (vrs == null) - throw new FHIRException("Unable to resolve variable \""+getParamId(vars, tgt.getParameterFirstRep())+"\""); - String profile = vrs.property.getProfileProperty().getStructure().getUrl(); - TypeDetails td = new TypeDetails(CollectionStatus.SINGLETON); - td.addType("Reference", profile); - return td; -////case DATEOP : -//// throw new Error("Transform "+tgt.getTransform().toCode()+" not supported yet"); -////case UUID : -//// return new IdType(UUID.randomUUID().toString()); -////case POINTER : -//// Base b = getParam(vars, tgt.getParameter().get(0)); -//// if (b instanceof Resource) -//// return new UriType("urn:uuid:"+((Resource) b).getId()); -//// else -//// throw new FHIRException("Transform engine cannot point at an element of type "+b.fhirType()); - default: - throw new Error("Transform Unknown or not handled yet: "+tgt.getTransform().toCode()); - } - } - private String getParamString(VariablesForProfiling vars, StructureMapGroupRuleTargetParameterComponent parameter) { - DataType p = parameter.getValue(); - if (p == null || p instanceof IdType) - return null; - if (!p.hasPrimitiveValue()) - return null; - return p.primitiveValue(); - } - - private String getParamId(VariablesForProfiling vars, StructureMapGroupRuleTargetParameterComponent parameter) { - DataType p = parameter.getValue(); - if (p == null || !(p instanceof IdType)) - return null; - return p.primitiveValue(); - } - - private boolean isParamId(VariablesForProfiling vars, StructureMapGroupRuleTargetParameterComponent parameter) { - DataType p = parameter.getValue(); - if (p == null || !(p instanceof IdType)) - return false; - return vars.get(null, p.primitiveValue()) != null; - } - - private TypeDetails getParam(VariablesForProfiling vars, StructureMapGroupRuleTargetParameterComponent parameter) throws DefinitionException { - DataType p = parameter.getValue(); - if (!(p instanceof IdType)) - return new TypeDetails(CollectionStatus.SINGLETON, ProfileUtilities.sdNs(p.fhirType(), worker.getOverrideVersionNs())); - else { - String n = ((IdType) p).asStringValue(); - VariableForProfiling b = vars.get(VariableMode.INPUT, n); - if (b == null) - b = vars.get(VariableMode.OUTPUT, n); - if (b == null) - throw new DefinitionException("Variable "+n+" not found ("+vars.summary()+")"); - return b.getProperty().getTypes(); - } - } - - private PropertyWithType createProfile(StructureMap map, List profiles, PropertyWithType prop, String sliceName, Base ctxt) throws FHIRException { - if (prop.getBaseProperty().getDefinition().getPath().contains(".")) - throw new DefinitionException("Unable to process entry point"); - - String type = prop.getBaseProperty().getDefinition().getPath(); - String suffix = ""; - if (ids.containsKey(type)) { - int id = ids.get(type); - id++; - ids.put(type, id); - suffix = "-"+Integer.toString(id); - } else - ids.put(type, 0); - - StructureDefinition profile = new StructureDefinition(); - profiles.add(profile); - profile.setDerivation(TypeDerivationRule.CONSTRAINT); - profile.setType(type); - profile.setBaseDefinition(prop.getBaseProperty().getStructure().getUrl()); - profile.setName("Profile for "+profile.getType()+" for "+sliceName); - profile.setUrl(map.getUrl().replace("StructureMap", "StructureDefinition")+"-"+profile.getType()+suffix); - ctxt.setUserData("profile", profile.getUrl()); // then we can easily assign this profile url for validation later when we actually transform - profile.setId(map.getId()+"-"+profile.getType()+suffix); - profile.setStatus(map.getStatus()); - profile.setExperimental(map.getExperimental()); - profile.setDescription("Generated automatically from the mapping by the Java Reference Implementation"); - for (ContactDetail c : map.getContact()) { - ContactDetail p = profile.addContact(); - p.setName(c.getName()); - for (ContactPoint cc : c.getTelecom()) - p.addTelecom(cc); - } - profile.setDate(map.getDate()); - profile.setCopyright(map.getCopyright()); - profile.setFhirVersion(FHIRVersion.fromCode(Constants.VERSION)); - profile.setKind(prop.getBaseProperty().getStructure().getKind()); - profile.setAbstract(false); - ElementDefinition ed = profile.getDifferential().addElement(); - ed.setPath(profile.getType()); - prop.profileProperty = new Property(worker, ed, profile); - return prop; - } - - private PropertyWithType resolveType(StructureMap map, String type, StructureMapInputMode mode) throws FHIRException { - for (StructureMapStructureComponent imp : map.getStructure()) { - if ((imp.getMode() == StructureMapModelMode.SOURCE && mode == StructureMapInputMode.SOURCE) || - (imp.getMode() == StructureMapModelMode.TARGET && mode == StructureMapInputMode.TARGET)) { - StructureDefinition sd = worker.fetchResource(StructureDefinition.class, imp.getUrl()); - if (sd == null) - throw new FHIRException("Import "+imp.getUrl()+" cannot be resolved"); - if (sd.getId().equals(type)) { - return new PropertyWithType(sd.getType(), new Property(worker, sd.getSnapshot().getElement().get(0), sd), null, new TypeDetails(CollectionStatus.SINGLETON, sd.getUrl())); - } - } - } - throw new FHIRException("Unable to find structure definition for "+type+" in imports"); - } - - - public StructureMap generateMapFromMappings(StructureDefinition sd) throws IOException, FHIRException { - String id = getLogicalMappingId(sd); - if (id == null) - return null; - String prefix = ToolingExtensions.readStringExtension(sd, ToolingExtensions.EXT_MAPPING_PREFIX); - String suffix = ToolingExtensions.readStringExtension(sd, ToolingExtensions.EXT_MAPPING_SUFFIX); - if (prefix == null || suffix == null) - return null; - // we build this by text. Any element that has a mapping, we put it's mappings inside it.... - StringBuilder b = new StringBuilder(); - b.append(prefix); - - ElementDefinition root = sd.getSnapshot().getElementFirstRep(); - String m = getMapping(root, id); - if (m != null) - b.append(m+"\r\n"); - addChildMappings(b, id, "", sd, root, false); - b.append("\r\n"); - b.append(suffix); - b.append("\r\n"); - StructureMap map = parse(b.toString(), sd.getUrl()); - map.setId(tail(map.getUrl())); - if (!map.hasStatus()) - map.setStatus(PublicationStatus.DRAFT); - map.getText().setStatus(NarrativeStatus.GENERATED); - map.getText().setDiv(new XhtmlNode(NodeType.Element, "div")); - map.getText().getDiv().addTag("pre").addText(render(map)); - return map; - } - - - private String tail(String url) { - return url.substring(url.lastIndexOf("/")+1); - } - - - private void addChildMappings(StringBuilder b, String id, String indent, StructureDefinition sd, ElementDefinition ed, boolean inner) throws DefinitionException { - boolean first = true; - List children = profileUtilities.getChildMap(sd, ed); - for (ElementDefinition child : children) { - if (first && inner) { - b.append(" then {\r\n"); - first = false; - } - String map = getMapping(child, id); - if (map != null) { - b.append(indent+" "+child.getPath()+": "+map); - addChildMappings(b, id, indent+" ", sd, child, true); - b.append("\r\n"); - } - } - if (!first && inner) - b.append(indent+"}"); - - } - - - private String getMapping(ElementDefinition ed, String id) { - for (ElementDefinitionMappingComponent map : ed.getMapping()) - if (id.equals(map.getIdentity())) - return map.getMap(); - return null; - } - - - private String getLogicalMappingId(StructureDefinition sd) { - String id = null; - for (StructureDefinitionMappingComponent map : sd.getMapping()) { - if ("http://hl7.org/fhir/logical".equals(map.getUri())) - return map.getIdentity(); - } - return null; - } - - public ValidationOptions getTerminologyServiceOptions() { - return terminologyServiceOptions; - } - - public void setTerminologyServiceOptions(ValidationOptions terminologyServiceOptions) { - this.terminologyServiceOptions = terminologyServiceOptions; - } - -} \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/FFHIRPathHostServices.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/FFHIRPathHostServices.java new file mode 100644 index 000000000..e5ca5ea32 --- /dev/null +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/FFHIRPathHostServices.java @@ -0,0 +1,99 @@ +package org.hl7.fhir.r5.utils.structuremap; + +import org.apache.commons.lang3.NotImplementedException; +import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.exceptions.PathEngineException; +import org.hl7.fhir.r5.elementmodel.Element; +import org.hl7.fhir.r5.model.Base; +import org.hl7.fhir.r5.model.Resource; +import org.hl7.fhir.r5.model.TypeDetails; +import org.hl7.fhir.r5.model.ValueSet; +import org.hl7.fhir.r5.utils.FHIRPathEngine; +import org.hl7.fhir.r5.utils.IResourceValidator; +import org.hl7.fhir.utilities.validation.ValidationMessage; + +import java.util.ArrayList; +import java.util.List; + +public class FFHIRPathHostServices implements FHIRPathEngine.IEvaluationContext { + + private final StructureMapUtilities structureMapUtilities; + + public FFHIRPathHostServices(StructureMapUtilities structureMapUtilities) { + this.structureMapUtilities = structureMapUtilities; + } + + public Base resolveConstant(Object appContext, String name, boolean beforeContext) throws PathEngineException { + Variables vars = (Variables) appContext; + Base res = vars.get(VariableMode.INPUT, name); + if (res == null) + res = vars.get(VariableMode.OUTPUT, name); + return res; + } + + @Override + public TypeDetails resolveConstantType(Object appContext, String name) throws PathEngineException { + if (!(appContext instanceof VariablesForProfiling)) + throw new Error("Internal Logic Error (wrong type '" + appContext.getClass().getName() + "' in resolveConstantType)"); + VariablesForProfiling vars = (VariablesForProfiling) appContext; + VariableForProfiling v = vars.get(null, name); + if (v == null) + throw new PathEngineException("Unknown variable '" + name + "' from variables " + vars.summary()); + return v.getProperty().getTypes(); + } + + @Override + public boolean log(String argument, List focus) { + throw new Error("Not Implemented Yet"); + } + + @Override + public FunctionDetails resolveFunction(String functionName) { + return null; // throw new Error("Not Implemented Yet"); + } + + @Override + public TypeDetails checkFunction(Object appContext, String functionName, List parameters) throws PathEngineException { + throw new Error("Not Implemented Yet"); + } + + @Override + public List executeFunction(Object appContext, List focus, String functionName, List> parameters) { + throw new Error("Not Implemented Yet"); + } + + @Override + public Base resolveReference(Object appContext, String url, Base refContext) throws FHIRException { + if (structureMapUtilities.getServices() == null) + return null; + return structureMapUtilities.getServices().resolveReference(appContext, url); + } + + private boolean noErrorValidationMessages(List valerrors) { + boolean ok = true; + for (ValidationMessage v : valerrors) + ok = ok && !v.getLevel().isError(); + return ok; + } + + @Override + public boolean conformsToProfile(Object appContext, Base item, String url) throws FHIRException { + IResourceValidator val = structureMapUtilities.getWorker().newValidator(); + List valerrors = new ArrayList(); + if (item instanceof Resource) { + val.validate(appContext, valerrors, (Resource) item, url); + return noErrorValidationMessages(valerrors); + } + if (item instanceof Element) { + val.validate(appContext, valerrors, (Element) item, url); + return noErrorValidationMessages(valerrors); + } + throw new NotImplementedException("Not done yet (FFHIRPathHostServices.conformsToProfile), when item is not element or not resource"); + } + + @Override + public ValueSet resolveValueSet(Object appContext, String url) { + throw new Error("Not Implemented Yet"); + } + +} diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/ITransformerServices.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/ITransformerServices.java new file mode 100644 index 000000000..bcd1444ce --- /dev/null +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/ITransformerServices.java @@ -0,0 +1,28 @@ +package org.hl7.fhir.r5.utils.structuremap; + +import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.r5.model.Base; +import org.hl7.fhir.r5.model.Coding; + +import java.util.List; + +public interface ITransformerServices { + // public boolean validateByValueSet(Coding code, String valuesetId); + public void log(String message); // log internal progress + + public Base createType(Object appInfo, String name) throws FHIRException; + + public Base createResource(Object appInfo, Base res, boolean atRootofTransform); // an already created resource is provided; this is to identify/store it + + public Coding translate(Object appInfo, Coding source, String conceptMapUrl) throws FHIRException; + + // public Coding translate(Coding code) + // ValueSet validation operation + // Translation operation + // Lookup another tree of data + // Create an instance tree + // Return the correct string format to refer to a tree (input or output) + public Base resolveReference(Object appContext, String url) throws FHIRException; + + public List performSearch(Object appContext, String url) throws FHIRException; +} diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/PropertyWithType.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/PropertyWithType.java new file mode 100644 index 000000000..f0e2d633f --- /dev/null +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/PropertyWithType.java @@ -0,0 +1,48 @@ +package org.hl7.fhir.r5.utils.structuremap; + +import org.hl7.fhir.r5.elementmodel.Property; +import org.hl7.fhir.r5.model.TypeDetails; + +public class PropertyWithType { + private String path; + private Property baseProperty; + private Property profileProperty; + private TypeDetails types; + + public PropertyWithType(String path, Property baseProperty, Property profileProperty, TypeDetails types) { + super(); + this.baseProperty = baseProperty; + this.profileProperty = profileProperty; + this.path = path; + this.types = types; + } + + public TypeDetails getTypes() { + return types; + } + + public String getPath() { + return path; + } + + public Property getBaseProperty() { + return baseProperty; + } + + public void setBaseProperty(Property baseProperty) { + this.baseProperty = baseProperty; + } + + public Property getProfileProperty() { + return profileProperty; + } + + public void setProfileProperty(Property profileProperty) { + this.profileProperty = profileProperty; + } + + public String summary() { + return path; + } + +} diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/ResolvedGroup.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/ResolvedGroup.java new file mode 100644 index 000000000..f10ff85ea --- /dev/null +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/ResolvedGroup.java @@ -0,0 +1,8 @@ +package org.hl7.fhir.r5.utils.structuremap; + +import org.hl7.fhir.r5.model.StructureMap; + +public class ResolvedGroup { + public StructureMap.StructureMapGroupComponent target; + public StructureMap targetMap; +} diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/SourceElementComponentWrapper.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/SourceElementComponentWrapper.java new file mode 100644 index 000000000..f57a4871a --- /dev/null +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/SourceElementComponentWrapper.java @@ -0,0 +1,22 @@ +package org.hl7.fhir.r5.utils.structuremap; + +import org.hl7.fhir.r5.model.ConceptMap; + +public class SourceElementComponentWrapper { + private ConceptMap.ConceptMapGroupComponent group; + private ConceptMap.SourceElementComponent comp; + + public SourceElementComponentWrapper(ConceptMap.ConceptMapGroupComponent group, ConceptMap.SourceElementComponent comp) { + super(); + this.group = group; + this.comp = comp; + } + + public ConceptMap.ConceptMapGroupComponent getGroup() { + return group; + } + + public ConceptMap.SourceElementComponent getComp() { + return comp; + } +} diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/StringPair.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/StringPair.java new file mode 100644 index 000000000..ef472264e --- /dev/null +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/StringPair.java @@ -0,0 +1,20 @@ +package org.hl7.fhir.r5.utils.structuremap; + +public class StringPair { + private String var; + private String desc; + + public StringPair(String var, String desc) { + super(); + this.var = var; + this.desc = desc; + } + + public String getVar() { + return var; + } + + public String getDesc() { + return desc; + } +} diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/StructureMapAnalysis.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/StructureMapAnalysis.java new file mode 100644 index 000000000..4e1f4c9e9 --- /dev/null +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/StructureMapAnalysis.java @@ -0,0 +1,20 @@ +package org.hl7.fhir.r5.utils.structuremap; + +import org.hl7.fhir.r5.model.StructureDefinition; +import org.hl7.fhir.utilities.xhtml.XhtmlNode; + +import java.util.ArrayList; +import java.util.List; + +public class StructureMapAnalysis { + public List profiles = new ArrayList(); + public XhtmlNode summary; + + public List getProfiles() { + return profiles; + } + + public XhtmlNode getSummary() { + return summary; + } +} diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/StructureMapUtilities.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/StructureMapUtilities.java new file mode 100644 index 000000000..19ebe962b --- /dev/null +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/StructureMapUtilities.java @@ -0,0 +1,2552 @@ +package org.hl7.fhir.r5.utils.structuremap; + +/* + Copyright (c) 2011+, HL7, Inc. + All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of HL7 nor the names of its contributors may be used to + endorse or promote products derived from this software without specific + prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + + */ + + +// remember group resolution +// trace - account for which wasn't transformed in the source + +import org.hl7.fhir.exceptions.DefinitionException; +import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.exceptions.FHIRFormatError; +import org.hl7.fhir.r5.conformance.ProfileUtilities; +import org.hl7.fhir.r5.conformance.ProfileUtilities.ProfileKnowledgeProvider; +import org.hl7.fhir.r5.context.IWorkerContext; +import org.hl7.fhir.r5.context.IWorkerContext.ValidationResult; +import org.hl7.fhir.r5.elementmodel.Element; +import org.hl7.fhir.r5.elementmodel.Property; +import org.hl7.fhir.r5.model.*; +import org.hl7.fhir.r5.model.ConceptMap.ConceptMapGroupComponent; +import org.hl7.fhir.r5.model.ConceptMap.ConceptMapGroupUnmappedMode; +import org.hl7.fhir.r5.model.ConceptMap.SourceElementComponent; +import org.hl7.fhir.r5.model.ConceptMap.TargetElementComponent; +import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionMappingComponent; +import org.hl7.fhir.r5.model.Enumeration; +import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent; +import org.hl7.fhir.r5.model.Enumerations.ConceptMapRelationship; +import org.hl7.fhir.r5.model.Enumerations.FHIRVersion; +import org.hl7.fhir.r5.model.Enumerations.PublicationStatus; +import org.hl7.fhir.r5.model.ExpressionNode.CollectionStatus; +import org.hl7.fhir.r5.model.Narrative.NarrativeStatus; +import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionMappingComponent; +import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule; +import org.hl7.fhir.r5.model.StructureMap.*; +import org.hl7.fhir.r5.model.TypeDetails.ProfiledType; +import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent; +import org.hl7.fhir.r5.renderers.TerminologyRenderer; +import org.hl7.fhir.r5.terminologies.ValueSetExpander.ValueSetExpansionOutcome; +import org.hl7.fhir.r5.utils.FHIRLexer; +import org.hl7.fhir.r5.utils.FHIRLexer.FHIRLexerException; +import org.hl7.fhir.r5.utils.FHIRPathEngine; +import org.hl7.fhir.r5.utils.ToolingExtensions; +import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; +import org.hl7.fhir.utilities.Utilities; +import org.hl7.fhir.utilities.validation.ValidationOptions; +import org.hl7.fhir.utilities.xhtml.NodeType; +import org.hl7.fhir.utilities.xhtml.XhtmlNode; + +import java.io.IOException; +import java.util.*; + +/** + * Services in this class: + *

+ * string render(map) - take a structure and convert it to text + * map parse(text) - take a text representation and parse it + * getTargetType(map) - return the definition for the type to create to hand in + * transform(appInfo, source, map, target) - transform from source to target following the map + * analyse(appInfo, map) - generate profiles and other analysis artifacts for the targets of the transform + * map generateMapFromMappings(StructureDefinition) - build a mapping from a structure definition with loigcal mappings + * + * @author Grahame Grieve + */ +public class StructureMapUtilities { + + public static final String MAP_WHERE_CHECK = "map.where.check"; + public static final String MAP_WHERE_LOG = "map.where.log"; + public static final String MAP_WHERE_EXPRESSION = "map.where.expression"; + public static final String MAP_SEARCH_EXPRESSION = "map.search.expression"; + public static final String MAP_EXPRESSION = "map.transform.expression"; + private static final boolean RENDER_MULTIPLE_TARGETS_ONELINE = true; + private static final String AUTO_VAR_NAME = "vvv"; + + private final IWorkerContext worker; + private final FHIRPathEngine fpe; + private ITransformerServices services; + private ProfileKnowledgeProvider pkp; + private final Map ids = new HashMap(); + private ValidationOptions terminologyServiceOptions = new ValidationOptions(); + private final ProfileUtilities profileUtilities; + + public StructureMapUtilities(IWorkerContext worker, ITransformerServices services, ProfileKnowledgeProvider pkp) { + super(); + this.worker = worker; + this.services = services; + this.pkp = pkp; + fpe = new FHIRPathEngine(worker); + fpe.setHostServices(new FFHIRPathHostServices(this)); + profileUtilities = new ProfileUtilities(worker, null, null); + } + + public StructureMapUtilities(IWorkerContext worker, ITransformerServices services) { + super(); + this.worker = worker; + this.services = services; + fpe = new FHIRPathEngine(worker); + fpe.setHostServices(new FFHIRPathHostServices(this)); + profileUtilities = new ProfileUtilities(worker, null, null); + } + + public StructureMapUtilities(IWorkerContext worker) { + super(); + this.worker = worker; + fpe = new FHIRPathEngine(worker); + fpe.setHostServices(new FFHIRPathHostServices(this)); + profileUtilities = new ProfileUtilities(worker, null, null); + + } + + public static String render(StructureMap map) { + StringBuilder b = new StringBuilder(); + b.append("map \""); + b.append(map.getUrl()); + b.append("\" = \""); + b.append(Utilities.escapeJson(map.getName())); + b.append("\"\r\n\r\n"); + if (map.getDescription() != null) { + renderMultilineDoco(b, map.getDescription(), 0); + b.append("\r\n"); + } + renderConceptMaps(b, map); + renderUses(b, map); + renderImports(b, map); + for (StructureMapGroupComponent g : map.getGroup()) + renderGroup(b, g); + return b.toString(); + } + + private static void renderConceptMaps(StringBuilder b, StructureMap map) { + for (Resource r : map.getContained()) { + if (r instanceof ConceptMap) { + produceConceptMap(b, (ConceptMap) r); + } + } + } + + private static void produceConceptMap(StringBuilder b, ConceptMap cm) { + b.append("conceptmap \""); + b.append(cm.getId()); + b.append("\" {\r\n"); + Map prefixesSrc = new HashMap(); + Map prefixesTgt = new HashMap(); + char prefix = 's'; + for (ConceptMapGroupComponent cg : cm.getGroup()) { + if (!prefixesSrc.containsKey(cg.getSource())) { + prefixesSrc.put(cg.getSource(), String.valueOf(prefix)); + b.append(" prefix "); + b.append(prefix); + b.append(" = \""); + b.append(cg.getSource()); + b.append("\"\r\n"); + prefix++; + } + if (!prefixesTgt.containsKey(cg.getTarget())) { + prefixesTgt.put(cg.getTarget(), String.valueOf(prefix)); + b.append(" prefix "); + b.append(prefix); + b.append(" = \""); + b.append(cg.getTarget()); + b.append("\"\r\n"); + prefix++; + } + } + b.append("\r\n"); + for (ConceptMapGroupComponent cg : cm.getGroup()) { + if (cg.hasUnmapped()) { + b.append(" unmapped for "); + b.append(prefixesSrc.get(cg.getSource())); + b.append(" = "); + b.append(cg.getUnmapped().getMode().toCode()); + b.append("\r\n"); + } + } + + for (ConceptMapGroupComponent cg : cm.getGroup()) { + for (SourceElementComponent ce : cg.getElement()) { + b.append(" "); + b.append(prefixesSrc.get(cg.getSource())); + b.append(":"); + if (Utilities.isToken(ce.getCode())) { + b.append(ce.getCode()); + } else { + b.append("\""); + b.append(ce.getCode()); + b.append("\""); + } + b.append(" "); + b.append(getChar(ce.getTargetFirstRep().getRelationship())); + b.append(" "); + b.append(prefixesTgt.get(cg.getTarget())); + b.append(":"); + if (Utilities.isToken(ce.getTargetFirstRep().getCode())) { + b.append(ce.getTargetFirstRep().getCode()); + } else { + b.append("\""); + b.append(ce.getTargetFirstRep().getCode()); + b.append("\""); + } + b.append("\r\n"); + } + } + b.append("}\r\n\r\n"); + } + + private static Object getChar(ConceptMapRelationship relationship) { + switch (relationship) { + case RELATEDTO: + return "-"; + case EQUIVALENT: + return "=="; + case NOTRELATEDTO: + return "!="; + case SOURCEISNARROWERTHANTARGET: + return "<="; + case SOURCEISBROADERTHANTARGET: + return ">="; + default: + return "??"; + } + } + + private static void renderUses(StringBuilder b, StructureMap map) { + for (StructureMapStructureComponent s : map.getStructure()) { + b.append("uses \""); + b.append(s.getUrl()); + b.append("\" "); + if (s.hasAlias()) { + b.append("alias "); + b.append(s.getAlias()); + b.append(" "); + } + b.append("as "); + b.append(s.getMode().toCode()); + renderDoco(b, s.getDocumentation()); + b.append("\r\n"); + } + if (map.hasStructure()) + b.append("\r\n"); + } + + private static void renderImports(StringBuilder b, StructureMap map) { + for (UriType s : map.getImport()) { + b.append("imports \""); + b.append(s.getValue()); + b.append("\"\r\n"); + } + if (map.hasImport()) + b.append("\r\n"); + } + + public static String groupToString(StructureMapGroupComponent g) { + StringBuilder b = new StringBuilder(); + renderGroup(b, g); + return b.toString(); + } + + private static void renderGroup(StringBuilder b, StructureMapGroupComponent g) { + if (g.hasDocumentation()) { + renderMultilineDoco(b, g.getDocumentation(), 0); + } + b.append("group "); + b.append(g.getName()); + b.append("("); + boolean first = true; + for (StructureMapGroupInputComponent gi : g.getInput()) { + if (first) + first = false; + else + b.append(", "); + b.append(gi.getMode().toCode()); + b.append(" "); + b.append(gi.getName()); + if (gi.hasType()) { + b.append(" : "); + b.append(gi.getType()); + } + } + b.append(")"); + if (g.hasExtends()) { + b.append(" extends "); + b.append(g.getExtends()); + } + + if (g.hasTypeMode()) { + switch (g.getTypeMode()) { + case TYPES: + b.append(" <>"); + break; + case TYPEANDTYPES: + b.append(" <>"); + break; + default: // NONE, NULL + } + } + b.append(" {\r\n"); + for (StructureMapGroupRuleComponent r : g.getRule()) { + renderRule(b, r, 2); + } + b.append("}\r\n\r\n"); + } + + public static String ruleToString(StructureMapGroupRuleComponent r) { + StringBuilder b = new StringBuilder(); + renderRule(b, r, 0); + return b.toString(); + } + + private static void renderRule(StringBuilder b, StructureMapGroupRuleComponent r, int indent) { + if (r.getDocumentation() != null) { + renderMultilineDoco(b, r.getDocumentation(), indent); + } + for (int i = 0; i < indent; i++) + b.append(' '); + boolean canBeAbbreviated = checkisSimple(r); + + boolean first = true; + for (StructureMapGroupRuleSourceComponent rs : r.getSource()) { + if (first) + first = false; + else + b.append(", "); + renderSource(b, rs, canBeAbbreviated); + } + if (r.getTarget().size() > 1) { + b.append(" -> "); + first = true; + for (StructureMapGroupRuleTargetComponent rt : r.getTarget()) { + if (first) + first = false; + else + b.append(", "); + if (RENDER_MULTIPLE_TARGETS_ONELINE) + b.append(' '); + else { + b.append("\r\n"); + for (int i = 0; i < indent + 4; i++) + b.append(' '); + } + renderTarget(b, rt, false); + } + } else if (r.hasTarget()) { + b.append(" -> "); + renderTarget(b, r.getTarget().get(0), canBeAbbreviated); + } + if (r.hasRule()) { + b.append(" then {\r\n"); + for (StructureMapGroupRuleComponent ir : r.getRule()) { + renderRule(b, ir, indent + 2); + } + for (int i = 0; i < indent; i++) + b.append(' '); + b.append("}"); + } else { + if (r.hasDependent()) { + b.append(" then "); + first = true; + for (StructureMapGroupRuleDependentComponent rd : r.getDependent()) { + if (first) + first = false; + else + b.append(", "); + b.append(rd.getName()); + b.append("("); + boolean ifirst = true; + for (StringType rdp : rd.getVariable()) { + if (ifirst) + ifirst = false; + else + b.append(", "); + b.append(rdp.asStringValue()); + } + b.append(")"); + } + } + } + if (r.hasName()) { + String n = ntail(r.getName()); + if (!n.startsWith("\"")) + n = "\"" + n + "\""; + if (!matchesName(n, r.getSource())) { + b.append(" "); + b.append(n); + } + } + b.append(";"); + b.append("\r\n"); + } + + private static boolean matchesName(String n, List source) { + if (source.size() != 1) + return false; + if (!source.get(0).hasElement()) + return false; + String s = source.get(0).getElement(); + if (n.equals(s) || n.equals("\"" + s + "\"")) + return true; + if (source.get(0).hasType()) { + s = source.get(0).getElement() + "-" + source.get(0).getType(); + return n.equals(s) || n.equals("\"" + s + "\""); + } + return false; + } + + private static String ntail(String name) { + if (name == null) + return null; + if (name.startsWith("\"")) { + name = name.substring(1); + name = name.substring(0, name.length() - 1); + } + return "\"" + (name.contains(".") ? name.substring(name.lastIndexOf(".") + 1) : name) + "\""; + } + + private static boolean checkisSimple(StructureMapGroupRuleComponent r) { + return + (r.getSource().size() == 1 && r.getSourceFirstRep().hasElement() && r.getSourceFirstRep().hasVariable()) && + (r.getTarget().size() == 1 && r.getTargetFirstRep().hasVariable() && (r.getTargetFirstRep().getTransform() == null || r.getTargetFirstRep().getTransform() == StructureMapTransform.CREATE) && r.getTargetFirstRep().getParameter().size() == 0) && + (r.getDependent().size() == 0) && (r.getRule().size() == 0); + } + + public static String sourceToString(StructureMapGroupRuleSourceComponent r) { + StringBuilder b = new StringBuilder(); + renderSource(b, r, false); + return b.toString(); + } + + private static void renderSource(StringBuilder b, StructureMapGroupRuleSourceComponent rs, boolean abbreviate) { + b.append(rs.getContext()); + if (rs.getContext().equals("@search")) { + b.append('('); + b.append(rs.getElement()); + b.append(')'); + } else if (rs.hasElement()) { + b.append('.'); + b.append(rs.getElement()); + } + if (rs.hasType()) { + b.append(" : "); + b.append(rs.getType()); + if (rs.hasMin()) { + b.append(" "); + b.append(rs.getMin()); + b.append(".."); + b.append(rs.getMax()); + } + } + + if (rs.hasListMode()) { + b.append(" "); + b.append(rs.getListMode().toCode()); + } + if (rs.hasDefaultValue()) { + b.append(" default "); + assert rs.getDefaultValue() instanceof StringType; + b.append("\"" + Utilities.escapeJson(((StringType) rs.getDefaultValue()).asStringValue()) + "\""); + } + if (!abbreviate && rs.hasVariable()) { + b.append(" as "); + b.append(rs.getVariable()); + } + if (rs.hasCondition()) { + b.append(" where "); + b.append(rs.getCondition()); + } + if (rs.hasCheck()) { + b.append(" check "); + b.append(rs.getCheck()); + } + if (rs.hasLogMessage()) { + b.append(" log "); + b.append(rs.getLogMessage()); + } + } + + public static String targetToString(StructureMapGroupRuleTargetComponent rt) { + StringBuilder b = new StringBuilder(); + renderTarget(b, rt, false); + return b.toString(); + } + + private static void renderTarget(StringBuilder b, StructureMapGroupRuleTargetComponent rt, boolean abbreviate) { + if (rt.hasContext()) { + if (rt.getContextType() == StructureMapContextType.TYPE) + b.append("@"); + b.append(rt.getContext()); + if (rt.hasElement()) { + b.append('.'); + b.append(rt.getElement()); + } + } + if (!abbreviate && rt.hasTransform()) { + if (rt.hasContext()) + b.append(" = "); + if (rt.getTransform() == StructureMapTransform.COPY && rt.getParameter().size() == 1) { + renderTransformParam(b, rt.getParameter().get(0)); + } else if (rt.getTransform() == StructureMapTransform.EVALUATE && rt.getParameter().size() == 1) { + b.append("("); + b.append(((StringType) rt.getParameter().get(0).getValue()).asStringValue()); + b.append(")"); + } else if (rt.getTransform() == StructureMapTransform.EVALUATE && rt.getParameter().size() == 2) { + b.append(rt.getTransform().toCode()); + b.append("("); + b.append(((IdType) rt.getParameter().get(0).getValue()).asStringValue()); + b.append(((StringType) rt.getParameter().get(1).getValue()).asStringValue()); + b.append(")"); + } else { + b.append(rt.getTransform().toCode()); + b.append("("); + boolean first = true; + for (StructureMapGroupRuleTargetParameterComponent rtp : rt.getParameter()) { + if (first) + first = false; + else + b.append(", "); + renderTransformParam(b, rtp); + } + b.append(")"); + } + } + if (!abbreviate && rt.hasVariable()) { + b.append(" as "); + b.append(rt.getVariable()); + } + for (Enumeration lm : rt.getListMode()) { + b.append(" "); + b.append(lm.getValue().toCode()); + if (lm.getValue() == StructureMapTargetListMode.SHARE) { + b.append(" "); + b.append(rt.getListRuleId()); + } + } + } + + public static String paramToString(StructureMapGroupRuleTargetParameterComponent rtp) { + StringBuilder b = new StringBuilder(); + renderTransformParam(b, rtp); + return b.toString(); + } + + private static void renderTransformParam(StringBuilder b, StructureMapGroupRuleTargetParameterComponent rtp) { + try { + if (rtp.hasValueBooleanType()) + b.append(rtp.getValueBooleanType().asStringValue()); + else if (rtp.hasValueDecimalType()) + b.append(rtp.getValueDecimalType().asStringValue()); + else if (rtp.hasValueIdType()) + b.append(rtp.getValueIdType().asStringValue()); + else if (rtp.hasValueDecimalType()) + b.append(rtp.getValueDecimalType().asStringValue()); + else if (rtp.hasValueIntegerType()) + b.append(rtp.getValueIntegerType().asStringValue()); + else + b.append("'" + Utilities.escapeJava(rtp.getValueStringType().asStringValue()) + "'"); + } catch (FHIRException e) { + e.printStackTrace(); + b.append("error!"); + } + } + + private static void renderDoco(StringBuilder b, String doco) { + if (Utilities.noString(doco)) + return; + if (b != null && b.length() > 1 && b.charAt(b.length() - 1) != '\n' && b.charAt(b.length() - 1) != ' ') { + b.append(" "); + } + b.append("// "); + b.append(doco.replace("\r\n", " ").replace("\r", " ").replace("\n", " ")); + } + + private static void renderMultilineDoco(StringBuilder b, String doco, int indent) { + if (Utilities.noString(doco)) + return; + String[] lines = doco.split("\\r?\\n"); + for (String line : lines) { + for (int i = 0; i < indent; i++) + b.append(' '); + renderDoco(b, line); + b.append("\r\n"); + } + } + + public ITransformerServices getServices() { + return services; + } + + public IWorkerContext getWorker() { + return worker; + } + + public StructureMap parse(String text, String srcName) throws FHIRException { + FHIRLexer lexer = new FHIRLexer(text, srcName); + if (lexer.done()) + throw lexer.error("Map Input cannot be empty"); + lexer.token("map"); + StructureMap result = new StructureMap(); + result.setUrl(lexer.readConstant("url")); + lexer.token("="); + result.setName(lexer.readConstant("name")); + result.setDescription(lexer.getAllComments()); + while (lexer.hasToken("conceptmap")) + parseConceptMap(result, lexer); + + while (lexer.hasToken("uses")) + parseUses(result, lexer); + while (lexer.hasToken("imports")) + parseImports(result, lexer); + + while (!lexer.done()) { + parseGroup(result, lexer); + } + + return result; + } + + private void parseConceptMap(StructureMap result, FHIRLexer lexer) throws FHIRLexerException { + lexer.token("conceptmap"); + ConceptMap map = new ConceptMap(); + String id = lexer.readConstant("map id"); + if (id.startsWith("#")) + throw lexer.error("Concept Map identifier must start with #"); + map.setId(id); + map.setStatus(PublicationStatus.DRAFT); // todo: how to add this to the text format + result.getContained().add(map); + lexer.token("{"); + // lexer.token("source"); + // map.setSource(new UriType(lexer.readConstant("source"))); + // lexer.token("target"); + // map.setSource(new UriType(lexer.readConstant("target"))); + Map prefixes = new HashMap(); + while (lexer.hasToken("prefix")) { + lexer.token("prefix"); + String n = lexer.take(); + lexer.token("="); + String v = lexer.readConstant("prefix url"); + prefixes.put(n, v); + } + while (lexer.hasToken("unmapped")) { + lexer.token("unmapped"); + lexer.token("for"); + String n = readPrefix(prefixes, lexer); + ConceptMapGroupComponent g = getGroup(map, n, null); + lexer.token("="); + String v = lexer.take(); + if (v.equals("provided")) { + g.getUnmapped().setMode(ConceptMapGroupUnmappedMode.PROVIDED); + } else + throw lexer.error("Only unmapped mode PROVIDED is supported at this time"); + } + while (!lexer.hasToken("}")) { + String srcs = readPrefix(prefixes, lexer); + lexer.token(":"); + String sc = lexer.getCurrent().startsWith("\"") ? lexer.readConstant("code") : lexer.take(); + ConceptMapRelationship rel = readRelationship(lexer); + String tgts = readPrefix(prefixes, lexer); + ConceptMapGroupComponent g = getGroup(map, srcs, tgts); + SourceElementComponent e = g.addElement(); + e.setCode(sc); + if (e.getCode().startsWith("\"")) + e.setCode(lexer.processConstant(e.getCode())); + TargetElementComponent tgt = e.addTarget(); + tgt.setRelationship(rel); + lexer.token(":"); + tgt.setCode(lexer.take()); + if (tgt.getCode().startsWith("\"")) + tgt.setCode(lexer.processConstant(tgt.getCode())); + tgt.setComment(lexer.getFirstComment()); + } + lexer.token("}"); + } + + private ConceptMapGroupComponent getGroup(ConceptMap map, String srcs, String tgts) { + for (ConceptMapGroupComponent grp : map.getGroup()) { + if (grp.getSource().equals(srcs)) + if (!grp.hasTarget() || tgts == null || tgts.equals(grp.getTarget())) { + if (!grp.hasTarget() && tgts != null) + grp.setTarget(tgts); + return grp; + } + } + ConceptMapGroupComponent grp = map.addGroup(); + grp.setSource(srcs); + grp.setTarget(tgts); + return grp; + } + + + private String readPrefix(Map prefixes, FHIRLexer lexer) throws FHIRLexerException { + String prefix = lexer.take(); + if (!prefixes.containsKey(prefix)) + throw lexer.error("Unknown prefix '" + prefix + "'"); + return prefixes.get(prefix); + } + + + private ConceptMapRelationship readRelationship(FHIRLexer lexer) throws FHIRLexerException { + String token = lexer.take(); + if (token.equals("-")) + return ConceptMapRelationship.RELATEDTO; + if (token.equals("==")) + return ConceptMapRelationship.EQUIVALENT; + if (token.equals("!=")) + return ConceptMapRelationship.NOTRELATEDTO; + if (token.equals("<=")) + return ConceptMapRelationship.SOURCEISNARROWERTHANTARGET; + if (token.equals(">=")) + return ConceptMapRelationship.SOURCEISBROADERTHANTARGET; + throw lexer.error("Unknown relationship token '" + token + "'"); + } + + + private void parseUses(StructureMap result, FHIRLexer lexer) throws FHIRException { + lexer.token("uses"); + int currentLine = lexer.getCurrentLocation().getLine(); + StructureMapStructureComponent st = result.addStructure(); + st.setUrl(lexer.readConstant("url")); + if (lexer.hasToken("alias")) { + lexer.token("alias"); + st.setAlias(lexer.take()); + } + lexer.token("as"); + st.setMode(StructureMapModelMode.fromCode(lexer.take())); + lexer.skipToken(";"); + st.setDocumentation(lexer.getFirstComment()); + } + + private void parseImports(StructureMap result, FHIRLexer lexer) throws FHIRException { + lexer.token("imports"); + int currentLine = lexer.getCurrentLocation().getLine(); + result.addImport(lexer.readConstant("url")); + lexer.skipToken(";"); + lexer.getFirstComment(); + } + + private void parseGroup(StructureMap result, FHIRLexer lexer) throws FHIRException { + String comment = lexer.getAllComments(); + lexer.token("group"); + StructureMapGroupComponent group = result.addGroup(); + if (comment != null) { + group.setDocumentation(comment); + } + boolean newFmt = false; + if (lexer.hasToken("for")) { + lexer.token("for"); + if ("type".equals(lexer.getCurrent())) { + lexer.token("type"); + lexer.token("+"); + lexer.token("types"); + group.setTypeMode(StructureMapGroupTypeMode.TYPEANDTYPES); + } else { + lexer.token("types"); + group.setTypeMode(StructureMapGroupTypeMode.TYPES); + } + } + group.setName(lexer.take()); + if (lexer.hasToken("(")) { + newFmt = true; + lexer.take(); + while (!lexer.hasToken(")")) { + parseInput(group, lexer, true); + if (lexer.hasToken(",")) + lexer.token(","); + } + lexer.take(); + } + if (lexer.hasToken("extends")) { + lexer.next(); + group.setExtends(lexer.take()); + } + if (newFmt) { + if (lexer.hasToken("<")) { + lexer.token("<"); + lexer.token("<"); + if (lexer.hasToken("types")) { + group.setTypeMode(StructureMapGroupTypeMode.TYPES); + lexer.token("types"); + } else { + lexer.token("type"); + lexer.token("+"); + group.setTypeMode(StructureMapGroupTypeMode.TYPEANDTYPES); + } + lexer.token(">"); + lexer.token(">"); + } + lexer.token("{"); + } + if (newFmt) { + while (!lexer.hasToken("}")) { + if (lexer.done()) + throw lexer.error("premature termination expecting 'endgroup'"); + parseRule(result, group.getRule(), lexer, true); + } + } else { + while (lexer.hasToken("input")) + parseInput(group, lexer, false); + while (!lexer.hasToken("endgroup")) { + if (lexer.done()) + throw lexer.error("premature termination expecting 'endgroup'"); + parseRule(result, group.getRule(), lexer, false); + } + } + lexer.next(); + if (newFmt && lexer.hasToken(";")) + lexer.next(); + } + + private void parseInput(StructureMapGroupComponent group, FHIRLexer lexer, boolean newFmt) throws FHIRException { + StructureMapGroupInputComponent input = group.addInput(); + if (newFmt) { + input.setMode(StructureMapInputMode.fromCode(lexer.take())); + } else + lexer.token("input"); + input.setName(lexer.take()); + if (lexer.hasToken(":")) { + lexer.token(":"); + input.setType(lexer.take()); + } + if (!newFmt) { + lexer.token("as"); + input.setMode(StructureMapInputMode.fromCode(lexer.take())); + input.setDocumentation(lexer.getFirstComment()); + lexer.skipToken(";"); + } + } + + private void parseRule(StructureMap map, List list, FHIRLexer lexer, boolean newFmt) throws FHIRException { + StructureMapGroupRuleComponent rule = new StructureMapGroupRuleComponent(); + if (!newFmt) { + rule.setName(lexer.takeDottedToken()); + lexer.token(":"); + lexer.token("for"); + } else { + rule.setDocumentation(lexer.getFirstComment()); + } + list.add(rule); + boolean done = false; + while (!done) { + parseSource(rule, lexer); + done = !lexer.hasToken(","); + if (!done) + lexer.next(); + } + if ((newFmt && lexer.hasToken("->")) || (!newFmt && lexer.hasToken("make"))) { + lexer.token(newFmt ? "->" : "make"); + done = false; + while (!done) { + parseTarget(rule, lexer); + done = !lexer.hasToken(","); + if (!done) + lexer.next(); + } + } + if (lexer.hasToken("then")) { + lexer.token("then"); + if (lexer.hasToken("{")) { + lexer.token("{"); + while (!lexer.hasToken("}")) { + if (lexer.done()) + throw lexer.error("premature termination expecting '}' in nested group"); + parseRule(map, rule.getRule(), lexer, newFmt); + } + lexer.token("}"); + } else { + done = false; + while (!done) { + parseRuleReference(rule, lexer); + done = !lexer.hasToken(","); + if (!done) + lexer.next(); + } + } + } + if (!rule.hasDocumentation() && lexer.hasComments()) { + rule.setDocumentation(lexer.getFirstComment()); + } + if (isSimpleSyntax(rule)) { + rule.getSourceFirstRep().setVariable(AUTO_VAR_NAME); + rule.getTargetFirstRep().setVariable(AUTO_VAR_NAME); + rule.getTargetFirstRep().setTransform(StructureMapTransform.CREATE); // with no parameter - e.g. imply what is to be created + // no dependencies - imply what is to be done based on types + } + if (newFmt) { + if (lexer.isConstant()) { + if (lexer.isStringConstant()) { + rule.setName(lexer.readConstant("ruleName")); + } else { + rule.setName(lexer.take()); + } + } else { + if (rule.getSource().size() != 1 || !rule.getSourceFirstRep().hasElement()) + throw lexer.error("Complex rules must have an explicit name"); + if (rule.getSourceFirstRep().hasType()) + rule.setName(rule.getSourceFirstRep().getElement() + "-" + rule.getSourceFirstRep().getType()); + else + rule.setName(rule.getSourceFirstRep().getElement()); + } + lexer.token(";"); + } + } + + private boolean isSimpleSyntax(StructureMapGroupRuleComponent rule) { + return + (rule.getSource().size() == 1 && rule.getSourceFirstRep().hasContext() && rule.getSourceFirstRep().hasElement() && !rule.getSourceFirstRep().hasVariable()) && + (rule.getTarget().size() == 1 && rule.getTargetFirstRep().hasContext() && rule.getTargetFirstRep().hasElement() && !rule.getTargetFirstRep().hasVariable() && !rule.getTargetFirstRep().hasParameter()) && + (rule.getDependent().size() == 0 && rule.getRule().size() == 0); + } + + private void parseRuleReference(StructureMapGroupRuleComponent rule, FHIRLexer lexer) throws FHIRLexerException { + StructureMapGroupRuleDependentComponent ref = rule.addDependent(); + ref.setName(lexer.take()); + lexer.token("("); + boolean done = false; + while (!done) { + ref.addVariable(lexer.take()); + done = !lexer.hasToken(","); + if (!done) + lexer.next(); + } + lexer.token(")"); + } + + private void parseSource(StructureMapGroupRuleComponent rule, FHIRLexer lexer) throws FHIRException { + StructureMapGroupRuleSourceComponent source = rule.addSource(); + source.setContext(lexer.take()); + if (source.getContext().equals("search") && lexer.hasToken("(")) { + source.setContext("@search"); + lexer.take(); + ExpressionNode node = fpe.parse(lexer); + source.setUserData(MAP_SEARCH_EXPRESSION, node); + source.setElement(node.toString()); + lexer.token(")"); + } else if (lexer.hasToken(".")) { + lexer.token("."); + source.setElement(lexer.take()); + } + if (lexer.hasToken(":")) { + // type and cardinality + lexer.token(":"); + source.setType(lexer.takeDottedToken()); + if (!lexer.hasToken("as", "first", "last", "not_first", "not_last", "only_one", "default")) { + source.setMin(lexer.takeInt()); + lexer.token(".."); + source.setMax(lexer.take()); + } + } + if (lexer.hasToken("default")) { + lexer.token("default"); + source.setDefaultValue(new StringType(lexer.readConstant("default value"))); + } + if (Utilities.existsInList(lexer.getCurrent(), "first", "last", "not_first", "not_last", "only_one")) + source.setListMode(StructureMapSourceListMode.fromCode(lexer.take())); + + if (lexer.hasToken("as")) { + lexer.take(); + source.setVariable(lexer.take()); + } + if (lexer.hasToken("where")) { + lexer.take(); + ExpressionNode node = fpe.parse(lexer); + source.setUserData(MAP_WHERE_EXPRESSION, node); + source.setCondition(node.toString()); + } + if (lexer.hasToken("check")) { + lexer.take(); + ExpressionNode node = fpe.parse(lexer); + source.setUserData(MAP_WHERE_CHECK, node); + source.setCheck(node.toString()); + } + if (lexer.hasToken("log")) { + lexer.take(); + ExpressionNode node = fpe.parse(lexer); + source.setUserData(MAP_WHERE_CHECK, node); + source.setLogMessage(node.toString()); + } + } + + private void parseTarget(StructureMapGroupRuleComponent rule, FHIRLexer lexer) throws FHIRException { + StructureMapGroupRuleTargetComponent target = rule.addTarget(); + String start = lexer.take(); + if (lexer.hasToken(".")) { + target.setContext(start); + target.setContextType(StructureMapContextType.VARIABLE); + start = null; + lexer.token("."); + target.setElement(lexer.take()); + } + String name; + boolean isConstant = false; + if (lexer.hasToken("=")) { + if (start != null) + target.setContext(start); + lexer.token("="); + isConstant = lexer.isConstant(); + name = lexer.take(); + } else + name = start; + + if ("(".equals(name)) { + // inline fluentpath expression + target.setTransform(StructureMapTransform.EVALUATE); + ExpressionNode node = fpe.parse(lexer); + target.setUserData(MAP_EXPRESSION, node); + target.addParameter().setValue(new StringType(node.toString())); + lexer.token(")"); + } else if (lexer.hasToken("(")) { + target.setTransform(StructureMapTransform.fromCode(name)); + lexer.token("("); + if (target.getTransform() == StructureMapTransform.EVALUATE) { + parseParameter(target, lexer); + lexer.token(","); + ExpressionNode node = fpe.parse(lexer); + target.setUserData(MAP_EXPRESSION, node); + target.addParameter().setValue(new StringType(node.toString())); + } else { + while (!lexer.hasToken(")")) { + parseParameter(target, lexer); + if (!lexer.hasToken(")")) + lexer.token(","); + } + } + lexer.token(")"); + } else if (name != null) { + target.setTransform(StructureMapTransform.COPY); + if (!isConstant) { + String id = name; + while (lexer.hasToken(".")) { + id = id + lexer.take() + lexer.take(); + } + target.addParameter().setValue(new IdType(id)); + } else + target.addParameter().setValue(readConstant(name, lexer)); + } + if (lexer.hasToken("as")) { + lexer.take(); + target.setVariable(lexer.take()); + } + while (Utilities.existsInList(lexer.getCurrent(), "first", "last", "share", "collate")) { + if (lexer.getCurrent().equals("share")) { + target.addListMode(StructureMapTargetListMode.SHARE); + lexer.next(); + target.setListRuleId(lexer.take()); + } else { + if (lexer.getCurrent().equals("first")) + target.addListMode(StructureMapTargetListMode.FIRST); + else + target.addListMode(StructureMapTargetListMode.LAST); + lexer.next(); + } + } + } + + + private void parseParameter(StructureMapGroupRuleTargetComponent target, FHIRLexer lexer) throws FHIRLexerException, FHIRFormatError { + if (!lexer.isConstant()) { + target.addParameter().setValue(new IdType(lexer.take())); + } else if (lexer.isStringConstant()) + target.addParameter().setValue(new StringType(lexer.readConstant("??"))); + else { + target.addParameter().setValue(readConstant(lexer.take(), lexer)); + } + } + + private DataType readConstant(String s, FHIRLexer lexer) throws FHIRLexerException { + if (Utilities.isInteger(s)) + return new IntegerType(s); + else if (Utilities.isDecimal(s, false)) + return new DecimalType(s); + else if (Utilities.existsInList(s, "true", "false")) + return new BooleanType(s.equals("true")); + else + return new StringType(lexer.processConstant(s)); + } + + public StructureDefinition getTargetType(StructureMap map) throws FHIRException { + boolean found = false; + StructureDefinition res = null; + for (StructureMapStructureComponent uses : map.getStructure()) { + if (uses.getMode() == StructureMapModelMode.TARGET) { + if (found) + throw new FHIRException("Multiple targets found in map " + map.getUrl()); + found = true; + res = worker.fetchResource(StructureDefinition.class, uses.getUrl()); + if (res == null) + throw new FHIRException("Unable to find " + uses.getUrl() + " referenced from map " + map.getUrl()); + } + } + if (res == null) + throw new FHIRException("No targets found in map " + map.getUrl()); + return res; + } + + private void log(String cnt) { + if (getServices() != null) + getServices().log(cnt); + else + System.out.println(cnt); + } + + /** + * Given an item, return all the children that conform to the pattern described in name + *

+ * Possible patterns: + * - a simple name (which may be the base of a name with [] e.g. value[x]) + * - a name with a type replacement e.g. valueCodeableConcept + * - * which means all children + * - ** which means all descendents + * + * @param item + * @param name + * @param result + * @throws FHIRException + */ + protected void getChildrenByName(Base item, String name, List result) throws FHIRException { + for (Base v : item.listChildrenByName(name, true)) + if (v != null) + result.add(v); + } + + public void transform(Object appInfo, Base source, StructureMap map, Base target) throws FHIRException { + TransformContext context = new TransformContext(appInfo); + log("Start Transform " + map.getUrl()); + StructureMapGroupComponent g = map.getGroup().get(0); + + Variables vars = new Variables(); + vars.add(VariableMode.INPUT, getInputName(g, StructureMapInputMode.SOURCE, "source"), source); + if (target != null) + vars.add(VariableMode.OUTPUT, getInputName(g, StructureMapInputMode.TARGET, "target"), target); + else if (getInputName(g, StructureMapInputMode.TARGET, null) != null) { + String type = getInputType(g, StructureMapInputMode.TARGET); + throw new Error("not handled yet: creating a type of " + type); + } + + executeGroup("", context, map, vars, g, true); + if (target instanceof Element) + ((Element) target).sort(); + } + + private String getInputType(StructureMapGroupComponent g, StructureMapInputMode mode) { + String type = null; + for (StructureMapGroupInputComponent inp : g.getInput()) { + if (inp.getMode() == mode) + if (type != null) + throw new DefinitionException("This engine does not support multiple source inputs"); + else + type = inp.getType(); + } + return type; + } + + private String getInputName(StructureMapGroupComponent g, StructureMapInputMode mode, String def) throws DefinitionException { + String name = null; + for (StructureMapGroupInputComponent inp : g.getInput()) { + if (inp.getMode() == mode) + if (name != null) + throw new DefinitionException("This engine does not support multiple source inputs"); + else + name = inp.getName(); + } + return name == null ? def : name; + } + + private void executeGroup(String indent, TransformContext context, StructureMap map, Variables vars, StructureMapGroupComponent group, boolean atRoot) throws FHIRException { + log(indent + "Group : " + group.getName() + "; vars = " + vars.summary()); + // todo: check inputs + if (group.hasExtends()) { + ResolvedGroup rg = resolveGroupReference(map, group, group.getExtends()); + executeGroup(indent + " ", context, rg.targetMap, vars, rg.target, false); + } + + for (StructureMapGroupRuleComponent r : group.getRule()) { + executeRule(indent + " ", context, map, vars, group, r, atRoot); + } + } + + private void executeRule(String indent, TransformContext context, StructureMap map, Variables vars, StructureMapGroupComponent group, StructureMapGroupRuleComponent rule, boolean atRoot) throws FHIRException { + log(indent + "rule : " + rule.getName() + "; vars = " + vars.summary()); + Variables srcVars = vars.copy(); + if (rule.getSource().size() != 1) + throw new FHIRException("Rule \"" + rule.getName() + "\": not handled yet"); + List source = processSource(rule.getName(), context, srcVars, rule.getSource().get(0), map.getUrl(), indent); + if (source != null) { + for (Variables v : source) { + for (StructureMapGroupRuleTargetComponent t : rule.getTarget()) { + processTarget(rule.getName(), context, v, map, group, t, rule.getSource().size() == 1 ? rule.getSourceFirstRep().getVariable() : null, atRoot, vars); + } + if (rule.hasRule()) { + for (StructureMapGroupRuleComponent childrule : rule.getRule()) { + executeRule(indent + " ", context, map, v, group, childrule, false); + } + } else if (rule.hasDependent()) { + for (StructureMapGroupRuleDependentComponent dependent : rule.getDependent()) { + executeDependency(indent + " ", context, map, v, group, dependent); + } + } else if (rule.getSource().size() == 1 && rule.getSourceFirstRep().hasVariable() && rule.getTarget().size() == 1 && rule.getTargetFirstRep().hasVariable() && rule.getTargetFirstRep().getTransform() == StructureMapTransform.CREATE && !rule.getTargetFirstRep().hasParameter()) { + // simple inferred, map by type + System.out.println(v.summary()); + Base src = v.get(VariableMode.INPUT, rule.getSourceFirstRep().getVariable()); + Base tgt = v.get(VariableMode.OUTPUT, rule.getTargetFirstRep().getVariable()); + String srcType = src.fhirType(); + String tgtType = tgt.fhirType(); + ResolvedGroup defGroup = resolveGroupByTypes(map, rule.getName(), group, srcType, tgtType); + Variables vdef = new Variables(); + vdef.add(VariableMode.INPUT, defGroup.target.getInput().get(0).getName(), src); + vdef.add(VariableMode.OUTPUT, defGroup.target.getInput().get(1).getName(), tgt); + executeGroup(indent + " ", context, defGroup.targetMap, vdef, defGroup.target, false); + } + } + } + } + + private void executeDependency(String indent, TransformContext context, StructureMap map, Variables vin, StructureMapGroupComponent group, StructureMapGroupRuleDependentComponent dependent) throws FHIRException { + ResolvedGroup rg = resolveGroupReference(map, group, dependent.getName()); + + if (rg.target.getInput().size() != dependent.getVariable().size()) { + throw new FHIRException("Rule '" + dependent.getName() + "' has " + rg.target.getInput().size() + " but the invocation has " + dependent.getVariable().size() + " variables"); + } + Variables v = new Variables(); + for (int i = 0; i < rg.target.getInput().size(); i++) { + StructureMapGroupInputComponent input = rg.target.getInput().get(i); + StringType rdp = dependent.getVariable().get(i); + String var = rdp.asStringValue(); + VariableMode mode = input.getMode() == StructureMapInputMode.SOURCE ? VariableMode.INPUT : VariableMode.OUTPUT; + Base vv = vin.get(mode, var); + if (vv == null && mode == VariableMode.INPUT) //* once source, always source. but target can be treated as source at user convenient + vv = vin.get(VariableMode.OUTPUT, var); + if (vv == null) + throw new FHIRException("Rule '" + dependent.getName() + "' " + mode.toString() + " variable '" + input.getName() + "' named as '" + var + "' has no value (vars = " + vin.summary() + ")"); + v.add(mode, input.getName(), vv); + } + executeGroup(indent + " ", context, rg.targetMap, v, rg.target, false); + } + + private String determineTypeFromSourceType(StructureMap map, StructureMapGroupComponent source, Base base, String[] types) throws FHIRException { + String type = base.fhirType(); + String kn = "type^" + type; + if (source.hasUserData(kn)) + return source.getUserString(kn); + + ResolvedGroup res = new ResolvedGroup(); + res.targetMap = null; + res.target = null; + for (StructureMapGroupComponent grp : map.getGroup()) { + if (matchesByType(map, grp, type)) { + if (res.targetMap == null) { + res.targetMap = map; + res.target = grp; + } else + throw new FHIRException("Multiple possible matches looking for default rule for '" + type + "'"); + } + } + if (res.targetMap != null) { + String result = getActualType(res.targetMap, res.target.getInput().get(1).getType()); + source.setUserData(kn, result); + return result; + } + + for (UriType imp : map.getImport()) { + List impMapList = findMatchingMaps(imp.getValue()); + if (impMapList.size() == 0) + throw new FHIRException("Unable to find map(s) for " + imp.getValue()); + for (StructureMap impMap : impMapList) { + if (!impMap.getUrl().equals(map.getUrl())) { + for (StructureMapGroupComponent grp : impMap.getGroup()) { + if (matchesByType(impMap, grp, type)) { + if (res.targetMap == null) { + res.targetMap = impMap; + res.target = grp; + } else + throw new FHIRException("Multiple possible matches for default rule for '" + type + "' in " + res.targetMap.getUrl() + " (" + res.target.getName() + ") and " + impMap.getUrl() + " (" + grp.getName() + ")"); + } + } + } + } + } + if (res.target == null) + throw new FHIRException("No matches found for default rule for '" + type + "' from " + map.getUrl()); + String result = getActualType(res.targetMap, res.target.getInput().get(1).getType()); // should be .getType, but R2... + source.setUserData(kn, result); + return result; + } + + private List findMatchingMaps(String value) { + List res = new ArrayList(); + if (value.contains("*")) { + for (StructureMap sm : worker.listTransforms()) { + if (urlMatches(value, sm.getUrl())) { + res.add(sm); + } + } + } else { + StructureMap sm = worker.getTransform(value); + if (sm != null) + res.add(sm); + } + Set check = new HashSet(); + for (StructureMap sm : res) { + if (check.contains(sm.getUrl())) + throw new Error("duplicate"); + else + check.add(sm.getUrl()); + } + return res; + } + + private boolean urlMatches(String mask, String url) { + return url.length() > mask.length() && url.startsWith(mask.substring(0, mask.indexOf("*"))) && url.endsWith(mask.substring(mask.indexOf("*") + 1)); + } + + private ResolvedGroup resolveGroupByTypes(StructureMap map, String ruleid, StructureMapGroupComponent source, String srcType, String tgtType) throws FHIRException { + String kn = "types^" + srcType + ":" + tgtType; + if (source.hasUserData(kn)) + return (ResolvedGroup) source.getUserData(kn); + + ResolvedGroup res = new ResolvedGroup(); + res.targetMap = null; + res.target = null; + for (StructureMapGroupComponent grp : map.getGroup()) { + if (matchesByType(map, grp, srcType, tgtType)) { + if (res.targetMap == null) { + res.targetMap = map; + res.target = grp; + } else + throw new FHIRException("Multiple possible matches looking for rule for '" + srcType + "/" + tgtType + "', from rule '" + ruleid + "'"); + } + } + if (res.targetMap != null) { + source.setUserData(kn, res); + return res; + } + + for (UriType imp : map.getImport()) { + List impMapList = findMatchingMaps(imp.getValue()); + if (impMapList.size() == 0) + throw new FHIRException("Unable to find map(s) for " + imp.getValue()); + for (StructureMap impMap : impMapList) { + if (!impMap.getUrl().equals(map.getUrl())) { + for (StructureMapGroupComponent grp : impMap.getGroup()) { + if (matchesByType(impMap, grp, srcType, tgtType)) { + if (res.targetMap == null) { + res.targetMap = impMap; + res.target = grp; + } else + throw new FHIRException("Multiple possible matches for rule for '" + srcType + "/" + tgtType + "' in " + res.targetMap.getUrl() + " and " + impMap.getUrl() + ", from rule '" + ruleid + "'"); + } + } + } + } + } + if (res.target == null) + throw new FHIRException("No matches found for rule for '" + srcType + " to " + tgtType + "' from " + map.getUrl() + ", from rule '" + ruleid + "'"); + source.setUserData(kn, res); + return res; + } + + + private boolean matchesByType(StructureMap map, StructureMapGroupComponent grp, String type) throws FHIRException { + if (grp.getTypeMode() != StructureMapGroupTypeMode.TYPEANDTYPES) + return false; + if (grp.getInput().size() != 2 || grp.getInput().get(0).getMode() != StructureMapInputMode.SOURCE || grp.getInput().get(1).getMode() != StructureMapInputMode.TARGET) + return false; + return matchesType(map, type, grp.getInput().get(0).getType()); + } + + private boolean matchesByType(StructureMap map, StructureMapGroupComponent grp, String srcType, String tgtType) throws FHIRException { + if (!grp.hasTypeMode()) + return false; + if (grp.getInput().size() != 2 || grp.getInput().get(0).getMode() != StructureMapInputMode.SOURCE || grp.getInput().get(1).getMode() != StructureMapInputMode.TARGET) + return false; + if (!grp.getInput().get(0).hasType() || !grp.getInput().get(1).hasType()) + return false; + return matchesType(map, srcType, grp.getInput().get(0).getType()) && matchesType(map, tgtType, grp.getInput().get(1).getType()); + } + + private boolean matchesType(StructureMap map, String actualType, String statedType) throws FHIRException { + // check the aliases + for (StructureMapStructureComponent imp : map.getStructure()) { + if (imp.hasAlias() && statedType.equals(imp.getAlias())) { + StructureDefinition sd = worker.fetchResource(StructureDefinition.class, imp.getUrl()); + if (sd != null) + statedType = sd.getType(); + break; + } + } + + if (Utilities.isAbsoluteUrl(actualType)) { + StructureDefinition sd = worker.fetchResource(StructureDefinition.class, actualType); + if (sd != null) + actualType = sd.getType(); + } + if (Utilities.isAbsoluteUrl(statedType)) { + StructureDefinition sd = worker.fetchResource(StructureDefinition.class, statedType); + if (sd != null) + statedType = sd.getType(); + } + return actualType.equals(statedType); + } + + private String getActualType(StructureMap map, String statedType) throws FHIRException { + // check the aliases + for (StructureMapStructureComponent imp : map.getStructure()) { + if (imp.hasAlias() && statedType.equals(imp.getAlias())) { + StructureDefinition sd = worker.fetchResource(StructureDefinition.class, imp.getUrl()); + if (sd == null) + throw new FHIRException("Unable to resolve structure " + imp.getUrl()); + return sd.getId(); // should be sd.getType(), but R2... + } + } + return statedType; + } + + + private ResolvedGroup resolveGroupReference(StructureMap map, StructureMapGroupComponent source, String name) throws FHIRException { + String kn = "ref^" + name; + if (source.hasUserData(kn)) + return (ResolvedGroup) source.getUserData(kn); + + ResolvedGroup res = new ResolvedGroup(); + res.targetMap = null; + res.target = null; + for (StructureMapGroupComponent grp : map.getGroup()) { + if (grp.getName().equals(name)) { + if (res.targetMap == null) { + res.targetMap = map; + res.target = grp; + } else + throw new FHIRException("Multiple possible matches for rule '" + name + "'"); + } + } + if (res.targetMap != null) { + source.setUserData(kn, res); + return res; + } + + for (UriType imp : map.getImport()) { + List impMapList = findMatchingMaps(imp.getValue()); + if (impMapList.size() == 0) + throw new FHIRException("Unable to find map(s) for " + imp.getValue()); + for (StructureMap impMap : impMapList) { + if (!impMap.getUrl().equals(map.getUrl())) { + for (StructureMapGroupComponent grp : impMap.getGroup()) { + if (grp.getName().equals(name)) { + if (res.targetMap == null) { + res.targetMap = impMap; + res.target = grp; + } else + throw new FHIRException("Multiple possible matches for rule group '" + name + "' in " + + res.targetMap.getUrl() + "#" + res.target.getName() + " and " + + impMap.getUrl() + "#" + grp.getName()); + } + } + } + } + } + if (res.target == null) + throw new FHIRException("No matches found for rule '" + name + "'. Reference found in " + map.getUrl()); + source.setUserData(kn, res); + return res; + } + + private List processSource(String ruleId, TransformContext context, Variables vars, StructureMapGroupRuleSourceComponent src, String pathForErrors, String indent) throws FHIRException { + List items; + if (src.getContext().equals("@search")) { + ExpressionNode expr = (ExpressionNode) src.getUserData(MAP_SEARCH_EXPRESSION); + if (expr == null) { + expr = fpe.parse(src.getElement()); + src.setUserData(MAP_SEARCH_EXPRESSION, expr); + } + String search = fpe.evaluateToString(vars, null, null, new StringType(), expr); // string is a holder of nothing to ensure that variables are processed correctly + items = services.performSearch(context.getAppInfo(), search); + } else { + items = new ArrayList(); + Base b = vars.get(VariableMode.INPUT, src.getContext()); + if (b == null) + throw new FHIRException("Unknown input variable " + src.getContext() + " in " + pathForErrors + " rule " + ruleId + " (vars = " + vars.summary() + ")"); + + if (!src.hasElement()) + items.add(b); + else { + getChildrenByName(b, src.getElement(), items); + if (items.size() == 0 && src.hasDefaultValue()) + items.add(src.getDefaultValue()); + } + } + + if (src.hasType()) { + List remove = new ArrayList(); + for (Base item : items) { + if (item != null && !isType(item, src.getType())) { + remove.add(item); + } + } + items.removeAll(remove); + } + + if (src.hasCondition()) { + ExpressionNode expr = (ExpressionNode) src.getUserData(MAP_WHERE_EXPRESSION); + if (expr == null) { + expr = fpe.parse(src.getCondition()); + // fpe.check(context.appInfo, ??, ??, expr) + src.setUserData(MAP_WHERE_EXPRESSION, expr); + } + List remove = new ArrayList(); + for (Base item : items) { + if (!fpe.evaluateToBoolean(vars, null, null, item, expr)) { + log(indent + " condition [" + src.getCondition() + "] for " + item.toString() + " : false"); + remove.add(item); + } else + log(indent + " condition [" + src.getCondition() + "] for " + item.toString() + " : true"); + } + items.removeAll(remove); + } + + if (src.hasCheck()) { + ExpressionNode expr = (ExpressionNode) src.getUserData(MAP_WHERE_CHECK); + if (expr == null) { + expr = fpe.parse(src.getCheck()); + // fpe.check(context.appInfo, ??, ??, expr) + src.setUserData(MAP_WHERE_CHECK, expr); + } + List remove = new ArrayList(); + for (Base item : items) { + if (!fpe.evaluateToBoolean(vars, null, null, item, expr)) + throw new FHIRException("Rule \"" + ruleId + "\": Check condition failed"); + } + } + + if (src.hasLogMessage()) { + ExpressionNode expr = (ExpressionNode) src.getUserData(MAP_WHERE_LOG); + if (expr == null) { + expr = fpe.parse(src.getLogMessage()); + // fpe.check(context.appInfo, ??, ??, expr) + src.setUserData(MAP_WHERE_LOG, expr); + } + CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); + for (Base item : items) + b.appendIfNotNull(fpe.evaluateToString(vars, null, null, item, expr)); + if (b.length() > 0) + services.log(b.toString()); + } + + + if (src.hasListMode() && !items.isEmpty()) { + switch (src.getListMode()) { + case FIRST: + Base bt = items.get(0); + items.clear(); + items.add(bt); + break; + case NOTFIRST: + if (items.size() > 0) + items.remove(0); + break; + case LAST: + bt = items.get(items.size() - 1); + items.clear(); + items.add(bt); + break; + case NOTLAST: + if (items.size() > 0) + items.remove(items.size() - 1); + break; + case ONLYONE: + if (items.size() > 1) + throw new FHIRException("Rule \"" + ruleId + "\": Check condition failed: the collection has more than one item"); + break; + case NULL: + } + } + List result = new ArrayList(); + for (Base r : items) { + Variables v = vars.copy(); + if (src.hasVariable()) + v.add(VariableMode.INPUT, src.getVariable(), r); + result.add(v); + } + return result; + } + + + private boolean isType(Base item, String type) { + return type.equals(item.fhirType()); + } + + private void processTarget(String ruleId, TransformContext context, Variables vars, StructureMap map, StructureMapGroupComponent group, StructureMapGroupRuleTargetComponent tgt, String srcVar, boolean atRoot, Variables sharedVars) throws FHIRException { + Base dest = null; + if (tgt.hasContext()) { + dest = vars.get(VariableMode.OUTPUT, tgt.getContext()); + if (dest == null) + throw new FHIRException("Rule \"" + ruleId + "\": target context not known: " + tgt.getContext()); + if (!tgt.hasElement()) + throw new FHIRException("Rule \"" + ruleId + "\": Not supported yet"); + } + Base v = null; + if (tgt.hasTransform()) { + v = runTransform(ruleId, context, map, group, tgt, vars, dest, tgt.getElement(), srcVar, atRoot); + if (v != null && dest != null) + v = dest.setProperty(tgt.getElement().hashCode(), tgt.getElement(), v); // reset v because some implementations may have to rewrite v when setting the value + } else if (dest != null) { + if (tgt.hasListMode(StructureMapTargetListMode.SHARE)) { + v = sharedVars.get(VariableMode.SHARED, tgt.getListRuleId()); + if (v == null) { + v = dest.makeProperty(tgt.getElement().hashCode(), tgt.getElement()); + sharedVars.add(VariableMode.SHARED, tgt.getListRuleId(), v); + } + } else { + v = dest.makeProperty(tgt.getElement().hashCode(), tgt.getElement()); + } + } + if (tgt.hasVariable() && v != null) + vars.add(VariableMode.OUTPUT, tgt.getVariable(), v); + } + + private Base runTransform(String ruleId, TransformContext context, StructureMap map, StructureMapGroupComponent group, StructureMapGroupRuleTargetComponent tgt, Variables vars, Base dest, String element, String srcVar, boolean root) throws FHIRException { + try { + switch (tgt.getTransform()) { + case CREATE: + String tn; + if (tgt.getParameter().isEmpty()) { + // we have to work out the type. First, we see if there is a single type for the target. If there is, we use that + String[] types = dest.getTypesForProperty(element.hashCode(), element); + if (types.length == 1 && !"*".equals(types[0]) && !types[0].equals("Resource")) + tn = types[0]; + else if (srcVar != null) { + tn = determineTypeFromSourceType(map, group, vars.get(VariableMode.INPUT, srcVar), types); + } else + throw new Error("Cannot determine type implicitly because there is no single input variable"); + } else { + tn = getParamStringNoNull(vars, tgt.getParameter().get(0), tgt.toString()); + // ok, now we resolve the type name against the import statements + for (StructureMapStructureComponent uses : map.getStructure()) { + if (uses.getMode() == StructureMapModelMode.TARGET && uses.hasAlias() && tn.equals(uses.getAlias())) { + tn = uses.getUrl(); + break; + } + } + } + Base res = services != null ? services.createType(context.getAppInfo(), tn) : ResourceFactory.createResourceOrType(tn); + if (res.isResource() && !res.fhirType().equals("Parameters")) { +// res.setIdBase(tgt.getParameter().size() > 1 ? getParamString(vars, tgt.getParameter().get(0)) : UUID.randomUUID().toString().toLowerCase()); + if (services != null) + res = services.createResource(context.getAppInfo(), res, root); + } + if (tgt.hasUserData("profile")) + res.setUserData("profile", tgt.getUserData("profile")); + return res; + case COPY: + return getParam(vars, tgt.getParameter().get(0)); + case EVALUATE: + ExpressionNode expr = (ExpressionNode) tgt.getUserData(MAP_EXPRESSION); + if (expr == null) { + expr = fpe.parse(getParamStringNoNull(vars, tgt.getParameter().get(0), tgt.toString())); + tgt.setUserData(MAP_WHERE_EXPRESSION, expr); + } + List v = fpe.evaluate(vars, null, null, tgt.getParameter().size() == 2 ? getParam(vars, tgt.getParameter().get(0)) : new BooleanType(false), expr); + if (v.size() == 0) + return null; + else if (v.size() != 1) + throw new FHIRException("Rule \"" + ruleId + "\": Evaluation of " + expr.toString() + " returned " + v.size() + " objects"); + else + return v.get(0); + + case TRUNCATE: + String src = getParamString(vars, tgt.getParameter().get(0)); + String len = getParamStringNoNull(vars, tgt.getParameter().get(1), tgt.toString()); + if (Utilities.isInteger(len)) { + int l = Integer.parseInt(len); + if (src.length() > l) + src = src.substring(0, l); + } + return new StringType(src); + case ESCAPE: + throw new Error("Rule \"" + ruleId + "\": Transform " + tgt.getTransform().toCode() + " not supported yet"); + case CAST: + src = getParamString(vars, tgt.getParameter().get(0)); + if (tgt.getParameter().size() == 1) + throw new FHIRException("Implicit type parameters on cast not yet supported"); + String t = getParamString(vars, tgt.getParameter().get(1)); + if (t.equals("string")) + return new StringType(src); + else + throw new FHIRException("cast to " + t + " not yet supported"); + case APPEND: + StringBuilder sb = new StringBuilder(getParamString(vars, tgt.getParameter().get(0))); + for (int i = 1; i < tgt.getParameter().size(); i++) + sb.append(getParamString(vars, tgt.getParameter().get(i))); + return new StringType(sb.toString()); + case TRANSLATE: + return translate(context, map, vars, tgt.getParameter()); + case REFERENCE: + Base b = getParam(vars, tgt.getParameter().get(0)); + if (b == null) + throw new FHIRException("Rule \"" + ruleId + "\": Unable to find parameter " + ((IdType) tgt.getParameter().get(0).getValue()).asStringValue()); + if (!b.isResource()) + throw new FHIRException("Rule \"" + ruleId + "\": Transform engine cannot point at an element of type " + b.fhirType()); + else { + String id = b.getIdBase(); + if (id == null) { + id = UUID.randomUUID().toString().toLowerCase(); + b.setIdBase(id); + } + return new StringType(b.fhirType() + "/" + id); + } + case DATEOP: + throw new Error("Rule \"" + ruleId + "\": Transform " + tgt.getTransform().toCode() + " not supported yet"); + case UUID: + return new IdType(UUID.randomUUID().toString()); + case POINTER: + b = getParam(vars, tgt.getParameter().get(0)); + if (b instanceof Resource) + return new UriType("urn:uuid:" + ((Resource) b).getId()); + else + throw new FHIRException("Rule \"" + ruleId + "\": Transform engine cannot point at an element of type " + b.fhirType()); + case CC: + CodeableConcept cc = new CodeableConcept(); + cc.addCoding(buildCoding(getParamStringNoNull(vars, tgt.getParameter().get(0), tgt.toString()), getParamStringNoNull(vars, tgt.getParameter().get(1), tgt.toString()))); + return cc; + case C: + Coding c = buildCoding(getParamStringNoNull(vars, tgt.getParameter().get(0), tgt.toString()), getParamStringNoNull(vars, tgt.getParameter().get(1), tgt.toString())); + return c; + default: + throw new Error("Rule \"" + ruleId + "\": Transform Unknown: " + tgt.getTransform().toCode()); + } + } catch (Exception e) { + throw new FHIRException("Exception executing transform " + tgt.toString() + " on Rule \"" + ruleId + "\": " + e.getMessage(), e); + } + } + + + private Coding buildCoding(String uri, String code) throws FHIRException { + // if we can get this as a valueSet, we will + String system = null; + String display = null; + ValueSet vs = Utilities.noString(uri) ? null : worker.fetchResourceWithException(ValueSet.class, uri); + if (vs != null) { + ValueSetExpansionOutcome vse = worker.expandVS(vs, true, false); + if (vse.getError() != null) + throw new FHIRException(vse.getError()); + CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); + for (ValueSetExpansionContainsComponent t : vse.getValueset().getExpansion().getContains()) { + if (t.hasCode()) + b.append(t.getCode()); + if (code.equals(t.getCode()) && t.hasSystem()) { + system = t.getSystem(); + display = t.getDisplay(); + break; + } + if (code.equalsIgnoreCase(t.getDisplay()) && t.hasSystem()) { + system = t.getSystem(); + display = t.getDisplay(); + break; + } + } + if (system == null) + throw new FHIRException("The code '" + code + "' is not in the value set '" + uri + "' (valid codes: " + b.toString() + "; also checked displays)"); + } else + system = uri; + ValidationResult vr = worker.validateCode(terminologyServiceOptions, system, code, null); + if (vr != null && vr.getDisplay() != null) + display = vr.getDisplay(); + return new Coding().setSystem(system).setCode(code).setDisplay(display); + } + + + private String getParamStringNoNull(Variables vars, StructureMapGroupRuleTargetParameterComponent parameter, String message) throws FHIRException { + Base b = getParam(vars, parameter); + if (b == null) + throw new FHIRException("Unable to find a value for " + parameter.toString() + ". Context: " + message); + if (!b.hasPrimitiveValue()) + throw new FHIRException("Found a value for " + parameter.toString() + ", but it has a type of " + b.fhirType() + " and cannot be treated as a string. Context: " + message); + return b.primitiveValue(); + } + + private String getParamString(Variables vars, StructureMapGroupRuleTargetParameterComponent parameter) throws DefinitionException { + Base b = getParam(vars, parameter); + if (b == null || !b.hasPrimitiveValue()) + return null; + return b.primitiveValue(); + } + + + private Base getParam(Variables vars, StructureMapGroupRuleTargetParameterComponent parameter) throws DefinitionException { + DataType p = parameter.getValue(); + if (!(p instanceof IdType)) + return p; + else { + String n = ((IdType) p).asStringValue(); + Base b = vars.get(VariableMode.INPUT, n); + if (b == null) + b = vars.get(VariableMode.OUTPUT, n); + if (b == null) + throw new DefinitionException("Variable " + n + " not found (" + vars.summary() + ")"); + return b; + } + } + + + private Base translate(TransformContext context, StructureMap map, Variables vars, List parameter) throws FHIRException { + Base src = getParam(vars, parameter.get(0)); + String id = getParamString(vars, parameter.get(1)); + String fld = parameter.size() > 2 ? getParamString(vars, parameter.get(2)) : null; + return translate(context, map, src, id, fld); + } + + public Base translate(TransformContext context, StructureMap map, Base source, String conceptMapUrl, String fieldToReturn) throws FHIRException { + Coding src = new Coding(); + if (source.isPrimitive()) { + src.setCode(source.primitiveValue()); + } else if ("Coding".equals(source.fhirType())) { + Base[] b = source.getProperty("system".hashCode(), "system", true); + if (b.length == 1) + src.setSystem(b[0].primitiveValue()); + b = source.getProperty("code".hashCode(), "code", true); + if (b.length == 1) + src.setCode(b[0].primitiveValue()); + } else if ("CE".equals(source.fhirType())) { + Base[] b = source.getProperty("codeSystem".hashCode(), "codeSystem", true); + if (b.length == 1) + src.setSystem(b[0].primitiveValue()); + b = source.getProperty("code".hashCode(), "code", true); + if (b.length == 1) + src.setCode(b[0].primitiveValue()); + } else + throw new FHIRException("Unable to translate source " + source.fhirType()); + + String su = conceptMapUrl; + if (conceptMapUrl.equals("http://hl7.org/fhir/ConceptMap/special-oid2uri")) { + String uri = worker.oid2Uri(src.getCode()); + if (uri == null) + uri = "urn:oid:" + src.getCode(); + if ("uri".equals(fieldToReturn)) + return new UriType(uri); + else + throw new FHIRException("Error in return code"); + } else { + ConceptMap cmap = null; + if (conceptMapUrl.startsWith("#")) { + for (Resource r : map.getContained()) { + if (r instanceof ConceptMap && r.getId().equals(conceptMapUrl.substring(1))) { + cmap = (ConceptMap) r; + su = map.getUrl() + "#" + conceptMapUrl; + } + } + if (cmap == null) + throw new FHIRException("Unable to translate - cannot find map " + conceptMapUrl); + } else { + if (conceptMapUrl.contains("#")) { + String[] p = conceptMapUrl.split("\\#"); + StructureMap mapU = worker.fetchResource(StructureMap.class, p[0]); + for (Resource r : mapU.getContained()) { + if (r instanceof ConceptMap && r.getId().equals(p[1])) { + cmap = (ConceptMap) r; + su = conceptMapUrl; + } + } + } + if (cmap == null) + cmap = worker.fetchResource(ConceptMap.class, conceptMapUrl); + } + Coding outcome = null; + boolean done = false; + String message = null; + if (cmap == null) { + if (services == null) + message = "No map found for " + conceptMapUrl; + else { + outcome = services.translate(context.getAppInfo(), src, conceptMapUrl); + done = true; + } + } else { + List list = new ArrayList(); + for (ConceptMapGroupComponent g : cmap.getGroup()) { + for (SourceElementComponent e : g.getElement()) { + if (!src.hasSystem() && src.getCode().equals(e.getCode())) + list.add(new SourceElementComponentWrapper(g, e)); + else if (src.hasSystem() && src.getSystem().equals(g.getSource()) && src.getCode().equals(e.getCode())) + list.add(new SourceElementComponentWrapper(g, e)); + } + } + if (list.size() == 0) + done = true; + else if (list.get(0).getComp().getTarget().size() == 0) + message = "Concept map " + su + " found no translation for " + src.getCode(); + else { + for (TargetElementComponent tgt : list.get(0).getComp().getTarget()) { + if (tgt.getRelationship() == null || EnumSet.of(ConceptMapRelationship.RELATEDTO, ConceptMapRelationship.EQUIVALENT, ConceptMapRelationship.SOURCEISNARROWERTHANTARGET).contains(tgt.getRelationship())) { + if (done) { + message = "Concept map " + su + " found multiple matches for " + src.getCode(); + done = false; + } else { + done = true; + outcome = new Coding().setCode(tgt.getCode()).setSystem(list.get(0).getGroup().getTarget()); + } + } + } + if (!done) + message = "Concept map " + su + " found no usable translation for " + src.getCode(); + } + } + if (!done) + throw new FHIRException(message); + if (outcome == null) + return null; + if ("code".equals(fieldToReturn)) + return new CodeType(outcome.getCode()); + else + return outcome; + } + } + + + /** + * Given a structure map, return a set of analyses on it. + *

+ * Returned: + * - a list or profiles for what it will create. First profile is the target + * - a table with a summary (in xhtml) for easy human undertanding of the mapping + * + * @param appInfo + * @param map + * @return + * @throws Exception + */ + public StructureMapAnalysis analyse(Object appInfo, StructureMap map) throws FHIRException { + ids.clear(); + StructureMapAnalysis result = new StructureMapAnalysis(); + TransformContext context = new TransformContext(appInfo); + VariablesForProfiling vars = new VariablesForProfiling(this, false, false); + StructureMapGroupComponent start = map.getGroup().get(0); + for (StructureMapGroupInputComponent t : start.getInput()) { + PropertyWithType ti = resolveType(map, t.getType(), t.getMode()); + if (t.getMode() == StructureMapInputMode.SOURCE) + vars.add(VariableMode.INPUT, t.getName(), ti); + else + vars.add(VariableMode.OUTPUT, t.getName(), createProfile(map, result.profiles, ti, start.getName(), start)); + } + + result.summary = new XhtmlNode(NodeType.Element, "table").setAttribute("class", "grid"); + XhtmlNode tr = result.summary.addTag("tr"); + tr.addTag("td").addTag("b").addText("Source"); + tr.addTag("td").addTag("b").addText("Target"); + + log("Start Profiling Transform " + map.getUrl()); + analyseGroup("", context, map, vars, start, result); + ProfileUtilities pu = new ProfileUtilities(worker, null, pkp); + for (StructureDefinition sd : result.getProfiles()) + pu.cleanUpDifferential(sd); + return result; + } + + + private void analyseGroup(String indent, TransformContext context, StructureMap map, VariablesForProfiling vars, StructureMapGroupComponent group, StructureMapAnalysis result) throws FHIRException { + log(indent + "Analyse Group : " + group.getName()); + // todo: extends + // todo: check inputs + XhtmlNode tr = result.summary.addTag("tr").setAttribute("class", "diff-title"); + XhtmlNode xs = tr.addTag("td"); + XhtmlNode xt = tr.addTag("td"); + for (StructureMapGroupInputComponent inp : group.getInput()) { + if (inp.getMode() == StructureMapInputMode.SOURCE) + noteInput(vars, inp, VariableMode.INPUT, xs); + if (inp.getMode() == StructureMapInputMode.TARGET) + noteInput(vars, inp, VariableMode.OUTPUT, xt); + } + for (StructureMapGroupRuleComponent r : group.getRule()) { + analyseRule(indent + " ", context, map, vars, group, r, result); + } + } + + + private void noteInput(VariablesForProfiling vars, StructureMapGroupInputComponent inp, VariableMode mode, XhtmlNode xs) { + VariableForProfiling v = vars.get(mode, inp.getName()); + if (v != null) + xs.addText("Input: " + v.getProperty().getPath()); + } + + private void analyseRule(String indent, TransformContext context, StructureMap map, VariablesForProfiling vars, StructureMapGroupComponent group, StructureMapGroupRuleComponent rule, StructureMapAnalysis result) throws FHIRException { + log(indent + "Analyse rule : " + rule.getName()); + XhtmlNode tr = result.summary.addTag("tr"); + XhtmlNode xs = tr.addTag("td"); + XhtmlNode xt = tr.addTag("td"); + + VariablesForProfiling srcVars = vars.copy(); + if (rule.getSource().size() != 1) + throw new FHIRException("Rule \"" + rule.getName() + "\": not handled yet"); + VariablesForProfiling source = analyseSource(rule.getName(), context, srcVars, rule.getSourceFirstRep(), xs); + + TargetWriter tw = new TargetWriter(); + for (StructureMapGroupRuleTargetComponent t : rule.getTarget()) { + analyseTarget(rule.getName(), context, source, map, t, rule.getSourceFirstRep().getVariable(), tw, result.profiles, rule.getName()); + } + tw.commit(xt); + + for (StructureMapGroupRuleComponent childrule : rule.getRule()) { + analyseRule(indent + " ", context, map, source, group, childrule, result); + } +// for (StructureMapGroupRuleDependentComponent dependent : rule.getDependent()) { +// executeDependency(indent+" ", context, map, v, group, dependent); // do we need group here? +// } + } + + private VariablesForProfiling analyseSource(String ruleId, TransformContext context, VariablesForProfiling vars, StructureMapGroupRuleSourceComponent src, XhtmlNode td) throws FHIRException { + VariableForProfiling var = vars.get(VariableMode.INPUT, src.getContext()); + if (var == null) + throw new FHIRException("Rule \"" + ruleId + "\": Unknown input variable " + src.getContext()); + PropertyWithType prop = var.getProperty(); + + boolean optional = false; + boolean repeating = false; + + if (src.hasCondition()) { + optional = true; + } + + if (src.hasElement()) { + Property element = prop.getBaseProperty().getChild(prop.getTypes().getType(), src.getElement()); + if (element == null) + throw new FHIRException("Rule \"" + ruleId + "\": Unknown element name " + src.getElement()); + if (element.getDefinition().getMin() == 0) + optional = true; + if (element.getDefinition().getMax().equals("*")) + repeating = true; + VariablesForProfiling result = vars.copy(optional, repeating); + TypeDetails type = new TypeDetails(CollectionStatus.SINGLETON); + for (TypeRefComponent tr : element.getDefinition().getType()) { + if (!tr.hasCode()) + throw new Error("Rule \"" + ruleId + "\": Element has no type"); + ProfiledType pt = new ProfiledType(tr.getWorkingCode()); + if (tr.hasProfile()) + pt.addProfiles(tr.getProfile()); + if (element.getDefinition().hasBinding()) + pt.addBinding(element.getDefinition().getBinding()); + type.addType(pt); + } + td.addText(prop.getPath() + "." + src.getElement()); + if (src.hasVariable()) + result.add(VariableMode.INPUT, src.getVariable(), new PropertyWithType(prop.getPath() + "." + src.getElement(), element, null, type)); + return result; + } else { + td.addText(prop.getPath()); // ditto! + return vars.copy(optional, repeating); + } + } + + + private void analyseTarget(String ruleId, TransformContext context, VariablesForProfiling vars, StructureMap map, StructureMapGroupRuleTargetComponent tgt, String tv, TargetWriter tw, List profiles, String sliceName) throws FHIRException { + VariableForProfiling var = null; + if (tgt.hasContext()) { + var = vars.get(VariableMode.OUTPUT, tgt.getContext()); + if (var == null) + throw new FHIRException("Rule \"" + ruleId + "\": target context not known: " + tgt.getContext()); + if (!tgt.hasElement()) + throw new FHIRException("Rule \"" + ruleId + "\": Not supported yet"); + } + + + TypeDetails type = null; + if (tgt.hasTransform()) { + type = analyseTransform(context, map, tgt, var, vars); + } else { + Property vp = var.getProperty().getBaseProperty().getChild(tgt.getElement(), tgt.getElement()); + if (vp == null) + throw new FHIRException("Unknown Property " + tgt.getElement() + " on " + var.getProperty().getPath()); + + type = new TypeDetails(CollectionStatus.SINGLETON, vp.getType(tgt.getElement())); + } + + if (tgt.getTransform() == StructureMapTransform.CREATE) { + String s = getParamString(vars, tgt.getParameter().get(0)); + if (worker.getResourceNames().contains(s)) + tw.newResource(tgt.getVariable(), s); + } else { + boolean mapsSrc = false; + for (StructureMapGroupRuleTargetParameterComponent p : tgt.getParameter()) { + DataType pr = p.getValue(); + if (pr instanceof IdType && ((IdType) pr).asStringValue().equals(tv)) + mapsSrc = true; + } + if (mapsSrc) { + if (var == null) + throw new Error("Rule \"" + ruleId + "\": Attempt to assign with no context"); + tw.valueAssignment(tgt.getContext(), var.getProperty().getPath() + "." + tgt.getElement() + getTransformSuffix(tgt.getTransform())); + } else if (tgt.hasContext()) { + if (isSignificantElement(var.getProperty(), tgt.getElement())) { + String td = describeTransform(tgt); + if (td != null) + tw.keyAssignment(tgt.getContext(), var.getProperty().getPath() + "." + tgt.getElement() + " = " + td); + } + } + } + DataType fixed = generateFixedValue(tgt); + + PropertyWithType prop = updateProfile(var, tgt.getElement(), type, map, profiles, sliceName, fixed, tgt); + if (tgt.hasVariable()) + if (tgt.hasElement()) + vars.add(VariableMode.OUTPUT, tgt.getVariable(), prop); + else + vars.add(VariableMode.OUTPUT, tgt.getVariable(), prop); + } + + private DataType generateFixedValue(StructureMapGroupRuleTargetComponent tgt) { + if (!allParametersFixed(tgt)) + return null; + if (!tgt.hasTransform()) + return null; + switch (tgt.getTransform()) { + case COPY: + return tgt.getParameter().get(0).getValue(); + case TRUNCATE: + return null; + //case ESCAPE: + //case CAST: + //case APPEND: + case TRANSLATE: + return null; + //case DATEOP, + //case UUID, + //case POINTER, + //case EVALUATE, + case CC: + CodeableConcept cc = new CodeableConcept(); + cc.addCoding(buildCoding(tgt.getParameter().get(0).getValue(), tgt.getParameter().get(1).getValue())); + return cc; + case C: + return buildCoding(tgt.getParameter().get(0).getValue(), tgt.getParameter().get(1).getValue()); + case QTY: + return null; + //case ID, + //case CP, + default: + return null; + } + } + + @SuppressWarnings("rawtypes") + private Coding buildCoding(DataType value1, DataType value2) { + return new Coding().setSystem(((PrimitiveType) value1).asStringValue()).setCode(((PrimitiveType) value2).asStringValue()); + } + + private boolean allParametersFixed(StructureMapGroupRuleTargetComponent tgt) { + for (StructureMapGroupRuleTargetParameterComponent p : tgt.getParameter()) { + DataType pr = p.getValue(); + if (pr instanceof IdType) + return false; + } + return true; + } + + private String describeTransform(StructureMapGroupRuleTargetComponent tgt) throws FHIRException { + switch (tgt.getTransform()) { + case COPY: + return null; + case TRUNCATE: + return null; + //case ESCAPE: + //case CAST: + //case APPEND: + case TRANSLATE: + return null; + //case DATEOP, + //case UUID, + //case POINTER, + //case EVALUATE, + case CC: + return describeTransformCCorC(tgt); + case C: + return describeTransformCCorC(tgt); + case QTY: + return null; + //case ID, + //case CP, + default: + return null; + } + } + + @SuppressWarnings("rawtypes") + private String describeTransformCCorC(StructureMapGroupRuleTargetComponent tgt) throws FHIRException { + if (tgt.getParameter().size() < 2) + return null; + DataType p1 = tgt.getParameter().get(0).getValue(); + DataType p2 = tgt.getParameter().get(1).getValue(); + if (p1 instanceof IdType || p2 instanceof IdType) + return null; + if (!(p1 instanceof PrimitiveType) || !(p2 instanceof PrimitiveType)) + return null; + String uri = ((PrimitiveType) p1).asStringValue(); + String code = ((PrimitiveType) p2).asStringValue(); + if (Utilities.noString(uri)) + throw new FHIRException("Describe Transform, but the uri is blank"); + if (Utilities.noString(code)) + throw new FHIRException("Describe Transform, but the code is blank"); + Coding c = buildCoding(uri, code); + return TerminologyRenderer.describeSystem(c.getSystem()) + "#" + c.getCode() + (c.hasDisplay() ? "(" + c.getDisplay() + ")" : ""); + } + + + private boolean isSignificantElement(PropertyWithType property, String element) { + if ("Observation".equals(property.getPath())) + return "code".equals(element); + else if ("Bundle".equals(property.getPath())) + return "type".equals(element); + else + return false; + } + + private String getTransformSuffix(StructureMapTransform transform) { + switch (transform) { + case COPY: + return ""; + case TRUNCATE: + return " (truncated)"; + //case ESCAPE: + //case CAST: + //case APPEND: + case TRANSLATE: + return " (translated)"; + //case DATEOP, + //case UUID, + //case POINTER, + //case EVALUATE, + case CC: + return " (--> CodeableConcept)"; + case C: + return " (--> Coding)"; + case QTY: + return " (--> Quantity)"; + //case ID, + //case CP, + default: + return " {??)"; + } + } + + private PropertyWithType updateProfile(VariableForProfiling var, String element, TypeDetails type, StructureMap map, List profiles, String sliceName, DataType fixed, StructureMapGroupRuleTargetComponent tgt) throws FHIRException { + if (var == null) { + assert (Utilities.noString(element)); + // 1. start the new structure definition + StructureDefinition sdn = worker.fetchResource(StructureDefinition.class, type.getType()); + if (sdn == null) + throw new FHIRException("Unable to find definition for " + type.getType()); + ElementDefinition edn = sdn.getSnapshot().getElementFirstRep(); + PropertyWithType pn = createProfile(map, profiles, new PropertyWithType(sdn.getId(), new Property(worker, edn, sdn), null, type), sliceName, tgt); + return pn; + } else { + assert (!Utilities.noString(element)); + Property pvb = var.getProperty().getBaseProperty(); + Property pvd = var.getProperty().getProfileProperty(); + Property pc = pvb.getChild(element, var.getProperty().getTypes()); + if (pc == null) + throw new DefinitionException("Unable to find a definition for " + pvb.getDefinition().getPath() + "." + element); + + // the profile structure definition (derived) + StructureDefinition sd = var.getProperty().getProfileProperty().getStructure(); + ElementDefinition ednew = sd.getDifferential().addElement(); + ednew.setPath(var.getProperty().getProfileProperty().getDefinition().getPath() + "." + pc.getName()); + ednew.setUserData("slice-name", sliceName); + ednew.setFixed(fixed); + for (ProfiledType pt : type.getProfiledTypes()) { + if (pt.hasBindings()) + ednew.setBinding(pt.getBindings().get(0)); + if (pt.getUri().startsWith("http://hl7.org/fhir/StructureDefinition/")) { + String t = pt.getUri().substring(40); + t = checkType(t, pc, pt.getProfiles()); + if (t != null) { + if (pt.hasProfiles()) { + for (String p : pt.getProfiles()) + if (t.equals("Reference")) + ednew.getType(t).addTargetProfile(p); + else + ednew.getType(t).addProfile(p); + } else + ednew.getType(t); + } + } + } + + return new PropertyWithType(var.getProperty().getPath() + "." + element, pc, new Property(worker, ednew, sd), type); + } + } + + + private String checkType(String t, Property pvb, List profiles) throws FHIRException { + if (pvb.getDefinition().getType().size() == 1 && isCompatibleType(t, pvb.getDefinition().getType().get(0).getWorkingCode()) && profilesMatch(profiles, pvb.getDefinition().getType().get(0).getProfile())) + return null; + for (TypeRefComponent tr : pvb.getDefinition().getType()) { + if (isCompatibleType(t, tr.getWorkingCode())) + return tr.getWorkingCode(); // note what is returned - the base type, not the inferred mapping type + } + throw new FHIRException("The type " + t + " is not compatible with the allowed types for " + pvb.getDefinition().getPath()); + } + + private boolean profilesMatch(List profiles, List profile) { + return profiles == null || profiles.size() == 0 || profile.size() == 0 || (profiles.size() == 1 && profiles.get(0).equals(profile.get(0).getValue())); + } + + private boolean isCompatibleType(String t, String code) { + if (t.equals(code)) + return true; + if (t.equals("string")) { + StructureDefinition sd = worker.fetchTypeDefinition(code); + return sd != null && sd.getBaseDefinition().equals("http://hl7.org/fhir/StructureDefinition/string"); + } + return false; + } + + private TypeDetails analyseTransform(TransformContext context, StructureMap map, StructureMapGroupRuleTargetComponent tgt, VariableForProfiling var, VariablesForProfiling vars) throws FHIRException { + switch (tgt.getTransform()) { + case CREATE: + String p = getParamString(vars, tgt.getParameter().get(0)); + return new TypeDetails(CollectionStatus.SINGLETON, p); + case COPY: + return getParam(vars, tgt.getParameter().get(0)); + case EVALUATE: + ExpressionNode expr = (ExpressionNode) tgt.getUserData(MAP_EXPRESSION); + if (expr == null) { + expr = fpe.parse(getParamString(vars, tgt.getParameter().get(tgt.getParameter().size() - 1))); + tgt.setUserData(MAP_WHERE_EXPRESSION, expr); + } + return fpe.check(vars, null, expr); + case TRANSLATE: + return new TypeDetails(CollectionStatus.SINGLETON, "CodeableConcept"); + case CC: + ProfiledType res = new ProfiledType("CodeableConcept"); + if (tgt.getParameter().size() >= 2 && isParamId(vars, tgt.getParameter().get(1))) { + TypeDetails td = vars.get(null, getParamId(vars, tgt.getParameter().get(1))).getProperty().getTypes(); + if (td != null && td.hasBinding()) + // todo: do we need to check that there's no implicit translation her? I don't think we do... + res.addBinding(td.getBinding()); + } + return new TypeDetails(CollectionStatus.SINGLETON, res); + case C: + return new TypeDetails(CollectionStatus.SINGLETON, "Coding"); + case QTY: + return new TypeDetails(CollectionStatus.SINGLETON, "Quantity"); + case REFERENCE: + VariableForProfiling vrs = vars.get(VariableMode.OUTPUT, getParamId(vars, tgt.getParameterFirstRep())); + if (vrs == null) + throw new FHIRException("Unable to resolve variable \"" + getParamId(vars, tgt.getParameterFirstRep()) + "\""); + String profile = vrs.getProperty().getProfileProperty().getStructure().getUrl(); + TypeDetails td = new TypeDetails(CollectionStatus.SINGLETON); + td.addType("Reference", profile); + return td; + default: + throw new Error("Transform Unknown or not handled yet: " + tgt.getTransform().toCode()); + } + } + + private String getParamString(VariablesForProfiling vars, StructureMapGroupRuleTargetParameterComponent parameter) { + DataType p = parameter.getValue(); + if (p == null || p instanceof IdType) + return null; + if (!p.hasPrimitiveValue()) + return null; + return p.primitiveValue(); + } + + private String getParamId(VariablesForProfiling vars, StructureMapGroupRuleTargetParameterComponent parameter) { + DataType p = parameter.getValue(); + if (p == null || !(p instanceof IdType)) + return null; + return p.primitiveValue(); + } + + private boolean isParamId(VariablesForProfiling vars, StructureMapGroupRuleTargetParameterComponent parameter) { + DataType p = parameter.getValue(); + if (p == null || !(p instanceof IdType)) + return false; + return vars.get(null, p.primitiveValue()) != null; + } + + private TypeDetails getParam(VariablesForProfiling vars, StructureMapGroupRuleTargetParameterComponent parameter) throws DefinitionException { + DataType p = parameter.getValue(); + if (!(p instanceof IdType)) + return new TypeDetails(CollectionStatus.SINGLETON, ProfileUtilities.sdNs(p.fhirType(), worker.getOverrideVersionNs())); + else { + String n = ((IdType) p).asStringValue(); + VariableForProfiling b = vars.get(VariableMode.INPUT, n); + if (b == null) + b = vars.get(VariableMode.OUTPUT, n); + if (b == null) + throw new DefinitionException("Variable " + n + " not found (" + vars.summary() + ")"); + return b.getProperty().getTypes(); + } + } + + private PropertyWithType createProfile(StructureMap map, List profiles, PropertyWithType prop, String sliceName, Base ctxt) throws FHIRException { + if (prop.getBaseProperty().getDefinition().getPath().contains(".")) + throw new DefinitionException("Unable to process entry point"); + + String type = prop.getBaseProperty().getDefinition().getPath(); + String suffix = ""; + if (ids.containsKey(type)) { + int id = ids.get(type); + id++; + ids.put(type, id); + suffix = "-" + id; + } else + ids.put(type, 0); + + StructureDefinition profile = new StructureDefinition(); + profiles.add(profile); + profile.setDerivation(TypeDerivationRule.CONSTRAINT); + profile.setType(type); + profile.setBaseDefinition(prop.getBaseProperty().getStructure().getUrl()); + profile.setName("Profile for " + profile.getType() + " for " + sliceName); + profile.setUrl(map.getUrl().replace("StructureMap", "StructureDefinition") + "-" + profile.getType() + suffix); + ctxt.setUserData("profile", profile.getUrl()); // then we can easily assign this profile url for validation later when we actually transform + profile.setId(map.getId() + "-" + profile.getType() + suffix); + profile.setStatus(map.getStatus()); + profile.setExperimental(map.getExperimental()); + profile.setDescription("Generated automatically from the mapping by the Java Reference Implementation"); + for (ContactDetail c : map.getContact()) { + ContactDetail p = profile.addContact(); + p.setName(c.getName()); + for (ContactPoint cc : c.getTelecom()) + p.addTelecom(cc); + } + profile.setDate(map.getDate()); + profile.setCopyright(map.getCopyright()); + profile.setFhirVersion(FHIRVersion.fromCode(Constants.VERSION)); + profile.setKind(prop.getBaseProperty().getStructure().getKind()); + profile.setAbstract(false); + ElementDefinition ed = profile.getDifferential().addElement(); + ed.setPath(profile.getType()); + prop.setProfileProperty(new Property(worker, ed, profile)); + return prop; + } + + private PropertyWithType resolveType(StructureMap map, String type, StructureMapInputMode mode) throws FHIRException { + for (StructureMapStructureComponent imp : map.getStructure()) { + if ((imp.getMode() == StructureMapModelMode.SOURCE && mode == StructureMapInputMode.SOURCE) || + (imp.getMode() == StructureMapModelMode.TARGET && mode == StructureMapInputMode.TARGET)) { + StructureDefinition sd = worker.fetchResource(StructureDefinition.class, imp.getUrl()); + if (sd == null) + throw new FHIRException("Import " + imp.getUrl() + " cannot be resolved"); + if (sd.getId().equals(type)) { + return new PropertyWithType(sd.getType(), new Property(worker, sd.getSnapshot().getElement().get(0), sd), null, new TypeDetails(CollectionStatus.SINGLETON, sd.getUrl())); + } + } + } + throw new FHIRException("Unable to find structure definition for " + type + " in imports"); + } + + + public StructureMap generateMapFromMappings(StructureDefinition sd) throws IOException, FHIRException { + String id = getLogicalMappingId(sd); + if (id == null) + return null; + String prefix = ToolingExtensions.readStringExtension(sd, ToolingExtensions.EXT_MAPPING_PREFIX); + String suffix = ToolingExtensions.readStringExtension(sd, ToolingExtensions.EXT_MAPPING_SUFFIX); + if (prefix == null || suffix == null) + return null; + // we build this by text. Any element that has a mapping, we put it's mappings inside it.... + StringBuilder b = new StringBuilder(); + b.append(prefix); + + ElementDefinition root = sd.getSnapshot().getElementFirstRep(); + String m = getMapping(root, id); + if (m != null) + b.append(m + "\r\n"); + addChildMappings(b, id, "", sd, root, false); + b.append("\r\n"); + b.append(suffix); + b.append("\r\n"); + StructureMap map = parse(b.toString(), sd.getUrl()); + map.setId(tail(map.getUrl())); + if (!map.hasStatus()) + map.setStatus(PublicationStatus.DRAFT); + map.getText().setStatus(NarrativeStatus.GENERATED); + map.getText().setDiv(new XhtmlNode(NodeType.Element, "div")); + map.getText().getDiv().addTag("pre").addText(render(map)); + return map; + } + + + private String tail(String url) { + return url.substring(url.lastIndexOf("/") + 1); + } + + + private void addChildMappings(StringBuilder b, String id, String indent, StructureDefinition sd, ElementDefinition ed, boolean inner) throws DefinitionException { + boolean first = true; + List children = profileUtilities.getChildMap(sd, ed); + for (ElementDefinition child : children) { + if (first && inner) { + b.append(" then {\r\n"); + first = false; + } + String map = getMapping(child, id); + if (map != null) { + b.append(indent + " " + child.getPath() + ": " + map); + addChildMappings(b, id, indent + " ", sd, child, true); + b.append("\r\n"); + } + } + if (!first && inner) + b.append(indent + "}"); + + } + + + private String getMapping(ElementDefinition ed, String id) { + for (ElementDefinitionMappingComponent map : ed.getMapping()) + if (id.equals(map.getIdentity())) + return map.getMap(); + return null; + } + + + private String getLogicalMappingId(StructureDefinition sd) { + String id = null; + for (StructureDefinitionMappingComponent map : sd.getMapping()) { + if ("http://hl7.org/fhir/logical".equals(map.getUri())) + return map.getIdentity(); + } + return null; + } + + public ValidationOptions getTerminologyServiceOptions() { + return terminologyServiceOptions; + } + + public void setTerminologyServiceOptions(ValidationOptions terminologyServiceOptions) { + this.terminologyServiceOptions = terminologyServiceOptions; + } + +} \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/TargetWriter.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/TargetWriter.java new file mode 100644 index 000000000..f083bcd70 --- /dev/null +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/TargetWriter.java @@ -0,0 +1,41 @@ +package org.hl7.fhir.r5.utils.structuremap; + +import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; +import org.hl7.fhir.utilities.xhtml.XhtmlNode; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class TargetWriter { + private Map newResources = new HashMap(); + private List assignments = new ArrayList(); + private List keyProps = new ArrayList(); + private CommaSeparatedStringBuilder txt = new CommaSeparatedStringBuilder(); + + public void newResource(String var, String name) { + newResources.put(var, name); + txt.append("new " + name); + } + + public void valueAssignment(String context, String desc) { + assignments.add(new StringPair(context, desc)); + txt.append(desc); + } + + public void keyAssignment(String context, String desc) { + keyProps.add(new StringPair(context, desc)); + txt.append(desc); + } + + public void commit(XhtmlNode xt) { + if (newResources.size() == 1 && assignments.size() == 1 && newResources.containsKey(assignments.get(0).getVar()) && keyProps.size() == 1 && newResources.containsKey(keyProps.get(0).getVar())) { + xt.addText("new " + assignments.get(0).getDesc() + " (" + keyProps.get(0).getDesc().substring(keyProps.get(0).getDesc().indexOf(".") + 1) + ")"); + } else if (newResources.size() == 1 && assignments.size() == 1 && newResources.containsKey(assignments.get(0).getVar()) && keyProps.size() == 0) { + xt.addText("new " + assignments.get(0).getDesc()); + } else { + xt.addText(txt.toString()); + } + } +} diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/TransformContext.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/TransformContext.java new file mode 100644 index 000000000..a9562c9bd --- /dev/null +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/TransformContext.java @@ -0,0 +1,15 @@ +package org.hl7.fhir.r5.utils.structuremap; + +public class TransformContext { + private Object appInfo; + + public TransformContext(Object appInfo) { + super(); + this.appInfo = appInfo; + } + + public Object getAppInfo() { + return appInfo; + } + +} diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/Variable.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/Variable.java new file mode 100644 index 000000000..74654adb2 --- /dev/null +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/Variable.java @@ -0,0 +1,38 @@ +package org.hl7.fhir.r5.utils.structuremap; + +import org.hl7.fhir.r5.model.Base; +import org.hl7.fhir.r5.model.PrimitiveType; + +public class Variable { + private VariableMode mode; + private String name; + private Base object; + + public Variable(VariableMode mode, String name, Base object) { + super(); + this.mode = mode; + this.name = name; + this.object = object; + } + + public VariableMode getMode() { + return mode; + } + + public String getName() { + return name; + } + + public Base getObject() { + return object; + } + + public String summary() { + if (object == null) + return null; + else if (object instanceof PrimitiveType) + return name + ": \"" + ((PrimitiveType) object).asStringValue() + '"'; + else + return name + ": (" + object.fhirType() + ")"; + } +} diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/VariableForProfiling.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/VariableForProfiling.java new file mode 100644 index 000000000..4265b516d --- /dev/null +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/VariableForProfiling.java @@ -0,0 +1,30 @@ +package org.hl7.fhir.r5.utils.structuremap; + +public class VariableForProfiling { + private VariableMode mode; + private String name; + private PropertyWithType property; + + public VariableForProfiling(VariableMode mode, String name, PropertyWithType property) { + super(); + this.mode = mode; + this.name = name; + this.property = property; + } + + public VariableMode getMode() { + return mode; + } + + public String getName() { + return name; + } + + public PropertyWithType getProperty() { + return property; + } + + public String summary() { + return name + ": " + property.summary(); + } +} diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/VariableMode.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/VariableMode.java new file mode 100644 index 000000000..620938996 --- /dev/null +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/VariableMode.java @@ -0,0 +1,5 @@ +package org.hl7.fhir.r5.utils.structuremap; + +public enum VariableMode { + INPUT, OUTPUT, SHARED +} diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/Variables.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/Variables.java new file mode 100644 index 000000000..2c9483b35 --- /dev/null +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/Variables.java @@ -0,0 +1,54 @@ +package org.hl7.fhir.r5.utils.structuremap; + +import org.hl7.fhir.r5.model.Base; +import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; + +import java.util.ArrayList; +import java.util.List; + +public class Variables { + private List list = new ArrayList(); + + public void add(VariableMode mode, String name, Base object) { + Variable vv = null; + for (Variable v : list) + if ((v.getMode() == mode) && v.getName().equals(name)) + vv = v; + if (vv != null) + list.remove(vv); + list.add(new Variable(mode, name, object)); + } + + public Variables copy() { + Variables result = new Variables(); + result.list.addAll(list); + return result; + } + + public Base get(VariableMode mode, String name) { + for (Variable v : list) + if ((v.getMode() == mode) && v.getName().equals(name)) + return v.getObject(); + return null; + } + + public String summary() { + CommaSeparatedStringBuilder s = new CommaSeparatedStringBuilder(); + CommaSeparatedStringBuilder t = new CommaSeparatedStringBuilder(); + CommaSeparatedStringBuilder sh = new CommaSeparatedStringBuilder(); + for (Variable v : list) + switch (v.getMode()) { + case INPUT: + s.append(v.summary()); + break; + case OUTPUT: + t.append(v.summary()); + break; + case SHARED: + sh.append(v.summary()); + break; + } + return "source variables [" + s.toString() + "], target variables [" + t.toString() + "], shared variables [" + sh.toString() + "]"; + } + +} diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/VariablesForProfiling.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/VariablesForProfiling.java new file mode 100644 index 000000000..080821af9 --- /dev/null +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/VariablesForProfiling.java @@ -0,0 +1,77 @@ +package org.hl7.fhir.r5.utils.structuremap; + +import org.hl7.fhir.r5.elementmodel.Property; +import org.hl7.fhir.r5.model.TypeDetails; +import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; + +import java.util.ArrayList; +import java.util.List; + +public class VariablesForProfiling { + private final StructureMapUtilities structureMapUtilities; + private List list = new ArrayList(); + private boolean optional; + private boolean repeating; + + public VariablesForProfiling(StructureMapUtilities structureMapUtilities, boolean optional, boolean repeating) { + this.structureMapUtilities = structureMapUtilities; + this.optional = optional; + this.repeating = repeating; + } + + public void add(VariableMode mode, String name, String path, Property property, TypeDetails types) { + add(mode, name, new PropertyWithType(path, property, null, types)); + } + + public void add(VariableMode mode, String name, String path, Property baseProperty, Property profileProperty, TypeDetails types) { + add(mode, name, new PropertyWithType(path, baseProperty, profileProperty, types)); + } + + public void add(VariableMode mode, String name, PropertyWithType property) { + VariableForProfiling vv = null; + for (VariableForProfiling v : list) + if ((v.getMode() == mode) && v.getName().equals(name)) + vv = v; + if (vv != null) + list.remove(vv); + list.add(new VariableForProfiling(mode, name, property)); + } + + public VariablesForProfiling copy(boolean optional, boolean repeating) { + VariablesForProfiling result = new VariablesForProfiling(structureMapUtilities, optional, repeating); + result.list.addAll(list); + return result; + } + + public VariablesForProfiling copy() { + VariablesForProfiling result = new VariablesForProfiling(structureMapUtilities, optional, repeating); + result.list.addAll(list); + return result; + } + + public VariableForProfiling get(VariableMode mode, String name) { + if (mode == null) { + for (VariableForProfiling v : list) + if ((v.getMode() == VariableMode.OUTPUT) && v.getName().equals(name)) + return v; + for (VariableForProfiling v : list) + if ((v.getMode() == VariableMode.INPUT) && v.getName().equals(name)) + return v; + } + for (VariableForProfiling v : list) + if ((v.getMode() == mode) && v.getName().equals(name)) + return v; + return null; + } + + public String summary() { + CommaSeparatedStringBuilder s = new CommaSeparatedStringBuilder(); + CommaSeparatedStringBuilder t = new CommaSeparatedStringBuilder(); + for (VariableForProfiling v : list) + if (v.getMode() == VariableMode.INPUT) + s.append(v.summary()); + else + t.append(v.summary()); + return "source variables [" + s.toString() + "], target variables [" + t.toString() + "]"; + } +} diff --git a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/StructureMapUtilitiesTest.java b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/StructureMapUtilitiesTest.java index f3aa9ea25..8fccee915 100644 --- a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/StructureMapUtilitiesTest.java +++ b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/StructureMapUtilitiesTest.java @@ -1,8 +1,5 @@ package org.hl7.fhir.r5.test; -import java.io.IOException; -import java.util.List; - import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.r5.context.SimpleWorkerContext; import org.hl7.fhir.r5.model.Base; @@ -10,18 +7,21 @@ import org.hl7.fhir.r5.model.Coding; import org.hl7.fhir.r5.model.StructureMap; import org.hl7.fhir.r5.model.StructureMap.StructureMapGroupRuleTargetComponent; import org.hl7.fhir.r5.test.utils.TestingUtilities; -import org.hl7.fhir.r5.utils.StructureMapUtilities; -import org.hl7.fhir.r5.utils.StructureMapUtilities.ITransformerServices; +import org.hl7.fhir.r5.utils.structuremap.StructureMapUtilities; +import org.hl7.fhir.r5.utils.structuremap.ITransformerServices; import org.hl7.fhir.utilities.npm.FilesystemPackageCacheManager; import org.hl7.fhir.utilities.npm.ToolsVersion; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import java.io.IOException; +import java.util.List; + public class StructureMapUtilitiesTest implements ITransformerServices { static private SimpleWorkerContext context; - + @BeforeAll static public void setUp() throws Exception { FilesystemPackageCacheManager pcm = new FilesystemPackageCacheManager(true, ToolsVersion.TOOLS_VERSION); @@ -37,10 +37,7 @@ public class StructureMapUtilitiesTest implements ITransformerServices { // StructureMap/ActivityDefinition3to4: StructureMap.group[3].rule[2].name error id value '"expression"' is not valid Assertions.assertEquals("expression", structureMap.getGroup().get(2).getRule().get(1).getName()); } - - - private void assertSerializeDeserialize(StructureMap structureMap) { Assertions.assertEquals("syntax", structureMap.getName()); Assertions.assertEquals("Title of this map\r\nAuthor", structureMap.getDescription()); @@ -54,7 +51,7 @@ public class StructureMapUtilitiesTest implements ITransformerServices { Assertions.assertEquals("Groups\r\nrule for patient group", structureMap.getGroup().get(0).getDocumentation()); Assertions.assertEquals("Comment to rule", structureMap.getGroup().get(0).getRule().get(0).getDocumentation()); Assertions.assertEquals("Copy identifier short syntax", structureMap.getGroup().get(0).getRule().get(1).getDocumentation()); - + StructureMapGroupRuleTargetComponent target = structureMap.getGroup().get(0).getRule().get(2).getTarget().get(1); Assertions.assertEquals("'urn:uuid:' + r.lower()", target.getParameter().get(0).toString()); } @@ -67,13 +64,13 @@ public class StructureMapUtilitiesTest implements ITransformerServices { StructureMap structureMap = scu.parse(fileMap, "Syntax"); assertSerializeDeserialize(structureMap); - + String renderedMap = StructureMapUtilities.render(structureMap); StructureMap map = scu.parse(renderedMap, "Syntax"); System.out.println(map); assertSerializeDeserialize(map); } - + @Override public void log(String message) { } @@ -102,5 +99,4 @@ public class StructureMapUtilitiesTest implements ITransformerServices { public List performSearch(Object appContext, String url) throws FHIRException { return null; } - } \ No newline at end of file diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/Content.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/Content.java new file mode 100644 index 000000000..5e09148bc --- /dev/null +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/Content.java @@ -0,0 +1,8 @@ +package org.hl7.fhir.validation; + +import org.hl7.fhir.r5.elementmodel.Manager; + +class Content { + byte[] focus = null; + Manager.FhirFormat cntType = null; +} diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java index 6dfb61a11..eb6b833a1 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java @@ -1,55 +1,9 @@ package org.hl7.fhir.validation; -import java.io.BufferedOutputStream; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.PrintWriter; -import java.net.HttpURLConnection; -import java.net.URISyntaxException; -import java.net.URL; -import java.net.URLConnection; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.UUID; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; - -import org.apache.commons.io.IOUtils; -import org.hl7.fhir.convertors.VersionConvertorAdvisor50; -import org.hl7.fhir.convertors.VersionConvertor_10_30; -import org.hl7.fhir.convertors.VersionConvertor_10_40; -import org.hl7.fhir.convertors.VersionConvertor_10_50; -import org.hl7.fhir.convertors.VersionConvertor_14_30; -import org.hl7.fhir.convertors.VersionConvertor_14_40; -import org.hl7.fhir.convertors.VersionConvertor_14_50; -import org.hl7.fhir.convertors.VersionConvertor_30_40; -import org.hl7.fhir.convertors.VersionConvertor_30_50; -import org.hl7.fhir.convertors.VersionConvertor_40_50; +import com.google.gson.JsonObject; +import org.hl7.fhir.convertors.*; import org.hl7.fhir.convertors.loaders.BaseLoaderR5.NullLoaderKnowledgeProvider; -import org.hl7.fhir.convertors.loaders.R2016MayToR5Loader; -import org.hl7.fhir.convertors.loaders.R2ToR5Loader; -import org.hl7.fhir.convertors.loaders.R3ToR5Loader; -import org.hl7.fhir.convertors.loaders.R4ToR5Loader; -import org.hl7.fhir.convertors.loaders.R5ToR5Loader; +import org.hl7.fhir.convertors.loaders.*; import org.hl7.fhir.convertors.txClient.TerminologyClientFactory; import org.hl7.fhir.exceptions.DefinitionException; import org.hl7.fhir.exceptions.FHIRException; @@ -60,49 +14,27 @@ import org.hl7.fhir.r5.context.SimpleWorkerContext; import org.hl7.fhir.r5.elementmodel.Element; import org.hl7.fhir.r5.elementmodel.Manager; import org.hl7.fhir.r5.elementmodel.Manager.FhirFormat; -import org.hl7.fhir.r5.elementmodel.ParserBase.ValidationPolicy; import org.hl7.fhir.r5.elementmodel.ObjectConverter; import org.hl7.fhir.r5.formats.FormatUtilities; import org.hl7.fhir.r5.formats.IParser.OutputStyle; import org.hl7.fhir.r5.formats.JsonParser; import org.hl7.fhir.r5.formats.XmlParser; -import org.hl7.fhir.r5.model.Base; -import org.hl7.fhir.r5.model.Bundle; -import org.hl7.fhir.r5.model.CanonicalResource; -import org.hl7.fhir.r5.model.Coding; -import org.hl7.fhir.r5.model.Constants; -import org.hl7.fhir.r5.model.DomainResource; -import org.hl7.fhir.r5.model.FhirPublication; -import org.hl7.fhir.r5.model.ImplementationGuide; +import org.hl7.fhir.r5.model.*; import org.hl7.fhir.r5.model.ImplementationGuide.ImplementationGuideGlobalComponent; -import org.hl7.fhir.r5.model.OperationOutcome; import org.hl7.fhir.r5.model.OperationOutcome.OperationOutcomeIssueComponent; -import org.hl7.fhir.r5.model.Parameters; -import org.hl7.fhir.r5.model.Reference; -import org.hl7.fhir.r5.model.Resource; -import org.hl7.fhir.r5.model.StructureDefinition; -import org.hl7.fhir.r5.model.StructureMap; import org.hl7.fhir.r5.renderers.RendererFactory; import org.hl7.fhir.r5.renderers.utils.RenderingContext; import org.hl7.fhir.r5.renderers.utils.RenderingContext.ResourceRendererMode; import org.hl7.fhir.r5.terminologies.ConceptMapEngine; import org.hl7.fhir.r5.utils.EOperationOutcome; import org.hl7.fhir.r5.utils.FHIRPathEngine; -import org.hl7.fhir.r5.utils.IResourceValidator.BestPracticeWarningLevel; -import org.hl7.fhir.r5.utils.IResourceValidator.BundleValidationRule; -import org.hl7.fhir.r5.utils.IResourceValidator.CheckDisplayOption; -import org.hl7.fhir.r5.utils.IResourceValidator.IValidatorResourceFetcher; -import org.hl7.fhir.r5.utils.IResourceValidator.IdStatus; -import org.hl7.fhir.r5.utils.IResourceValidator.ReferenceValidationPolicy; +import org.hl7.fhir.r5.utils.IResourceValidator.*; import org.hl7.fhir.r5.utils.OperationOutcomeUtilities; -import org.hl7.fhir.r5.utils.StructureMapUtilities; -import org.hl7.fhir.r5.utils.StructureMapUtilities.ITransformerServices; import org.hl7.fhir.r5.utils.ToolingExtensions; -import org.hl7.fhir.utilities.IniFile; -import org.hl7.fhir.utilities.TextFile; +import org.hl7.fhir.r5.utils.structuremap.ITransformerServices; +import org.hl7.fhir.r5.utils.structuremap.StructureMapUtilities; import org.hl7.fhir.utilities.TimeTracker; -import org.hl7.fhir.utilities.Utilities; -import org.hl7.fhir.utilities.VersionUtilities; +import org.hl7.fhir.utilities.*; import org.hl7.fhir.utilities.i18n.I18nConstants; import org.hl7.fhir.utilities.json.JSONUtil; import org.hl7.fhir.utilities.json.JsonTrackingParser; @@ -111,22 +43,28 @@ import org.hl7.fhir.utilities.npm.NpmPackage; import org.hl7.fhir.utilities.npm.ToolsVersion; import org.hl7.fhir.utilities.turtle.Turtle; import org.hl7.fhir.utilities.validation.ValidationMessage; -import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; -import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType; -import org.hl7.fhir.utilities.validation.ValidationMessage.Source; import org.hl7.fhir.utilities.xhtml.XhtmlComposer; import org.hl7.fhir.utilities.xml.XMLUtil; import org.hl7.fhir.validation.BaseValidator.ValidationControl; -import org.hl7.fhir.validation.ValidationEngine.ValidationRecord; import org.hl7.fhir.validation.cli.model.ScanOutputItem; import org.hl7.fhir.validation.cli.services.StandAloneValidatorFetcher.IPackageInstaller; import org.hl7.fhir.validation.cli.utils.*; import org.hl7.fhir.validation.instance.InstanceValidator; import org.w3c.dom.Document; import org.xml.sax.SAXException; -import org.xmlpull.v1.builder.XmlDocument; -import com.google.gson.JsonObject; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import java.io.*; +import java.net.HttpURLConnection; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLConnection; +import java.util.*; +import java.util.Map.Entry; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; /* @@ -160,92 +98,47 @@ POSSIBILITY OF SUCH DAMAGE. /** * This is just a wrapper around the InstanceValidator class for convenient use - * + *

* The following resource formats are supported: XML, JSON, Turtle * The following versions are supported: 1.0.2, 1.4.0, 3.0.2, 4.0.1, and current - * + *

* Note: the validation engine is intended to be threadsafe * To Use: - * + *

* 1/ Initialize - * ValidationEngine validator = new ValidationEngine(src); - * - this must be the packageId of the relevant core specification - * for the version you want to validate against (e.g. hl7.fhir.r4.core) - * - * validator.connectToTSServer(txServer); - * - this is optional; in the absence of a terminology service, snomed, loinc etc will not be validated - * - * validator.loadIg(src); - * - call this any number of times for the Implementation Guide(s) of interest. - * - See https://confluence.hl7.org/display/FHIR/Using+the+FHIR+Validator for documentation about the src parameter (-ig parameter) - * - * validator.loadQuestionnaire(src) - * - url or filename of a questionnaire to load. Any loaded questionnaires will be used while validating - * - * validator.setNative(doNative); - * - whether to do xml/json/rdf schema validation as well - * - * You only need to do this initialization once. You can validate as many times as you like - * + * ValidationEngine validator = new ValidationEngine(src); + * - this must be the packageId of the relevant core specification + * for the version you want to validate against (e.g. hl7.fhir.r4.core) + *

+ * validator.connectToTSServer(txServer); + * - this is optional; in the absence of a terminology service, snomed, loinc etc will not be validated + *

+ * validator.loadIg(src); + * - call this any number of times for the Implementation Guide(s) of interest. + * - See https://confluence.hl7.org/display/FHIR/Using+the+FHIR+Validator for documentation about the src parameter (-ig parameter) + *

+ * validator.loadQuestionnaire(src) + * - url or filename of a questionnaire to load. Any loaded questionnaires will be used while validating + *

+ * validator.setNative(doNative); + * - whether to do xml/json/rdf schema validation as well + *

+ * You only need to do this initialization once. You can validate as many times as you like + *

* 2. validate - * validator.validate(src, profiles); - * - source (as stream, byte[]), or url or filename of a resource to validate. - * Also validate against any profiles (as canonical URLS, equivalent to listing them in Resource.meta.profile) - * - * if the source is provided as byte[] or stream, you need to provide a format too, though you can - * leave that as null, and the validator will guess - * + * validator.validate(src, profiles); + * - source (as stream, byte[]), or url or filename of a resource to validate. + * Also validate against any profiles (as canonical URLS, equivalent to listing them in Resource.meta.profile) + *

+ * if the source is provided as byte[] or stream, you need to provide a format too, though you can + * leave that as null, and the validator will guess + *

* 3. Or, instead of validating, transform (see documentation and use in Validator.java) * * @author Grahame Grieve - * */ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInstaller { - public class ValidationRecord { - - private String location; - private List messages; - int err = 0; - int warn = 0; - int info = 0; - - public ValidationRecord(String location, List messages) { - this.location = location; - this.messages = messages; - for (ValidationMessage vm : messages) { - if (vm.getLevel().equals(ValidationMessage.IssueSeverity.FATAL)||vm.getLevel().equals(ValidationMessage.IssueSeverity.ERROR)) - err++; - else if (vm.getLevel().equals(ValidationMessage.IssueSeverity.WARNING)) - warn++; - else if (!vm.isSignpost()) { - info++; - } - } - } - - public String getLocation() { - return location; - } - - public List getMessages() { - return messages; - } - - public int getErr() { - return err; - } - - public int getWarn() { - return warn; - } - - public int getInfo() { - return info; - } - - } - public class TransformSupportServices implements ITransformerServices { private List outputs; @@ -292,7 +185,6 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst } private SimpleWorkerContext context; -// private FHIRPathEngine fpe; private Map binaries = new HashMap(); private boolean doNative; private boolean noInvariantChecks; @@ -321,34 +213,6 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst initContext(null); } - public String setTerminologyServer(String src, String log, FhirPublication version) throws FHIRException, URISyntaxException { - return connectToTSServer(src, log, version); - } - - public boolean isHintAboutNonMustSupport() { - return hintAboutNonMustSupport; - } - - public void setHintAboutNonMustSupport(boolean hintAboutNonMustSupport) { - this.hintAboutNonMustSupport = hintAboutNonMustSupport; - } - - public boolean isAnyExtensionsAllowed() { - return anyExtensionsAllowed; - } - - public void setAnyExtensionsAllowed(boolean anyExtensionsAllowed) { - this.anyExtensionsAllowed = anyExtensionsAllowed; - } - - public boolean isShowTimes() { - return showTimes; - } - - public void setShowTimes(boolean showTimes) { - this.showTimes = showTimes; - } - public ValidationEngine(String src, String txsrvr, String txLog, FhirPublication version, boolean canRunWithoutTerminologyServer, String vString) throws FHIRException, IOException, URISyntaxException { pcm = new FilesystemPackageCacheManager(true, ToolsVersion.TOOLS_VERSION); loadCoreDefinitions(src, false, null); @@ -376,14 +240,6 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst pcm = new FilesystemPackageCacheManager(true, ToolsVersion.TOOLS_VERSION); } - public String getLanguage() { - return language; - } - - public void setLanguage(String language) { - this.language = language; - } - private void loadCoreDefinitions(String src, boolean recursive, TimeTracker tt) throws FHIRException, IOException { if (pcm == null) { pcm = new FilesystemPackageCacheManager(true, ToolsVersion.TOOLS_VERSION); @@ -423,15 +279,15 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst if (Utilities.noString(version)) return null; if (version.startsWith("1.0")) - return new R2ToR5Loader(new String[] { "Conformance", "StructureDefinition", "ValueSet", "SearchParameter", "OperationDefinition", "Questionnaire","ConceptMap","StructureMap", "NamingSystem"}, new NullLoaderKnowledgeProvider()); + return new R2ToR5Loader(new String[]{"Conformance", "StructureDefinition", "ValueSet", "SearchParameter", "OperationDefinition", "Questionnaire", "ConceptMap", "StructureMap", "NamingSystem"}, new NullLoaderKnowledgeProvider()); if (version.startsWith("1.4")) - return new R2016MayToR5Loader(new String[] { "Conformance", "StructureDefinition", "ValueSet", "CodeSystem", "SearchParameter", "OperationDefinition", "Questionnaire","ConceptMap","StructureMap", "NamingSystem"}, new NullLoaderKnowledgeProvider()); // special case + return new R2016MayToR5Loader(new String[]{"Conformance", "StructureDefinition", "ValueSet", "CodeSystem", "SearchParameter", "OperationDefinition", "Questionnaire", "ConceptMap", "StructureMap", "NamingSystem"}, new NullLoaderKnowledgeProvider()); // special case if (version.startsWith("3.0")) - return new R3ToR5Loader(new String[] { "CapabilityStatement", "StructureDefinition", "ValueSet", "CodeSystem", "SearchParameter", "OperationDefinition", "Questionnaire","ConceptMap","StructureMap", "NamingSystem"}, new NullLoaderKnowledgeProvider()); + return new R3ToR5Loader(new String[]{"CapabilityStatement", "StructureDefinition", "ValueSet", "CodeSystem", "SearchParameter", "OperationDefinition", "Questionnaire", "ConceptMap", "StructureMap", "NamingSystem"}, new NullLoaderKnowledgeProvider()); if (version.startsWith("4.0")) - return new R4ToR5Loader(new String[] { "CapabilityStatement", "StructureDefinition", "ValueSet", "CodeSystem", "SearchParameter", "OperationDefinition", "Questionnaire","ConceptMap","StructureMap", "NamingSystem"}, new NullLoaderKnowledgeProvider()); + return new R4ToR5Loader(new String[]{"CapabilityStatement", "StructureDefinition", "ValueSet", "CodeSystem", "SearchParameter", "OperationDefinition", "Questionnaire", "ConceptMap", "StructureMap", "NamingSystem"}, new NullLoaderKnowledgeProvider()); if (version.startsWith("5.0")) - return new R5ToR5Loader(new String[] { "CapabilityStatement", "StructureDefinition", "ValueSet", "CodeSystem", "SearchParameter", "OperationDefinition", "Questionnaire","ConceptMap","StructureMap", "NamingSystem"}, new NullLoaderKnowledgeProvider()); + return new R5ToR5Loader(new String[]{"CapabilityStatement", "StructureDefinition", "ValueSet", "CodeSystem", "SearchParameter", "OperationDefinition", "Questionnaire", "ConceptMap", "StructureMap", "NamingSystem"}, new NullLoaderKnowledgeProvider()); return null; } @@ -452,14 +308,17 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst } private Parameters makeExpProfile() { - Parameters ep = new Parameters(); + Parameters ep = new Parameters(); ep.addParameter("profile-url", "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891"); // change this to blow the cache // all defaults.... return ep; } - /** explore should be true if we're trying to load an -ig parameter, and false if we're loading source - * @throws IOException **/ + /** + * explore should be true if we're trying to load an -ig parameter, and false if we're loading source + * + * @throws IOException + **/ private Map loadIgSource(String src, boolean recursive, boolean explore) throws FHIRException, IOException { // src can be one of the following: @@ -470,14 +329,14 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst if (Common.isNetworkPath(src)) { String v = null; if (src.contains("|")) { - v = src.substring(src.indexOf("|")+1); + v = src.substring(src.indexOf("|") + 1); src = src.substring(0, src.indexOf("|")); } String pid = explore ? pcm.getPackageId(src) : null; if (!Utilities.noString(pid)) - return fetchByPackage(pid+(v == null ? "" : "#"+v)); + return fetchByPackage(pid + (v == null ? "" : "#" + v)); else - return fetchFromUrl(src+(v == null ? "" : "|"+v), explore); + return fetchFromUrl(src + (v == null ? "" : "|" + v), explore); } File f = new File(Utilities.path(src)); @@ -499,35 +358,35 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst FhirFormat fmt = checkIsResource(src); if (fmt != null) { Map res = new HashMap(); - res.put(Utilities.changeFileExt(src, "."+fmt.getExtension()), TextFile.fileToBytesNCS(src)); + res.put(Utilities.changeFileExt(src, "." + fmt.getExtension()), TextFile.fileToBytesNCS(src)); return res; } } else if ((src.matches(FilesystemPackageCacheManager.PACKAGE_REGEX) || src.matches(FilesystemPackageCacheManager.PACKAGE_VERSION_REGEX)) && !src.endsWith(".zip") && !src.endsWith(".tgz")) { return fetchByPackage(src); } - throw new FHIRException("Unable to find/resolve/read "+(explore ? "-ig " : "")+src); + throw new FHIRException("Unable to find/resolve/read " + (explore ? "-ig " : "") + src); } private Map loadIgSourceForVersion(String src, boolean recursive, boolean explore, VersionSourceInformation versions) throws FHIRException, IOException { if (Common.isNetworkPath(src)) { String v = null; if (src.contains("|")) { - v = src.substring(src.indexOf("|")+1); + v = src.substring(src.indexOf("|") + 1); src = src.substring(0, src.indexOf("|")); } String pid = pcm.getPackageId(src); if (!Utilities.noString(pid)) { - versions.see(fetchVersionByPackage(pid+(v == null ? "" : "#"+v)), "Package "+src); + versions.see(fetchVersionByPackage(pid + (v == null ? "" : "#" + v)), "Package " + src); return null; } else { - return fetchVersionFromUrl(src+(v == null ? "" : "|"+v), explore, versions); + return fetchVersionFromUrl(src + (v == null ? "" : "|" + v), explore, versions); } } File f = new File(Utilities.path(src)); if (f.exists()) { if (f.isDirectory() && new File(Utilities.path(src, "package.tgz")).exists()) { - versions.see(loadPackageForVersion(new FileInputStream(Utilities.path(src, "package.tgz")), Utilities.path(src, "package.tgz")), "Package "+src); + versions.see(loadPackageForVersion(new FileInputStream(Utilities.path(src, "package.tgz")), Utilities.path(src, "package.tgz")), "Package " + src); return null; } if (f.isDirectory() && new File(Utilities.path(src, "igpack.zip")).exists()) @@ -537,7 +396,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst if (f.isDirectory()) return scanDirectory(f, recursive); if (src.endsWith(".tgz")) { - versions.see(loadPackageForVersion(new FileInputStream(src), src), "Package "+src); + versions.see(loadPackageForVersion(new FileInputStream(src), src), "Package " + src); return null; } if (src.endsWith(".pack")) @@ -547,14 +406,14 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst FhirFormat fmt = checkIsResource(src); if (fmt != null) { Map res = new HashMap(); - res.put(Utilities.changeFileExt(src, "."+fmt.getExtension()), TextFile.fileToBytesNCS(src)); + res.put(Utilities.changeFileExt(src, "." + fmt.getExtension()), TextFile.fileToBytesNCS(src)); return res; } } else if ((src.matches(FilesystemPackageCacheManager.PACKAGE_REGEX) || src.matches(FilesystemPackageCacheManager.PACKAGE_VERSION_REGEX)) && !src.endsWith(".zip") && !src.endsWith(".tgz")) { - versions.see(fetchVersionByPackage(src), "Package "+src); + versions.see(fetchVersionByPackage(src), "Package " + src); return null; } - throw new FHIRException("Unable to find/resolve/read -ig "+src); + throw new FHIRException("Unable to find/resolve/read -ig " + src); } @@ -594,21 +453,21 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst } } if (cnt == null) { - throw new FHIRException("Unable to fetch content from "+src+" ("+errors.toString()+")"); + throw new FHIRException("Unable to fetch content from " + src + " (" + errors.toString() + ")"); } FhirFormat fmt = checkFormat(cnt, src); if (fmt != null) { Map res = new HashMap(); - res.put(Utilities.changeFileExt(src, "."+fmt.getExtension()), cnt); + res.put(Utilities.changeFileExt(src, "." + fmt.getExtension()), cnt); return res; } - throw new FHIRException("Unable to read content from "+src+": cannot determine format"); + throw new FHIRException("Unable to read content from " + src + ": cannot determine format"); } private Map fetchVersionFromUrl(String src, boolean explore, VersionSourceInformation versions) throws FHIRException, IOException { if (src.endsWith(".tgz")) { - versions.see(loadPackageForVersion(fetchFromUrlSpecific(src, false), src), "From Package "+src); + versions.see(loadPackageForVersion(fetchFromUrlSpecific(src, false), src), "From Package " + src); return null; } if (src.endsWith(".pack")) @@ -620,7 +479,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst if (explore) { stream = fetchFromUrlSpecific(Utilities.pathURL(src, "package.tgz"), true); if (stream != null) { - versions.see(loadPackageForVersion(stream, Utilities.pathURL(src, "package.tgz")), "From Package at "+src); + versions.see(loadPackageForVersion(stream, Utilities.pathURL(src, "package.tgz")), "From Package at " + src); return null; } // todo: these options are deprecated - remove once all IGs have been rebuilt post R4 technical correction @@ -644,15 +503,15 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst FhirFormat fmt = checkIsResource(cnt, src); if (fmt != null) { Map res = new HashMap(); - res.put(Utilities.changeFileExt(src, "."+fmt.getExtension()), cnt); + res.put(Utilities.changeFileExt(src, "." + fmt.getExtension()), cnt); return res; } String fn = Utilities.path("[tmp]", "fetch-resource-error-content.bin"); TextFile.bytesToFile(cnt, fn); - System.out.println("Error Fetching "+src); - System.out.println("Some content was found, saved to "+fn); - System.out.println("1st 100 bytes = "+presentForDebugging(cnt)); - throw new FHIRException("Unable to find/resolve/read "+(explore ? "-ig " : "")+src); + System.out.println("Error Fetching " + src); + System.out.println("Some content was found, saved to " + fn); + System.out.println("1st 100 bytes = " + presentForDebugging(cnt)); + throw new FHIRException("Unable to find/resolve/read " + (explore ? "-ig " : "") + src); } private String presentForDebugging(byte[] cnt) { @@ -665,7 +524,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst private InputStream fetchFromUrlSpecific(String source, boolean optional) throws FHIRException, IOException { try { - URL url = new URL(source+"?nocache=" + System.currentTimeMillis()); + URL url = new URL(source + "?nocache=" + System.currentTimeMillis()); URLConnection c = url.openConnection(); return c.getInputStream(); } catch (IOException e) { @@ -680,7 +539,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst try { try { // try with cache-busting option and then try withhout in case the server doesn't support that - URL url = new URL(source+"?nocache=" + System.currentTimeMillis()); + URL url = new URL(source + "?nocache=" + System.currentTimeMillis()); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestProperty("Accept", contentType); return TextFile.streamToBytes(conn.getInputStream()); @@ -692,7 +551,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst } } catch (IOException e) { if (errors != null) { - errors.add("Error accessing "+source+": "+e.getMessage()); + errors.add("Error accessing " + source + ": " + e.getMessage()); } if (optional) return null; @@ -704,13 +563,12 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst private Map scanDirectory(File f, boolean recursive) throws FileNotFoundException, IOException { Map res = new HashMap<>(); for (File ff : f.listFiles()) { - if (ff.isDirectory() && recursive){ - res.putAll(scanDirectory(ff, true)); - } - else if (!isIgnoreFile(ff)) { + if (ff.isDirectory() && recursive) { + res.putAll(scanDirectory(ff, true)); + } else if (!isIgnoreFile(ff)) { FhirFormat fmt = checkIsResource(ff.getAbsolutePath()); if (fmt != null) { - res.put(Utilities.changeFileExt(ff.getName(), "."+fmt.getExtension()), TextFile.fileToBytes(ff.getAbsolutePath())); + res.put(Utilities.changeFileExt(ff.getName(), "." + fmt.getExtension()), TextFile.fileToBytes(ff.getAbsolutePath())); } } } @@ -718,7 +576,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst } private boolean isIgnoreFile(File ff) { - if (ff.getName().startsWith(".")|| ff.getAbsolutePath().contains(".git")){ + if (ff.getName().startsWith(".") || ff.getAbsolutePath().contains(".git")) { return true; } return Utilities.existsInList(Utilities.getFileExtension(ff.getName()).toLowerCase(), "md", "css", "js", "png", "gif", "jpg", "html", "tgz", "pack", "zip"); @@ -733,33 +591,33 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst } public Map loadPackage(NpmPackage pi) throws FHIRException, IOException { - context.getLoadedPackages().add(pi.name()+"#"+pi.version()); + context.getLoadedPackages().add(pi.name() + "#" + pi.version()); Map res = new HashMap(); for (String s : pi.dependencies()) { - if (s.endsWith(".x") && s.length()>2) { - String packageMajorMinor = s.substring(0, s.length()-2); + if (s.endsWith(".x") && s.length() > 2) { + String packageMajorMinor = s.substring(0, s.length() - 2); boolean found = false; - for (int i=0; i -1) { - b.write(buf, 0, n); - } + String name = ze.getName(); + InputStream in = zip; + ByteArrayOutputStream b = new ByteArrayOutputStream(); + int n; + byte[] buf = new byte[1024]; + while ((n = in.read(buf, 0, 1024)) > -1) { + b.write(buf, 0, n); + } res.put(name, b.toByteArray()); zip.closeEntry(); } @@ -793,7 +651,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst String version = null; if (src.contains("#")) { id = src.substring(0, src.indexOf("#")); - version = src.substring(src.indexOf("#")+1); + version = src.substring(src.indexOf("#") + 1); } if (pcm == null) { log("Creating Package manager?"); @@ -806,7 +664,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst if (version == null) { pi = pcm.loadPackageFromCacheOnly(id); if (pi != null) - log(" ... Using version "+pi.version()); + log(" ... Using version " + pi.version()); } else pi = pcm.loadPackageFromCacheOnly(id, version); if (pi == null) { @@ -820,7 +678,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst String version = null; if (src.contains("#")) { id = src.substring(0, src.indexOf("#")); - version = src.substring(src.indexOf("#")+1); + version = src.substring(src.indexOf("#") + 1); } if (pcm == null) { log("Creating Package manager?"); @@ -833,7 +691,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst if (version == null) { pi = pcm.loadPackageFromCacheOnly(id); if (pi != null) - log(" ... Using version "+pi.version()); + log(" ... Using version " + pi.version()); } else pi = pcm.loadPackageFromCacheOnly(id, version); if (pi == null) { @@ -846,7 +704,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst private Map resolvePackage(String id, String v) throws FHIRException, IOException { NpmPackage pi = pcm.loadPackage(id, v); if (pi != null && v == null) - log(" ... Using version "+pi.version()); + log(" ... Using version " + pi.version()); return loadPackage(pi); } @@ -859,23 +717,14 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst return context; } - - public boolean isNoInvariantChecks() { - return noInvariantChecks; - } - - public void setNoInvariantChecks(boolean value) { - this.noInvariantChecks = value; - } - private FhirFormat checkFormat(byte[] cnt, String filename) { - System.out.println(" ..Detect format for "+filename); + System.out.println(" ..Detect format for " + filename); try { JsonTrackingParser.parseJson(cnt); return FhirFormat.JSON; } catch (Exception e) { if (debug) { - System.out.println("Not JSON: "+e.getMessage()); + System.out.println("Not JSON: " + e.getMessage()); } } try { @@ -883,7 +732,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst return FhirFormat.XML; } catch (Exception e) { if (debug) { - System.out.println("Not XML: "+e.getMessage()); + System.out.println("Not XML: " + e.getMessage()); } } try { @@ -891,7 +740,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst return FhirFormat.TURTLE; } catch (Exception e) { if (debug) { - System.out.println("Not Turtle: "+e.getMessage()); + System.out.println("Not Turtle: " + e.getMessage()); } } try { @@ -899,23 +748,23 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst return FhirFormat.TEXT; } catch (Exception e) { if (debug) { - System.out.println("Not Text: "+e.getMessage()); + System.out.println("Not Text: " + e.getMessage()); } } if (debug) - System.out.println(" .. not a resource: "+filename); + System.out.println(" .. not a resource: " + filename); return null; } private FhirFormat checkIsResource(byte[] cnt, String filename) { - System.out.println(" ..Detect format for "+filename); + System.out.println(" ..Detect format for " + filename); try { Manager.parse(context, new ByteArrayInputStream(cnt), FhirFormat.JSON); return FhirFormat.JSON; } catch (Exception e) { if (debug) { - System.out.println("Not JSON: "+e.getMessage()); + System.out.println("Not JSON: " + e.getMessage()); } } try { @@ -923,15 +772,15 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst return FhirFormat.XML; } catch (Exception e) { if (debug) { - System.out.println("Not XML: "+e.getMessage()); + System.out.println("Not XML: " + e.getMessage()); } } try { - Manager.parse(context, new ByteArrayInputStream(cnt),FhirFormat.TURTLE); + Manager.parse(context, new ByteArrayInputStream(cnt), FhirFormat.TURTLE); return FhirFormat.TURTLE; } catch (Exception e) { if (debug) { - System.out.println("Not Turtle: "+e.getMessage()); + System.out.println("Not Turtle: " + e.getMessage()); } } try { @@ -939,11 +788,11 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst return FhirFormat.TEXT; } catch (Exception e) { if (debug) { - System.out.println("Not Text: "+e.getMessage()); + System.out.println("Not Text: " + e.getMessage()); } } if (debug) - System.out.println(" .. not a resource: "+filename); + System.out.println(" .. not a resource: " + filename); return null; } @@ -987,7 +836,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst return context.connectToTSServer(TerminologyClientFactory.makeClient(url, version), log); } catch (Exception e) { if (context.isCanRunWithoutTerminology()) { - return "n/a: Running without Terminology Server (error: "+e.getMessage()+")"; + return "n/a: Running without Terminology Server (error: " + e.getMessage() + ")"; } else throw e; } @@ -1009,7 +858,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst public void scanForIgVersion(String src, boolean recursive, VersionSourceInformation versions) throws IOException, FHIRException, Exception { Map source = loadIgSourceForVersion(src, recursive, true, versions); if (source != null && source.containsKey("version.info")) - versions.see(readInfoVersion(source.get("version.info")), "version.info in "+src); + versions.see(readInfoVersion(source.get("version.info")), "version.info in " + src); } public void loadIg(String src, boolean recursive) throws IOException, FHIRException { @@ -1024,10 +873,10 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst } System.out.print(" Load " + src); if (!src.contains("#")) { - System.out.print("#"+npm.version()); + System.out.print("#" + npm.version()); } int count = context.loadFromPackage(npm, loaderForVersion(npm.fhirVersion())); - System.out.println(" - "+count+" resources ("+context.clock().milestone()+")"); + System.out.println(" - " + count + " resources (" + context.clock().milestone() + ")"); } else { System.out.print(" Load " + src); String canonical = null; @@ -1062,13 +911,13 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst if (canonical != null) { grabNatives(source, canonical); } - System.out.println(" - "+count+" resources ("+context.clock().milestone()+")"); + System.out.println(" - " + count + " resources (" + context.clock().milestone() + ")"); } } public Resource loadFileWithErrorChecking(String version, Entry t, String fn) { if (debug) - System.out.print("* load file: "+fn); + System.out.print("* load file: " + fn); Resource r = null; try { r = loadResourceByVersion(version, t.getValue(), fn); @@ -1076,9 +925,9 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst System.out.println(" .. success"); } catch (Exception e) { if (!debug) { - System.out.print("* load file: "+fn); + System.out.print("* load file: " + fn); } - System.out.println(" - ignored due to error: "+(e.getMessage() == null ? " (null - NPE)" : e.getMessage())); + System.out.println(" - ignored due to error: " + (e.getMessage() == null ? " (null - NPE)" : e.getMessage())); if (debug || ((e.getMessage() != null && e.getMessage().contains("cannot be cast")))) { e.printStackTrace(); } @@ -1094,10 +943,10 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst res = new org.hl7.fhir.dstu3.formats.XmlParser().parse(new ByteArrayInputStream(content)); else if (fn.endsWith(".json") && !fn.endsWith("template.json")) res = new org.hl7.fhir.dstu3.formats.JsonParser().parse(new ByteArrayInputStream(content)); - else if (fn.endsWith(".txt") || fn.endsWith(".map") ) + else if (fn.endsWith(".txt") || fn.endsWith(".map")) res = new org.hl7.fhir.dstu3.utils.StructureMapUtilities(null).parse(new String(content)); else - throw new FHIRException("Unsupported format for "+fn); + throw new FHIRException("Unsupported format for " + fn); r = VersionConvertor_30_50.convertResource(res, false); } else if (version.startsWith("4.0")) { org.hl7.fhir.r4.model.Resource res; @@ -1105,10 +954,10 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst res = new org.hl7.fhir.r4.formats.XmlParser().parse(new ByteArrayInputStream(content)); else if (fn.endsWith(".json") && !fn.endsWith("template.json")) res = new org.hl7.fhir.r4.formats.JsonParser().parse(new ByteArrayInputStream(content)); - else if (fn.endsWith(".txt") || fn.endsWith(".map") ) + else if (fn.endsWith(".txt") || fn.endsWith(".map")) res = new org.hl7.fhir.r4.utils.StructureMapUtilities(null).parse(new String(content), fn); else - throw new FHIRException("Unsupported format for "+fn); + throw new FHIRException("Unsupported format for " + fn); r = VersionConvertor_40_50.convertResource(res); } else if (version.startsWith("1.4")) { org.hl7.fhir.dstu2016may.model.Resource res; @@ -1117,7 +966,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst else if (fn.endsWith(".json") && !fn.endsWith("template.json")) res = new org.hl7.fhir.dstu2016may.formats.JsonParser().parse(new ByteArrayInputStream(content)); else - throw new FHIRException("Unsupported format for "+fn); + throw new FHIRException("Unsupported format for " + fn); r = VersionConvertor_14_50.convertResource(res); } else if (version.startsWith("1.0")) { org.hl7.fhir.dstu2.model.Resource res; @@ -1126,7 +975,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst else if (fn.endsWith(".json") && !fn.endsWith("template.json")) res = new org.hl7.fhir.dstu2.formats.JsonParser().parse(new ByteArrayInputStream(content)); else - throw new FHIRException("Unsupported format for "+fn); + throw new FHIRException("Unsupported format for " + fn); VersionConvertorAdvisor50 advisor = new org.hl7.fhir.convertors.misc.IGR2ConvertorAdvisor5(); r = VersionConvertor_10_50.convertResource(res, advisor); } else if (version.equals(Constants.VERSION) || "current".equals(version)) { @@ -1136,12 +985,12 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst r = new JsonParser().parse(new ByteArrayInputStream(content)); else if (fn.endsWith(".txt")) r = new StructureMapUtilities(context, null, null).parse(TextFile.bytesToString(content), fn); - else if (fn.endsWith(".txt") || fn.endsWith(".map") ) - r = new org.hl7.fhir.r5.utils.StructureMapUtilities(null).parse(new String(content), fn); + else if (fn.endsWith(".txt") || fn.endsWith(".map")) + r = new StructureMapUtilities(null).parse(new String(content), fn); else - throw new FHIRException("Unsupported format for "+fn); + throw new FHIRException("Unsupported format for " + fn); } else - throw new FHIRException("Unsupported version "+version); + throw new FHIRException("Unsupported version " + version); return r; } @@ -1159,29 +1008,16 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst private void grabNatives(Map source, String prefix) { for (Entry e : source.entrySet()) { if (e.getKey().endsWith(".zip")) - binaries.put(prefix+"#"+e.getKey(), e.getValue()); + binaries.put(prefix + "#" + e.getKey(), e.getValue()); } } - public void setQuestionnaireMode(QuestionnaireMode questionnaireMode) { - this.questionnaireMode = questionnaireMode; - } - - public void setNative(boolean doNative) { - this.doNative = doNative; - } - - private class Content { - byte[] focus = null; - FhirFormat cntType = null; - } - public Content loadContent(String source, String opName, boolean asIg) throws FHIRException, IOException { Map s = loadIgSource(source, false, asIg); Content res = new Content(); if (s.size() != 1) - throw new FHIRException("Unable to find resource " + source + " to "+opName); - for (Entry t: s.entrySet()) { + throw new FHIRException("Unable to find resource " + source + " to " + opName); + for (Entry t : s.entrySet()) { res.focus = t.getValue(); if (t.getKey().endsWith(".json")) res.cntType = FhirFormat.JSON; @@ -1211,7 +1047,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst for (String p : profiles) { StructureDefinition sd = context.fetchResource(StructureDefinition.class, p); if (sd == null) { - throw new Error("Unable to resolve profile "+p); + throw new Error("Unable to resolve profile " + p); } list.add(sd); } @@ -1222,7 +1058,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst public OperationOutcome validate(String source, List profiles) throws FHIRException, IOException { List l = new ArrayList(); l.add(source); - return (OperationOutcome)validate(l, profiles, null); + return (OperationOutcome) validate(l, profiles, null); } public List validateScan(List sources, Set guides) throws FHIRException, IOException, EOperationOutcome { @@ -1237,7 +1073,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst List messages = new ArrayList(); Element e = null; try { - System.out.println("Validate "+ref); + System.out.println("Validate " + ref); messages.clear(); e = validator.validate(null, messages, new ByteArrayInputStream(cnt.focus), cnt.cntType); res.add(new ScanOutputItem(ref, null, null, messagesToOutcome(messages))); @@ -1248,12 +1084,12 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst String rt = e.fhirType(); for (String u : guides) { ImplementationGuide ig = context.fetchResource(ImplementationGuide.class, u); - System.out.println("Check Guide "+ig.getUrl()); + System.out.println("Check Guide " + ig.getUrl()); String canonical = ig.getUrl().contains("/Impl") ? ig.getUrl().substring(0, ig.getUrl().indexOf("/Impl")) : ig.getUrl(); String url = getGlobal(ig, rt); if (url != null) { try { - System.out.println("Validate "+ref+" against "+ig.getUrl()); + System.out.println("Validate " + ref + " against " + ig.getUrl()); messages.clear(); validator.validate(null, messages, new ByteArrayInputStream(cnt.focus), cnt.cntType, url); res.add(new ScanOutputItem(ref, ig, null, messagesToOutcome(messages))); @@ -1267,7 +1103,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst done.add(sd.getUrl()); if (sd.getUrl().startsWith(canonical) && rt.equals(sd.getType())) { try { - System.out.println("Validate "+ref+" against "+sd.getUrl()); + System.out.println("Validate " + ref + " against " + sd.getUrl()); messages.clear(); validator.validate(null, messages, new ByteArrayInputStream(cnt.focus), cnt.cntType, asSdList(sd)); res.add(new ScanOutputItem(ref, ig, sd, messagesToOutcome(messages))); @@ -1304,28 +1140,28 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst Content cnt = loadContent(ref, "validate", false); String s = TextFile.bytesToString(cnt.focus); if (s.contains("http://hl7.org/fhir/3.0")) { - versions.see("3.0", "Profile in "+ref); + versions.see("3.0", "Profile in " + ref); } if (s.contains("http://hl7.org/fhir/1.0")) { - versions.see("1.0", "Profile in "+ref); + versions.see("1.0", "Profile in " + ref); } if (s.contains("http://hl7.org/fhir/4.0")) { - versions.see("4.0", "Profile in "+ref); + versions.see("4.0", "Profile in " + ref); } if (s.contains("http://hl7.org/fhir/1.4")) { - versions.see("1.4", "Profile in "+ref); + versions.see("1.4", "Profile in " + ref); } try { if (s.startsWith("{")) { - JsonObject json = JsonTrackingParser.parse(s, null); + JsonObject json = JsonTrackingParser.parse(s, null); if (json.has("fhirVersion")) { - versions.see(VersionUtilities.getMajMin(JSONUtil.str(json, "fhirVersion")), "fhirVersion in "+ref); + versions.see(VersionUtilities.getMajMin(JSONUtil.str(json, "fhirVersion")), "fhirVersion in " + ref); } } else { - Document doc = parseXml(cnt.focus); + Document doc = parseXml(cnt.focus); String v = XMLUtil.getNamedChildValue(doc.getDocumentElement(), "fhirVersion"); if (v != null) { - versions.see(VersionUtilities.getMajMin(v), "fhirVersion in "+ref); + versions.see(VersionUtilities.getMajMin(v), "fhirVersion in " + ref); } } } catch (Exception e) { @@ -1336,7 +1172,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst public Resource validate(List sources, List profiles, List record) throws FHIRException, IOException { if (profiles.size() > 0) { - System.out.println(" Profiles: "+profiles); + System.out.println(" Profiles: " + profiles); } List refs = new ArrayList(); boolean asBundle = parseSources(sources, refs); @@ -1354,7 +1190,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst results.addEntry().setResource(outcome); tts.end(); } catch (Exception e) { - System.out.println("Validation Infrastructure fail validating "+ref+": "+e.getMessage()); + System.out.println("Validation Infrastructure fail validating " + ref + ": " + e.getMessage()); tts.end(); throw new FHIRException(e); } @@ -1367,6 +1203,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst /** * Iterates through the list of passed in sources, extracting all references and populated them in the passed in list. + * * @return {@link Boolean#TRUE} if more than one reference is found. */ public boolean parseSources(List sources, List refs) throws IOException { @@ -1379,6 +1216,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst /** * Parses passed in resource path, adding any found references to the passed in list. + * * @return {@link Boolean#TRUE} if more than one reference is found. */ private boolean extractReferences(String name, List refs) throws IOException { @@ -1404,14 +1242,14 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst if (file.isFile()) { refs.add(name); } else { - for (int i=0; i < file.listFiles().length; i++) { + for (int i = 0; i < file.listFiles().length; i++) { File[] fileList = file.listFiles(); if (fileList[i].isFile()) refs.add(fileList[i].getPath()); } } } - return refs.size() > 1 ; + return refs.size() > 1; } public OperationOutcome validate(byte[] source, FhirFormat cntType, List profiles, List messages) throws FHIRException, IOException, EOperationOutcome { @@ -1430,7 +1268,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst InstanceValidator validator = getValidator(); validator.validate(null, messages, new ByteArrayInputStream(source), cntType, asSdList(profiles)); if (showTimes) { - System.out.println(location+": "+validator.reportTimes()); + System.out.println(location + ": " + validator.reportTimes()); } if (record != null) { record.add(new ValidationRecord(location, messages)); @@ -1476,7 +1314,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst try { fpe.parse(vm.getLocation()); } catch (Exception e) { - System.out.println("Internal error in location for message: '"+e.getMessage()+"', loc = '"+vm.getLocation()+"', err = '"+vm.getMessage()+"'"); + System.out.println("Internal error in location for message: '" + e.getMessage() + "', loc = '" + vm.getLocation() + "', err = '" + vm.getMessage() + "'"); } op.getIssue().add(OperationOutcomeUtilities.convertToIssue(vm, op)); } @@ -1500,7 +1338,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst org.hl7.fhir.r5.elementmodel.Element src = Manager.parse(context, new ByteArrayInputStream(source), cntType); StructureMap map = context.getTransform(mapUri); if (map == null) - throw new Error("Unable to find map "+mapUri+" (Known Maps = "+context.listMapUrls()+")"); + throw new Error("Unable to find map " + mapUri + " (Known Maps = " + context.listMapUrls() + ")"); org.hl7.fhir.r5.elementmodel.Element resource = getTargetResourceFromStructureMap(map); scu.transform(null, src, map, resource); @@ -1509,26 +1347,26 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst private org.hl7.fhir.r5.elementmodel.Element getTargetResourceFromStructureMap(StructureMap map) { String targetTypeUrl = null; - for(StructureMap.StructureMapStructureComponent component: map.getStructure()) { - if(component.getMode() == StructureMap.StructureMapModelMode.TARGET) { + for (StructureMap.StructureMapStructureComponent component : map.getStructure()) { + if (component.getMode() == StructureMap.StructureMapModelMode.TARGET) { targetTypeUrl = component.getUrl(); break; } } - if(targetTypeUrl == null) + if (targetTypeUrl == null) throw new FHIRException("Unable to determine resource URL for target type"); StructureDefinition structureDefinition = null; - for(StructureDefinition sd:this.context.getStructures()) { - if(sd.getUrl().equalsIgnoreCase(targetTypeUrl)) { + for (StructureDefinition sd : this.context.getStructures()) { + if (sd.getUrl().equalsIgnoreCase(targetTypeUrl)) { structureDefinition = sd; break; } } - if(structureDefinition == null) - throw new FHIRException("Unable to find StructureDefinition for target type ('"+targetTypeUrl+"')"); + if (structureDefinition == null) + throw new FHIRException("Unable to find StructureDefinition for target type ('" + targetTypeUrl + "')"); return Manager.build(getContext(), structureDefinition); } @@ -1576,14 +1414,6 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst } - public String getVersion() { - return version; - } - - public void setVersion(String version) { - this.version = version; - } - public InstanceValidator getValidator() { InstanceValidator validator = new InstanceValidator(context, null, null); validator.setHintAboutNonMustSupport(hintAboutNonMustSupport); @@ -1598,23 +1428,17 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst validator.setFetcher(this); validator.getImplementationGuides().addAll(igs); validator.getBundleValidationRules().addAll(bundleValidationRules); - validator.getValidationControl().putAll(validationControl ); + validator.getValidationControl().putAll(validationControl); validator.setQuestionnaireMode(questionnaireMode); return validator; } - public void setMapLog(String mapLog) throws FileNotFoundException { - if (mapLog != null) { - this.mapLog = new PrintWriter(mapLog); - } - } - public void prepare() { for (StructureDefinition sd : context.allStructures()) { try { makeSnapshot(sd); } catch (Exception e) { - System.out.println("Process Note: Unable to generate snapshot for "+sd.present()+": "+e.getMessage()); + System.out.println("Process Note: Unable to generate snapshot for " + sd.present() + ": " + e.getMessage()); if (debug) { e.printStackTrace(); } @@ -1633,22 +1457,14 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst } - public boolean isDebug() { - return debug; - } - - public void setDebug(boolean debug) { - this.debug = debug; - } - public void genScanOutput(String folder, List items) throws IOException, FHIRException, EOperationOutcome { String f = Utilities.path(folder, "comparison.zip"); download("http://fhir.org/archive/comparison.zip", f); unzip(f, folder); for (int i = 0; i < items.size(); i++) { - items.get(i).setId("c"+Integer.toString(i)); - genScanOutputItem(items.get(i), Utilities.path(folder, items.get(i).getId()+".html")); + items.get(i).setId("c" + Integer.toString(i)); + genScanOutputItem(items.get(i), Utilities.path(folder, items.get(i).getId() + ".html")); } StringBuilder b = new StringBuilder(); @@ -1697,7 +1513,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst b.append(""); for (String s : sorted(igs)) { ImplementationGuide ig = context.fetchResource(ImplementationGuide.class, s); - b.append(""+ig.present()+""); + b.append("" + ig.present() + ""); } b.append("\r\n"); b.append("SourceCore Spec"); @@ -1706,14 +1522,14 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst b.append("Global"); for (String sp : sorted(profiles.get(s))) { StructureDefinition sd = context.fetchResource(StructureDefinition.class, sp); - b.append(""+sd.present()+""); + b.append("" + sd.present() + ""); } } b.append("\r\n"); for (String s : sorted(refs)) { b.append(""); - b.append(""+s+""); + b.append("" + s + ""); b.append(genOutcome(items, s, null, null)); for (String si : sorted(igs)) { ImplementationGuide ig = context.fetchResource(ImplementationGuide.class, si); @@ -1730,7 +1546,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst b.append(""); b.append(""); for (String s : sorted(refs)) { - b.append(""); + b.append(""); } b.append("\r\n"); b.append(""); @@ -1741,7 +1557,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst for (String si : sorted(igs)) { b.append(""); ImplementationGuide ig = context.fetchResource(ImplementationGuide.class, si); - b.append(""); + b.append(""); b.append(""); for (String s : sorted(refs)) { b.append(genOutcome(items, s, si, null)); @@ -1751,7 +1567,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst for (String sp : sorted(profiles.get(ig.getUrl()))) { b.append(""); StructureDefinition sd = context.fetchResource(StructureDefinition.class, sp); - b.append(""); + b.append(""); for (String s : sorted(refs)) { b.append(genOutcome(items, s, si, sp)); } @@ -1790,31 +1606,31 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst } } if (ok) - return ""; + return ""; else - return ""; + return ""; } public void unzip(String zipFilePath, String destDirectory) throws IOException { File destDir = new File(destDirectory); if (!destDir.exists()) { - destDir.mkdir(); + destDir.mkdir(); } ZipInputStream zipIn = new ZipInputStream(new FileInputStream(zipFilePath)); ZipEntry entry = zipIn.getNextEntry(); // iterates over entries in the zip file while (entry != null) { - String filePath = destDirectory + File.separator + entry.getName(); - if (!entry.isDirectory()) { - // if the entry is a file, extracts it - extractFile(zipIn, filePath); - } else { - // if the entry is a directory, make the directory - File dir = new File(filePath); - dir.mkdir(); - } - zipIn.closeEntry(); - entry = zipIn.getNextEntry(); + String filePath = destDirectory + File.separator + entry.getName(); + if (!entry.isDirectory()) { + // if the entry is a file, extracts it + extractFile(zipIn, filePath); + } else { + // if the entry is a directory, make the directory + File dir = new File(filePath); + dir.mkdir(); + } + zipIn.closeEntry(); + entry = zipIn.getNextEntry(); } zipIn.close(); } @@ -1829,7 +1645,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst bos.write(bytesIn, 0, read); } bos.close(); -} + } private void download(String address, String filename) throws IOException { URL url = new URL(address); @@ -1857,11 +1673,11 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst StringBuilder b = new StringBuilder(); b.append(""); b.append(""); - b.append(""+title+""); + b.append("" + title + ""); b.append("\r\n"); b.append(""); b.append(""); - b.append("

"+title+"

"); + b.append("

" + title + "

"); b.append(s); b.append(""); b.append(""); @@ -1911,16 +1727,17 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst if (context.fetchResource(Resource.class, url) != null) return true; if (Utilities.existsInList(url, "http://hl7.org/fhir/sid/us-ssn", "http://hl7.org/fhir/sid/cvx", "http://hl7.org/fhir/sid/ndc", "http://hl7.org/fhir/sid/us-npi", "http://hl7.org/fhir/sid/icd-10", - "http://hl7.org/fhir/sid/icd-10-vn", "http://hl7.org/fhir/sid/icd-10-cm", "http://hl7.org/fhir/sid/icd-9-cm", "http://hl7.org/fhir/w5", "http://hl7.org/fhir/fivews", - "http://hl7.org/fhir/workflow", "http://hl7.org/fhir/ConsentPolicy/opt-out", "http://hl7.org/fhir/ConsentPolicy/opt-in")) { + "http://hl7.org/fhir/sid/icd-10-vn", "http://hl7.org/fhir/sid/icd-10-cm", "http://hl7.org/fhir/sid/icd-9-cm", "http://hl7.org/fhir/w5", "http://hl7.org/fhir/fivews", + "http://hl7.org/fhir/workflow", "http://hl7.org/fhir/ConsentPolicy/opt-out", "http://hl7.org/fhir/ConsentPolicy/opt-in")) { return true; } if (Utilities.existsInList(url, "http://loinc.org", "http://unitsofmeasure.org", "http://snomed.info/sct")) { - return true; - } + return true; + } if (fetcher != null) { return fetcher.resolveURL(appContext, path, url, type); - }; + } + ; return false; } @@ -1949,8 +1766,8 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst c.setDoOutput(true); c.setDoInput(true); c.setRequestMethod("POST"); - c.setRequestProperty( "Content-type", "application/fhir+xml"); - c.setRequestProperty( "Accept", "application/fhir+xml" ); + c.setRequestProperty("Content-type", "application/fhir+xml"); + c.setRequestProperty("Accept", "application/fhir+xml"); c.getOutputStream().write(bs.toByteArray()); c.getOutputStream().close(); @@ -1961,7 +1778,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst // System.out.println(line); // } // reader.close(); - throw new IOException("Unable to PUT to "+output+": "+c.getResponseMessage()); + throw new IOException("Unable to PUT to " + output + ": " + c.getResponseMessage()); } } else { FileOutputStream s = new FileOutputStream(output); @@ -1978,20 +1795,20 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst new org.hl7.fhir.dstu3.formats.XmlParser().setOutputStyle(org.hl7.fhir.dstu3.formats.IParser.OutputStyle.PRETTY).compose(s, res); else if (fn.endsWith(".json") && !fn.endsWith("template.json")) new org.hl7.fhir.dstu3.formats.JsonParser().setOutputStyle(org.hl7.fhir.dstu3.formats.IParser.OutputStyle.PRETTY).compose(s, res); - else if (fn.endsWith(".txt") || fn.endsWith(".map") ) + else if (fn.endsWith(".txt") || fn.endsWith(".map")) TextFile.stringToStream(org.hl7.fhir.dstu3.utils.StructureMapUtilities.render((org.hl7.fhir.dstu3.model.StructureMap) res), s, false); else - throw new FHIRException("Unsupported format for "+fn); + throw new FHIRException("Unsupported format for " + fn); } else if (version.startsWith("4.0")) { org.hl7.fhir.r4.model.Resource res = VersionConvertor_40_50.convertResource(r); if (fn.endsWith(".xml") && !fn.endsWith("template.xml")) new org.hl7.fhir.r4.formats.XmlParser().setOutputStyle(org.hl7.fhir.r4.formats.IParser.OutputStyle.PRETTY).compose(s, res); else if (fn.endsWith(".json") && !fn.endsWith("template.json")) new org.hl7.fhir.r4.formats.JsonParser().setOutputStyle(org.hl7.fhir.r4.formats.IParser.OutputStyle.PRETTY).compose(s, res); - else if (fn.endsWith(".txt") || fn.endsWith(".map") ) + else if (fn.endsWith(".txt") || fn.endsWith(".map")) TextFile.stringToStream(org.hl7.fhir.r4.utils.StructureMapUtilities.render((org.hl7.fhir.r4.model.StructureMap) res), s, false); else - throw new FHIRException("Unsupported format for "+fn); + throw new FHIRException("Unsupported format for " + fn); } else if (version.startsWith("1.4")) { org.hl7.fhir.dstu2016may.model.Resource res = VersionConvertor_14_50.convertResource(r); if (fn.endsWith(".xml") && !fn.endsWith("template.xml")) @@ -1999,7 +1816,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst else if (fn.endsWith(".json") && !fn.endsWith("template.json")) new org.hl7.fhir.dstu2016may.formats.JsonParser().setOutputStyle(org.hl7.fhir.dstu2016may.formats.IParser.OutputStyle.PRETTY).compose(s, res); else - throw new FHIRException("Unsupported format for "+fn); + throw new FHIRException("Unsupported format for " + fn); } else if (version.startsWith("1.0")) { VersionConvertorAdvisor50 advisor = new org.hl7.fhir.convertors.misc.IGR2ConvertorAdvisor5(); org.hl7.fhir.dstu2.model.Resource res = VersionConvertor_10_50.convertResource(r, advisor); @@ -2008,65 +1825,22 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst else if (fn.endsWith(".json") && !fn.endsWith("template.json")) new org.hl7.fhir.dstu2.formats.JsonParser().setOutputStyle(org.hl7.fhir.dstu2.formats.IParser.OutputStyle.PRETTY).compose(s, res); else - throw new FHIRException("Unsupported format for "+fn); + throw new FHIRException("Unsupported format for " + fn); } else if (version.equals(Constants.VERSION)) { if (fn.endsWith(".xml") && !fn.endsWith("template.xml")) new XmlParser().setOutputStyle(org.hl7.fhir.r5.formats.IParser.OutputStyle.PRETTY).compose(s, r); else if (fn.endsWith(".json") && !fn.endsWith("template.json")) new JsonParser().setOutputStyle(org.hl7.fhir.r5.formats.IParser.OutputStyle.PRETTY).compose(s, r); - else if (fn.endsWith(".txt") || fn.endsWith(".map") ) - TextFile.stringToStream(org.hl7.fhir.r5.utils.StructureMapUtilities.render((org.hl7.fhir.r5.model.StructureMap) r), s, false); + else if (fn.endsWith(".txt") || fn.endsWith(".map")) + TextFile.stringToStream(StructureMapUtilities.render((org.hl7.fhir.r5.model.StructureMap) r), s, false); else - throw new FHIRException("Unsupported format for "+fn); + throw new FHIRException("Unsupported format for " + fn); } else - throw new FHIRException("Encounted unsupported configured version "+version+" loading "+fn); + throw new FHIRException("Encounted unsupported configured version " + version + " loading " + fn); s.close(); } - public void setSnomedExtension(String sct) { - context.getExpansionParameters().addParameter("system-version", "http://snomed.info/sct|http://snomed.info/sct/"+sct); - } - - public IValidatorResourceFetcher getFetcher() { - return fetcher; - } - - public void setFetcher(IValidatorResourceFetcher fetcher) { - this.fetcher = fetcher; - } - - public void setAssumeValidRestReferences(boolean assumeValidRestReferences) { - this.assumeValidRestReferences = assumeValidRestReferences; - } - - public boolean isNoExtensibleBindingMessages() { - return noExtensibleBindingMessages; - } - - public void setNoExtensibleBindingMessages(boolean noExtensibleBindingMessages) { - this.noExtensibleBindingMessages = noExtensibleBindingMessages; - } - - - - public boolean isSecurityChecks() { - return securityChecks; - } - - public void setSecurityChecks(boolean securityChecks) { - this.securityChecks = securityChecks; - } - - - public boolean isCrumbTrails() { - return crumbTrails; - } - - public void setCrumbTrails(boolean crumbTrails) { - this.crumbTrails = crumbTrails; - } - public byte[] transformVersion(String source, String targetVer, FhirFormat format, Boolean canDoNative) throws FHIRException, IOException, Exception { Content cnt = loadContent(source, "validate", false); org.hl7.fhir.r5.elementmodel.Element src = Manager.parse(context, new ByteArrayInputStream(cnt.focus), cnt.cntType); @@ -2083,10 +1857,10 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst } else if (VersionUtilities.isR4Ver(version)) { return convertVersionNativeR4(targetVer, cnt, format); } else { - throw new FHIRException("Source version not supported yet: "+version); + throw new FHIRException("Source version not supported yet: " + version); } } catch (Exception e) { - System.out.println("Conversion failed using Java convertor: "+e.getMessage()); + System.out.println("Conversion failed using Java convertor: " + e.getMessage()); } } // ok, we try converting using the structure maps @@ -2098,7 +1872,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst StructureMapUtilities scu = new StructureMapUtilities(context, new TransformSupportServices(outputs)); StructureMap map = context.getTransform(url); if (map == null) - throw new Error("Unable to find map "+url+" (Known Maps = "+context.listMapUrls()+")"); + throw new Error("Unable to find map " + url + " (Known Maps = " + context.listMapUrls() + ")"); org.hl7.fhir.r5.elementmodel.Element resource = getTargetResourceFromStructureMap(map); scu.transform(null, src, map, resource); ByteArrayOutputStream bs = new ByteArrayOutputStream(); @@ -2109,226 +1883,225 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst private String getMapId(String type, String targetVer) { if (VersionUtilities.isR2Ver(version)) { if (VersionUtilities.isR3Ver(targetVer)) { - return "http://hl7.org/fhir/StructureMap/"+type+"2to3"; + return "http://hl7.org/fhir/StructureMap/" + type + "2to3"; } } else if (VersionUtilities.isR3Ver(version)) { if (VersionUtilities.isR2Ver(targetVer)) { - return "http://hl7.org/fhir/StructureMap/"+type+"3to2"; + return "http://hl7.org/fhir/StructureMap/" + type + "3to2"; } else if (VersionUtilities.isR4Ver(targetVer)) { - return "http://hl7.org/fhir/StructureMap/"+type+"3to4"; + return "http://hl7.org/fhir/StructureMap/" + type + "3to4"; } } else if (VersionUtilities.isR4Ver(version)) { if (VersionUtilities.isR3Ver(targetVer)) { - return "http://hl7.org/fhir/StructureMap/"+type+"4to3"; + return "http://hl7.org/fhir/StructureMap/" + type + "4to3"; } } - throw new FHIRException("Source/Target version not supported: "+version+" -> "+targetVer); + throw new FHIRException("Source/Target version not supported: " + version + " -> " + targetVer); } public byte[] convertVersionNativeR2(String targetVer, Content cnt, FhirFormat format) throws IOException, Exception { org.hl7.fhir.dstu2.model.Resource r2; switch (cnt.cntType) { - case JSON: - r2 = new org.hl7.fhir.dstu2.formats.JsonParser().parse(cnt.focus); - break; - case XML: - r2 = new org.hl7.fhir.dstu2.formats.XmlParser().parse(cnt.focus); - break; - default: - throw new FHIRException("Unsupported input format: "+cnt.cntType.toString()); + case JSON: + r2 = new org.hl7.fhir.dstu2.formats.JsonParser().parse(cnt.focus); + break; + case XML: + r2 = new org.hl7.fhir.dstu2.formats.XmlParser().parse(cnt.focus); + break; + default: + throw new FHIRException("Unsupported input format: " + cnt.cntType.toString()); } if (VersionUtilities.isR2Ver(targetVer)) { ByteArrayOutputStream bs = new ByteArrayOutputStream(); switch (format) { - case JSON: - new org.hl7.fhir.dstu2.formats.JsonParser().compose(bs, r2); - return bs.toByteArray(); - case XML: - new org.hl7.fhir.dstu2.formats.XmlParser().compose(bs, r2); - return bs.toByteArray(); - default: - throw new FHIRException("Unsupported output format: "+cnt.cntType.toString()); + case JSON: + new org.hl7.fhir.dstu2.formats.JsonParser().compose(bs, r2); + return bs.toByteArray(); + case XML: + new org.hl7.fhir.dstu2.formats.XmlParser().compose(bs, r2); + return bs.toByteArray(); + default: + throw new FHIRException("Unsupported output format: " + cnt.cntType.toString()); } } else if (VersionUtilities.isR2BVer(targetVer)) { org.hl7.fhir.dstu3.model.Resource r3 = VersionConvertor_10_30.convertResource(r2); org.hl7.fhir.dstu2016may.model.Resource r2b = VersionConvertor_14_30.convertResource(r3); ByteArrayOutputStream bs = new ByteArrayOutputStream(); switch (format) { - case JSON: - new org.hl7.fhir.dstu2016may.formats.JsonParser().compose(bs, r2b); - return bs.toByteArray(); - case XML: - new org.hl7.fhir.dstu2016may.formats.XmlParser().compose(bs, r2b); - return bs.toByteArray(); - default: - throw new FHIRException("Unsupported output format: "+cnt.cntType.toString()); + case JSON: + new org.hl7.fhir.dstu2016may.formats.JsonParser().compose(bs, r2b); + return bs.toByteArray(); + case XML: + new org.hl7.fhir.dstu2016may.formats.XmlParser().compose(bs, r2b); + return bs.toByteArray(); + default: + throw new FHIRException("Unsupported output format: " + cnt.cntType.toString()); } } else if (VersionUtilities.isR3Ver(targetVer)) { org.hl7.fhir.dstu3.model.Resource r3 = VersionConvertor_10_30.convertResource(r2); ByteArrayOutputStream bs = new ByteArrayOutputStream(); switch (format) { - case JSON: - new org.hl7.fhir.dstu3.formats.JsonParser().compose(bs, r3); - return bs.toByteArray(); - case XML: - new org.hl7.fhir.dstu3.formats.XmlParser().compose(bs, r3); - return bs.toByteArray(); - default: - throw new FHIRException("Unsupported output format: "+cnt.cntType.toString()); + case JSON: + new org.hl7.fhir.dstu3.formats.JsonParser().compose(bs, r3); + return bs.toByteArray(); + case XML: + new org.hl7.fhir.dstu3.formats.XmlParser().compose(bs, r3); + return bs.toByteArray(); + default: + throw new FHIRException("Unsupported output format: " + cnt.cntType.toString()); } } else if (VersionUtilities.isR4Ver(targetVer)) { org.hl7.fhir.r4.model.Resource r4 = VersionConvertor_10_40.convertResource(r2); ByteArrayOutputStream bs = new ByteArrayOutputStream(); switch (format) { - case JSON: - new org.hl7.fhir.r4.formats.JsonParser().compose(bs, r4); - return bs.toByteArray(); - case XML: - new org.hl7.fhir.r4.formats.XmlParser().compose(bs, r4); - return bs.toByteArray(); - default: - throw new FHIRException("Unsupported output format: "+cnt.cntType.toString()); + case JSON: + new org.hl7.fhir.r4.formats.JsonParser().compose(bs, r4); + return bs.toByteArray(); + case XML: + new org.hl7.fhir.r4.formats.XmlParser().compose(bs, r4); + return bs.toByteArray(); + default: + throw new FHIRException("Unsupported output format: " + cnt.cntType.toString()); } } else { - throw new FHIRException("Target Version not supported yet: "+targetVer); + throw new FHIRException("Target Version not supported yet: " + targetVer); } } public byte[] convertVersionNativeR2b(String targetVer, Content cnt, FhirFormat format) throws IOException, Exception { org.hl7.fhir.dstu2016may.model.Resource r2b; switch (cnt.cntType) { - case JSON: - r2b = new org.hl7.fhir.dstu2016may.formats.JsonParser().parse(cnt.focus); - break; - case XML: - r2b = new org.hl7.fhir.dstu2016may.formats.XmlParser().parse(cnt.focus); - break; - default: - throw new FHIRException("Unsupported input format: "+cnt.cntType.toString()); + case JSON: + r2b = new org.hl7.fhir.dstu2016may.formats.JsonParser().parse(cnt.focus); + break; + case XML: + r2b = new org.hl7.fhir.dstu2016may.formats.XmlParser().parse(cnt.focus); + break; + default: + throw new FHIRException("Unsupported input format: " + cnt.cntType.toString()); } if (VersionUtilities.isR2Ver(targetVer)) { org.hl7.fhir.dstu3.model.Resource r3 = VersionConvertor_14_30.convertResource(r2b); org.hl7.fhir.dstu2.model.Resource r2 = VersionConvertor_10_30.convertResource(r3); ByteArrayOutputStream bs = new ByteArrayOutputStream(); switch (format) { - case JSON: - new org.hl7.fhir.dstu2.formats.JsonParser().compose(bs, r2); - return bs.toByteArray(); - case XML: - new org.hl7.fhir.dstu2.formats.XmlParser().compose(bs, r2); - return bs.toByteArray(); - default: - throw new FHIRException("Unsupported output format: "+cnt.cntType.toString()); + case JSON: + new org.hl7.fhir.dstu2.formats.JsonParser().compose(bs, r2); + return bs.toByteArray(); + case XML: + new org.hl7.fhir.dstu2.formats.XmlParser().compose(bs, r2); + return bs.toByteArray(); + default: + throw new FHIRException("Unsupported output format: " + cnt.cntType.toString()); } } else if (VersionUtilities.isR2BVer(targetVer)) { ByteArrayOutputStream bs = new ByteArrayOutputStream(); switch (format) { - case JSON: - new org.hl7.fhir.dstu2016may.formats.JsonParser().compose(bs, r2b); - return bs.toByteArray(); - case XML: - new org.hl7.fhir.dstu2016may.formats.XmlParser().compose(bs, r2b); - return bs.toByteArray(); - default: - throw new FHIRException("Unsupported output format: "+cnt.cntType.toString()); + case JSON: + new org.hl7.fhir.dstu2016may.formats.JsonParser().compose(bs, r2b); + return bs.toByteArray(); + case XML: + new org.hl7.fhir.dstu2016may.formats.XmlParser().compose(bs, r2b); + return bs.toByteArray(); + default: + throw new FHIRException("Unsupported output format: " + cnt.cntType.toString()); } } else if (VersionUtilities.isR3Ver(targetVer)) { org.hl7.fhir.dstu3.model.Resource r3 = VersionConvertor_14_30.convertResource(r2b); ByteArrayOutputStream bs = new ByteArrayOutputStream(); switch (format) { - case JSON: - new org.hl7.fhir.dstu3.formats.JsonParser().compose(bs, r3); - return bs.toByteArray(); - case XML: - new org.hl7.fhir.dstu3.formats.XmlParser().compose(bs, r3); - return bs.toByteArray(); - default: - throw new FHIRException("Unsupported output format: "+cnt.cntType.toString()); + case JSON: + new org.hl7.fhir.dstu3.formats.JsonParser().compose(bs, r3); + return bs.toByteArray(); + case XML: + new org.hl7.fhir.dstu3.formats.XmlParser().compose(bs, r3); + return bs.toByteArray(); + default: + throw new FHIRException("Unsupported output format: " + cnt.cntType.toString()); } } else if (VersionUtilities.isR4Ver(targetVer)) { org.hl7.fhir.r4.model.Resource r4 = VersionConvertor_14_40.convertResource(r2b); ByteArrayOutputStream bs = new ByteArrayOutputStream(); switch (format) { - case JSON: - new org.hl7.fhir.r4.formats.JsonParser().compose(bs, r4); - return bs.toByteArray(); - case XML: - new org.hl7.fhir.r4.formats.XmlParser().compose(bs, r4); - return bs.toByteArray(); - default: - throw new FHIRException("Unsupported output format: "+cnt.cntType.toString()); + case JSON: + new org.hl7.fhir.r4.formats.JsonParser().compose(bs, r4); + return bs.toByteArray(); + case XML: + new org.hl7.fhir.r4.formats.XmlParser().compose(bs, r4); + return bs.toByteArray(); + default: + throw new FHIRException("Unsupported output format: " + cnt.cntType.toString()); } } else { - throw new FHIRException("Target Version not supported yet: "+targetVer); + throw new FHIRException("Target Version not supported yet: " + targetVer); } } - public byte[] convertVersionNativeR3(String targetVer, Content cnt, FhirFormat format) throws IOException, Exception { org.hl7.fhir.dstu3.model.Resource r3; switch (cnt.cntType) { - case JSON: - r3 = new org.hl7.fhir.dstu3.formats.JsonParser().parse(cnt.focus); - break; - case XML: - r3 = new org.hl7.fhir.dstu3.formats.XmlParser().parse(cnt.focus); - break; - default: - throw new FHIRException("Unsupported input format: "+cnt.cntType.toString()); + case JSON: + r3 = new org.hl7.fhir.dstu3.formats.JsonParser().parse(cnt.focus); + break; + case XML: + r3 = new org.hl7.fhir.dstu3.formats.XmlParser().parse(cnt.focus); + break; + default: + throw new FHIRException("Unsupported input format: " + cnt.cntType.toString()); } if (VersionUtilities.isR2Ver(targetVer)) { org.hl7.fhir.dstu2.model.Resource r2 = VersionConvertor_10_30.convertResource(r3); ByteArrayOutputStream bs = new ByteArrayOutputStream(); switch (format) { - case JSON: - new org.hl7.fhir.dstu2.formats.JsonParser().compose(bs, r2); - return bs.toByteArray(); - case XML: - new org.hl7.fhir.dstu2.formats.XmlParser().compose(bs, r2); - return bs.toByteArray(); - default: - throw new FHIRException("Unsupported output format: "+cnt.cntType.toString()); + case JSON: + new org.hl7.fhir.dstu2.formats.JsonParser().compose(bs, r2); + return bs.toByteArray(); + case XML: + new org.hl7.fhir.dstu2.formats.XmlParser().compose(bs, r2); + return bs.toByteArray(); + default: + throw new FHIRException("Unsupported output format: " + cnt.cntType.toString()); } } else if (VersionUtilities.isR2BVer(targetVer)) { org.hl7.fhir.dstu2016may.model.Resource r2b = VersionConvertor_14_30.convertResource(r3); ByteArrayOutputStream bs = new ByteArrayOutputStream(); switch (format) { - case JSON: - new org.hl7.fhir.dstu2016may.formats.JsonParser().compose(bs, r2b); - return bs.toByteArray(); - case XML: - new org.hl7.fhir.dstu2016may.formats.XmlParser().compose(bs, r2b); - return bs.toByteArray(); - default: - throw new FHIRException("Unsupported output format: "+cnt.cntType.toString()); + case JSON: + new org.hl7.fhir.dstu2016may.formats.JsonParser().compose(bs, r2b); + return bs.toByteArray(); + case XML: + new org.hl7.fhir.dstu2016may.formats.XmlParser().compose(bs, r2b); + return bs.toByteArray(); + default: + throw new FHIRException("Unsupported output format: " + cnt.cntType.toString()); } } else if (VersionUtilities.isR3Ver(targetVer)) { ByteArrayOutputStream bs = new ByteArrayOutputStream(); switch (format) { - case JSON: - new org.hl7.fhir.dstu3.formats.JsonParser().compose(bs, r3); - return bs.toByteArray(); - case XML: - new org.hl7.fhir.dstu3.formats.XmlParser().compose(bs, r3); - return bs.toByteArray(); - default: - throw new FHIRException("Unsupported output format: "+cnt.cntType.toString()); + case JSON: + new org.hl7.fhir.dstu3.formats.JsonParser().compose(bs, r3); + return bs.toByteArray(); + case XML: + new org.hl7.fhir.dstu3.formats.XmlParser().compose(bs, r3); + return bs.toByteArray(); + default: + throw new FHIRException("Unsupported output format: " + cnt.cntType.toString()); } } else if (VersionUtilities.isR4Ver(targetVer)) { org.hl7.fhir.r4.model.Resource r4 = VersionConvertor_30_40.convertResource(r3, false); ByteArrayOutputStream bs = new ByteArrayOutputStream(); switch (format) { - case JSON: - new org.hl7.fhir.r4.formats.JsonParser().compose(bs, r4); - return bs.toByteArray(); - case XML: - new org.hl7.fhir.r4.formats.XmlParser().compose(bs, r4); - return bs.toByteArray(); - default: - throw new FHIRException("Unsupported output format: "+cnt.cntType.toString()); + case JSON: + new org.hl7.fhir.r4.formats.JsonParser().compose(bs, r4); + return bs.toByteArray(); + case XML: + new org.hl7.fhir.r4.formats.XmlParser().compose(bs, r4); + return bs.toByteArray(); + default: + throw new FHIRException("Unsupported output format: " + cnt.cntType.toString()); } } else { - throw new FHIRException("Target Version not supported yet: "+targetVer); + throw new FHIRException("Target Version not supported yet: " + targetVer); } } @@ -2336,68 +2109,68 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst public byte[] convertVersionNativeR4(String targetVer, Content cnt, FhirFormat format) throws IOException, Exception { org.hl7.fhir.r4.model.Resource r4; switch (cnt.cntType) { - case JSON: - r4 = new org.hl7.fhir.r4.formats.JsonParser().parse(cnt.focus); - break; - case XML: - r4 = new org.hl7.fhir.r4.formats.XmlParser().parse(cnt.focus); - break; - default: - throw new FHIRException("Unsupported input format: "+cnt.cntType.toString()); + case JSON: + r4 = new org.hl7.fhir.r4.formats.JsonParser().parse(cnt.focus); + break; + case XML: + r4 = new org.hl7.fhir.r4.formats.XmlParser().parse(cnt.focus); + break; + default: + throw new FHIRException("Unsupported input format: " + cnt.cntType.toString()); } if (VersionUtilities.isR2Ver(targetVer)) { org.hl7.fhir.dstu2.model.Resource r2 = VersionConvertor_10_40.convertResource(r4); ByteArrayOutputStream bs = new ByteArrayOutputStream(); switch (format) { - case JSON: - new org.hl7.fhir.dstu2.formats.JsonParser().compose(bs, r2); - return bs.toByteArray(); - case XML: - new org.hl7.fhir.dstu2.formats.XmlParser().compose(bs, r2); - return bs.toByteArray(); - default: - throw new FHIRException("Unsupported output format: "+cnt.cntType.toString()); + case JSON: + new org.hl7.fhir.dstu2.formats.JsonParser().compose(bs, r2); + return bs.toByteArray(); + case XML: + new org.hl7.fhir.dstu2.formats.XmlParser().compose(bs, r2); + return bs.toByteArray(); + default: + throw new FHIRException("Unsupported output format: " + cnt.cntType.toString()); } } else if (VersionUtilities.isR2BVer(targetVer)) { org.hl7.fhir.dstu2016may.model.Resource r2b = VersionConvertor_14_40.convertResource(r4); ByteArrayOutputStream bs = new ByteArrayOutputStream(); switch (format) { - case JSON: - new org.hl7.fhir.dstu2016may.formats.JsonParser().compose(bs, r2b); - return bs.toByteArray(); - case XML: - new org.hl7.fhir.dstu2016may.formats.XmlParser().compose(bs, r2b); - return bs.toByteArray(); - default: - throw new FHIRException("Unsupported output format: "+cnt.cntType.toString()); + case JSON: + new org.hl7.fhir.dstu2016may.formats.JsonParser().compose(bs, r2b); + return bs.toByteArray(); + case XML: + new org.hl7.fhir.dstu2016may.formats.XmlParser().compose(bs, r2b); + return bs.toByteArray(); + default: + throw new FHIRException("Unsupported output format: " + cnt.cntType.toString()); } } else if (VersionUtilities.isR3Ver(targetVer)) { org.hl7.fhir.dstu3.model.Resource r3 = VersionConvertor_30_40.convertResource(r4, false); ByteArrayOutputStream bs = new ByteArrayOutputStream(); switch (format) { - case JSON: - new org.hl7.fhir.dstu3.formats.JsonParser().compose(bs, r3); - return bs.toByteArray(); - case XML: - new org.hl7.fhir.dstu3.formats.XmlParser().compose(bs, r3); - return bs.toByteArray(); - default: - throw new FHIRException("Unsupported output format: "+cnt.cntType.toString()); + case JSON: + new org.hl7.fhir.dstu3.formats.JsonParser().compose(bs, r3); + return bs.toByteArray(); + case XML: + new org.hl7.fhir.dstu3.formats.XmlParser().compose(bs, r3); + return bs.toByteArray(); + default: + throw new FHIRException("Unsupported output format: " + cnt.cntType.toString()); } } else if (VersionUtilities.isR4Ver(targetVer)) { ByteArrayOutputStream bs = new ByteArrayOutputStream(); switch (format) { - case JSON: - new org.hl7.fhir.r4.formats.JsonParser().compose(bs, r4); - return bs.toByteArray(); - case XML: - new org.hl7.fhir.r4.formats.XmlParser().compose(bs, r4); - return bs.toByteArray(); - default: - throw new FHIRException("Unsupported output format: "+cnt.cntType.toString()); + case JSON: + new org.hl7.fhir.r4.formats.JsonParser().compose(bs, r4); + return bs.toByteArray(); + case XML: + new org.hl7.fhir.r4.formats.XmlParser().compose(bs, r4); + return bs.toByteArray(); + default: + throw new FHIRException("Unsupported output format: " + cnt.cntType.toString()); } } else { - throw new FHIRException("Target Version not supported yet: "+targetVer); + throw new FHIRException("Target Version not supported yet: " + targetVer); } } @@ -2408,34 +2181,26 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst return TextFile.streamToBytes(c.getInputStream()); } - public FilesystemPackageCacheManager getPcm() { - return pcm; - } - - public List getBundleValidationRules() { - return bundleValidationRules; - } - public boolean packageExists(String id, String ver) throws IOException, FHIRException { return pcm.packageExists(id, ver); } public void loadPackage(String id, String ver) throws IOException, FHIRException { - loadIg(id+(ver == null ? "" : "#"+ver), true); + loadIg(id + (ver == null ? "" : "#" + ver), true); } /** * Systems that host the ValidationEngine can use this to control what validation the validator performs. - * + *

* Using this, you can turn particular kinds of validation on and off. In addition, you can override * the error | warning | hint level and make it a different level. - * + *

* Each entry has * * 'allowed': a boolean flag. if this is false, the Validator will not report the error. * * 'level' : set to error, warning, information - * + *

* Entries are registered by ID, using the IDs in /org.hl7.fhir.utilities/src/main/resources/Messages.properties - * + *

* This feature is not supported by the validator CLI - and won't be. It's for systems hosting * the validation framework in their own implementation context */ @@ -2443,4 +2208,167 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst return validationControl; } + public String setTerminologyServer(String src, String log, FhirPublication version) throws FHIRException, URISyntaxException { + return connectToTSServer(src, log, version); + } + + /* + * Accessors + */ + + public boolean isDoNative() { + return doNative; + } + + public ValidationEngine setNative(boolean doNative) { + this.doNative = doNative; + return this; + } + + public boolean isNoInvariantChecks() { + return noInvariantChecks; + } + + public ValidationEngine setNoInvariantChecks(boolean value) { + this.noInvariantChecks = value; + return this; + } + + public boolean isHintAboutNonMustSupport() { + return hintAboutNonMustSupport; + } + + public ValidationEngine setHintAboutNonMustSupport(boolean hintAboutNonMustSupport) { + this.hintAboutNonMustSupport = hintAboutNonMustSupport; + return this; + } + + public boolean isAnyExtensionsAllowed() { + return anyExtensionsAllowed; + } + + public ValidationEngine setAnyExtensionsAllowed(boolean anyExtensionsAllowed) { + this.anyExtensionsAllowed = anyExtensionsAllowed; + return this; + } + + public String getVersion() { + return version; + } + + public ValidationEngine setVersion(String version) { + this.version = version; + return this; + } + + public String getLanguage() { + return language; + } + + public ValidationEngine setLanguage(String language) { + this.language = language; + return this; + } + + public FilesystemPackageCacheManager getPcm() { + return pcm; + } + + public PrintWriter getMapLog() { + return mapLog; + } + + public ValidationEngine setMapLog(String mapLog) throws FileNotFoundException { + if (mapLog != null) { + this.mapLog = new PrintWriter(mapLog); + } + return this; + } + + public boolean isDebug() { + return debug; + } + + public ValidationEngine setDebug(boolean debug) { + this.debug = debug; + return this; + } + + public IValidatorResourceFetcher getFetcher() { + return fetcher; + } + + public ValidationEngine setFetcher(IValidatorResourceFetcher fetcher) { + this.fetcher = fetcher; + return this; + } + + public boolean isAssumeValidRestReferences() { + return assumeValidRestReferences; + } + + public ValidationEngine setAssumeValidRestReferences(boolean assumeValidRestReferences) { + this.assumeValidRestReferences = assumeValidRestReferences; + return this; + } + + public boolean isNoExtensibleBindingMessages() { + return noExtensibleBindingMessages; + } + + public ValidationEngine setNoExtensibleBindingMessages(boolean noExtensibleBindingMessages) { + this.noExtensibleBindingMessages = noExtensibleBindingMessages; + return this; + } + + public boolean isSecurityChecks() { + return securityChecks; + } + + public ValidationEngine setSecurityChecks(boolean securityChecks) { + this.securityChecks = securityChecks; + return this; + } + + public boolean isCrumbTrails() { + return crumbTrails; + } + + public ValidationEngine setCrumbTrails(boolean crumbTrails) { + this.crumbTrails = crumbTrails; + return this; + } + + public boolean isShowTimes() { + return showTimes; + } + + public ValidationEngine setShowTimes(boolean showTimes) { + this.showTimes = showTimes; + return this; + } + + public List getBundleValidationRules() { + return bundleValidationRules; + } + + public ValidationEngine setBundleValidationRules(List bundleValidationRules) { + this.bundleValidationRules = bundleValidationRules; + return this; + } + + public QuestionnaireMode getQuestionnaireMode() { + return questionnaireMode; + } + + public ValidationEngine setQuestionnaireMode(QuestionnaireMode questionnaireMode) { + this.questionnaireMode = questionnaireMode; + return this; + } + + public ValidationEngine setSnomedExtension(String sct) { + context.getExpansionParameters().addParameter("system-version", "http://snomed.info/sct|http://snomed.info/sct/" + sct); + return this; + } + } \ No newline at end of file diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationRecord.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationRecord.java new file mode 100644 index 000000000..6dd2eafc6 --- /dev/null +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationRecord.java @@ -0,0 +1,49 @@ +package org.hl7.fhir.validation; + +import org.hl7.fhir.utilities.validation.ValidationMessage; + +import java.util.List; + +public class ValidationRecord { + + private String location; + private List messages; + int err = 0; + int warn = 0; + int info = 0; + + public ValidationRecord(String location, List messages) { + this.location = location; + this.messages = messages; + for (ValidationMessage vm : messages) { + if (vm.getLevel().equals(ValidationMessage.IssueSeverity.FATAL) || vm.getLevel().equals(ValidationMessage.IssueSeverity.ERROR)) + err++; + else if (vm.getLevel().equals(ValidationMessage.IssueSeverity.WARNING)) + warn++; + else if (!vm.isSignpost()) { + info++; + } + } + } + + public String getLocation() { + return location; + } + + public List getMessages() { + return messages; + } + + public int getErr() { + return err; + } + + public int getWarn() { + return warn; + } + + public int getInfo() { + return info; + } + +} diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/HTMLOutputGenerator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/HTMLOutputGenerator.java index a13abb813..5137a0a2d 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/HTMLOutputGenerator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/HTMLOutputGenerator.java @@ -7,7 +7,7 @@ import java.util.List; import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.validation.ValidationMessage; import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; -import org.hl7.fhir.validation.ValidationEngine.ValidationRecord; +import org.hl7.fhir.validation.ValidationRecord; import org.hl7.fhir.validation.cli.utils.VersionUtil; public class HTMLOutputGenerator { diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/ValidationService.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/ValidationService.java index e3f3eaff6..126df31ca 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/ValidationService.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/ValidationService.java @@ -27,7 +27,7 @@ import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.VersionUtilities; import org.hl7.fhir.utilities.validation.ValidationMessage; import org.hl7.fhir.validation.ValidationEngine; -import org.hl7.fhir.validation.ValidationEngine.ValidationRecord; +import org.hl7.fhir.validation.ValidationRecord; import org.hl7.fhir.validation.cli.model.*; import org.hl7.fhir.validation.cli.utils.EngineMode; import org.hl7.fhir.validation.cli.utils.VersionSourceInformation; diff --git a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/r5/test/FHIRMappingLanguageTests.java b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/r5/test/FHIRMappingLanguageTests.java index fa7aa045c..1bde16c0e 100644 --- a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/r5/test/FHIRMappingLanguageTests.java +++ b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/r5/test/FHIRMappingLanguageTests.java @@ -19,6 +19,7 @@ import org.hl7.fhir.r5.model.Resource; import org.hl7.fhir.r5.model.StructureDefinition; import org.hl7.fhir.r5.model.StructureMap; import org.hl7.fhir.r5.test.utils.TestingUtilities; +import org.hl7.fhir.r5.utils.structuremap.StructureMapUtilities; import org.hl7.fhir.utilities.TextFile; import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.xml.XMLUtil; @@ -89,7 +90,7 @@ public class FHIRMappingLanguageTests { String msg = null; try { - StructureMap r = new org.hl7.fhir.r5.utils.StructureMapUtilities(context).parse(stringMap, map); + StructureMap r = new StructureMapUtilities(context).parse(stringMap, map); context.cacheResource(r); org.hl7.fhir.r5.elementmodel.Element element = validationEngine.transform(byteSource, FhirFormat.JSON, r.getUrl()); s = new ByteArrayOutputStream();

"+s+"" + s + "
Core Spec
"+ig.present()+"" + ig.present() + "Global
"+sd.present()+"" + sd.present() + "\u2714\u2714\u2716\u2716