diff --git a/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/utils/FHIRLexer.java b/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/utils/FHIRLexer.java index 5dd11ecab..ddf92256f 100644 --- a/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/utils/FHIRLexer.java +++ b/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/utils/FHIRLexer.java @@ -76,6 +76,7 @@ public class FHIRLexer { private SourceLocation currentStartLocation; private int id; private String name; + private boolean liquidMode; // in liquid mode, || terminates the expression and hands the parser back to the host public FHIRLexer(String source, String name) throws FHIRLexerException { this.source = source == null ? "" : source; @@ -201,7 +202,7 @@ public class FHIRLexer { cursor++; if (cursor < source.length() && (source.charAt(cursor) == '/')) { // this is en error - should already have been skipped - error("This shoudn't happen?"); + error("This shouldn't happen?"); } current = source.substring(currentStart, cursor); } else if (ch == '$') { @@ -274,6 +275,12 @@ public class FHIRLexer { throw error("Unterminated string"); cursor++; current = "`"+source.substring(currentStart+1, cursor-1)+"`"; + } else if (ch == '|' && liquidMode) { + cursor++; + ch = source.charAt(cursor); + if (ch == '|') + cursor++; + current = source.substring(currentStart, cursor); } else if (ch == '@'){ int start = cursor; cursor++; @@ -519,5 +526,14 @@ public class FHIRLexer { public int getCurrentStart() { return currentStart; } + public String getSource() { + return source; + } + public boolean isLiquidMode() { + return liquidMode; + } + public void setLiquidMode(boolean liquidMode) { + this.liquidMode = liquidMode; + } } \ No newline at end of file diff --git a/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/utils/FHIRPathEngine.java b/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/utils/FHIRPathEngine.java index fe3bdca4d..b11a92f6d 100644 --- a/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/utils/FHIRPathEngine.java +++ b/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/utils/FHIRPathEngine.java @@ -243,6 +243,7 @@ public class FHIRPathEngine { return false; } } + } private IWorkerContext worker; private IEvaluationContext hostServices; @@ -255,6 +256,7 @@ public class FHIRPathEngine { private String location; // for error messages private boolean allowPolymorphicNames; private boolean doImplicitStringConversion; + private boolean liquidMode; // in liquid mode, || terminates the expression and hands the parser back to the host // if the fhir path expressions are allowed to use constants beyond those defined in the specification // the application can implement them by providing a constant resolver @@ -4754,7 +4756,11 @@ public class FHIRPathEngine { } } } else if (hostServices != null) { - res = hostServices.resolveReference(context.appInfo, s, refContext); + try { + res = hostServices.resolveReference(context.appInfo, s, refContext); + } catch (Exception e) { + res = null; + } } if (res != null) { result.add(res); @@ -6023,5 +6029,13 @@ public class FHIRPathEngine { this.allowPolymorphicNames = allowPolymorphicNames; } + public boolean isLiquidMode() { + return liquidMode; + } + + public void setLiquidMode(boolean liquidMode) { + this.liquidMode = liquidMode; + } + } diff --git a/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/utils/LiquidEngine.java b/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/utils/LiquidEngine.java index 946dc3eb7..ce4035aea 100644 --- a/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/utils/LiquidEngine.java +++ b/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/utils/LiquidEngine.java @@ -1,5 +1,6 @@ package org.hl7.fhir.r4b.utils; +import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -37,6 +38,7 @@ import java.util.Map; */ import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.exceptions.FHIRFormatError; import org.hl7.fhir.exceptions.PathEngineException; import org.hl7.fhir.r4b.context.IWorkerContext; import org.hl7.fhir.r4b.model.Base; @@ -53,6 +55,10 @@ import org.hl7.fhir.utilities.xhtml.XhtmlNode; public class LiquidEngine implements IEvaluationContext { + public interface ILiquidRenderingSupport { + String renderForLiquid(Object appContext, Base i) throws FHIRException; + } + public interface ILiquidEngineIncludeResolver { public String fetchInclude(LiquidEngine engine, String name); } @@ -60,6 +66,7 @@ public class LiquidEngine implements IEvaluationContext { private IEvaluationContext externalHostServices; private FHIRPathEngine engine; private ILiquidEngineIncludeResolver includeResolver; + private ILiquidRenderingSupport renderingSupport; private class LiquidEngineContext { private Object externalContext; @@ -82,6 +89,7 @@ public class LiquidEngine implements IEvaluationContext { this.externalHostServices = hostServices; engine = new FHIRPathEngine(context); engine.setHostServices(this); + engine.setLiquidMode(true); } public ILiquidEngineIncludeResolver getIncludeResolver() { @@ -92,6 +100,14 @@ public class LiquidEngine implements IEvaluationContext { this.includeResolver = includeResolver; } + public ILiquidRenderingSupport getRenderingSupport() { + return renderingSupport; + } + + public void setRenderingSupport(ILiquidRenderingSupport renderingSupport) { + this.renderingSupport = renderingSupport; + } + public LiquidDocument parse(String source, String sourceName) throws FHIRException { return new LiquidParser(source).parse(sourceName); } @@ -130,17 +146,84 @@ public class LiquidEngine implements IEvaluationContext { public void evaluate(StringBuilder b, Base resource, LiquidEngineContext ctxt) { b.append(constant); } + + } + + private enum LiquidFilter { + PREPEND; + + public static LiquidFilter fromCode(String code) { + if ("prepend".equals(code)) { + return PREPEND; + } + return null; + } } + private class LiquidExpressionNode { + private LiquidFilter filter; // null at root + private ExpressionNode expression; // null for some filters + public LiquidExpressionNode(LiquidFilter filter, ExpressionNode expression) { + super(); + this.filter = filter; + this.expression = expression; + } + + } + private class LiquidStatement extends LiquidNode { private String statement; - private ExpressionNode compiled; + private List compiled = new ArrayList<>(); @Override public void evaluate(StringBuilder b, Base resource, LiquidEngineContext ctxt) throws FHIRException { - if (compiled == null) - compiled = engine.parse(statement); - b.append(engine.evaluateToString(ctxt, resource, resource, resource, compiled)); + if (compiled.size() == 0) { + FHIRLexer lexer = new FHIRLexer(statement, "liquid statement"); + lexer.setLiquidMode(true); + compiled.add(new LiquidExpressionNode(null, engine.parse(lexer))); + while (!lexer.done()) { + if (lexer.getCurrent().equals("||")) { + lexer.next(); + String f = lexer.getCurrent(); + LiquidFilter filter = LiquidFilter.fromCode(f); + if (filter == null) { + lexer.error("Unknown Liquid filter '"+f+"'"); + } + lexer.next(); + if (!lexer.done() && lexer.getCurrent().equals(":")) { + lexer.next(); + compiled.add(new LiquidExpressionNode(filter, engine.parse(lexer))); + } else { + compiled.add(new LiquidExpressionNode(filter, null)); + } + } else { + lexer.error("Unexpected syntax parsing liquid statement"); + } + } + } + + String t = null; + for (LiquidExpressionNode i : compiled) { + if (i.filter == null) { // first + t = stmtToString(ctxt, engine.evaluate(ctxt, resource, resource, resource, i.expression)); + } else switch (i.filter) { + case PREPEND: + t = stmtToString(ctxt, engine.evaluate(ctxt, resource, resource, resource, i.expression)) + t; + break; + } + } + b.append(t); + } + + private String stmtToString(LiquidEngineContext ctxt, List items) { + StringBuilder b = new StringBuilder(); + boolean first = true; + for (Base i : items) { + if (first) first = false; else b.append(", "); + String s = renderingSupport != null ? renderingSupport.renderForLiquid(ctxt.externalContext, i) : null; + b.append(s != null ? s : engine.convertToString(i)); + } + return b.toString(); } } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/BaseWorkerContext.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/BaseWorkerContext.java index b658fc6a9..8a8419001 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/BaseWorkerContext.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/BaseWorkerContext.java @@ -104,8 +104,8 @@ import org.hl7.fhir.r5.model.Bundle.BundleType; import org.hl7.fhir.r5.model.Bundle.HTTPVerb; import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent; import org.hl7.fhir.r5.model.ValueSet.ValueSetComposeComponent; -import org.hl7.fhir.r5.profilemodel.ProfiledElement; -import org.hl7.fhir.r5.profilemodel.ProfiledElementBuilder; +import org.hl7.fhir.r5.profilemodel.PEDefinition; +import org.hl7.fhir.r5.profilemodel.PEBuilder; import org.hl7.fhir.r5.renderers.OperationOutcomeRenderer; import org.hl7.fhir.r5.terminologies.CodeSystemUtilities; import org.hl7.fhir.r5.terminologies.TerminologyClient; @@ -2391,8 +2391,8 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte @Override - public ProfiledElementBuilder getProfiledElementBuilder() { + public PEBuilder getProfiledElementBuilder(boolean elementProps) { // TODO Auto-generated method stub - return new ProfiledElementBuilder(this); + return new PEBuilder(this, elementProps); } } \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/IWorkerContext.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/IWorkerContext.java index 57d8c2571..8c1fdb593 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/IWorkerContext.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/IWorkerContext.java @@ -65,8 +65,8 @@ import org.hl7.fhir.r5.model.StructureDefinition; import org.hl7.fhir.r5.model.StructureMap; import org.hl7.fhir.r5.model.ValueSet; import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent; -import org.hl7.fhir.r5.profilemodel.ProfiledElement; -import org.hl7.fhir.r5.profilemodel.ProfiledElementBuilder; +import org.hl7.fhir.r5.profilemodel.PEDefinition; +import org.hl7.fhir.r5.profilemodel.PEBuilder; import org.hl7.fhir.r5.terminologies.ValueSetExpander.TerminologyServiceErrorClass; import org.hl7.fhir.r5.terminologies.ValueSetExpander.ValueSetExpansionOutcome; import org.hl7.fhir.r5.utils.validation.IResourceValidator; @@ -803,6 +803,6 @@ public interface IWorkerContext { public String getSpecUrl(); - public ProfiledElementBuilder getProfiledElementBuilder(); + public PEBuilder getProfiledElementBuilder(boolean elementProps); } \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/SimpleWorkerContext.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/SimpleWorkerContext.java index 2c03bd393..573cf5875 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/SimpleWorkerContext.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/SimpleWorkerContext.java @@ -61,8 +61,8 @@ import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind; import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule; import org.hl7.fhir.r5.model.StructureMap.StructureMapModelMode; import org.hl7.fhir.r5.model.StructureMap.StructureMapStructureComponent; -import org.hl7.fhir.r5.profilemodel.ProfiledElement; -import org.hl7.fhir.r5.profilemodel.ProfiledElementBuilder; +import org.hl7.fhir.r5.profilemodel.PEDefinition; +import org.hl7.fhir.r5.profilemodel.PEBuilder; import org.hl7.fhir.r5.terminologies.CodeSystemUtilities; import org.hl7.fhir.r5.terminologies.JurisdictionUtilities; import org.hl7.fhir.r5.terminologies.TerminologyClient; diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEBuilder.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEBuilder.java new file mode 100644 index 000000000..b90d84c1c --- /dev/null +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEBuilder.java @@ -0,0 +1,295 @@ +package org.hl7.fhir.r5.profilemodel; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang3.NotImplementedException; +import org.hl7.fhir.exceptions.DefinitionException; +import org.hl7.fhir.r5.conformance.profile.ProfileUtilities; +import org.hl7.fhir.r5.context.IWorkerContext; +import org.hl7.fhir.r5.model.CanonicalType; +import org.hl7.fhir.r5.model.ElementDefinition; +import org.hl7.fhir.r5.model.ElementDefinition.SlicingRules; +import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent; +import org.hl7.fhir.r5.model.Resource; +import org.hl7.fhir.r5.model.ResourceFactory; +import org.hl7.fhir.r5.model.StructureDefinition; +import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule; +import org.hl7.fhir.utilities.Utilities; + +public class PEBuilder { + + private IWorkerContext context; + private ProfileUtilities pu; + private boolean elementProps; + + public PEBuilder(IWorkerContext context, boolean elementProps) { + super(); + this.context = context; + this.elementProps = elementProps; + pu = new ProfileUtilities(context, null, null); + } + + + /** + * Given a profile, return a tree of the elements defined in the profile model. This builds the profile model + * for the latest version of the nominated profile + * + * The tree of elements in the profile model is different to those defined in the base resource: + * - some elements are removed (max = 0) + * - extensions are turned into named elements + * - slices are turned into named elements + * - element properties - doco, cardinality, binding etc is updated for what the profile says + * + * Warning: profiles and resources are recursive; you can't iterate this tree until it you get + * to the leaves because there are nodes that don't terminate (extensions have extensions) + * + */ + public PEDefinition buildPEDefinition(String url) { + StructureDefinition profile = getProfile(url); + if (profile == null) { + throw new DefinitionException("Unable to find profile for URL '"+url+"'"); + } + StructureDefinition base = context.fetchTypeDefinition(profile.getType()); + if (base == null) { + throw new DefinitionException("Unable to find base type '"+profile.getType()+"' for URL '"+url+"'"); + } + return new PEDefinitionResource(this, base, profile); + } + + /** + * Given a profile, return a tree of the elements defined in the profile model. This builds the profile model + * for the nominated version of the nominated profile + * + * The tree of elements in the profile model is different to the the base resource: + * - some elements are removed (max = 0) + * - extensions are turned into named elements + * - slices are turned into named elements + * - element properties - doco, cardinality, binding etc is updated for what the profile says + * + * Warning: profiles and resources can be recursive; you can't iterate this tree until it you get + * to the leaves because you will never get to a child that doesn't have children + * + */ + public PEDefinition buildPEDefinition(String url, String version) { + StructureDefinition profile = getProfile(url, version); + if (profile == null) { + throw new DefinitionException("Unable to find profile for URL '"+url+"'"); + } + StructureDefinition base = context.fetchTypeDefinition(profile.getType()); + if (base == null) { + throw new DefinitionException("Unable to find base type '"+profile.getType()+"' for URL '"+url+"'"); + } + return new PEDefinitionResource(this, base, profile); + } + + /** + * Given a resource and a profile, return a tree of instance data as defined by the profile model + * using the latest version of the profile + * + * The tree is a facade to the underlying resource - all actual data is stored against the resource, + * and retrieved on the fly from the resource, so that applications can work at either level, as + * convenient. + * + * Note that there's a risk that deleting something through the resource while holding + * a handle to a PEInstance that is a facade on what is deleted leaves an orphan facade + * that will continue to function, but is making changes to resource content that is no + * longer part of the resource + * + */ + public PEInstance buildPEInstance(String url, Resource resource) { + throw new NotImplementedException("NOt done yet"); + } + + /** + * Given a resource and a profile, return a tree of instance data as defined by the profile model + * using the nominated version of the profile + * + * The tree is a facade to the underlying resource - all actual data is stored against the resource, + * and retrieved on the fly from the resource, so that applications can work at either level, as + * convenient. + * + * Note that there's a risk that deleting something through the resource while holding + * a handle to a PEInstance that is a facade on what is deleted leaves an orphan facade + * that will continue to function, but is making changes to resource content that is no + * longer part of the resource + */ + public PEInstance buildPEInstance(String url, String version, Resource resource) { + throw new NotImplementedException("Not done yet"); + } + + /** + * Given a profile, construct an empty resource of the type being profiled (to use as input + * to the buildPEInstance method + * + * No version, because the version doesn't change the type of the resource + */ + public Resource makeProfileBase(String url) { + StructureDefinition profile = getProfile(url); + return ResourceFactory.createResource(profile.getType()); + } + + + // -- methods below here are only used internally to the package + + private StructureDefinition getProfile(String url) { + return context.fetchResource(StructureDefinition.class, url); + } + + + private StructureDefinition getProfile(String url, String version) { + return context.fetchResource(StructureDefinition.class, url, version); + } + + protected List listChildren(StructureDefinition baseStructure, ElementDefinition baseDefinition, + StructureDefinition profileStructure, ElementDefinition profiledDefinition, TypeRefComponent t, CanonicalType u) { + // TODO Auto-generated method stub + return null; + } + + protected List listChildren(StructureDefinition baseStructure, ElementDefinition baseDefinition, StructureDefinition profileStructure, ElementDefinition profileDefinition, String url) { + StructureDefinition profile = profileStructure; + List list = pu.getChildList(profile, profileDefinition); + if (profileDefinition.getType().size() == 1 || (!profileDefinition.getPath().contains(".")) || list.isEmpty()) { + assert url == null || checkType(profileDefinition, url); + List res = new ArrayList<>(); + if (list.size() == 0) { + profile = context.fetchResource(StructureDefinition.class, url); + list = pu.getChildList(profile, profile.getSnapshot().getElementFirstRep()); + } + if (list.size() > 0) { + StructureDefinition base = baseStructure; + List blist = pu.getChildList(baseStructure, baseDefinition); + if (blist.size() == 0) { + base = context.fetchTypeDefinition(url); + blist = pu.getChildList(base, base.getSnapshot().getElementFirstRep()); + } + int i = 0; + while (i < list.size()) { + ElementDefinition defn = list.get(i); + ElementDefinition bdefn = getByName(blist, defn); + if (bdefn == null) { + throw new Error("no base definition for "+defn.getId()); + } + if (elementProps || (!Utilities.existsInList(bdefn.getBase().getPath(), "Element.id", "Element.extension"))) { + PEDefinitionElement pe = new PEDefinitionElement(this, base, bdefn, profileStructure, defn); + pe.setRecursing(profileDefinition == defn || (profile.getDerivation() == TypeDerivationRule.SPECIALIZATION && profile.getType().equals("Extension"))); + if (defn.hasSlicing()) { + if (defn.getSlicing().getRules() != SlicingRules.CLOSED) { + res.add(pe); + } + i++; + while (i < list.size() && list.get(i).getPath().equals(defn.getPath())) { + StructureDefinition ext = getExtensionDefinition(list.get(i)); + if (ext != null) { + res.add(new PEDefinitionExtension(this, list.get(i).getSliceName(), baseStructure, getByName(blist, defn), profileStructure, list.get(i), defn, ext)); + } else { + res.add(new PEDefinitionSlice(this, list.get(i).getSliceName(), baseStructure, getByName(blist, defn), profileStructure, list.get(i), defn)); + } + i++; + } + } else { + res.add(pe); + i++; + } + } else { + i++; + } + } + } + return res; + } else if (list.isEmpty()) { + throw new DefinitionException("not done yet!"); + } else { + throw new DefinitionException("not done yet"); + } + } + + + protected List listSlices(StructureDefinition baseStructure, ElementDefinition baseDefinition, StructureDefinition profileStructure, ElementDefinition profileDefinition) { + List list = pu.getSliceList(profileStructure, profileDefinition); + List res = new ArrayList<>(); + for (ElementDefinition ed : list) { + if (profileStructure.getDerivation() == TypeDerivationRule.CONSTRAINT && profileStructure.getType().equals("Extension")) { + res.add(new PEDefinitionSubExtension(this, baseStructure, baseDefinition, profileStructure, ed)); + } else { + PEDefinitionElement pe = new PEDefinitionElement(this, baseStructure, baseDefinition, profileStructure, ed); + pe.setRecursing(profileDefinition == ed || (profileStructure.getDerivation() == TypeDerivationRule.SPECIALIZATION && profileStructure.getType().equals("Extension"))); + res.add(pe); + } + } + return res; + } + + + private boolean checkType(ElementDefinition defn, String url) { + for (TypeRefComponent t : defn.getType()) { + if (("http://hl7.org/fhir/StructureDefinition/"+t.getWorkingCode()).equals(url)) { + return true; + } + for (CanonicalType u : t.getProfile()) { + if (url.equals(u.getValue())) { + return true; + } + } + } + return false; + } + + + private StructureDefinition getExtensionDefinition(ElementDefinition ed) { + if ("Extension".equals(ed.getTypeFirstRep().getWorkingCode()) && ed.getTypeFirstRep().getProfile().size() == 1) { + return context.fetchResource(StructureDefinition.class, ed.getTypeFirstRep().getProfile().get(0).asStringValue()); + } else { + return null; + } + } + + + private ElementDefinition getByName(List blist, ElementDefinition defn) { + for (ElementDefinition ed : blist) { + if (ed.getName().equals(defn.getName())) { + return ed; + } + } + return null; + } + + + public PEType makeType(TypeRefComponent t) { + if (t.hasProfile()) { + StructureDefinition sd = context.fetchResource(StructureDefinition.class, t.getProfile().get(0).getValue()); + if (sd == null) { + return new PEType(tail(t.getProfile().get(0).getValue()), t.getWorkingCode(), t.getProfile().get(0).getValue()); + } else { + return new PEType(sd.getName(), t.getWorkingCode(), t.getProfile().get(0).getValue()); + } + } else { + return makeType(t.getWorkingCode()); + } + } + + public PEType makeType(TypeRefComponent t, CanonicalType u) { + StructureDefinition sd = context.fetchResource(StructureDefinition.class, u.getValue()); + if (sd == null) { + return new PEType(tail(u.getValue()), t.getWorkingCode(), u.getValue()); + } else { + return new PEType(sd.getName(), t.getWorkingCode(), u.getValue()); + } + } + + + public PEType makeType(String tn) { + return new PEType(tn, tn, "http://hl7.org/fhir/StructureDefinition/"+ tn); + } + + private String tail(String value) { + return value.contains("/") ? value.substring(value.lastIndexOf("/")+1) : value; + } + + + public List getChildren(StructureDefinition profileStructure, ElementDefinition profiledDefinition) { + return pu.getChildList(profileStructure, profiledDefinition); + } + +} diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEDefinition.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEDefinition.java new file mode 100644 index 000000000..869d239b9 --- /dev/null +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEDefinition.java @@ -0,0 +1,173 @@ +package org.hl7.fhir.r5.profilemodel; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang3.NotImplementedException; +import org.hl7.fhir.r5.model.Base; +import org.hl7.fhir.r5.model.ElementDefinition; +import org.hl7.fhir.r5.model.StructureDefinition; +import org.hl7.fhir.r5.model.ValueSet; + +public abstract class PEDefinition { + + protected PEBuilder builder; + protected String name; + protected StructureDefinition baseStructure; + protected ElementDefinition baseDefinition; + protected StructureDefinition profileStructure; + protected ElementDefinition profiledDefinition; + protected List types; + protected Map> children = new HashMap<>(); + private boolean recursing; + + /** + * Don't create one of these directly - always use the public methods on ProfiledElementBuilder + * + * @param builder + * @param baseElement + * @param profiledElement + * @param data + */ + protected PEDefinition(PEBuilder builder, String name, ElementDefinition baseDefinition, + ElementDefinition profiledDefinition, Base data) { + super(); + this.builder = builder; + this.name = name; + this.baseDefinition = baseDefinition; + this.profiledDefinition = profiledDefinition; +// this.data = data; + } + + protected PEDefinition(PEBuilder builder, String name, StructureDefinition base, ElementDefinition baseDefinition, + StructureDefinition profile, ElementDefinition profiledDefinition) { + this.builder = builder; + this.name = name; + this.baseStructure = base; + this.baseDefinition = baseDefinition; + this.profileStructure = profile; + this.profiledDefinition = profiledDefinition; + } + + + /** + * @return The name of the element or slice in the profile (always unique amongst children) + */ + public String name() { + return name; + } + + /** + * @return The name of the element in the resource (may be different to the slice name) + */ + public String schemaName() { + return baseDefinition.getName(); + } + + /** + * @return a list of types. There is usually at least one type; it might be Element, Type, BackboneElement or BackboneType + * + * The following elements don't have types (true primitives): Element.id. Extension.url, PrimitiveType.value + */ + public List types() { + if (types == null) { + List ltypes = new ArrayList<>(); + listTypes(ltypes); + types = ltypes; + } + return types; + } + + protected abstract void listTypes(List types); + + /** + * @return The minimum number of repeats allowed + */ + public int min() { + return profiledDefinition.getMin(); + } + + /** + * @return the maximum number of repeats allowed + */ + public int max() { + return "*".equals(profiledDefinition.getMax()) ? Integer.MAX_VALUE : Integer.parseInt(profiledDefinition.getMax()); + } + + /** + * @return the definition of the element in the profile (fully populated) + * + * Note that the profile definition might be the same as a base definition, when the tree runs off the end of what's profiled + */ + public ElementDefinition definition() { + return profiledDefinition; + } + + /** + * @return the definition of the element in the base specification + * + * Note that the profile definition might be the same as a base definition, when the tree runs off the end of what's profiled + */ + public ElementDefinition baseDefinition() { + return baseDefinition; + } + + /** + * @return the short documentation of the definition (shown in the profile table view) + */ + public String shortDocumentation() { + return profiledDefinition.getShort(); + } + + /** + * @return the full definition of the element (markdown syntax) + */ + public String documentation() { + return profiledDefinition.getDefinition(); + } + +// /** +// * @return if the profiled definition has a value set +// */ +// public ValueSet expansion() { +// throw new NotImplementedException("Not done yet"); +// } +// + /** + * @param typeUrl - the url of one of the types listed in types() + * @return - the list of children for the nominated type + * + * Warning: profiles and resources can be recursive; you can't iterate this tree until you get + * to the leaves because you will never get to a child that doesn't have children (extensions have extensions etc) + * + */ + public List children(String typeUrl) { + if (children.containsKey(typeUrl)) { + return children.get(typeUrl); + } + List res = new ArrayList<>(); + makeChildren(typeUrl, res); + children.put(typeUrl, res); + return res; + } + + protected abstract void makeChildren(String typeUrl, List children); + + @Override + public String toString() { + return name+"("+schemaName()+"):"+types().toString()+" ["+min()+":"+(max() == Integer.MAX_VALUE ? "*" : max() )+"] \""+shortDocumentation()+"\""; + } + + public boolean isRecursing() { + return recursing; + } + + public void setRecursing(boolean recursing) { + this.recursing = recursing; + } + +} + + diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEDefinitionElement.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEDefinitionElement.java new file mode 100644 index 000000000..0e1900d42 --- /dev/null +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEDefinitionElement.java @@ -0,0 +1,36 @@ +package org.hl7.fhir.r5.profilemodel; + +import java.util.List; + +import org.hl7.fhir.r5.model.CanonicalType; +import org.hl7.fhir.r5.model.ElementDefinition; +import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent; +import org.hl7.fhir.r5.model.StructureDefinition; + +public class PEDefinitionElement extends PEDefinition { + + public PEDefinitionElement(PEBuilder builder, + StructureDefinition baseStructure, ElementDefinition baseDefinition, + StructureDefinition profileStructure, ElementDefinition profileDefinition) { + super(builder, baseDefinition.getName(), baseStructure, baseDefinition, profileStructure, profileDefinition); + } + + @Override + public void listTypes(List types) { + for (TypeRefComponent t : profiledDefinition.getType()) { + if (t.hasProfile()) { + for (CanonicalType u : t.getProfile()) { + types.add(builder.makeType(t, u)); + } + } else if (!t.getCode().startsWith("http://hl7.org/fhirpath/")) { + types.add(new PEType(t.getWorkingCode(), t.getWorkingCode(), "http://hl7.org/fhir/StructureDefinition/"+t.getWorkingCode())); + } + } + } + + @Override + protected void makeChildren(String typeUrl, List children) { + children.addAll(builder.listChildren(baseStructure, baseDefinition, profileStructure, profiledDefinition, typeUrl)); + } + +} diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEExtension.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEDefinitionExtension.java similarity index 54% rename from org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEExtension.java rename to org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEDefinitionExtension.java index 9c55172ca..62eb747ea 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEExtension.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEDefinitionExtension.java @@ -1,50 +1,54 @@ package org.hl7.fhir.r5.profilemodel; -import java.util.ArrayList; import java.util.List; import org.hl7.fhir.r5.model.CanonicalType; import org.hl7.fhir.r5.model.ElementDefinition; -import org.hl7.fhir.r5.model.StructureDefinition; import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent; +import org.hl7.fhir.r5.model.StructureDefinition; -public class PEExtension extends ProfiledElement { +public class PEDefinitionExtension extends PEDefinition { private StructureDefinition extension; + private ElementDefinition sliceDefinition; - public PEExtension(ProfiledElementBuilder builder, String name, + public PEDefinitionExtension(PEBuilder builder, String name, StructureDefinition baseStructure, ElementDefinition baseDefinition, StructureDefinition profileStructure, ElementDefinition profileDefinition, ElementDefinition sliceDefinition, StructureDefinition extension) { - super(builder, name, baseStructure, baseDefinition, profileStructure, profileDefinition, sliceDefinition); + super(builder, name, baseStructure, baseDefinition, profileStructure, profileDefinition); + this.sliceDefinition = sliceDefinition; this.extension= extension; } @Override - public List types() { - List res = new ArrayList<>(); + public void listTypes(List types) { ElementDefinition eed = extension.getSnapshot().getElementByPath("Extension.extension"); ElementDefinition ved = extension.getSnapshot().getElementByPath("Extension.value[x]"); if (ved.isRequired() || eed.isProhibited()) { for (TypeRefComponent t : ved.getType()) { if (t.hasProfile()) { for (CanonicalType u : t.getProfile()) { - res.add(t.getWorkingCode()+"["+u.getValue()+"]"); + types.add(builder.makeType(t, u)); } } else { - res.add(t.getWorkingCode()); + types.add(builder.makeType(t.getWorkingCode())); } } } else { - res.add("Extension"); + types.add(builder.makeType("Extension")); } - return res; } @Override - public List children(String type) { - throw new Error("Not done yet"); - + protected void makeChildren(String typeUrl, List children) { + ElementDefinition eed = extension.getSnapshot().getElementByPath("Extension.extension"); + ElementDefinition ved = extension.getSnapshot().getElementByPath("Extension.value[x]"); + if (ved.isRequired() || eed.isProhibited()) { + children.addAll(builder.listChildren(extension, ved, extension, ved, typeUrl)); + } else { + children.addAll(builder.listSlices(extension, eed, extension, eed)); + } } } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEDefinitionResource.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEDefinitionResource.java new file mode 100644 index 000000000..ee167c745 --- /dev/null +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEDefinitionResource.java @@ -0,0 +1,23 @@ +package org.hl7.fhir.r5.profilemodel; + +import java.util.List; + +import org.hl7.fhir.r5.model.StructureDefinition; + +public class PEDefinitionResource extends PEDefinition { + + public PEDefinitionResource(PEBuilder builder, StructureDefinition base, StructureDefinition profile) { + super(builder, profile.getName(), base, base.getSnapshot().getElementFirstRep(), profile, profile.getSnapshot().getElementFirstRep()); + } + + @Override + public void listTypes(List types) { + types.add(new PEType(profileStructure.getName(), profileStructure.getType(), profileStructure.getUrl())); + } + + @Override + protected void makeChildren(String typeUrl, List children) { + children.addAll(builder.listChildren(baseStructure, baseDefinition, profileStructure, profiledDefinition, null)); + } + +} diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEDefinitionSlice.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEDefinitionSlice.java new file mode 100644 index 000000000..e90e669a2 --- /dev/null +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEDefinitionSlice.java @@ -0,0 +1,30 @@ +package org.hl7.fhir.r5.profilemodel; + +import java.util.List; + +import org.hl7.fhir.r5.model.Base; +import org.hl7.fhir.r5.model.ElementDefinition; +import org.hl7.fhir.r5.model.StructureDefinition; + +public class PEDefinitionSlice extends PEDefinition { + + protected ElementDefinition sliceDefinition; + + public PEDefinitionSlice(PEBuilder builder, String name, StructureDefinition baseStructure, + ElementDefinition baseDefinition, StructureDefinition profileStructure, ElementDefinition profileDefinition, + ElementDefinition sliceDefinition) { + super(builder, name, baseStructure, baseDefinition, profileStructure, profileDefinition); + this.sliceDefinition = sliceDefinition; + } + + @Override + public void listTypes(List types) { + throw new Error("Not done yet"); + } + + @Override + protected void makeChildren(String typeUrl, List children) { + throw new Error("Not done yet"); + } + +} diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEDefinitionSubExtension.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEDefinitionSubExtension.java new file mode 100644 index 000000000..8a80341b2 --- /dev/null +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEDefinitionSubExtension.java @@ -0,0 +1,57 @@ +package org.hl7.fhir.r5.profilemodel; + +import java.util.List; + +import org.hl7.fhir.r5.model.CanonicalType; +import org.hl7.fhir.r5.model.ElementDefinition; +import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent; +import org.hl7.fhir.r5.model.StructureDefinition; + +public class PEDefinitionSubExtension extends PEDefinition { + + public PEDefinitionSubExtension(PEBuilder builder, StructureDefinition baseStructure, ElementDefinition baseDefinition, StructureDefinition profileStructure, ElementDefinition profileDefinition) { + super(builder, profileDefinition.getSliceName(), baseStructure, baseDefinition, profileStructure, profileDefinition); + } + + @Override + public void listTypes(List types) { + List childDefs = builder.getChildren(profileStructure, profiledDefinition); + ElementDefinition eed = getElementByName(childDefs, "extension"); + ElementDefinition ved = getElementByName(childDefs, "value[x]"); + if (ved.isRequired() || eed.isProhibited()) { + for (TypeRefComponent t : ved.getType()) { + if (t.hasProfile()) { + for (CanonicalType u : t.getProfile()) { + types.add(builder.makeType(t, u)); + } + } else { + types.add(builder.makeType(t.getWorkingCode())); + } + } + } else { + types.add(builder.makeType("Extension")); + } + } + + private ElementDefinition getElementByName(List children, String name) { + for (ElementDefinition ed : children) { + if (name.equals(ed.getName())) { + return ed; + } + } + return null; + } + + @Override + protected void makeChildren(String typeUrl, List children) { + List childDefs = builder.getChildren(profileStructure, profiledDefinition); + ElementDefinition eed = getElementByName(childDefs, "extension"); + ElementDefinition ved = getElementByName(childDefs, "value[x]"); + if (ved.isRequired() || eed.isProhibited()) { + children.addAll(builder.listChildren(baseStructure, baseDefinition, profileStructure, ved, typeUrl)); + } else { + children.addAll(builder.listSlices(baseStructure, baseDefinition, profileStructure, eed)); + } + } + +} diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEElement.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEElement.java deleted file mode 100644 index 9a0c0711f..000000000 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEElement.java +++ /dev/null @@ -1,58 +0,0 @@ -package org.hl7.fhir.r5.profilemodel; - -import java.util.ArrayList; -import java.util.List; - -import org.hl7.fhir.exceptions.DefinitionException; -import org.hl7.fhir.r5.model.CanonicalType; -import org.hl7.fhir.r5.model.ElementDefinition; -import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent; -import org.hl7.fhir.r5.model.StructureDefinition; - -public class PEElement extends ProfiledElement { - - public PEElement(ProfiledElementBuilder builder, - StructureDefinition baseStructure, ElementDefinition baseDefinition, - StructureDefinition profileStructure, ElementDefinition profileDefinition) { - super(builder, baseDefinition.getName(), baseStructure, baseDefinition, profileStructure, profileDefinition); - } - - @Override - public List types() { - List res = new ArrayList<>(); - for (TypeRefComponent t : profiledDefinition.getType()) { - if (t.hasProfile()) { - for (CanonicalType u : t.getProfile()) { - res.add(t.getWorkingCode()+"["+u.getValue()+"]"); - } - } else if (!t.getCode().startsWith("http://hl7.org/fhirpath/")) { - res.add(t.getWorkingCode()); - } - } - return res; - } - - @Override - public List children(String type) { - if (children == null) { - for (TypeRefComponent t : profiledDefinition.getType()) { - if (t.hasProfile()) { - for (CanonicalType u : t.getProfile()) { - if ((t.getWorkingCode()+"["+u.getValue()+"]").equals(type)) { - children = builder.listChildren(baseStructure, baseDefinition, profileStructure, profiledDefinition, t, u); - } - } - } else { - if (t.getWorkingCode().equals(type)) { - children = builder.listChildren(baseStructure, baseDefinition, profileStructure, profiledDefinition, t); - } - } - } - } - if (children != null) { - return children; - } - throw new DefinitionException("Unable to understand type '"+type+"'"); - } - -} diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEInstance.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEInstance.java new file mode 100644 index 000000000..f4cf97419 --- /dev/null +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEInstance.java @@ -0,0 +1,81 @@ +package org.hl7.fhir.r5.profilemodel; + +import java.util.List; + +import org.hl7.fhir.r5.model.Base; +import org.hl7.fhir.r5.model.DataType; +import org.hl7.fhir.r5.model.Resource; + +public abstract class PEInstance { + + private PEDefinition definition; + private Base data; + + protected PEInstance(PEDefinition definition, Base data) { + super(); + this.definition = definition; + this.data = data; + } + + /** + * @return definition information about this instance data + */ + public abstract PEDefinition definition(); + + /** + * @return the type of this element + */ + public abstract PEType type(); + + /** + * @return all the children of this instance data + */ + public abstract List children(); + + /** + * @return all the children of this instance data for the named property + */ + public abstract List children(String name); + + /** + * @return all the children of this instance data with the named property and the named type (for polymorphic + */ + public abstract List children(String name, String type); + + /** + * @return make a child, and append it to existing children (if they exist) + */ + public abstract PEInstance makeChild(String name); + + /** + * remove the nominated child from the resource + */ + public abstract void removeChild(PEInstance child); + + + public enum PEInstanceDataKind { + Resource, Complex, DataType, PrimitiveValue + } + + /** + * @return the kind of data behind this profiled node + */ + public abstract PEInstanceDataKind getDataKind(); + + /** + * @return if dataKind = Resource, get the underlying resource, otherwise an exception + */ + public abstract Resource asResource(); + + /** + * @return if dataKind = Datatype, get the underlying resource, otherwise an exception + */ + public abstract DataType asDataType(); + + /** + * @return if dataKind = PrimitiveValue, get the underlying resource, otherwise an exception + * + * Note that this is for e.g. String.value, not String itself + */ + public abstract String getPrimitiveValue(); +} diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEResource.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEResource.java deleted file mode 100644 index 4c445d9f0..000000000 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEResource.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.hl7.fhir.r5.profilemodel; - -import java.util.ArrayList; -import java.util.List; - -import org.hl7.fhir.r5.model.StructureDefinition; - -public class PEResource extends ProfiledElement { - - public PEResource(ProfiledElementBuilder builder, StructureDefinition base, StructureDefinition profile) { - super(builder, profile.getName(), base, base.getSnapshot().getElementFirstRep(), profile, profile.getSnapshot().getElementFirstRep()); - } - - @Override - public List types() { - List res = new ArrayList<>(); - res.add(profileStructure.getType()); - return res; - } - - @Override - public List children(String type) { - if (children == null) { - children = builder.listChildren(baseStructure, baseDefinition, profileStructure, profiledDefinition, null); - } - return children; - } - -} diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PESlice.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PESlice.java deleted file mode 100644 index b36782fd4..000000000 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PESlice.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.hl7.fhir.r5.profilemodel; - -import java.util.List; - -import org.hl7.fhir.r5.model.ElementDefinition; -import org.hl7.fhir.r5.model.StructureDefinition; - -public class PESlice extends ProfiledElement { - - public PESlice(ProfiledElementBuilder builder, String name, - StructureDefinition baseStructure, ElementDefinition baseDefinition, - StructureDefinition profileStructure, ElementDefinition profileDefinition, - ElementDefinition sliceDefinition) { - super(builder, name, baseStructure, baseDefinition, profileStructure, profileDefinition, sliceDefinition); - } - - @Override - public List types() { - throw new Error("Not done yet"); - } - - @Override - public List children(String type) { - throw new Error("Not done yet"); - } - -} diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEType.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEType.java new file mode 100644 index 000000000..c481eb4e3 --- /dev/null +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/PEType.java @@ -0,0 +1,45 @@ +package org.hl7.fhir.r5.profilemodel; + +public class PEType { + private String name; + private String type; + private String url; + public PEType(String name, String type, String url) { + super(); + this.name = name; + this.url = url; + this.type = type; + } + + /** + * Human presentable name + * + * @return + */ + public String getName() { + return name; + } + + /** + * The concrete FHIR type name for the type + * + * @return + */ + public String getType() { + return type; + } + + /** + * URL that identifies the type + * @return + */ + public String getUrl() { + return url; + } + + @Override + public String toString() { + return (name.equals(type) ? name : name+"->"+type)+"["+url+"]"; + } + +} \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/ProfiledElement.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/ProfiledElement.java deleted file mode 100644 index 51bcad80a..000000000 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/ProfiledElement.java +++ /dev/null @@ -1,216 +0,0 @@ -package org.hl7.fhir.r5.profilemodel; - -import java.util.List; - -import org.apache.commons.lang3.NotImplementedException; -import org.hl7.fhir.r5.model.Base; -import org.hl7.fhir.r5.model.ElementDefinition; -import org.hl7.fhir.r5.model.StructureDefinition; -import org.hl7.fhir.r5.model.ValueSet; - -public abstract class ProfiledElement { - - protected ProfiledElementBuilder builder; - protected String name; - protected StructureDefinition baseStructure; - protected ElementDefinition baseDefinition; - protected StructureDefinition profileStructure; - protected ElementDefinition profiledDefinition; - protected Base data; // might be null if we're not attached to an instance - protected List children; - protected Object sliceDefinition; - - /** - * Don't create one of these directly - always use the public methods on ProfiledElementBuilder - * - * @param builder - * @param baseElement - * @param profiledElement - * @param data - */ - protected ProfiledElement(ProfiledElementBuilder builder, String name, ElementDefinition baseDefinition, - ElementDefinition profiledDefinition, Base data) { - super(); - this.builder = builder; - this.name = name; - this.baseDefinition = baseDefinition; - this.profiledDefinition = profiledDefinition; - this.data = data; - } - - protected ProfiledElement(ProfiledElementBuilder builder, String name, StructureDefinition base, ElementDefinition baseDefinition, - StructureDefinition profile, ElementDefinition profiledDefinition) { - this.builder = builder; - this.name = name; - this.baseStructure = base; - this.baseDefinition = baseDefinition; - this.profileStructure = profile; - this.profiledDefinition = profiledDefinition; - } - - public ProfiledElement(ProfiledElementBuilder builder, String name, StructureDefinition base, ElementDefinition baseDefinition, - StructureDefinition profile, ElementDefinition profiledDefinition, ElementDefinition sliceDefinition) { - this.builder = builder; - this.name = name; - this.baseStructure = base; - this.baseDefinition = baseDefinition; - this.profileStructure = profile; - this.profiledDefinition = profiledDefinition; - this.sliceDefinition = sliceDefinition; - } - - /** - * @return The name of the element or slice in the profile (always unique amongst children) - */ - public String name() { - return name; - } - - /** - * @return The name of the element in the resource (may be different to the slice name) - */ - public String schemaName() { - return baseDefinition.getName(); - } - - /** - * @return a list of types. There is usually at least one type; it might be Element, Type, BackboneElement or BackboneType - * - * The following elements don't have types (true primitives): Element.id. Extension.url, PrimitiveType.value - */ - public abstract List types(); - - /** - * @return The minimum number of repeats allowed - */ - public int min() { - return profiledDefinition.getMin(); - } - - /** - * @return the maximum number of repeats allowed - */ - public int max() { - return "*".equals(profiledDefinition.getMax()) ? Integer.MAX_VALUE : Integer.parseInt(profiledDefinition.getMax()); - } - - /** - * @return the definition of the element in the profile (fully populated) - */ - public ElementDefinition definition() { - return profiledDefinition; - } - - /** - * @return the definition of the element in the base specification - */ - public ElementDefinition baseDefinition() { - return baseDefinition; - } - - /** - * @return the short documentation of the definition (shown in the profile table view) - */ - public String shortDocumentation() { - return profiledDefinition.getShort(); - } - - /** - * @return the full definition of the element (markdown syntax) - */ - public String documentation() { - return profiledDefinition.getDefinition(); - } - - /** - * @return if the base definition - */ - public ValueSet expansion() { - throw new NotImplementedException("Not done yet"); - } - - /** - * @param type - one of the types listed in types() - * @return - the list of children for the nominated type - * - * Note that the children returned from this instance can run off the - * end of the data provided, and then inDataMode() is false - * - * Warning: profiles and resources can be recursive; you can't iterate this tree until it you get - * to the leaves because you will never get to a child that doesn't have children - * - */ - public abstract List children(String type); -// -// // -- instance data --------------------------------------- -// -// /** -// * true if the profiled element is part of a tree built based on -// * a resource -// */ -// public void inDataMode() { -// throw new NotImplementedException("Not done yet"); -// } -// -// /** -// * return a list of profiled elements that are instances of -// * of this element - these are attached to acual underlying data in the resource -// * @return -// */ -// public List instances() { -// throw new NotImplementedException("Not done yet"); -// }; -// -// /** -// * Create a new instance of data that conforms to this profiled element -// * -// * @return -// */ -// public ProfiledElement addInstance(){ -// throw new NotImplementedException("Not done yet"); -// } -// -// /** -// * @return true if this element can have a primitive value -// * -// * Note that an element can have extensions as well as a value, so that doesn't mean it can't have children -// */ -// public boolean canHavePrimitiveValue() { -// throw new NotImplementedException("Not done yet"); -// } -// -// /** -// * @return true if this element has a primitive value -// * -// * Note that an element can have extensions as well as a value, so that doesn't mean it can't have children -// */ -// public boolean hasPrimitiveValue() { -// throw new NotImplementedException("Not done yet"); -// } -// -// /** -// * @return true if this element has a primitive value -// * -// * Note that an element can have extensions as well as a value, so that doesn't mean it can't have children -// */ -// public String getPrimitiveValue() { -// throw new NotImplementedException("Not done yet"); -// } -// -// /** -// * @return true if this element has a primitive value -// * -// * Note that an element can have extensions as well as a value, so that doesn't mean it can't have children -// */ -// public String setPrimitiveValue() { -// throw new NotImplementedException("Not done yet"); -// } - - @Override - public String toString() { - return name+"("+schemaName()+"):"+types().toString()+" ["+min()+":"+(max() == Integer.MAX_VALUE ? "*" : max() )+"] \""+shortDocumentation()+"\""; - } - -} - - diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/ProfiledElementBuilder.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/ProfiledElementBuilder.java deleted file mode 100644 index 0a7df45d7..000000000 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/profilemodel/ProfiledElementBuilder.java +++ /dev/null @@ -1,221 +0,0 @@ -package org.hl7.fhir.r5.profilemodel; - -import java.util.ArrayList; -import java.util.List; - -import org.apache.commons.lang3.NotImplementedException; -import org.hl7.fhir.exceptions.DefinitionException; -import org.hl7.fhir.r5.conformance.profile.ProfileUtilities; -import org.hl7.fhir.r5.context.IWorkerContext; -import org.hl7.fhir.r5.model.CanonicalType; -import org.hl7.fhir.r5.model.ElementDefinition; -import org.hl7.fhir.r5.model.ElementDefinition.SlicingRules; -import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent; -import org.hl7.fhir.r5.model.Resource; -import org.hl7.fhir.r5.model.ResourceFactory; -import org.hl7.fhir.r5.model.StructureDefinition; - -public class ProfiledElementBuilder { - - private IWorkerContext context; - private ProfileUtilities pu; - - public ProfiledElementBuilder(IWorkerContext context) { - super(); - this.context = context; - pu = new ProfileUtilities(context, null, null); - } - - - /** - * Given a profile, return a tree of elements in the profile model. This builds the profile model - * for the latest version of the nominated profile - * - * THe tree of elements in the profile model is different to the the base resource: - * - some elements are removed (max = 0) - * - extensions are turned into named elements - * - slices are turned into named elements - * - element properties - doco, cardinality, binding etc is updated for what the profile says - * - * When built with this method, the profile element can't have instance data - * - * Warning: profiles and resources are recursive; you can't iterate this tree until it you get - * to the leaves because there are nodes that don't terminate (extensions have extensions) - * - */ - public ProfiledElement buildProfileElement(String url) { - StructureDefinition profile = getProfile(url); - if (profile == null) { - throw new DefinitionException("Unable to find profile for URL '"+url+"'"); - } - StructureDefinition base = context.fetchTypeDefinition(profile.getType()); - if (base == null) { - throw new DefinitionException("Unable to find base type '"+profile.getType()+"' for URL '"+url+"'"); - } - return new PEResource(this, base, profile); - } - - /** - * Given a profile, return a tree of elements in the profile model. This builds the profile model - * for the nominated version of the nominated profile - * - * THe tree of elements in the profile model is different to the the base resource: - * - some elements are removed (max = 0) - * - extensions are turned into named elements - * - slices are turned into named elements - * - element properties - doco, cardinality, binding etc is updated for what the profile says - * - * When built with this method, the profile element can't have instance data - * - * Warning: profiles and resources can be recursive; you can't iterate this tree until it you get - * to the leaves because you will never get to a child that doesn't have children - * - */ - public ProfiledElement buildProfileElement(String url, String version) { - StructureDefinition profile = getProfile(url, version); - if (profile == null) { - throw new DefinitionException("Unable to find profile for URL '"+url+"'"); - } - StructureDefinition base = context.fetchTypeDefinition(profile.getType()); - if (base == null) { - throw new DefinitionException("Unable to find base type '"+profile.getType()+"' for URL '"+url+"'"); - } - return new PEResource(this, base, profile); - } - - /** - * Given a profile, return a tree of elements in the profile model with matching instance data. - * This builds the profile model for the latest version of the nominated profile and matches - * the data in the resource against the profile. Data can be added or read from the profile element - * - * THe tree of elements in the profile model is different to the the base resource: - * - some elements are removed (max = 0) - * - extensions are turned into named elements - * - slices are turned into named elements - * - element properties - doco, cardinality, binding etc is updated for what the profile says - * - * When built with this method, the profile element can't have instance data - * - * Warning: profiles and resources can be recursive; you can't iterate this tree until it you get - * to the leaves because you will never get to a child that doesn't have children - * - */ - public ProfiledElement buildProfileElement(String url, Resource resource) { - throw new NotImplementedException("NOt done yet"); - } - - /** - * Given a profile, return a tree of elements in the profile model with matching instance data. - * This builds the profile model for the nominated version of the nominated profile and matches - * the data in the resource against the profile. Data can be added or read from the profile element - * - * THe tree of elements in the profile model is different to the the base resource: - * - some elements are removed (max = 0) - * - extensions are turned into named elements - * - slices are turned into named elements - * - element properties - doco, cardinality, binding etc is updated for what the profile says - * - * When built with this method, the profile element can't have instance data - * - */ - public ProfiledElement buildProfileElement(String url, String version, Resource resource) { - throw new NotImplementedException("NOt done yet"); - } - - /** - * Given a profile, construct an empty resource of the type being profiled (to use as input - * to the buildProfileElement method - * - * No version, because the version doesn't change the type of the resource - */ - public Resource makeProfileBase(String url) { - StructureDefinition profile = getProfile(url); - return ResourceFactory.createResource(profile.getType()); - } - - - // -- methods below here are only used internally to the package - - private StructureDefinition getProfile(String url) { - return context.fetchResource(StructureDefinition.class, url); - } - - - private StructureDefinition getProfile(String url, String version) { - return context.fetchResource(StructureDefinition.class, url, version); - } - - protected List listChildren(StructureDefinition baseStructure, ElementDefinition baseDefinition, - StructureDefinition profileStructure, ElementDefinition profiledDefinition, TypeRefComponent t, CanonicalType u) { - // TODO Auto-generated method stub - return null; - } - - - protected List listChildren(StructureDefinition baseStructure, ElementDefinition baseDefinition, StructureDefinition profileStructure, ElementDefinition profileDefinition, TypeRefComponent t) { - if (profileDefinition.getType().size() == 1 || (!profileDefinition.getPath().contains("."))) { - assert profileDefinition.getType().size() != 1 || profileDefinition.getType().contains(t); - List res = new ArrayList<>(); - StructureDefinition profile = profileStructure; - List list = pu.getChildList(profile, profileDefinition); - if (list.size() == 0) { - profile = t.hasProfile() ? context.fetchResource(StructureDefinition.class, t.getProfile().get(0).getValue()) : context.fetchTypeDefinition(t.getWorkingCode()); - list = pu.getChildList(profile, profile.getSnapshot().getElementFirstRep()); - } - if (list.size() > 0) { - StructureDefinition base = baseStructure; - List blist = pu.getChildList(baseStructure, baseDefinition); - if (blist.size() == 0) { - base = context.fetchTypeDefinition(t.getWorkingCode()); - blist = pu.getChildList(base, base.getSnapshot().getElementFirstRep()); - } - int i = 0; - while (i < list.size()) { - ElementDefinition defn = list.get(i); - if (defn.hasSlicing()) { - if (defn.getSlicing().getRules() != SlicingRules.CLOSED) { - res.add(new PEElement(this, base, getByName(blist, defn), profileStructure, defn)); - } - i++; - while (i < list.size() && list.get(i).getPath().equals(defn.getPath())) { - StructureDefinition ext = getExtensionDefinition(list.get(i)); - if (ext != null) { - res.add(new PEExtension(this, list.get(i).getSliceName(), baseStructure, getByName(blist, defn), profileStructure, list.get(i), defn, ext)); - } else { - res.add(new PESlice(this, list.get(i).getSliceName(), baseStructure, getByName(blist, defn), profileStructure, list.get(i), defn)); - } - i++; - } - } else { - res.add(new PEElement(this, base, getByName(blist, defn), profileStructure, defn)); - i++; - } - } - } - return res; - } else { - throw new DefinitionException("not done yet"); - } - } - - - private StructureDefinition getExtensionDefinition(ElementDefinition ed) { - if ("Extension".equals(ed.getTypeFirstRep().getWorkingCode()) && ed.getTypeFirstRep().getProfile().size() == 1) { - return context.fetchResource(StructureDefinition.class, ed.getTypeFirstRep().getProfile().get(0).asStringValue()); - } else { - return null; - } - } - - - private ElementDefinition getByName(List blist, ElementDefinition defn) { - for (ElementDefinition ed : blist) { - if (ed.getName().equals(defn.getName())) { - return ed; - } - } - return null; - } - - -} diff --git a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/profiles/ProfiledElementTests.java b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/profiles/ProfiledElementTests.java index 3270d40da..8fadd2945 100644 --- a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/profiles/ProfiledElementTests.java +++ b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/profiles/ProfiledElementTests.java @@ -4,8 +4,9 @@ import java.io.IOException; import java.util.List; import org.hl7.fhir.r5.context.IWorkerContext; -import org.hl7.fhir.r5.profilemodel.ProfiledElement; -import org.hl7.fhir.r5.profilemodel.ProfiledElementBuilder; +import org.hl7.fhir.r5.profilemodel.PEDefinition; +import org.hl7.fhir.r5.profilemodel.PEType; +import org.hl7.fhir.r5.profilemodel.PEBuilder; import org.hl7.fhir.r5.test.utils.TestPackageLoader; import org.hl7.fhir.r5.test.utils.TestingUtilities; import org.hl7.fhir.utilities.npm.FilesystemPackageCacheManager; @@ -16,26 +17,37 @@ import org.junit.jupiter.api.Test; public class ProfiledElementTests { + private IWorkerContext ctxt; + + + public void load() throws IOException { + if (ctxt == null) { + ctxt = TestingUtilities.getSharedWorkerContext(); + FilesystemPackageCacheManager pc = new FilesystemPackageCacheManager(true); + NpmPackage npm = pc.loadPackage("hl7.fhir.us.core", "5.0.0"); + ctxt.loadFromPackage(npm, new TestPackageLoader(new String[] { "StructureDefinition" })); + } + } + + @Test - public void testPatientCore() throws IOException { - IWorkerContext ctxt = TestingUtilities.getSharedWorkerContext(); - FilesystemPackageCacheManager pc = new FilesystemPackageCacheManager(true); - NpmPackage npm = pc.loadPackage("hl7.fhir.us.core", "5.0.0"); - ctxt.loadFromPackage(npm, new TestPackageLoader(new String[] { "StructureDefinition" })); + public void testUSPatientCore() throws IOException { + load(); - ProfiledElement pe = new ProfiledElementBuilder(ctxt).buildProfileElement("http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient"); + PEDefinition pe = new PEBuilder(ctxt, true).buildPEDefinition("http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient"); Assertions.assertEquals("USCorePatientProfile", pe.name()); Assertions.assertEquals("Patient", pe.schemaName()); Assertions.assertEquals(0, pe.min()); Assertions.assertEquals(Integer.MAX_VALUE, pe.max()); - Assertions.assertEquals("Patient", pe.types().get(0)); + Assertions.assertEquals("Patient", pe.types().get(0).getType()); + Assertions.assertEquals("USCorePatientProfile", pe.types().get(0).getName()); Assertions.assertNotNull(pe.definition()); Assertions.assertNotNull(pe.baseDefinition()); Assertions.assertEquals("Information about an individual or animal receiving health care services", pe.shortDocumentation()); Assertions.assertEquals("\\-", pe.documentation()); - List children = pe.children("Patient"); + List children = pe.children("Patient"); Assertions.assertEquals(28, children.size()); pe = children.get(9); @@ -43,7 +55,7 @@ public class ProfiledElementTests { Assertions.assertEquals("extension", pe.schemaName()); Assertions.assertEquals(0, pe.min()); Assertions.assertEquals(1, pe.max()); - Assertions.assertEquals("code", pe.types().get(0)); + Assertions.assertEquals("code", pe.types().get(0).getName()); Assertions.assertNotNull(pe.definition()); Assertions.assertNotNull(pe.baseDefinition()); Assertions.assertEquals("Extension", pe.shortDocumentation()); @@ -55,7 +67,7 @@ public class ProfiledElementTests { Assertions.assertEquals("extension", pe.schemaName()); Assertions.assertEquals(0, pe.min()); Assertions.assertEquals(1, pe.max()); - Assertions.assertEquals("Extension", pe.types().get(0)); + Assertions.assertEquals("Extension", pe.types().get(0).getName()); Assertions.assertNotNull(pe.definition()); Assertions.assertNotNull(pe.baseDefinition()); Assertions.assertEquals("US Core ethnicity Extension", pe.shortDocumentation()); @@ -67,14 +79,14 @@ public class ProfiledElementTests { Assertions.assertEquals("identifier", pe.schemaName()); Assertions.assertEquals(1, pe.min()); Assertions.assertEquals(Integer.MAX_VALUE, pe.max()); - Assertions.assertEquals("Identifier", pe.types().get(0)); + Assertions.assertEquals("Identifier", pe.types().get(0).getName()); Assertions.assertNotNull(pe.definition()); Assertions.assertNotNull(pe.baseDefinition()); Assertions.assertEquals("An identifier for this patient", pe.shortDocumentation()); Assertions.assertEquals("An identifier for this patient.", pe.documentation()); - List iChildren = pe.children("Identifier"); + List iChildren = pe.children("http://hl7.org/fhir/StructureDefinition/Identifier"); Assertions.assertEquals(8, iChildren.size()); @@ -84,13 +96,13 @@ public class ProfiledElementTests { Assertions.assertEquals("use", pe.schemaName()); Assertions.assertEquals(0, pe.min()); Assertions.assertEquals(1, pe.max()); - Assertions.assertEquals("code", pe.types().get(0)); + Assertions.assertEquals("code", pe.types().get(0).getName()); Assertions.assertNotNull(pe.definition()); Assertions.assertNotNull(pe.baseDefinition()); Assertions.assertEquals("usual | official | temp | secondary | old (If known)", pe.shortDocumentation()); Assertions.assertEquals("The purpose of this identifier.", pe.documentation()); - iChildren = pe.children("code"); + iChildren = pe.children("http://hl7.org/fhir/StructureDefinition/code"); Assertions.assertEquals(3, iChildren.size()); pe = iChildren.get(2); @@ -121,38 +133,66 @@ public class ProfiledElementTests { Assertions.assertEquals("extension", pe.schemaName()); Assertions.assertEquals(0, pe.min()); Assertions.assertEquals(Integer.MAX_VALUE, pe.max()); - Assertions.assertEquals("Extension", pe.types().get(0)); + Assertions.assertEquals("Extension", pe.types().get(0).getName()); Assertions.assertNotNull(pe.definition()); Assertions.assertNotNull(pe.baseDefinition()); Assertions.assertEquals("Additional content defined by implementations", pe.shortDocumentation()); Assertions.assertEquals("May be used to represent additional information that is not part of the basic definition of the resource. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.", pe.documentation()); - iChildren = pe.children("Extension"); + iChildren = pe.children("http://hl7.org/fhir/StructureDefinition/Extension"); Assertions.assertEquals(4, iChildren.size()); pe = iChildren.get(1); Assertions.assertEquals("extension", pe.name()); Assertions.assertEquals("extension", pe.schemaName()); Assertions.assertEquals(0, pe.min()); Assertions.assertEquals(Integer.MAX_VALUE, pe.max()); - Assertions.assertEquals("Extension", pe.types().get(0)); + Assertions.assertEquals("Extension", pe.types().get(0).getName()); Assertions.assertNotNull(pe.definition()); Assertions.assertNotNull(pe.baseDefinition()); Assertions.assertEquals("Additional content defined by implementations", pe.shortDocumentation()); Assertions.assertEquals("May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.", pe.documentation()); - iChildren = pe.children("Extension"); + iChildren = pe.children("http://hl7.org/fhir/StructureDefinition/Extension"); Assertions.assertEquals(4, iChildren.size()); pe = iChildren.get(1); Assertions.assertEquals("extension", pe.name()); Assertions.assertEquals("extension", pe.schemaName()); Assertions.assertEquals(0, pe.min()); Assertions.assertEquals(Integer.MAX_VALUE, pe.max()); - Assertions.assertEquals("Extension", pe.types().get(0)); + Assertions.assertEquals("Extension", pe.types().get(0).getName()); Assertions.assertNotNull(pe.definition()); Assertions.assertNotNull(pe.baseDefinition()); Assertions.assertEquals("Additional content defined by implementations", pe.shortDocumentation()); Assertions.assertEquals("May be used to represent additional information that is not part of the basic definition of the element. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension.", pe.documentation()); } + + @Test + public void dumpUSPatientCore() throws IOException { + load(); + + PEDefinition pe = new PEBuilder(ctxt, false).buildPEDefinition("http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient"); + dump(pe, ""); + } + + + private void dump(PEDefinition pe, String indent) { + System.out.println(indent+pe.toString()); + if (!pe.isRecursing() && indent.length() < 20) { + if (pe.types().size() == 1) { + for (PEDefinition p : pe.children(pe.types().get(0).getUrl())) { + dump(p, indent+" "); + } + } else { + for (PEType t : pe.types()) { + System.out.println(indent+" +type:"+t.getName()); + for (PEDefinition p : pe.children(t.getUrl())) { + dump(p, indent+" "); + } + } + } + } + } + }