Merge branch 'master' of https://github.com/hapifhir/org.hl7.fhir.core into main_convertor_files

This commit is contained in:
markiantorno 2020-02-13 10:53:24 -05:00
commit a4a6f852bd
46 changed files with 1841 additions and 885 deletions

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.core</artifactId> <artifactId>org.hl7.fhir.core</artifactId>
<version>4.1.54-SNAPSHOT</version> <version>4.1.63-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -152,8 +152,32 @@ public class VersionConvertor_30_50 {
return tgt; return tgt;
} }
public static org.hl7.fhir.r5.model.CodeType convertStringToCode(org.hl7.fhir.dstu3.model.StringType src) throws FHIRException {
org.hl7.fhir.r5.model.CodeType tgt = new org.hl7.fhir.r5.model.CodeType(src.getValue());
copyElement(src, tgt);
return tgt;
}
public static org.hl7.fhir.dstu3.model.StringType convertCodeToString(org.hl7.fhir.r5.model.CodeType src) throws FHIRException {
org.hl7.fhir.dstu3.model.StringType tgt = new org.hl7.fhir.dstu3.model.StringType(src.getValue());
copyElement(src, tgt);
return tgt;
}
public static org.hl7.fhir.dstu3.model.CodeType convertStringToCode(org.hl7.fhir.r5.model.StringType src) throws FHIRException {
org.hl7.fhir.dstu3.model.CodeType tgt = new org.hl7.fhir.dstu3.model.CodeType(src.getValue());
copyElement(src, tgt);
return tgt;
}
public static org.hl7.fhir.r5.model.StringType convertCodeToString(org.hl7.fhir.dstu3.model.CodeType src) throws FHIRException {
org.hl7.fhir.r5.model.StringType tgt = new org.hl7.fhir.r5.model.StringType(src.getValue());
copyElement(src, tgt);
return tgt;
}
public static org.hl7.fhir.r5.model.DateType convertDate(org.hl7.fhir.dstu3.model.DateType src) throws FHIRException { public static org.hl7.fhir.r5.model.DateType convertDate(org.hl7.fhir.dstu3.model.DateType src) throws FHIRException {
org.hl7.fhir.r5.model.DateType tgt = new org.hl7.fhir.r5.model.DateType(src.getValueAsString()); org.hl7.fhir.r5.model.DateType tgt = new org.hl7.fhir.r5.model.DateType(src.getValueAsString());
copyElement(src, tgt); copyElement(src, tgt);
return tgt; return tgt;
} }
@ -290,6 +314,18 @@ public class VersionConvertor_30_50 {
return tgt; return tgt;
} }
public static org.hl7.fhir.r5.model.MarkdownType convertStringToMarkdown(org.hl7.fhir.dstu3.model.StringType src) throws FHIRException {
org.hl7.fhir.r5.model.MarkdownType tgt = new org.hl7.fhir.r5.model.MarkdownType(src.getValue());
copyElement(src, tgt);
return tgt;
}
public static org.hl7.fhir.dstu3.model.StringType convertMarkdownToString(org.hl7.fhir.r5.model.MarkdownType src) throws FHIRException {
org.hl7.fhir.dstu3.model.StringType tgt = new org.hl7.fhir.dstu3.model.StringType(src.getValue());
copyElement(src, tgt);
return tgt;
}
public static org.hl7.fhir.r5.model.TimeType convertTime(org.hl7.fhir.dstu3.model.TimeType src) throws FHIRException { public static org.hl7.fhir.r5.model.TimeType convertTime(org.hl7.fhir.dstu3.model.TimeType src) throws FHIRException {
org.hl7.fhir.r5.model.TimeType tgt = new org.hl7.fhir.r5.model.TimeType(src.getValue()); org.hl7.fhir.r5.model.TimeType tgt = new org.hl7.fhir.r5.model.TimeType(src.getValue());
copyElement(src, tgt); copyElement(src, tgt);
@ -326,6 +362,18 @@ public class VersionConvertor_30_50 {
return tgt; return tgt;
} }
public static org.hl7.fhir.r5.model.UriType convertCodeToUri(org.hl7.fhir.dstu3.model.CodeType src) throws FHIRException {
org.hl7.fhir.r5.model.UriType tgt = new org.hl7.fhir.r5.model.UriType(src.getValue());
copyElement(src, tgt);
return tgt;
}
public static org.hl7.fhir.dstu3.model.CodeType convertUriToCode(org.hl7.fhir.r5.model.UriType src) throws FHIRException {
org.hl7.fhir.dstu3.model.CodeType tgt = new org.hl7.fhir.dstu3.model.CodeType(src.getValue());
copyElement(src, tgt);
return tgt;
}
public static org.hl7.fhir.r5.model.UuidType convertUuid(org.hl7.fhir.dstu3.model.UuidType src) throws FHIRException { public static org.hl7.fhir.r5.model.UuidType convertUuid(org.hl7.fhir.dstu3.model.UuidType src) throws FHIRException {
org.hl7.fhir.r5.model.UuidType tgt = new org.hl7.fhir.r5.model.UuidType(src.getValue()); org.hl7.fhir.r5.model.UuidType tgt = new org.hl7.fhir.r5.model.UuidType(src.getValue());
copyElement(src, tgt); copyElement(src, tgt);

View File

@ -49,7 +49,7 @@ public class OperationDefinition30_50 {
if (src.hasCodeElement()) if (src.hasCodeElement())
tgt.setCodeElement((org.hl7.fhir.r5.model.CodeType) VersionConvertor_30_50.convertType(src.getCodeElement())); tgt.setCodeElement((org.hl7.fhir.r5.model.CodeType) VersionConvertor_30_50.convertType(src.getCodeElement()));
if (src.hasCommentElement()) if (src.hasCommentElement())
tgt.setCommentElement((org.hl7.fhir.r5.model.MarkdownType) VersionConvertor_30_50.convertType(src.getCommentElement())); tgt.setCommentElement(VersionConvertor_30_50.convertStringToMarkdown(src.getCommentElement()));
if (src.hasBase()) if (src.hasBase())
tgt.setBaseElement(VersionConvertor_30_50.convertReferenceToCanonical(src.getBase())); tgt.setBaseElement(VersionConvertor_30_50.convertReferenceToCanonical(src.getBase()));
if (src.hasResource()) { if (src.hasResource()) {
@ -109,7 +109,7 @@ public class OperationDefinition30_50 {
if (src.hasCodeElement()) if (src.hasCodeElement())
tgt.setCodeElement((org.hl7.fhir.dstu3.model.CodeType) VersionConvertor_30_50.convertType(src.getCodeElement())); tgt.setCodeElement((org.hl7.fhir.dstu3.model.CodeType) VersionConvertor_30_50.convertType(src.getCodeElement()));
if (src.hasCommentElement()) if (src.hasCommentElement())
tgt.setCommentElement((org.hl7.fhir.dstu3.model.StringType) VersionConvertor_30_50.convertType(src.getCommentElement())); tgt.setCommentElement(VersionConvertor_30_50.convertMarkdownToString(src.getCommentElement()));
if (src.hasBase()) if (src.hasBase())
tgt.setBase(VersionConvertor_30_50.convertCanonicalToReference(src.getBaseElement())); tgt.setBase(VersionConvertor_30_50.convertCanonicalToReference(src.getBaseElement()));
if (src.hasResource()) { if (src.hasResource()) {

View File

@ -175,8 +175,9 @@ public class StructureDefinition30_50 {
if (src.hasAbstractElement()) if (src.hasAbstractElement())
tgt.setAbstractElement((org.hl7.fhir.dstu3.model.BooleanType) VersionConvertor_30_50.convertType(src.getAbstractElement())); tgt.setAbstractElement((org.hl7.fhir.dstu3.model.BooleanType) VersionConvertor_30_50.convertType(src.getAbstractElement()));
for (org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionContextComponent t : src.getContext()) { for (org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionContextComponent t : src.getContext()) {
if (!tgt.hasContextType()) if (!tgt.hasContextType()) {
tgt.setTypeElement((org.hl7.fhir.dstu3.model.CodeType) VersionConvertor_30_50.convertType(src.getTypeElement())); tgt.setTypeElement((org.hl7.fhir.dstu3.model.CodeType) VersionConvertor_30_50.convertUriToCode(src.getTypeElement()));
}
tgt.addContext("Element".equals(t.getExpression()) ? "*" : t.getExpression()); tgt.addContext("Element".equals(t.getExpression()) ? "*" : t.getExpression());
} }
if (src.hasContextInvariant()) { if (src.hasContextInvariant()) {

View File

@ -117,7 +117,7 @@ public class ValueSet30_50 {
if (src.hasOp()) if (src.hasOp())
tgt.setOp(convertFilterOperator2(src.getOp())); tgt.setOp(convertFilterOperator2(src.getOp()));
if (src.hasValueElement()) if (src.hasValueElement())
tgt.setValueElement((org.hl7.fhir.dstu3.model.CodeType) VersionConvertor_30_50.convertType(src.getValueElement())); tgt.setValueElement((org.hl7.fhir.dstu3.model.CodeType) VersionConvertor_30_50.convertStringToCode(src.getValueElement()));
return tgt; return tgt;
} }
@ -131,7 +131,7 @@ public class ValueSet30_50 {
if (src.hasOp()) if (src.hasOp())
tgt.setOp(VersionConvertor_30_50.convertFilterOperator(src.getOp())); tgt.setOp(VersionConvertor_30_50.convertFilterOperator(src.getOp()));
if (src.hasValueElement()) if (src.hasValueElement())
tgt.setValueElement((org.hl7.fhir.r5.model.StringType) VersionConvertor_30_50.convertType(src.getValueElement())); tgt.setValueElement((org.hl7.fhir.r5.model.StringType) VersionConvertor_30_50.convertCodeToString(src.getValueElement()));
return tgt; return tgt;
} }

View File

@ -29,7 +29,7 @@
* <code>null</code> if none * <code>null</code> if none
*/ */
public Coding getSecurity(String theSystem, String theCode) { public Coding getSecurity(String theSystem, String theCode) {
for (Coding next : getTag()) { for (Coding next : getSecurity()) {
if (ca.uhn.fhir.util.ObjectUtil.equals(next.getSystem(), theSystem) && ca.uhn.fhir.util.ObjectUtil.equals(next.getCode(), theCode)) { if (ca.uhn.fhir.util.ObjectUtil.equals(next.getSystem(), theSystem) && ca.uhn.fhir.util.ObjectUtil.equals(next.getCode(), theCode)) {
return next; return next;
} }

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.core</artifactId> <artifactId>org.hl7.fhir.core</artifactId>
<version>4.1.54-SNAPSHOT</version> <version>4.1.63-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -277,7 +277,7 @@ public class Meta extends Type implements IBaseMetaType {
* <code>null</code> if none * <code>null</code> if none
*/ */
public Coding getSecurity(String theSystem, String theCode) { public Coding getSecurity(String theSystem, String theCode) {
for (Coding next : getTag()) { for (Coding next : getSecurity()) {
if (ca.uhn.fhir.util.ObjectUtil.equals(next.getSystem(), theSystem) && ca.uhn.fhir.util.ObjectUtil.equals(next.getCode(), theCode)) { if (ca.uhn.fhir.util.ObjectUtil.equals(next.getSystem(), theSystem) && ca.uhn.fhir.util.ObjectUtil.equals(next.getCode(), theCode)) {
return next; return next;
} }

View File

@ -0,0 +1,24 @@
package org.hl7.fhir.dstu2.test;
import org.hl7.fhir.dstu2.model.Coding;
import org.hl7.fhir.dstu2.model.Meta;
import org.junit.Test;
import static org.junit.Assert.*;
public class MetaTest {
public static String TEST_SYSTEM = "TEST_SYSTEM";
public static String TEST_CODE = "TEST_CODE";
@Test
public void testMetaSecurity() {
Meta meta = new Meta();
Coding coding = meta.addSecurity().setSystem(TEST_SYSTEM).setCode(TEST_CODE);
assertTrue(meta.hasSecurity());
assertNotNull(meta.getSecurity());
assertNotNull(meta.getSecurity(TEST_SYSTEM, TEST_CODE));
assertEquals(1, meta.getSecurity().size());
assertEquals(meta.getSecurity().get(0), meta.getSecurity(TEST_SYSTEM, TEST_CODE));
assertEquals(coding, meta.getSecurity(TEST_SYSTEM, TEST_CODE));
}
}

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.core</artifactId> <artifactId>org.hl7.fhir.core</artifactId>
<version>4.1.54-SNAPSHOT</version> <version>4.1.63-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.core</artifactId> <artifactId>org.hl7.fhir.core</artifactId>
<version>4.1.54-SNAPSHOT</version> <version>4.1.63-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -421,7 +421,7 @@ public class Meta extends Type implements IBaseMetaType {
* <code>null</code> if none * <code>null</code> if none
*/ */
public Coding getSecurity(String theSystem, String theCode) { public Coding getSecurity(String theSystem, String theCode) {
for (Coding next : getTag()) { for (Coding next : getSecurity()) {
if (ca.uhn.fhir.util.ObjectUtil.equals(next.getSystem(), theSystem) && ca.uhn.fhir.util.ObjectUtil.equals(next.getCode(), theCode)) { if (ca.uhn.fhir.util.ObjectUtil.equals(next.getSystem(), theSystem) && ca.uhn.fhir.util.ObjectUtil.equals(next.getCode(), theCode)) {
return next; return next;
} }

View File

@ -0,0 +1,26 @@
package org.hl7.fhir.dstu3.test;
import org.hl7.fhir.dstu3.model.Coding;
import org.hl7.fhir.dstu3.model.Meta;
import org.junit.Test;
import static org.junit.Assert.*;
public class MetaTest {
public static String TEST_SYSTEM = "TEST_SYSTEM";
public static String TEST_CODE = "TEST_CODE";
@Test
public void testMetaSecurity() {
Meta meta = new Meta();
Coding coding = meta.addSecurity().setSystem(TEST_SYSTEM).setCode(TEST_CODE);
assertTrue(meta.hasSecurity());
assertNotNull(meta.getSecurity());
assertNotNull(meta.getSecurity(TEST_SYSTEM, TEST_CODE));
assertEquals(1, meta.getSecurity().size());
assertEquals(meta.getSecurity().get(0), meta.getSecurity(TEST_SYSTEM, TEST_CODE));
assertEquals(meta.getSecurityFirstRep(), meta.getSecurity(TEST_SYSTEM, TEST_CODE));
assertEquals(coding, meta.getSecurity(TEST_SYSTEM, TEST_CODE));
}
}

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.core</artifactId> <artifactId>org.hl7.fhir.core</artifactId>
<version>4.1.54-SNAPSHOT</version> <version>4.1.63-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -476,7 +476,7 @@ public class Meta extends Type implements IBaseMetaType {
* <code>null</code> if none * <code>null</code> if none
*/ */
public Coding getSecurity(String theSystem, String theCode) { public Coding getSecurity(String theSystem, String theCode) {
for (Coding next : getTag()) { for (Coding next : getSecurity()) {
if (ca.uhn.fhir.util.ObjectUtil.equals(next.getSystem(), theSystem) && ca.uhn.fhir.util.ObjectUtil.equals(next.getCode(), theCode)) { if (ca.uhn.fhir.util.ObjectUtil.equals(next.getSystem(), theSystem) && ca.uhn.fhir.util.ObjectUtil.equals(next.getCode(), theCode)) {
return next; return next;
} }

View File

@ -0,0 +1,26 @@
package org.hl7.fhir.r4.test;
import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.Meta;
import org.junit.Test;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
public class MetaTest {
public static String TEST_SYSTEM = "TEST_SYSTEM";
public static String TEST_CODE = "TEST_CODE";
@Test
public void testMetaSecurity() {
Meta meta = new Meta();
Coding coding = meta.addSecurity().setSystem(TEST_SYSTEM).setCode(TEST_CODE);
assertTrue(meta.hasSecurity());
assertNotNull(meta.getSecurity());
assertNotNull(meta.getSecurity(TEST_SYSTEM, TEST_CODE));
assertEquals(1, meta.getSecurity().size());
assertEquals(meta.getSecurity().get(0), meta.getSecurity(TEST_SYSTEM, TEST_CODE));
assertEquals(meta.getSecurityFirstRep(), meta.getSecurity(TEST_SYSTEM, TEST_CODE));
assertEquals(coding, meta.getSecurity(TEST_SYSTEM, TEST_CODE));
}
}

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.core</artifactId> <artifactId>org.hl7.fhir.core</artifactId>
<version>4.1.54-SNAPSHOT</version> <version>4.1.63-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -463,6 +463,9 @@ public class ProfileUtilities extends TranslatingUtilities {
if (derived == null) { if (derived == null) {
throw new DefinitionException("no derived structure provided"); throw new DefinitionException("no derived structure provided");
} }
checkNotGenerating(base, "Base for generating a snapshot for the profile "+derived.getUrl());
checkNotGenerating(derived, "Focus for generating a snapshot");
derived.setUserData("profileutils.snapshot.generating", true);
if (!base.hasType()) { if (!base.hasType()) {
throw new DefinitionException("Base profile "+base.getUrl()+" has no type"); throw new DefinitionException("Base profile "+base.getUrl()+" has no type");
@ -622,6 +625,7 @@ public class ProfileUtilities extends TranslatingUtilities {
derived.setSnapshot(null); derived.setSnapshot(null);
throw e; throw e;
} }
derived.clearUserData("profileutils.snapshot.generating");
} }
private void checkDifferential(List<ElementDefinition> elements, String type, String url) { private void checkDifferential(List<ElementDefinition> elements, String type, String url) {
@ -853,10 +857,12 @@ public class ProfileUtilities extends TranslatingUtilities {
CanonicalType p = diffMatches.get(0).getType().get(0).getProfile().get(0); CanonicalType p = diffMatches.get(0).getType().get(0).getProfile().get(0);
StructureDefinition sd = context.fetchResource(StructureDefinition.class, p.getValue()); StructureDefinition sd = context.fetchResource(StructureDefinition.class, p.getValue());
if (sd != null) { if (sd != null) {
checkNotGenerating(sd, "an extension definition");
if (!sd.hasSnapshot()) { if (!sd.hasSnapshot()) {
StructureDefinition sdb = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition()); StructureDefinition sdb = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition());
if (sdb == null) if (sdb == null)
throw new DefinitionException("Unable to find base "+sd.getBaseDefinition()+" for "+sd.getUrl()); throw new DefinitionException("Unable to find base "+sd.getBaseDefinition()+" for "+sd.getUrl());
checkNotGenerating(sdb, "an extension base");
generateSnapshot(sdb, sd, sd.getUrl(), (sdb.hasUserData("path")) ? Utilities.extractBaseUrl(sdb.getUserString("path")) : webUrl, sd.getName()); generateSnapshot(sdb, sd, sd.getUrl(), (sdb.hasUserData("path")) ? Utilities.extractBaseUrl(sdb.getUserString("path")) : webUrl, sd.getName());
} }
ElementDefinition src; ElementDefinition src;
@ -905,7 +911,7 @@ public class ProfileUtilities extends TranslatingUtilities {
result.getElement().add(outcome); result.getElement().add(outcome);
baseCursor++; baseCursor++;
diffCursor = differential.getElement().indexOf(diffMatches.get(0))+1; diffCursor = differential.getElement().indexOf(diffMatches.get(0))+1;
if (diffLimit >= diffCursor && outcome.getPath().contains(".") && (isDataType(outcome.getType()) || outcome.hasContentReference())) { // don't want to do this for the root, since that's base, and we're already processing it if (diffLimit >= diffCursor && outcome.getPath().contains(".") && (isDataType(outcome.getType()) || isBaseResource(outcome.getType()) || outcome.hasContentReference())) { // don't want to do this for the root, since that's base, and we're already processing it
if (pathStartsWith(differential.getElement().get(diffCursor).getPath(), diffMatches.get(0).getPath()+".") && !baseWalksInto(base.getElement(), baseCursor)) { if (pathStartsWith(differential.getElement().get(diffCursor).getPath(), diffMatches.get(0).getPath()+".") && !baseWalksInto(base.getElement(), baseCursor)) {
if (outcome.getType().size() > 1) { if (outcome.getType().size() > 1) {
if (outcome.getPath().endsWith("[x]") && !diffMatches.get(0).getPath().endsWith("[x]")) { if (outcome.getPath().endsWith("[x]") && !diffMatches.get(0).getPath().endsWith("[x]")) {
@ -1424,7 +1430,7 @@ public class ProfileUtilities extends TranslatingUtilities {
outcome.setSlicing(null); outcome.setSlicing(null);
if (!outcome.getPath().startsWith(resultPathBase)) if (!outcome.getPath().startsWith(resultPathBase))
throw new DefinitionException("Adding wrong path"); throw new DefinitionException("Adding wrong path");
if (diffpos < diffMatches.size() && diffMatches.get(diffpos).getSliceName().equals(outcome.getSliceName())) { if (diffpos < diffMatches.size() && diffMatches.get(diffpos).hasSliceName() && diffMatches.get(diffpos).getSliceName().equals(outcome.getSliceName())) {
// if there's a diff, we update the outcome with diff // if there's a diff, we update the outcome with diff
// no? updateFromDefinition(outcome, diffMatches.get(diffpos), profileName, closed, url); // no? updateFromDefinition(outcome, diffMatches.get(diffpos), profileName, closed, url);
//then process any children //then process any children
@ -1540,6 +1546,24 @@ public class ProfileUtilities extends TranslatingUtilities {
} }
private void checkNotGenerating(StructureDefinition sd, String role) {
if (sd.hasUserData("profileutils.snapshot.generating")) {
throw new FHIRException("Attempt to use a snapshot on profile '"+sd.getUrl()+"' as "+role+" before it is generated");
}
}
private boolean isBaseResource(List<TypeRefComponent> types) {
if (types.isEmpty())
return false;
for (TypeRefComponent type : types) {
String t = type.getWorkingCode();
if ("Resource".equals(t))
return false;
}
return true;
}
public String determineFixedType(List<ElementDefinition> diffMatches, String fixedType, int i) { public String determineFixedType(List<ElementDefinition> diffMatches, String fixedType, int i) {
if (diffMatches.get(i).getType().size() == 0 && diffMatches.get(i).hasSliceName()) { if (diffMatches.get(i).getType().size() == 0 && diffMatches.get(i).hasSliceName()) {
String n = tail(diffMatches.get(i).getPath()).replace("[x]", ""); String n = tail(diffMatches.get(i).getPath()).replace("[x]", "");
@ -2441,62 +2465,7 @@ public class ProfileUtilities extends TranslatingUtilities {
if (!Base.compareDeep(derived.getType(), base.getType(), false)) { if (!Base.compareDeep(derived.getType(), base.getType(), false)) {
if (base.hasType()) { if (base.hasType()) {
for (TypeRefComponent ts : derived.getType()) { for (TypeRefComponent ts : derived.getType()) {
boolean ok = false; checkTypeDerivation(purl, srcSD, base, derived, ts);
CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
String t = ts.getWorkingCode();
for (TypeRefComponent td : base.getType()) {;
String tt = td.getWorkingCode();
b.append(tt);
if (td.hasCode() && (tt.equals(t))) {
ok = true;
}
if (!ok) {
StructureDefinition sdt = context.fetchTypeDefinition(tt);
if (sdt != null && sdt.getAbstract()) {
StructureDefinition sdb = context.fetchTypeDefinition(t);
while (sdb != null && !ok) {
ok = sdb.getType().equals(sdt.getUrl());
sdb = context.fetchResource(StructureDefinition.class, sdb.getBaseDefinition());
}
}
}
// work around for old badly generated SDs
if (DONT_DO_THIS && Utilities.existsInList(tt, "Extension", "uri", "string", "Element")) {
ok = true;
}
if (DONT_DO_THIS && Utilities.existsInList(tt, "Resource","DomainResource") && pkp.isResource(t)) {
ok = true;
}
if (ok && ts.hasTargetProfile()) {
// check that any derived target has a reference chain back to one of the base target profiles
for (UriType u : ts.getTargetProfile()) {
String url = u.getValue();
boolean tgtOk = !td.hasTargetProfile() || td.hasTargetProfile(url);
while (url != null && !tgtOk) {
StructureDefinition sd = context.fetchResource(StructureDefinition.class, url);
if (sd == null) {
if (messages != null) {
messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, purl+"#"+derived.getPath(), "Connect check whether the target profile "+url+" is valid constraint on the base because it is not known", IssueSeverity.WARNING));
}
url = null;
tgtOk = true; // suppress error message
} else {
url = sd.getBaseDefinition();
tgtOk = td.hasTargetProfile(url);
}
}
if (!tgtOk) {
if (messages == null) {
throw new FHIRException("Error at "+purl+"#"+derived.getPath()+": The target profile "+url+" is not valid constraint on the base ("+td.getTargetProfile()+")");
} else {
messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, derived.getPath(), "The target profile "+u.getValue()+" is not a valid constraint on the base ("+td.getTargetProfile()+") at "+derived.getPath(), IssueSeverity.ERROR));
}
}
}
}
}
if (!ok)
throw new DefinitionException("StructureDefinition "+purl+" at "+derived.getPath()+": illegal constrained type "+t+" from "+b.toString()+" in "+srcSD.getUrl());
} }
} }
base.getType().clear(); base.getType().clear();
@ -2576,6 +2545,66 @@ public class ProfileUtilities extends TranslatingUtilities {
} }
} }
public void checkTypeDerivation(String purl, StructureDefinition srcSD, ElementDefinition base, ElementDefinition derived, TypeRefComponent ts) {
boolean ok = false;
CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
String t = ts.getWorkingCode();
for (TypeRefComponent td : base.getType()) {;
String tt = td.getWorkingCode();
b.append(tt);
if (td.hasCode() && (tt.equals(t))) {
ok = true;
}
if (!ok) {
StructureDefinition sdt = context.fetchTypeDefinition(tt);
if (sdt != null && sdt.getAbstract()) {
StructureDefinition sdb = context.fetchTypeDefinition(t);
while (sdb != null && !ok) {
ok = sdb.getType().equals(sdt.getType());
sdb = context.fetchResource(StructureDefinition.class, sdb.getBaseDefinition());
}
}
}
// work around for old badly generated SDs
if (DONT_DO_THIS && Utilities.existsInList(tt, "Extension", "uri", "string", "Element")) {
ok = true;
}
if (DONT_DO_THIS && Utilities.existsInList(tt, "Resource","DomainResource") && pkp.isResource(t)) {
ok = true;
}
if (ok && ts.hasTargetProfile()) {
// check that any derived target has a reference chain back to one of the base target profiles
for (UriType u : ts.getTargetProfile()) {
String url = u.getValue();
boolean tgtOk = !td.hasTargetProfile() || td.hasTargetProfile(url);
while (url != null && !tgtOk) {
StructureDefinition sd = context.fetchRawProfile(url);
if (sd == null) {
if (messages != null) {
messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, purl+"#"+derived.getPath(), "Connect check whether the target profile "+url+" is valid constraint on the base because it is not known", IssueSeverity.WARNING));
}
url = null;
tgtOk = true; // suppress error message
} else {
url = sd.getBaseDefinition();
tgtOk = td.hasTargetProfile(url);
}
}
if (!tgtOk) {
if (messages == null) {
throw new FHIRException("Error at "+purl+"#"+derived.getPath()+": The target profile "+url+" is not valid constraint on the base ("+td.getTargetProfile()+")");
} else {
messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, derived.getPath(), "The target profile "+u.getValue()+" is not a valid constraint on the base ("+td.getTargetProfile()+") at "+derived.getPath(), IssueSeverity.ERROR));
}
}
}
}
}
if (!ok) {
throw new DefinitionException("StructureDefinition "+purl+" at "+derived.getPath()+": illegal constrained type "+t+" from "+b.toString()+" in "+srcSD.getUrl());
}
}
public void checkTypeOk(ElementDefinition dest, String ft) { public void checkTypeOk(ElementDefinition dest, String ft) {
boolean ok = false; boolean ok = false;
@ -4354,13 +4383,13 @@ public class ProfileUtilities extends TranslatingUtilities {
@Override @Override
public int compare(ElementDefinitionHolder o1, ElementDefinitionHolder o2) { public int compare(ElementDefinitionHolder o1, ElementDefinitionHolder o2) {
if (o1.getBaseIndex() == 0) if (o1.getBaseIndex() == 0)
o1.setBaseIndex(find(o1.getSelf().getPath())); o1.setBaseIndex(find(o1.getSelf().getPath(), true));
if (o2.getBaseIndex() == 0) if (o2.getBaseIndex() == 0)
o2.setBaseIndex(find(o2.getSelf().getPath())); o2.setBaseIndex(find(o2.getSelf().getPath(), true));
return o1.getBaseIndex() - o2.getBaseIndex(); return o1.getBaseIndex() - o2.getBaseIndex();
} }
private int find(String path) { private int find(String path, boolean mandatory) {
String op = path; String op = path;
int lc = 0; int lc = 0;
String actual = base+path.substring(prefixLength); String actual = base+path.substring(prefixLength);
@ -4392,10 +4421,12 @@ public class ProfileUtilities extends TranslatingUtilities {
throw new Error("Internal recursion detection: find() loop path recursion > "+MAX_RECURSION_LIMIT+" - check paths are valid (for path "+path+"/"+op+")"); throw new Error("Internal recursion detection: find() loop path recursion > "+MAX_RECURSION_LIMIT+" - check paths are valid (for path "+path+"/"+op+")");
} }
} }
if (prefixLength == 0) if (mandatory) {
errors.add("Differential contains path "+path+" which is not found in the base"); if (prefixLength == 0)
else errors.add("Differential contains path "+path+" which is not found in the base");
errors.add("Differential contains path "+path+" which is actually "+actual+", which is not found in the base"); else
errors.add("Differential contains path "+path+" which is actually "+actual+", which is not found in the base");
}
return 0; return 0;
} }
@ -4508,7 +4539,7 @@ public class ProfileUtilities extends TranslatingUtilities {
private void sortElements(ElementDefinitionHolder edh, ElementDefinitionComparer cmp, List<String> errors) throws FHIRException { private void sortElements(ElementDefinitionHolder edh, ElementDefinitionComparer cmp, List<String> errors) throws FHIRException {
if (edh.getChildren().size() == 1) if (edh.getChildren().size() == 1)
// special case - sort needsto allocate base numbers, but there'll be no sort if there's only 1 child. So in that case, we just go ahead and allocated base number directly // special case - sort needsto allocate base numbers, but there'll be no sort if there's only 1 child. So in that case, we just go ahead and allocated base number directly
edh.getChildren().get(0).baseIndex = cmp.find(edh.getChildren().get(0).getSelf().getPath()); edh.getChildren().get(0).baseIndex = cmp.find(edh.getChildren().get(0).getSelf().getPath(), false);
else else
Collections.sort(edh.getChildren(), cmp); Collections.sort(edh.getChildren(), cmp);
cmp.checkForErrors(errors); cmp.checkForErrors(errors);
@ -4516,8 +4547,9 @@ public class ProfileUtilities extends TranslatingUtilities {
for (ElementDefinitionHolder child : edh.getChildren()) { for (ElementDefinitionHolder child : edh.getChildren()) {
if (child.getChildren().size() > 0) { if (child.getChildren().size() > 0) {
ElementDefinitionComparer ccmp = getComparer(cmp, child); ElementDefinitionComparer ccmp = getComparer(cmp, child);
if (ccmp != null) if (ccmp != null) {
sortElements(child, ccmp, errors); sortElements(child, ccmp, errors);
}
} }
} }
} }
@ -4528,7 +4560,22 @@ public class ProfileUtilities extends TranslatingUtilities {
ElementDefinition ed = cmp.snapshot.get(child.getBaseIndex()); ElementDefinition ed = cmp.snapshot.get(child.getBaseIndex());
ElementDefinitionComparer ccmp; ElementDefinitionComparer ccmp;
if (ed.getType().isEmpty() || isAbstract(ed.getType().get(0).getWorkingCode()) || ed.getType().get(0).getWorkingCode().equals(ed.getPath())) { if (ed.getType().isEmpty() || isAbstract(ed.getType().get(0).getWorkingCode()) || ed.getType().get(0).getWorkingCode().equals(ed.getPath())) {
ccmp = new ElementDefinitionComparer(true, cmp.snapshot, cmp.base, cmp.prefixLength, cmp.name); if (ed.hasType() && "Resource".equals(ed.getType().get(0).getWorkingCode()) && child.getSelf().getType().get(0).hasProfile()) {
if (child.getSelf().getType().get(0).getProfile().size() > 1) {
throw new FHIRException("Unhandled situation: resource is profiled to more than one option - cannot sort profile");
}
StructureDefinition profile = context.fetchResource(StructureDefinition.class, child.getSelf().getType().get(0).getProfile().get(0).getValue());
while (profile != null && profile.getDerivation() == TypeDerivationRule.CONSTRAINT) {
profile = context.fetchResource(StructureDefinition.class, profile.getBaseDefinition());
}
if (profile==null) {
ccmp = null; // this might happen before everything is loaded. And we don't so much care about sot order in this case
} else {
ccmp = new ElementDefinitionComparer(true, profile.getSnapshot().getElement(), profile.getType(), child.getSelf().getPath().length(), cmp.name);
}
} else {
ccmp = new ElementDefinitionComparer(true, cmp.snapshot, cmp.base, cmp.prefixLength, cmp.name);
}
} else if (ed.getType().get(0).getWorkingCode().equals("Extension") && child.getSelf().getType().size() == 1 && child.getSelf().getType().get(0).hasProfile()) { } else if (ed.getType().get(0).getWorkingCode().equals("Extension") && child.getSelf().getType().size() == 1 && child.getSelf().getType().get(0).hasProfile()) {
StructureDefinition profile = context.fetchResource(StructureDefinition.class, child.getSelf().getType().get(0).getProfile().get(0).getValue()); StructureDefinition profile = context.fetchResource(StructureDefinition.class, child.getSelf().getType().get(0).getProfile().get(0).getValue());
if (profile==null) if (profile==null)
@ -5264,14 +5311,18 @@ public class ProfileUtilities extends TranslatingUtilities {
private String getCardinality(ElementDefinition ed, List<ElementDefinition> list) { private String getCardinality(ElementDefinition ed, List<ElementDefinition> list) {
int min = ed.getMin(); int min = ed.getMin();
int max = !ed.hasMax() || ed.getMax().equals("*") ? Integer.MAX_VALUE : Integer.parseInt(ed.getMax()); int max = !ed.hasMax() || ed.getMax().equals("*") ? Integer.MAX_VALUE : Integer.parseInt(ed.getMax());
while (ed != null && ed.getPath().contains(".")) { ElementDefinition ned = ed;
ed = findParent(ed, list); while (ned != null && ned.getPath().contains(".")) {
if (ed.getMax().equals("0")) ned = findParent(ned, list);
max = 0; if (ned != null) { // todo: this can happen if we've walked into a resoruce. Not sure what to about that?
else if (!ed.getMax().equals("1") && !ed.hasSlicing()) if ("0".equals(ned.getMax()))
max = Integer.MAX_VALUE; max = 0;
if (ed.getMin() == 0) else if (!ned.getMax().equals("1") && !ned.hasSlicing())
min = 0; max = Integer.MAX_VALUE;
if (ned.getMin() == 0) {
min = 0;
}
}
} }
return Integer.toString(min)+".."+(max == Integer.MAX_VALUE ? "*" : Integer.toString(max)); return Integer.toString(min)+".."+(max == Integer.MAX_VALUE ? "*" : Integer.toString(max));
} }

View File

@ -539,6 +539,7 @@ public interface IWorkerContext {
public void setOverrideVersionNs(String value); public void setOverrideVersionNs(String value);
public StructureDefinition fetchTypeDefinition(String typeName); public StructureDefinition fetchTypeDefinition(String typeName);
public StructureDefinition fetchRawProfile(String url);
public void setUcumService(UcumService ucumService); public void setUcumService(UcumService ucumService);

View File

@ -640,6 +640,12 @@ public class SimpleWorkerContext extends BaseWorkerContext implements IWorkerCon
return r; return r;
} }
@Override
public StructureDefinition fetchRawProfile(String uri) {
StructureDefinition r = super.fetchResource(StructureDefinition.class, uri);
return r;
}
@Override @Override
public void generateSnapshot(StructureDefinition p) throws DefinitionException, FHIRException { public void generateSnapshot(StructureDefinition p) throws DefinitionException, FHIRException {
generateSnapshot(p, false); generateSnapshot(p, false);

View File

@ -671,7 +671,7 @@ public class Meta extends DataType implements IBaseMetaType {
* <code>null</code> if none * <code>null</code> if none
*/ */
public Coding getSecurity(String theSystem, String theCode) { public Coding getSecurity(String theSystem, String theCode) {
for (Coding next : getTag()) { for (Coding next : getSecurity()) {
if (ca.uhn.fhir.util.ObjectUtil.equals(next.getSystem(), theSystem) && ca.uhn.fhir.util.ObjectUtil.equals(next.getCode(), theCode)) { if (ca.uhn.fhir.util.ObjectUtil.equals(next.getSystem(), theSystem) && ca.uhn.fhir.util.ObjectUtil.equals(next.getCode(), theCode)) {
return next; return next;
} }

View File

@ -1620,6 +1620,8 @@ public class NarrativeGenerator implements INarrativeGenerator {
return false; return false;
} else if (e instanceof ElementDefinition) { } else if (e instanceof ElementDefinition) {
return false; return false;
} else if (e instanceof Base64BinaryType) {
return false;
} else if (!(e instanceof Attachment)) } else if (!(e instanceof Attachment))
throw new NotImplementedException("type "+e.getClass().getName()+" not handled yet"); throw new NotImplementedException("type "+e.getClass().getName()+" not handled yet");
return false; return false;
@ -2515,6 +2517,8 @@ public class NarrativeGenerator implements INarrativeGenerator {
if (!x.hasAttribute("xmlns")) if (!x.hasAttribute("xmlns"))
x.setAttribute("xmlns", "http://www.w3.org/1999/xhtml"); x.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
if (r.hasLanguage()) { if (r.hasLanguage()) {
// use both - see https://www.w3.org/TR/i18n-html-tech-lang/#langvalues
x.setAttribute("lang", r.getLanguage());
x.setAttribute("xml:lang", r.getLanguage()); x.setAttribute("xml:lang", r.getLanguage());
} }
if (!r.hasText() || !r.getText().hasDiv() || r.getText().getDiv().getChildNodes().isEmpty()) { if (!r.hasText() || !r.getText().hasDiv() || r.getText().getDiv().getChildNodes().isEmpty()) {
@ -2539,6 +2543,13 @@ public class NarrativeGenerator implements INarrativeGenerator {
private void inject(Element er, XhtmlNode x, NarrativeStatus status) { private void inject(Element er, XhtmlNode x, NarrativeStatus status) {
if (!x.hasAttribute("xmlns")) if (!x.hasAttribute("xmlns"))
x.setAttribute("xmlns", "http://www.w3.org/1999/xhtml"); x.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
Element le = XMLUtil.getNamedChild(er, "language");
String l = le == null ? null : le.getAttribute("value");
if (!Utilities.noString(l)) {
// use both - see https://www.w3.org/TR/i18n-html-tech-lang/#langvalues
x.setAttribute("lang", l);
x.setAttribute("xml:lang", l);
}
Element txt = XMLUtil.getNamedChild(er, "text"); Element txt = XMLUtil.getNamedChild(er, "text");
if (txt == null) { if (txt == null) {
txt = er.getOwnerDocument().createElementNS(FormatUtilities.FHIR_NS, "text"); txt = er.getOwnerDocument().createElementNS(FormatUtilities.FHIR_NS, "text");
@ -2574,6 +2585,12 @@ public class NarrativeGenerator implements INarrativeGenerator {
private void inject(org.hl7.fhir.r5.elementmodel.Element er, XhtmlNode x, NarrativeStatus status) throws IOException, FHIRException { private void inject(org.hl7.fhir.r5.elementmodel.Element er, XhtmlNode x, NarrativeStatus status) throws IOException, FHIRException {
if (!x.hasAttribute("xmlns")) if (!x.hasAttribute("xmlns"))
x.setAttribute("xmlns", "http://www.w3.org/1999/xhtml"); x.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
String l = er.getChildValue("language");
if (!Utilities.noString(l)) {
// use both - see https://www.w3.org/TR/i18n-html-tech-lang/#langvalues
x.setAttribute("lang", l);
x.setAttribute("xml:lang", l);
}
org.hl7.fhir.r5.elementmodel.Element txt = er.getNamedChild("text"); org.hl7.fhir.r5.elementmodel.Element txt = er.getNamedChild("text");
if (txt == null) { if (txt == null) {
txt = new org.hl7.fhir.r5.elementmodel.Element("text", er.getProperty().getChild(null, "text")); txt = new org.hl7.fhir.r5.elementmodel.Element("text", er.getProperty().getChild(null, "text"));
@ -2919,6 +2936,9 @@ public class NarrativeGenerator implements INarrativeGenerator {
} }
private ConceptMapRenderInstructions findByTarget(DataType source) { private ConceptMapRenderInstructions findByTarget(DataType source) {
if (source == null) {
return null;
}
String src = source.primitiveValue(); String src = source.primitiveValue();
if (src != null) if (src != null)
for (ConceptMapRenderInstructions t : renderingMaps) { for (ConceptMapRenderInstructions t : renderingMaps) {

View File

@ -7,6 +7,8 @@ import org.junit.runners.Suite.SuiteClasses;
@RunWith(Suite.class) @RunWith(Suite.class)
@SuiteClasses({ @SuiteClasses({
NpmPackageTests.class,
PackageClientTests.class,
SnomedExpressionsTests.class, SnomedExpressionsTests.class,
GraphQLParserTests.class, GraphQLParserTests.class,
TurtleTests.class, TurtleTests.class,
@ -21,7 +23,8 @@ import org.junit.runners.Suite.SuiteClasses;
BaseDateTimeTypeTest.class, BaseDateTimeTypeTest.class,
OpenApiGeneratorTest.class, OpenApiGeneratorTest.class,
MetadataResourceManagerTester.class, MetadataResourceManagerTester.class,
NpmPackageTests.class, MetaTest.class,
UtilitiesTests.class,
SnapShotGenerationTests.class}) SnapShotGenerationTests.class})
public class AllR5Tests { public class AllR5Tests {

View File

@ -99,7 +99,7 @@ public class FHIRPathTests {
@Parameters(name = "{index}: file {0}") @Parameters(name = "{index}: file {0}")
public static Iterable<Object[]> data() throws ParserConfigurationException, SAXException, IOException { public static Iterable<Object[]> data() throws ParserConfigurationException, SAXException, IOException {
Document dom = XMLUtil.parseToDom(TestingUtilities.loadTestResource("r5", "fhirpath", "tests-fhir-r4.xml")); Document dom = XMLUtil.parseToDom(TestingUtilities.loadTestResource("r5", "fhirpath", "tests-fhir-r5.xml"));
List<Element> list = new ArrayList<Element>(); List<Element> list = new ArrayList<Element>();
List<Element> groups = new ArrayList<Element>(); List<Element> groups = new ArrayList<Element>();

View File

@ -0,0 +1,26 @@
package org.hl7.fhir.r5.test;
import org.hl7.fhir.r5.model.Coding;
import org.hl7.fhir.r5.model.Meta;
import org.junit.Test;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
public class MetaTest {
public static String TEST_SYSTEM = "TEST_SYSTEM";
public static String TEST_CODE = "TEST_CODE";
@Test
public void testMetaSecurity() {
Meta meta = new Meta();
Coding coding = meta.addSecurity().setSystem(TEST_SYSTEM).setCode(TEST_CODE);
assertTrue(meta.hasSecurity());
assertNotNull(meta.getSecurity());
assertNotNull(meta.getSecurity(TEST_SYSTEM, TEST_CODE));
assertEquals(1, meta.getSecurity().size());
assertEquals(meta.getSecurity().get(0), meta.getSecurity(TEST_SYSTEM, TEST_CODE));
assertEquals(meta.getSecurityFirstRep(), meta.getSecurity(TEST_SYSTEM, TEST_CODE));
assertEquals(coding, meta.getSecurity(TEST_SYSTEM, TEST_CODE));
}
}

View File

@ -0,0 +1,36 @@
package org.hl7.fhir.r5.test;
import java.io.File;
import java.io.IOException;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.cache.NpmPackage;
import org.hl7.fhir.utilities.cache.PackageCacheManager;
import org.hl7.fhir.utilities.cache.ToolsVersion;
import org.junit.Assert;
import org.junit.Test;
public class PackageCacheTests {
@Test
public void testPath() throws IOException {
PackageCacheManager cache = new PackageCacheManager(true, ToolsVersion.TOOLS_VERSION);
cache.clear();
NpmPackage npm = cache.loadPackage("hl7.fhir.pubpack", "0.0.3");
npm.loadAllFiles();
Assert.assertNotNull(npm);
File dir = new File(Utilities.path("[tmp]", "cache"));
if (dir.exists()) {
Utilities.clearDirectory(dir.getAbsolutePath());
} else {
Utilities.createDirectory(dir.getAbsolutePath());
}
npm.save(dir);
NpmPackage npm2 = cache.loadPackage("hl7.fhir.pubpack", "file:"+dir.getAbsolutePath());
Assert.assertNotNull(npm2);
}
}

View File

@ -0,0 +1,110 @@
package org.hl7.fhir.r5.test;
import java.io.IOException;
import java.util.List;
import org.hl7.fhir.utilities.cache.PackageClient;
import org.hl7.fhir.utilities.cache.PackageClient.PackageInfo;
import org.junit.Assert;
import org.junit.Test;
public class PackageClientTests {
@Test
public void testExists() throws IOException {
PackageClient client = new PackageClient("http://packages.fhir.org");
Assert.assertTrue(client.exists("hl7.fhir.r4.core", "4.0.1"));
Assert.assertTrue(!client.exists("hl7.fhir.r4.core", "1.0.2"));
Assert.assertTrue(!client.exists("hl7.fhir.nothing", "1.0.1"));
}
@Test
public void testSearch() throws IOException {
PackageClient client = new PackageClient("http://packages.fhir.org");
List<PackageInfo> matches = client.search("core", null, null, false);
for (PackageInfo pi : matches) {
System.out.println(pi.toString());
}
Assert.assertTrue(matches.size() > 0);
}
@Test
public void testSearchNoMatches() throws IOException {
PackageClient client = new PackageClient("http://packages.fhir.org");
List<PackageInfo> matches = client.search("corezxxx", null, null, false);
Assert.assertTrue(matches.size() == 0);
}
@Test
public void testVersions() throws IOException {
PackageClient client = new PackageClient("http://packages.fhir.org");
List<PackageInfo> matches = client.getVersions("Simplifier.Core.STU3");
for (PackageInfo pi : matches) {
System.out.println(pi.toString());
}
Assert.assertTrue(matches.size() > 0);
}
@Test
public void testVersionsNone() throws IOException {
PackageClient client = new PackageClient("http://packages.fhir.org");
List<PackageInfo> matches = client.getVersions("Simplifier.Core.STU3X");
Assert.assertTrue(matches.size() == 0);
}
@Test
public void testExists2() throws IOException {
PackageClient client = new PackageClient("http://test.fhir.org/packages");
Assert.assertTrue(client.exists("hl7.fhir.r4.core", "4.0.1"));
Assert.assertTrue(!client.exists("hl7.fhir.r4.core", "1.0.2"));
Assert.assertTrue(!client.exists("hl7.fhir.nothing", "1.0.1"));
}
@Test
public void testSearch2() throws IOException {
PackageClient client = new PackageClient("http://test.fhir.org/packages");
List<PackageInfo> matches = client.search("core", null, null, false);
for (PackageInfo pi : matches) {
System.out.println(pi.toString());
}
Assert.assertTrue(matches.size() > 0);
}
@Test
public void testSearchNoMatches2() throws IOException {
PackageClient client = new PackageClient("http://test.fhir.org/packages");
List<PackageInfo> matches = client.search("corezxxx", null, null, false);
Assert.assertTrue(matches.size() == 0);
}
@Test
public void testVersions2() throws IOException {
PackageClient client = new PackageClient("http://test.fhir.org/packages");
List<PackageInfo> matches = client.getVersions("Simplifier.Core.STU3");
for (PackageInfo pi : matches) {
System.out.println(pi.toString());
}
Assert.assertTrue(matches.size() == 0);
}
@Test
public void testVersions2A() throws IOException {
PackageClient client = new PackageClient("http://test.fhir.org/packages");
List<PackageInfo> matches = client.getVersions("hl7.fhir.us.core");
for (PackageInfo pi : matches) {
System.out.println(pi.toString());
}
Assert.assertTrue(matches.size() > 0);
}
@Test
public void testVersionsNone2() throws IOException {
PackageClient client = new PackageClient("http://test.fhir.org/packages");
List<PackageInfo> matches = client.getVersions("Simplifier.Core.STU3X");
Assert.assertTrue(matches.size() == 0);
}
}

View File

@ -473,7 +473,9 @@ public class SnapShotGenerationTests {
pu.setNewSlicingProcessing(true); pu.setNewSlicingProcessing(true);
pu.setIds(test.included, false); pu.setIds(test.included, false);
StructureDefinition base = TestingUtilities.context().fetchResource(StructureDefinition.class, test.included.getBaseDefinition()); StructureDefinition base = TestingUtilities.context().fetchResource(StructureDefinition.class, test.included.getBaseDefinition());
pu.generateSnapshot(base, test.included, test.included.getUrl(), "http://test.org/profile", test.included.getName()); if (base != null) {
pu.generateSnapshot(base, test.included, test.included.getUrl(), "http://test.org/profile", test.included.getName());
}
if (!TestingUtilities.context().hasResource(StructureDefinition.class, test.included.getUrl())) if (!TestingUtilities.context().hasResource(StructureDefinition.class, test.included.getUrl()))
TestingUtilities.context().cacheResource(test.included); TestingUtilities.context().cacheResource(test.included);
int ec = 0; int ec = 0;

View File

@ -0,0 +1,19 @@
package org.hl7.fhir.r5.test;
import java.io.IOException;
import org.apache.commons.lang3.SystemUtils;
import org.hl7.fhir.utilities.Utilities;
import org.junit.Test;
import junit.framework.Assert;
public class UtilitiesTests {
@Test
public void testPath() throws IOException {
Assert.assertEquals(Utilities.path("[tmp]", "test.txt"), SystemUtils.IS_OS_WINDOWS ? "c:\\temp\\test.txt" : "/temp/test.txt");
Assert.assertEquals(Utilities.path("[user]", "test.txt"), System.getProperty("user.home")+"\\test.txt");
Assert.assertEquals(Utilities.path("[JAVA_HOME]", "test.txt"), System.getenv("JAVA_HOME")+"\\test.txt");
}
}

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.core</artifactId> <artifactId>org.hl7.fhir.core</artifactId>
<version>4.1.54-SNAPSHOT</version> <version>4.1.63-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -63,6 +63,7 @@ import java.io.UnsupportedEncodingException;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.nio.channels.FileChannel; import java.nio.channels.FileChannel;
import java.nio.file.Paths;
import java.time.Duration; import java.time.Duration;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@ -568,11 +569,20 @@ public class Utilities {
else if (!s.toString().endsWith(File.separator)) else if (!s.toString().endsWith(File.separator))
s.append(File.separator); s.append(File.separator);
String a = arg; String a = arg;
if ("[tmp]".equals(a)) { if (s.length() == 0) {
if (hasCTempDir()) { if ("[tmp]".equals(a)) {
a = "c:\\temp"; if (hasCTempDir()) {
} else { a = "c:\\temp";
a = System.getProperty("java.io.tmpdir"); } else {
a = System.getProperty("java.io.tmpdir");
}
} else if ("[user]".equals(a)) {
a = System.getProperty("user.home");
} else if (a.startsWith("[") && a.endsWith("]")){
String ev = System.getenv(a.replace("[", "").replace("]", ""));
if (ev != null) {
a = ev;
}
} }
} }
a = a.replace("\\", File.separator); a = a.replace("\\", File.separator);
@ -581,11 +591,15 @@ public class Utilities {
a = a.substring(File.separator.length()); a = a.substring(File.separator.length());
while (a.startsWith(".."+File.separator)) { while (a.startsWith(".."+File.separator)) {
String p = s.toString().substring(0, s.length()-1); if (s.length() == 0) {
if (!p.contains(File.separator)) { s = new StringBuilder(Paths.get(".").toAbsolutePath().normalize().toString());
s = new StringBuilder();
} else { } else {
s = new StringBuilder(p.substring(0, p.lastIndexOf(File.separator))+File.separator); String p = s.toString().substring(0, s.length()-1);
if (!p.contains(File.separator)) {
s = new StringBuilder();
} else {
s = new StringBuilder(p.substring(0, p.lastIndexOf(File.separator))+File.separator);
}
} }
a = a.substring(3); a = a.substring(3);
} }
@ -599,6 +613,9 @@ public class Utilities {
} }
private static boolean hasCTempDir() { private static boolean hasCTempDir() {
if (!System.getProperty("os.name").toLowerCase().contains("win")) {
return false;
}
File tmp = new File("c:\\temp"); File tmp = new File("c:\\temp");
return tmp.exists() && tmp.isDirectory(); return tmp.exists() && tmp.isDirectory();
} }

View File

@ -186,6 +186,7 @@ public class NpmPackage {
public static void loadFiles(NpmPackage res, String path, File source, String... exemptions) throws FileNotFoundException, IOException { public static void loadFiles(NpmPackage res, String path, File source, String... exemptions) throws FileNotFoundException, IOException {
res.npm = (JsonObject) new com.google.gson.JsonParser().parse(TextFile.fileToString(Utilities.path(path, "package", "package.json"))); res.npm = (JsonObject) new com.google.gson.JsonParser().parse(TextFile.fileToString(Utilities.path(path, "package", "package.json")));
res.path = path;
File dir = new File(path); File dir = new File(path);
for (File f : dir.listFiles()) { for (File f : dir.listFiles()) {
@ -622,6 +623,37 @@ public class NpmPackage {
return folders; return folders;
} }
public void save(File directory) throws IOException {
File dir = new File(Utilities.path(directory.getAbsolutePath(), name()));
if (!dir.exists()) {
Utilities.createDirectory(dir.getAbsolutePath());
} else {
Utilities.clearDirectory(dir.getAbsolutePath());
}
for (NpmPackageFolder folder : folders.values()) {
String n = folder.name;
File pd = new File(Utilities.path(dir.getAbsolutePath(), n));
if (!pd.exists()) {
Utilities.createDirectory(pd.getAbsolutePath());
}
NpmPackageIndexBuilder indexer = new NpmPackageIndexBuilder();
indexer.start();
for (String s : folder.content.keySet()) {
byte[] b = folder.content.get(s);
indexer.seeFile(s, b);
if (!s.equals(".index.json") && !s.equals("package.json")) {
TextFile.bytesToFile(b, Utilities.path(dir.getAbsolutePath(), n, s));
}
}
byte[] cnt = indexer.build().getBytes(Charset.forName("UTF-8"));
TextFile.bytesToFile(cnt, Utilities.path(dir.getAbsolutePath(), n, ".index.json"));
}
byte[] cnt = TextFile.stringToBytes(new GsonBuilder().setPrettyPrinting().create().toJson(npm), false);
TextFile.bytesToFile(cnt, Utilities.path(dir.getAbsolutePath(), "package", "package.json"));
}
public void save(OutputStream stream) throws IOException { public void save(OutputStream stream) throws IOException {
TarArchiveOutputStream tar; TarArchiveOutputStream tar;
ByteArrayOutputStream OutputStream; ByteArrayOutputStream OutputStream;
@ -756,5 +788,35 @@ public class NpmPackage {
Collections.sort(res); Collections.sort(res);
return res ; return res ;
} }
public void clearFolder(String folderName) {
NpmPackageFolder folder = folders.get(folderName);
folder.content.clear();
folder.types.clear();
}
public void deleteFolder(String folderName) {
folders.remove(folderName);
}
public void addFile(String folderName, String name, byte[] cnt, String type) {
NpmPackageFolder folder = folders.get(folderName);
folder.content.put(name, cnt);
if (!folder.types.containsKey(type))
folder.types.put(type, new ArrayList<>());
folder.types.get(type).add(name);
}
public void loadAllFiles() throws IOException {
for (String folder : folders.keySet()) {
NpmPackageFolder pf = folders.get(folder);
String p = Utilities.path(path, folder);
for (File f : new File(p).listFiles()) {
if (!f.isDirectory()) {
pf.getContent().put(f.getName(), TextFile.fileToBytes(f));
}
}
}
}
} }

View File

@ -268,8 +268,10 @@ public class PackageCacheManager {
private void clearCache() throws IOException { private void clearCache() throws IOException {
for (File f : new File(cacheFolder).listFiles()) { for (File f : new File(cacheFolder).listFiles()) {
if (f.isDirectory()) if (f.isDirectory()) {
Utilities.clearDirectory(f.getAbsolutePath());
FileUtils.deleteDirectory(f); FileUtils.deleteDirectory(f);
}
else if (!f.getName().equals("packages.ini")) else if (!f.getName().equals("packages.ini"))
FileUtils.forceDelete(f); FileUtils.forceDelete(f);
} }
@ -316,6 +318,10 @@ public class PackageCacheManager {
save = checkIniHasMapping("hl7.fhir.core", "http://hl7.org/fhir", ini) || save; save = checkIniHasMapping("hl7.fhir.core", "http://hl7.org/fhir", ini) || save;
save = checkIniHasMapping("hl7.fhir.pubpack", "http://fhir.org/packages/hl7.fhir.pubpack", ini) || save; save = checkIniHasMapping("hl7.fhir.pubpack", "http://fhir.org/packages/hl7.fhir.pubpack", ini) || save;
save = checkIniHasMapping("hl7.fhir.xver-extensions", "http://fhir.org/packages/hl7.fhir.xver-extensions", ini) || save; save = checkIniHasMapping("hl7.fhir.xver-extensions", "http://fhir.org/packages/hl7.fhir.xver-extensions", ini) || save;
save = checkIniHasMapping("fhir.base.template", "http://fhir.org/templates/fhir.base.template", ini) || save;
save = checkIniHasMapping("hl7.base.template", "http://fhir.org/templates/hl7.base.template", ini) || save;
save = checkIniHasMapping("hl7.fhir.template", "http://fhir.org/templates/hl7.fhir.template", ini) || save;
save = checkIniHasMapping("ihe.fhir.template", "http://fhir.org/templates/ihe.fhir.template", ini) || save;
save = checkIniHasMapping("hl7.fhir.r2.core", "http://hl7.org/fhir/DSTU2/hl7.fhir.r2.core.tgz", ini) || save; save = checkIniHasMapping("hl7.fhir.r2.core", "http://hl7.org/fhir/DSTU2/hl7.fhir.r2.core.tgz", ini) || save;
save = checkIniHasMapping("hl7.fhir.r2.examples", "http://hl7.org/fhir/DSTU2/hl7.fhir.r2.examples.tgz", ini) || save; save = checkIniHasMapping("hl7.fhir.r2.examples", "http://hl7.org/fhir/DSTU2/hl7.fhir.r2.examples.tgz", ini) || save;
@ -382,6 +388,9 @@ public class PackageCacheManager {
public void recordMap(String url, String id) throws IOException { public void recordMap(String url, String id) throws IOException {
if (url == null) if (url == null)
return; return;
if (url.contains("github.com")) {
return;
}
if (!(new File(Utilities.path(cacheFolder, "packages.ini")).exists())) if (!(new File(Utilities.path(cacheFolder, "packages.ini")).exists()))
throw new Error("File "+Utilities.path(cacheFolder, "packages.ini")+" not found #1"); throw new Error("File "+Utilities.path(cacheFolder, "packages.ini")+" not found #1");
@ -431,7 +440,7 @@ public class PackageCacheManager {
String u = o.get("url").getAsString(); String u = o.get("url").getAsString();
if (u.contains("/ImplementationGuide/")) if (u.contains("/ImplementationGuide/"))
u = u.substring(0, u.indexOf("/ImplementationGuide/")); u = u.substring(0, u.indexOf("/ImplementationGuide/"));
builds.add(new BuildRecord(u, o.get("package-id").getAsString(), o.get("repo").getAsString(), readDate(o.get("date").getAsString()))); builds.add(new BuildRecord(u, o.get("package-id").getAsString(), getRepo(o.get("repo").getAsString()), readDate(o.get("date").getAsString())));
} }
} }
Collections.sort(builds, new BuildRecordSorter()); Collections.sort(builds, new BuildRecordSorter());
@ -443,6 +452,11 @@ public class PackageCacheManager {
} }
} }
private String getRepo(String path) {
String[] p = path.split("\\/");
return p[0]+"/"+p[1];
}
private Date readDate(String s) throws ParseException { private Date readDate(String s) throws ParseException {
SimpleDateFormat sdf = new SimpleDateFormat("EEE, dd MMM, yyyy HH:mm:ss Z", new Locale("en", "US")); SimpleDateFormat sdf = new SimpleDateFormat("EEE, dd MMM, yyyy HH:mm:ss Z", new Locale("en", "US"));
return sdf.parse(s); return sdf.parse(s);
@ -540,6 +554,13 @@ public class PackageCacheManager {
* @throws IOException * @throws IOException
*/ */
public NpmPackage loadPackageFromCacheOnly(String id, String version) throws IOException { public NpmPackage loadPackageFromCacheOnly(String id, String version) throws IOException {
if (Utilities.noString(version)) {
throw new FHIRException("Invalid version - ''");
}
if (version.startsWith("file:")) {
return loadPackageFromFile(id, version.substring(5));
}
for (NpmPackage p : temporaryPackages) { for (NpmPackage p : temporaryPackages) {
if (p.name().equals(id) && ("current".equals(version) || "dev".equals(version) || p.version().equals(version))) if (p.name().equals(id) && ("current".equals(version) || "dev".equals(version) || p.version().equals(version)))
return p; return p;
@ -559,6 +580,7 @@ public class PackageCacheManager {
* Add an already fetched package to the cache * Add an already fetched package to the cache
*/ */
public NpmPackage addPackageToCache(String id, String version, InputStream tgz, String sourceDesc) throws IOException { public NpmPackage addPackageToCache(String id, String version, InputStream tgz, String sourceDesc) throws IOException {
checkValidVersionString(version, id);
if (progress ) { if (progress ) {
System.out.println("Installing "+id+"#"+(version == null ? "?" : version)+" to the package cache"); System.out.println("Installing "+id+"#"+(version == null ? "?" : version)+" to the package cache");
System.out.print(" Fetching:"); System.out.print(" Fetching:");
@ -636,11 +658,31 @@ public class PackageCacheManager {
return pck; return pck;
} }
private void checkValidVersionString(String version, String id) {
if (Utilities.noString(version)) {
throw new FHIRException("Cannot add package "+id+" to the package cache - a version must be provided");
}
if (version.startsWith("file:")) {
throw new FHIRException("Cannot add package "+id+" to the package cache - the version '"+version+"' is illegal in this context");
}
for (char ch : version.toCharArray()) {
if (!Character.isAlphabetic(ch) && !Character.isDigit(ch) && !Utilities.existsInList(ch, '.', '-')) {
throw new FHIRException("Cannot add package "+id+" to the package cache - the version '"+version+"' is illegal (ch '"+ch+"'");
}
}
}
public NpmPackage loadPackage(String id) throws FHIRException, IOException { public NpmPackage loadPackage(String id) throws FHIRException, IOException {
throw new Error("Not done yet"); throw new Error("Not done yet");
} }
public NpmPackage loadPackage(String id, String v) throws FHIRException, IOException { public NpmPackage loadPackage(String id, String v) throws FHIRException, IOException {
if (Utilities.noString(v)) {
throw new FHIRException("Invalid version - ''");
}
if (v.startsWith("file:")) {
return loadPackageFromFile(id, v.substring(5));
}
NpmPackage p = loadPackageFromCacheOnly(id, v); NpmPackage p = loadPackageFromCacheOnly(id, v);
if (p != null) { if (p != null) {
if ("current".equals(v)) { if ("current".equals(v)) {
@ -659,33 +701,42 @@ public class PackageCacheManager {
} }
String url = getPackageUrl(id); String url = getPackageUrl(id);
if (url == null) if (url == null) {
throw new FHIRException("Unable to resolve the package '"+id+"'"); throw new FHIRException("Unable to resolve the package '"+id+"'");
}
String aurl = null;
try {
if (url.contains(".tgz")) { if (url.contains(".tgz")) {
aurl = url;
InputStream stream = fetchFromUrlSpecific(url, true); InputStream stream = fetchFromUrlSpecific(url, true);
if (stream != null) if (stream != null)
return addPackageToCache(id, v, stream, url); return addPackageToCache(id, v, stream, url);
throw new FHIRException("Unable to find the package source for '"+id+"' at "+url); throw new FHIRException("Unable to find the package source for '"+id+"' at "+url);
} }
if (v == null) { if (v == null) {
aurl = Utilities.pathURL(url, "package.tgz");
InputStream stream = fetchFromUrlSpecific(Utilities.pathURL(url, "package.tgz"), true); InputStream stream = fetchFromUrlSpecific(Utilities.pathURL(url, "package.tgz"), true);
if (stream == null && isBuildLoaded()) { if (stream == null && isBuildLoaded()) {
aurl = Utilities.pathURL(buildPath(url), "package.tgz");
stream = fetchFromUrlSpecific(Utilities.pathURL(buildPath(url), "package.tgz"), true); stream = fetchFromUrlSpecific(Utilities.pathURL(buildPath(url), "package.tgz"), true);
} }
if (stream != null) if (stream != null)
return addPackageToCache(id, null, stream, url); return addPackageToCache(id, null, stream, url);
throw new FHIRException("Unable to find the package source for '"+id+"' at "+url); throw new FHIRException("Unable to find the package source for '"+id+"' at "+url);
} else if ("current".equals(v) && ciList.containsKey(id)){ } else if ("current".equals(v) && ciList.containsKey(id)){
aurl = Utilities.pathURL(buildPath(url), "package.tgz");
InputStream stream = fetchFromUrlSpecific(Utilities.pathURL(ciList.get(id), "package.tgz"), true); InputStream stream = fetchFromUrlSpecific(Utilities.pathURL(ciList.get(id), "package.tgz"), true);
return addPackageToCache(id, v, stream, Utilities.pathURL(ciList.get(id), "package.tgz")); return addPackageToCache(id, v, stream, Utilities.pathURL(ciList.get(id), "package.tgz"));
} else { } else {
String pu = Utilities.pathURL(url, "package-list.json"); String pu = Utilities.pathURL(url, "package-list.json");
aurl = pu;
JsonObject json; JsonObject json;
try { try {
json = fetchJson(pu); json = fetchJson(pu);
} catch (Exception e) { } catch (Exception e) {
String pv = Utilities.pathURL(url, v, "package.tgz"); String pv = Utilities.pathURL(url, v, "package.tgz");
try { try {
aurl = pv;
InputStream stream = fetchFromUrlSpecific(pv, true); InputStream stream = fetchFromUrlSpecific(pv, true);
return addPackageToCache(id, v, stream, pv); return addPackageToCache(id, v, stream, pv);
} catch (Exception e1) { } catch (Exception e1) {
@ -697,6 +748,7 @@ public class PackageCacheManager {
for (JsonElement e : json.getAsJsonArray("list")) { for (JsonElement e : json.getAsJsonArray("list")) {
JsonObject vo = (JsonObject) e; JsonObject vo = (JsonObject) e;
if (v.equals(JSONUtil.str(vo, "version"))) { if (v.equals(JSONUtil.str(vo, "version"))) {
aurl = Utilities.pathURL(JSONUtil.str(vo, "path"), "package.tgz");
InputStream stream = fetchFromUrlSpecific(Utilities.pathURL(JSONUtil.str(vo, "path"), "package.tgz"), true); InputStream stream = fetchFromUrlSpecific(Utilities.pathURL(JSONUtil.str(vo, "path"), "package.tgz"), true);
if (stream == null) if (stream == null)
throw new FHIRException("Unable to find the package source for '"+id+"#"+v+"' at "+Utilities.pathURL(JSONUtil.str(vo, "path"), "package.tgz")); throw new FHIRException("Unable to find the package source for '"+id+"#"+v+"' at "+Utilities.pathURL(JSONUtil.str(vo, "path"), "package.tgz"));
@ -712,6 +764,9 @@ public class PackageCacheManager {
// } // }
throw new FHIRException("Unable to resolve version "+v+" for package "+id); throw new FHIRException("Unable to resolve version "+v+" for package "+id);
} }
} catch (Exception e) {
throw new FHIRException(e.getMessage()+(aurl == null ? "" : " (url = "+aurl+")"), e);
}
} }
@ -731,6 +786,16 @@ public class PackageCacheManager {
* @return * @return
*/ */
public boolean hasPackage(String id, String version) { public boolean hasPackage(String id, String version) {
if (Utilities.noString(version)) {
throw new FHIRException("Invalid version - ''");
}
if (version.startsWith("file:")) {
try {
return loadPackageFromFile(id, version.substring(5)) != null;
} catch (IOException e) {
return false;
}
}
for (NpmPackage p : temporaryPackages) { for (NpmPackage p : temporaryPackages) {
if (p.name().equals(id) && ("current".equals(version) || "dev".equals(version) || p.version().equals(version))) if (p.name().equals(id) && ("current".equals(version) || "dev".equals(version) || p.version().equals(version)))
return true; return true;
@ -747,6 +812,21 @@ public class PackageCacheManager {
} }
private NpmPackage loadPackageFromFile(String id, String folder) throws IOException {
File f = new File(Utilities.path(folder, id));
if (!f.exists()) {
throw new FHIRException("Package '"+id+" not found in folder "+folder);
}
if (!f.isDirectory()) {
throw new FHIRException("File for '"+id+" found in folder "+folder+", not a folder");
}
File fp = new File(Utilities.path(folder, id, "package", "package.json"));
if (!fp.exists()) {
throw new FHIRException("Package '"+id+" found in folder "+folder+", but does not contain a package.json file in /package");
}
return NpmPackage.fromFolder(f.getAbsolutePath());
}
/** /**
* List which versions of a package are available * List which versions of a package are available
* *

View File

@ -0,0 +1,165 @@
package org.hl7.fhir.utilities.cache;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Set;
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
import org.hl7.fhir.utilities.TextFile;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.cache.PackageClient.PackageInfo;
import org.hl7.fhir.utilities.json.JSONUtil;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
public class PackageClient {
public class PackageInfo {
private String id;
private String version;
private String fhirVersion;
private String description;
private String url;
public PackageInfo(String id, String version, String fhirVersion, String description, String url) {
super();
this.id = id;
this.version = version;
this.fhirVersion = fhirVersion;
this.description = description;
this.url = url;
}
public String getId() {
return id;
}
public String getVersion() {
return version;
}
public String getFhirVersion() {
return fhirVersion;
}
public String getDescription() {
return description;
}
public String getUrl() {
return url;
}
@Override
public String toString() {
return id+"#"+(version == null ? "??" : version)+(fhirVersion == null ? "": "for FHIR "+fhirVersion)+(url == null ? "" : " @"+url)+(description == null ? "" : " '"+description+"'");
}
}
private String address;
public PackageClient(String address) {
super();
this.address = address;
}
public boolean exists(String id, String ver) throws IOException {
List<PackageInfo> vl = getVersions(id);
for (PackageInfo pi : vl) {
if (ver.equals(pi.getVersion())) {
return true;
}
}
return false;
}
public InputStream fetch(String id, String ver) throws IOException {
return fetchUrl(Utilities.pathURL(address, id, ver));
}
public InputStream fetch(PackageInfo info) throws IOException {
return fetchUrl(Utilities.pathURL(address, info.getId(), info.getVersion()));
}
public InputStream fetchNpm(String id, String ver) throws IOException {
return fetchUrl(Utilities.pathURL(address, id, "-", id+"-"+ver+".tgz"));
}
public List<PackageInfo> getVersions(String id) throws IOException {
List<PackageInfo> res = new ArrayList<>();
JsonObject json;
try {
json = fetchJson(Utilities.pathURL(address, id));
JsonObject versions = json.getAsJsonObject("versions");
if (versions != null) {
for (String v : sorted(versions.keySet())) {
JsonObject obj = versions.getAsJsonObject(v);
res.add(new PackageInfo(JSONUtil.str(obj, "name"), JSONUtil.str(obj, "version"), JSONUtil.str(obj, "FhirVersion"), JSONUtil.str(obj, "description"), JSONUtil.str(obj, "url")));
}
}
} catch (FileNotFoundException e) {
}
return res;
}
private List<String> sorted(Set<String> keys) {
List<String> res = new ArrayList<>();
res.addAll(keys);
Collections.sort(res);
return res;
}
public List<PackageInfo> search(String name, String canonical, String fhirVersion, boolean preRelease) throws IOException {
CommaSeparatedStringBuilder params = new CommaSeparatedStringBuilder("&");
if (!Utilities.noString(name)) {
params.append("name="+name);
}
if (!Utilities.noString(canonical)) {
params.append("canonical="+canonical);
}
if (!Utilities.noString(fhirVersion)) {
params.append("fhirversion="+fhirVersion);
}
if (preRelease) {
params.append("prerelease="+preRelease);
}
List<PackageInfo> res = new ArrayList<>();
try {
JsonArray json = fetchJsonArray(Utilities.pathURL(address, "catalog?")+params.toString());
for (JsonElement e : json) {
JsonObject obj = (JsonObject) e;
res.add(new PackageInfo(JSONUtil.str(obj, "Name"), null, JSONUtil.str(obj, "FhirVersion"), JSONUtil.str(obj, "Description"), null));
}
} catch (FileNotFoundException e1) {
}
return res;
}
public Date getNewPackages(Date lastCalled, List<PackageInfo> updates) {
return null;
}
private InputStream fetchUrl(String source) throws IOException {
URL url = new URL(source);
URLConnection c = url.openConnection();
return c.getInputStream();
}
private JsonObject fetchJson(String source) throws IOException {
String src = TextFile.streamToString(fetchUrl(source));
//System.out.println(src);
return (JsonObject) new com.google.gson.JsonParser().parse(src);
}
private JsonArray fetchJsonArray(String source) throws IOException {
String src = TextFile.streamToString(fetchUrl(source));
//System.out.println(src);
return (JsonArray) new com.google.gson.JsonParser().parse(src);
}
}

View File

@ -28,6 +28,7 @@ import java.util.Map.Entry;
import com.google.gson.JsonArray; import com.google.gson.JsonArray;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
public class JSONUtil { public class JSONUtil {
@ -73,7 +74,7 @@ public class JSONUtil {
public static String str(JsonObject json, String name) { public static String str(JsonObject json, String name) {
JsonElement e = json.get(name); JsonElement e = json.get(name);
return e == null ? null : e.getAsString(); return e == null || e instanceof JsonNull ? null : e.getAsString();
} }
public static String str(JsonObject json, String name1, String name2) { public static String str(JsonObject json, String name1, String name2) {

View File

@ -487,6 +487,8 @@ public class ValidationMessage implements Comparator<ValidationMessage>, Compara
private String html; private String html;
private String locationLink; private String locationLink;
private String txLink; private String txLink;
public String sliceHtml;
private boolean slicingHint;
/** /**
@ -746,5 +748,22 @@ public class ValidationMessage implements Comparator<ValidationMessage>, Compara
this.html = html; this.html = html;
} }
public boolean isSlicingHint() {
return slicingHint;
}
public ValidationMessage setSlicingHint(boolean slicingHint) {
this.slicingHint = slicingHint;
return this;
}
public String getSliceHtml() {
return sliceHtml;
}
public void setSliceHtml(String sliceHtml) {
this.sliceHtml = sliceHtml;
}
} }

View File

@ -0,0 +1,18 @@
package org.hl7.fhir.utilities.tests;
import java.io.IOException;
import org.hl7.fhir.utilities.Utilities;
import org.junit.Test;
import junit.framework.Assert;
public class UtilitiesTests {
@Test
public void testPath() throws IOException {
Assert.assertEquals(Utilities.path("[tmp]", "test.txt"), "c:\\temp\\test.txt");
Assert.assertEquals(Utilities.path("[user]", "test.txt"), System.getProperty("user.home")+"\\test.txt");
Assert.assertEquals(Utilities.path("[JAVA_HOME]", "test.txt"), System.getenv("JAVA_HOME")+"\\test.txt");
}
}

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.core</artifactId> <artifactId>org.hl7.fhir.core</artifactId>
<version>4.1.54-SNAPSHOT</version> <version>4.1.63-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -5,7 +5,7 @@
<parent> <parent>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.core</artifactId> <artifactId>org.hl7.fhir.core</artifactId>
<version>4.1.54-SNAPSHOT</version> <version>4.1.63-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>

View File

@ -145,8 +145,23 @@ public class BaseValidator {
*/ */
protected boolean hint(List<ValidationMessage> errors, IssueType type, int line, int col, String path, boolean thePass, String msg) { protected boolean hint(List<ValidationMessage> errors, IssueType type, int line, int col, String path, boolean thePass, String msg) {
if (!thePass) { if (!thePass) {
addValidationMessage(errors, type, line, col, path, msg, IssueSeverity.INFORMATION); addValidationMessage(errors, type, line, col, path, msg, IssueSeverity.INFORMATION);
} }
return thePass;
}
/**
* Test a rule and add a {@link IssueSeverity#INFORMATION} validation message if the validation fails. And mark it as a slicing hint for later recovery if appropriate
*
* @param thePass
* Set this parameter to <code>false</code> if the validation does not pass
* @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
*/
protected boolean slicingHint(List<ValidationMessage> errors, IssueType type, int line, int col, String path, boolean thePass, String msg, String html) {
if (!thePass) {
addValidationMessage(errors, type, line, col, path, msg, IssueSeverity.INFORMATION).setSlicingHint(true).setSliceHtml(html);
}
return thePass; return thePass;
} }
@ -168,8 +183,7 @@ public class BaseValidator {
protected boolean txHint(List<ValidationMessage> errors, String txLink, IssueType type, int line, int col, String path, boolean thePass, String theMessage, Object... theMessageArguments) { protected boolean txHint(List<ValidationMessage> errors, String txLink, IssueType type, int line, int col, String path, boolean thePass, String theMessage, Object... theMessageArguments) {
if (!thePass) { if (!thePass) {
String message = formatMessage(theMessage, theMessageArguments); String message = formatMessage(theMessage, theMessageArguments);
addValidationMessage(errors, type, line, col, path, message, IssueSeverity.INFORMATION, Source.TerminologyEngine) addValidationMessage(errors, type, line, col, path, message, IssueSeverity.INFORMATION, Source.TerminologyEngine).setTxLink(txLink);
.setTxLink(txLink);
} }
return thePass; return thePass;
} }
@ -338,9 +352,9 @@ public class BaseValidator {
} }
protected void addValidationMessage(List<ValidationMessage> errors, IssueType type, int line, int col, String path, String msg, IssueSeverity theSeverity) { protected ValidationMessage addValidationMessage(List<ValidationMessage> errors, IssueType type, int line, int col, String path, String msg, IssueSeverity theSeverity) {
Source source = this.source; Source source = this.source;
addValidationMessage(errors, type, line, col, path, msg, theSeverity, source); return addValidationMessage(errors, type, line, col, path, msg, theSeverity, source);
} }
protected ValidationMessage addValidationMessage(List<ValidationMessage> errors, IssueType type, int line, int col, String path, String msg, IssueSeverity theSeverity, Source theSource) { protected ValidationMessage addValidationMessage(List<ValidationMessage> errors, IssueType type, int line, int col, String path, String msg, IssueSeverity theSeverity, Source theSource) {

View File

@ -108,6 +108,7 @@ import org.hl7.fhir.r5.model.Range;
import org.hl7.fhir.r5.model.Ratio; import org.hl7.fhir.r5.model.Ratio;
import org.hl7.fhir.r5.model.Resource; import org.hl7.fhir.r5.model.Resource;
import org.hl7.fhir.r5.model.SampledData; import org.hl7.fhir.r5.model.SampledData;
import org.hl7.fhir.r5.model.SearchParameter;
import org.hl7.fhir.r5.model.StringType; import org.hl7.fhir.r5.model.StringType;
import org.hl7.fhir.r5.model.StructureDefinition; import org.hl7.fhir.r5.model.StructureDefinition;
import org.hl7.fhir.r5.model.StructureDefinition.ExtensionContextType; import org.hl7.fhir.r5.model.StructureDefinition.ExtensionContextType;
@ -156,6 +157,9 @@ import ca.uhn.fhir.util.ObjectUtil;
* Thinking of using this in a java program? Don't! * Thinking of using this in a java program? Don't!
* You should use one of the wrappers instead. Either in HAPI, or use ValidationEngine * You should use one of the wrappers instead. Either in HAPI, or use ValidationEngine
* *
* Validation todo:
* - support @default slices
*
* @author Grahame Grieve * @author Grahame Grieve
* *
*/ */
@ -207,6 +211,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
private Element rootResource; private Element rootResource;
private StructureDefinition profile; // the profile that contains the content being validated private StructureDefinition profile; // the profile that contains the content being validated
private boolean checkSpecials = true; private boolean checkSpecials = true;
private Map<String, List<ValidationMessage>> sliceRecords;
public ValidatorHostContext(Object appContext) { public ValidatorHostContext(Object appContext) {
this.appContext = appContext; this.appContext = appContext;
@ -242,6 +247,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
res.rootResource = rootResource; res.rootResource = rootResource;
res.container = container; res.container = container;
res.profile = profile; res.profile = profile;
res.sliceRecords = sliceRecords != null ? sliceRecords : new HashMap<String, List<ValidationMessage>>();
return res; return res;
} }
@ -277,6 +283,21 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
return resource; return resource;
} }
public void sliceNotes(String url, List<ValidationMessage> record) {
sliceRecords.put(url, record);
}
public ValidatorHostContext forSlicing() {
ValidatorHostContext res = new ValidatorHostContext(appContext);
res.resource = resource;
res.rootResource = resource;
res.container = resource;
res.profile = profile;
res.checkSpecials = false;
res.sliceRecords = new HashMap<String, List<ValidationMessage>>();
return res;
}
} }
@ -405,8 +426,16 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
} else } else
throw new NotImplementedException("Not done yet (ValidatorHostServices.conformsToProfile), when item is not an element"); throw new NotImplementedException("Not done yet (ValidatorHostServices.conformsToProfile), when item is not an element");
boolean ok = true; boolean ok = true;
for (ValidationMessage v : valerrors) List<ValidationMessage> record = new ArrayList<>();
for (ValidationMessage v : valerrors) {
ok = ok && !v.getLevel().isError(); ok = ok && !v.getLevel().isError();
if (v.getLevel().isError() || v.isSlicingHint()) {
record.add(v);
}
}
if (!ok && !record.isEmpty()) {
ctxt.sliceNotes(url, record);
}
return ok; return ok;
} }
@ -1597,9 +1626,9 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
Set<String> allowedTypes = listExtensionTypes(ex); Set<String> allowedTypes = listExtensionTypes(ex);
String actualType = getExtensionType(element); String actualType = getExtensionType(element);
if (actualType == null) if (actualType == null)
rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path + "[url='" + url + "']", allowedTypes.isEmpty(), "The Extension '" + url + "' definition is for a simple extension, so it must contain a value, not extensions"); rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, allowedTypes.isEmpty(), "The Extension '" + url + "' definition is for a simple extension, so it must contain a value, not extensions");
else else
rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path + "[url='" + url + "']", allowedTypes.contains(actualType), "The Extension '" + url + "' definition allows for the types "+allowedTypes.toString()+" but found type "+actualType); rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, allowedTypes.contains(actualType), "The Extension '" + url + "' definition allows for the types "+allowedTypes.toString()+" but found type "+actualType);
// 3. is the content of the extension valid? // 3. is the content of the extension valid?
validateElement(hostContext, errors, ex, ex.getSnapshot().getElement().get(0), null, null, resource, element, "Extension", stack, false, true, url); validateElement(hostContext, errors, ex, ex.getSnapshot().getElement().get(0), null, null, resource, element, "Extension", stack, false, true, url);
@ -1691,7 +1720,11 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
ok = true; ok = true;
break; break;
} }
sd = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition()); if (sd.getBaseDefinition() != null) {
sd = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition());
} else {
sd = null;
}
} }
} }
} }
@ -1959,7 +1992,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
if (fetcher != null) { if (fetcher != null) {
boolean found; boolean found;
try { try {
found = (allowExamples && (url.contains("example.org") || url.contains("acme.com"))) || (url.startsWith("http://hl7.org/fhir/tools")) || fetcher.resolveURL(appContext, path, url); found = isDefinitionURL(url) || (allowExamples && (url.contains("example.org") || url.contains("acme.com"))) || (url.startsWith("http://hl7.org/fhir/tools")) || fetcher.resolveURL(appContext, path, url);
} catch (IOException e1) { } catch (IOException e1) {
found = false; found = false;
} }
@ -2122,6 +2155,11 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
// for nothing to check // for nothing to check
} }
private boolean isDefinitionURL(String url) {
return Utilities.existsInList(url, "http://hl7.org/fhirpath/System.Boolean", "http://hl7.org/fhirpath/System.String", "http://hl7.org/fhirpath/System.Integer",
"http://hl7.org/fhirpath/System.Decimal", "http://hl7.org/fhirpath/System.Date", "http://hl7.org/fhirpath/System.Time", "http://hl7.org/fhirpath/System.DateTime", "http://hl7.org/fhirpath/System.Quantity");
}
private void checkInnerNames(List<ValidationMessage> errors, Element e, String path, List<XhtmlNode> list) { private void checkInnerNames(List<ValidationMessage> errors, Element e, String path, List<XhtmlNode> list) {
for (XhtmlNode node : list) { for (XhtmlNode node : list) {
if (node.getNodeType() == NodeType.Element) { if (node.getNodeType() == NodeType.Element) {
@ -2353,7 +2391,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
if (!isShowMessagesFromReferences()) { if (!isShowMessagesFromReferences()) {
rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, areAllBaseProfiles(profiles), "Unable to find matching profile for "+ref+" among choices: " + asList(type.getTargetProfile())); rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, areAllBaseProfiles(profiles), "Unable to find matching profile for "+ref+" among choices: " + asList(type.getTargetProfile()));
for (StructureDefinition sd : badProfiles.keySet()) { for (StructureDefinition sd : badProfiles.keySet()) {
hint(errors, IssueType.STRUCTURE, element.line(), element.col(), path, false, "Profile "+sd.getUrl()+" does not match for "+ref+" because of the following errors: "+errorSummary(badProfiles.get(sd))); slicingHint(errors, IssueType.STRUCTURE, element.line(), element.col(), path, false, "Details for "+ref+" matching against Profile"+sd.getUrl(), errorSummaryForSlicingAsHtml(badProfiles.get(sd)));
} }
} else { } else {
rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, profiles.size()==1, "Unable to find matching profile for "+ref+" among choices: " + asList(type.getTargetProfile())); rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, profiles.size()==1, "Unable to find matching profile for "+ref+" among choices: " + asList(type.getTargetProfile()));
@ -2369,7 +2407,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
if (!isShowMessagesFromReferences()) { if (!isShowMessagesFromReferences()) {
warning(errors, IssueType.STRUCTURE, element.line(), element.col(), path, false, "Found multiple matching profiles for "+ref+" among choices: " + asListByUrl(goodProfiles.keySet())); warning(errors, IssueType.STRUCTURE, element.line(), element.col(), path, false, "Found multiple matching profiles for "+ref+" among choices: " + asListByUrl(goodProfiles.keySet()));
for (StructureDefinition sd : badProfiles.keySet()) { for (StructureDefinition sd : badProfiles.keySet()) {
hint(errors, IssueType.STRUCTURE, element.line(), element.col(), path, false, "Profile "+sd.getUrl()+" does not match for "+ref+" because of the following errors: "+errorSummary(badProfiles.get(sd))); slicingHint(errors, IssueType.STRUCTURE, element.line(), element.col(), path, false, "Details for "+ref+" matching against Profile"+sd.getUrl(), errorSummaryForSlicingAsHtml(badProfiles.get(sd)));
} }
} else { } else {
warning(errors, IssueType.STRUCTURE, element.line(), element.col(), path, false, "Found multiple matching profiles for "+ref+" among choices: " + asListByUrl(goodProfiles.keySet())); warning(errors, IssueType.STRUCTURE, element.line(), element.col(), path, false, "Found multiple matching profiles for "+ref+" among choices: " + asListByUrl(goodProfiles.keySet()));
@ -2404,6 +2442,19 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
boolean okToRef = !type.hasAggregation() || type.hasAggregation(AggregationMode.REFERENCED); boolean okToRef = !type.hasAggregation() || type.hasAggregation(AggregationMode.REFERENCED);
rule(errors, IssueType.REQUIRED, -1, -1, path, okToRef, "Bundled or contained reference not found within the bundle/resource " + ref); rule(errors, IssueType.REQUIRED, -1, -1, path, okToRef, "Bundled or contained reference not found within the bundle/resource " + ref);
} }
if (we == null && ft != null && assumeValidRestReferences) {
// if we == null, we inferred ft from the reference. if we are told to treat this as gospel
TypeRefComponent type = getReferenceTypeRef(container.getType());
Set<String> types = new HashSet<>();
for (CanonicalType tp : type.getTargetProfile()) {
StructureDefinition sd = context.fetchResource(StructureDefinition.class, tp.getValue());
if (sd != null) {
types.add(sd.getType());
}
}
rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, types.isEmpty() || types.contains(ft), "The type '"+ft+"' implied by the reference URL "+ref+" is not a valid Target for this element (must be one of "+types+")");
}
if (pol == ReferenceValidationPolicy.CHECK_VALID) { if (pol == ReferenceValidationPolicy.CHECK_VALID) {
// todo.... // todo....
} }
@ -2434,16 +2485,28 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
return true; return true;
} }
private String errorSummary(List<ValidationMessage> list) { private String errorSummaryForSlicing(List<ValidationMessage> list) {
CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
for (ValidationMessage vm : list) { for (ValidationMessage vm : list) {
if (vm.getLevel() == IssueSeverity.ERROR || vm.getLevel() == IssueSeverity.FATAL) { if (vm.getLevel() == IssueSeverity.ERROR || vm.getLevel() == IssueSeverity.FATAL || vm.isSlicingHint()) {
b.append(vm.getLocation()+": "+vm.getMessage()); b.append(vm.getLocation()+": "+vm.getMessage());
} }
} }
return b.toString(); return b.toString();
} }
private String errorSummaryForSlicingAsHtml(List<ValidationMessage> list) {
CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
for (ValidationMessage vm : list) {
if (vm.isSlicingHint()) {
b.append("<li>"+vm.getLocation()+": "+vm.getSliceHtml()+"</li>");
} else if (vm.getLevel() == IssueSeverity.ERROR || vm.getLevel() == IssueSeverity.FATAL ) {
b.append("<li>"+vm.getLocation()+": "+vm.getHtml()+"</li>");
}
}
return "<ul>"+b.toString()+"</ul>";
}
private TypeRefComponent getReferenceTypeRef(List<TypeRefComponent> types) { private TypeRefComponent getReferenceTypeRef(List<TypeRefComponent> types) {
for (TypeRefComponent tr : types) { for (TypeRefComponent tr : types) {
if ("Reference".equals(tr.getCode())) { if ("Reference".equals(tr.getCode())) {
@ -3186,7 +3249,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
* @throws IOException * @throws IOException
* @throws FHIRException * @throws FHIRException
*/ */
private boolean sliceMatches(ValidatorHostContext hostContext, Element element, String path, ElementDefinition slicer, ElementDefinition ed, StructureDefinition profile, List<ValidationMessage> errors, NodeStack stack) throws DefinitionException, FHIRException { private boolean sliceMatches(ValidatorHostContext hostContext, Element element, String path, ElementDefinition slicer, ElementDefinition ed, StructureDefinition profile, List<ValidationMessage> errors, List<ValidationMessage> sliceInfo, NodeStack stack) throws DefinitionException, FHIRException {
if (!slicer.getSlicing().hasDiscriminator()) if (!slicer.getSlicing().hasDiscriminator())
return false; // cannot validate in this case return false; // cannot validate in this case
@ -3221,19 +3284,19 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
} else } else
throw new DefinitionException("Discriminator (" + discriminator + ") is based on type, but slice " + ed.getId() + " in "+profile.getUrl()+" has no types"); throw new DefinitionException("Discriminator (" + discriminator + ") is based on type, but slice " + ed.getId() + " in "+profile.getUrl()+" has no types");
if (discriminator.isEmpty()) if (discriminator.isEmpty())
expression.append(" and this is " + type); expression.append(" and $this is " + type);
else else
expression.append(" and " + discriminator + " is " + type); expression.append(" and " + discriminator + " is " + type);
} else if (s.getType() == DiscriminatorType.PROFILE) { } else if (s.getType() == DiscriminatorType.PROFILE) {
if (criteriaElement.getType().size() == 0) { if (criteriaElement.getType().size() == 0) {
throw new DefinitionException("Profile based discriminators must have a type ("+criteriaElement.getId()+")"); throw new DefinitionException("Profile based discriminators must have a type ("+criteriaElement.getId()+" in profile "+profile.getUrl()+")");
} }
if (criteriaElement.getType().size() != 1) { if (criteriaElement.getType().size() != 1) {
throw new DefinitionException("Profile based discriminators must have only one type ("+criteriaElement.getId()+")"); throw new DefinitionException("Profile based discriminators must have only one type ("+criteriaElement.getId()+" in profile "+profile.getUrl()+")");
} }
List<CanonicalType> list = discriminator.endsWith(".resolve()") || discriminator.equals("resolve()") ? criteriaElement.getType().get(0).getTargetProfile() : criteriaElement.getType().get(0).getProfile(); List<CanonicalType> list = discriminator.endsWith(".resolve()") || discriminator.equals("resolve()") ? criteriaElement.getType().get(0).getTargetProfile() : criteriaElement.getType().get(0).getProfile();
if (list.size() == 0) { if (list.size() == 0) {
throw new DefinitionException("Profile based discriminators must have a type with a profile ("+criteriaElement.getId()+")"); throw new DefinitionException("Profile based discriminators must have a type with a profile ("+criteriaElement.getId()+" in profile "+profile.getUrl()+")");
} else if (list.size() > 1) { } else if (list.size() > 1) {
CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(" or "); CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(" or ");
for (CanonicalType c : list) { for (CanonicalType c : list) {
@ -3281,7 +3344,16 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
ed.setUserData("slice.expression.cache", n); ed.setUserData("slice.expression.cache", n);
} }
return evaluateSlicingExpression(hostContext, element, path, profile, n); ValidatorHostContext shc = hostContext.forSlicing();
boolean pass = evaluateSlicingExpression(shc, element, path, profile, n);
if (!pass) {
slicingHint(sliceInfo, IssueType.STRUCTURE, element.line(), element.col(), path, false, "Does not match slice'"+ed.getSliceName(), "discriminator = "+Utilities.escapeXml(n.toString()));
for (String url : shc.sliceRecords.keySet()) {
slicingHint(sliceInfo, IssueType.STRUCTURE, element.line(), element.col(), path, false, "Details for "+stack.getLiteralPath()+" against profile "+url,
"Profile "+url+" does not match for "+stack.getLiteralPath()+" because of the following profile issues: "+errorSummaryForSlicingAsHtml(shc.sliceRecords.get(url)));
}
}
return pass;
} }
public boolean evaluateSlicingExpression(ValidatorHostContext hostContext, Element element, String path, StructureDefinition profile, ExpressionNode n) throws FHIRException { public boolean evaluateSlicingExpression(ValidatorHostContext hostContext, Element element, String path, StructureDefinition profile, ExpressionNode n) throws FHIRException {
@ -3518,22 +3590,30 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
} }
} }
if (checkSpecials) { if (checkSpecials) {
// specific known special validations checkSpecials(hostContext, errors, element, stack, checkSpecials);
if (element.getType().equals("Bundle")) {
validateBundle(errors, element, stack);
} else if (element.getType().equals("Observation")) {
validateObservation(errors, element, stack);
} else if (element.getType().equals("Questionnaire")) {
validateQuestionannaire(errors, element, stack);
} else if (element.getType().equals("QuestionnaireResponse")) {
validateQuestionannaireResponse(hostContext, errors, element, stack);
} else if (element.getType().equals("CodeSystem")) {
validateCodeSystem(errors, element, stack);
}
validateResourceRules(errors, element, stack); validateResourceRules(errors, element, stack);
} }
} }
public void checkSpecials(ValidatorHostContext hostContext, List<ValidationMessage> errors, Element element, NodeStack stack, boolean checkSpecials) {
// specific known special validations
if (element.getType().equals("Bundle")) {
validateBundle(errors, element, stack, checkSpecials);
} else if (element.getType().equals("Observation")) {
validateObservation(errors, element, stack);
} else if (element.getType().equals("Questionnaire")) {
ArrayList<Element> parents = new ArrayList<>();
parents.add(element);
validateQuestionannaireItem(errors, element, element, stack, parents);
} else if (element.getType().equals("QuestionnaireResponse")) {
validateQuestionannaireResponse(hostContext, errors, element, stack);
} else if (element.getType().equals("CapabilityStatement")) {
validateCapabilityStatement(errors, element, stack);
} else if (element.getType().equals("CodeSystem")) {
validateCodeSystem(errors, element, stack);
}
}
private ResourceValidationTracker getResourceTracker(Element element) { private ResourceValidationTracker getResourceTracker(Element element) {
ResourceValidationTracker res = resourceTracker.get(element); ResourceValidationTracker res = resourceTracker.get(element);
if (res == null) { if (res == null) {
@ -3543,29 +3623,37 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
return res; return res;
} }
private void validateQuestionannaire(List<ValidationMessage> errors, Element element, NodeStack stack) { private void validateQuestionannaireItem(List<ValidationMessage> errors, Element element, Element questionnaire, NodeStack stack, List<Element> parents) {
List<Element> list = getItems(element); List<Element> list = getItems(element);
for (int i = 0; i < list.size(); i++) { for (int i = 0; i < list.size(); i++) {
Element e = list.get(i); Element e = list.get(i);
NodeStack ns = stack.push(element, i, e.getProperty().getDefinition(), e.getProperty().getDefinition()); NodeStack ns = stack.push(e, i, e.getProperty().getDefinition(), e.getProperty().getDefinition());
validateQuestionnaireElement(errors, ns, element, e, new ArrayList<>()); validateQuestionnaireElement(errors, ns, questionnaire, e, parents);
List<Element> np = new ArrayList<Element>();
np.add(e);
np.addAll(parents);
validateQuestionannaireItem(errors, e, questionnaire, ns, np);
} }
} }
private void validateQuestionnaireElement(List<ValidationMessage> errors, NodeStack ns, Element questionnaire, Element item, List<Element> parents) { private void validateQuestionnaireElement(List<ValidationMessage> errors, NodeStack ns, Element questionnaire, Element item, List<Element> parents) {
// R4+ // R4+
if (FHIRVersion.isR4Plus(context.getVersion())) { if (FHIRVersion.isR4Plus(context.getVersion())) {
if (item.hasChild("enableWhen")) { if (item.hasChildren("enableWhen")) {
Element ew = item.getNamedChild("enableWhen"); List<Element> ewl = item.getChildren("enableWhen");
String ql = ew.getNamedChildValue("question"); for (Element ew : ewl) {
if (rule(errors, IssueType.BUSINESSRULE, ns.literalPath, ql != null, "Questions with an enableWhen must have a value for the question link")) { // Element ew = item.getNamedChild("enableWhen");
Element tgt = getQuestionById(item, ql); String ql = ew.getNamedChildValue("question");
if (rule(errors, IssueType.BUSINESSRULE, ns.literalPath, tgt == null, "Questions with an enableWhen cannot refer to an inner question for it's enableWhen condition")) { if (rule(errors, IssueType.BUSINESSRULE, ns.literalPath, ql != null, "Questions with an enableWhen must have a value for the question link")) {
tgt = getQuestionById(questionnaire, ql); Element tgt = getQuestionById(item, ql);
if (rule(errors, IssueType.BUSINESSRULE, ns.literalPath, tgt != null, "Unable to find "+ql+" target for this question enableWhen")) { if (rule(errors, IssueType.BUSINESSRULE, ns.literalPath, tgt == null, "Questions with an enableWhen cannot refer to an inner question for it's enableWhen condition")) {
if (rule(errors, IssueType.BUSINESSRULE, ns.literalPath, tgt != item, "Target for this question enableWhen can't reference itself")) { tgt = getQuestionById(questionnaire, ql);
warning(errors, IssueType.BUSINESSRULE, ns.literalPath, isBefore(item, tgt, parents), "The target of this enableWhen rule ("+ql+") comes after the question itself"); if (rule(errors, IssueType.BUSINESSRULE, ns.literalPath, tgt != null, "Unable to find target '"+ql+"' for this question enableWhen")) {
if (rule(errors, IssueType.BUSINESSRULE, ns.literalPath, tgt != item, "Target for this question enableWhen can't reference itself")) {
if (!isBefore(item, tgt, parents)) {
warning(errors, IssueType.BUSINESSRULE, ns.literalPath, false, "The target of this enableWhen rule ("+ql+") comes after the question itself");
}
}
} }
} }
} }
@ -3576,6 +3664,10 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
private boolean isBefore(Element item, Element tgt, List<Element> parents) { private boolean isBefore(Element item, Element tgt, List<Element> parents) {
// we work up the list, looking for tgt in the children of the parents // we work up the list, looking for tgt in the children of the parents
if (parents.contains(tgt)) {
// actually, if the target is a parent, that's automatically ok
return true;
}
for (Element p : parents) { for (Element p : parents) {
int i = findIndex(p, item); int i = findIndex(p, item);
int t = findIndex(p, tgt); int t = findIndex(p, tgt);
@ -3599,7 +3691,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
for (Element e : element.getChildren()) { for (Element e : element.getChildren()) {
if (e == descendant) if (e == descendant)
return true; return true;
if (isChild(element, descendant)) if (isChild(e, descendant))
return true; return true;
} }
return false; return false;
@ -3638,11 +3730,21 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
Element div = text.getNamedChild("div"); Element div = text.getNamedChild("div");
if (lang != null && div != null) { if (lang != null && div != null) {
XhtmlNode xhtml = div.getXhtml(); XhtmlNode xhtml = div.getXhtml();
String xl = xhtml.getAttribute("lang"); String l = xhtml.getAttribute("lang");
if (xl == null) { String xl = xhtml.getAttribute("xml:lang");
warning(errors, IssueType.BUSINESSRULE, div.line(), div.col(), stack.getLiteralPath(), false, "Resource has a language, but the XHTML does not have a language tag"); if (l == null && xl == null) {
} else if (!xl.equals(lang)) { warning(errors, IssueType.BUSINESSRULE, div.line(), div.col(), stack.getLiteralPath(), false, "Resource has a language, but the XHTML does not have an lang or an xml:lang tag (needs both - see https://www.w3.org/TR/i18n-html-tech-lang/#langvalues)");
warning(errors, IssueType.BUSINESSRULE, div.line(), div.col(), stack.getLiteralPath(), false, "Resource has a language ("+lang+"), and the XHTML has a language ("+xl+"), but they differ "); } else {
if (l == null) {
warning(errors, IssueType.BUSINESSRULE, div.line(), div.col(), stack.getLiteralPath(), false, "Resource has a language, but the XHTML does not have a lang tag (needs both lang and xml:lang - see https://www.w3.org/TR/i18n-html-tech-lang/#langvalues)");
} else if (!l.equals(lang)) {
warning(errors, IssueType.BUSINESSRULE, div.line(), div.col(), stack.getLiteralPath(), false, "Resource has a language ("+lang+"), and the XHTML has a lang ("+l+"), but they differ ");
}
if (xl == null) {
warning(errors, IssueType.BUSINESSRULE, div.line(), div.col(), stack.getLiteralPath(), false, "Resource has a language, but the XHTML does not have an xml:lang tag (needs both lang and xml:lang - see https://www.w3.org/TR/i18n-html-tech-lang/#langvalues)");
} else if (!xl.equals(lang)) {
warning(errors, IssueType.BUSINESSRULE, div.line(), div.col(), stack.getLiteralPath(), false, "Resource has a language ("+lang+"), and the XHTML has an xml:lang ("+xl+"), but they differ ");
}
} }
} }
} }
@ -3662,6 +3764,29 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
} }
} }
private void validateCapabilityStatement(List<ValidationMessage> errors, Element cs, NodeStack stack) {
int iRest = 0;
for (Element rest : cs.getChildrenByName("rest")) {
int iResource = 0;
for (Element resource : rest.getChildrenByName("resource")) {
int iSP = 0;
for (Element searchParam : resource.getChildrenByName("searchParam")) {
String ref = searchParam.getChildValue("definition");
String type = searchParam.getChildValue("type");
if (!Utilities.noString(ref)) {
SearchParameter sp = context.fetchResource(SearchParameter.class, ref);
if (sp != null) {
rule(errors, IssueType.INVALID, searchParam.line(), searchParam.col(), stack.literalPath+".rest["+iRest+"].resource["+iResource+"].searchParam["+iSP+"]",
sp.getType().toCode().equals(type), "Type mismatch - SearchParameter '"+sp.getUrl()+"' type is "+sp.getType().toCode()+", but type here is "+type);
}
}
iSP++;
}
iResource++;
}
iRest++;
}
}
private void validateCodeSystem(List<ValidationMessage> errors, Element cs, NodeStack stack) { private void validateCodeSystem(List<ValidationMessage> errors, Element cs, NodeStack stack) {
String url = cs.getNamedChildValue("url"); String url = cs.getNamedChildValue("url");
String vsu = cs.getNamedChildValue("valueSet"); String vsu = cs.getNamedChildValue("valueSet");
@ -4247,7 +4372,7 @@ private boolean isAnswerRequirementFulfilled(QuestionnaireItemComponent qItem, L
return true; return true;
} }
private void validateBundle(List<ValidationMessage> errors, Element bundle, NodeStack stack) { private void validateBundle(List<ValidationMessage> errors, Element bundle, NodeStack stack, boolean checkSpecials) {
List<Element> entries = new ArrayList<Element>(); List<Element> entries = new ArrayList<Element>();
bundle.getNamedChildren("entry", entries); bundle.getNamedChildren("entry", entries);
String type = bundle.getNamedChildValue("type"); String type = bundle.getNamedChildValue("type");
@ -4291,6 +4416,7 @@ private boolean isAnswerRequirementFulfilled(QuestionnaireItemComponent qItem, L
rule(errors, IssueType.INVALID, entry.line(), entry.col(), stack.addToLiteralPath("entry", ":0"), false, "The canonical URL ("+url+") cannot match the fullUrl ("+fullUrl+") unless the resource id ("+id+") also matches"); rule(errors, IssueType.INVALID, entry.line(), entry.col(), stack.addToLiteralPath("entry", ":0"), false, "The canonical URL ("+url+") cannot match the fullUrl ("+fullUrl+") unless the resource id ("+id+") also matches");
rule(errors, IssueType.INVALID, entry.line(), entry.col(), stack.addToLiteralPath("entry", ":0"), !url.equals(fullUrl) || serverBase == null || (url.equals(Utilities.pathURL(serverBase, entry.getNamedChild("resource").fhirType(), id))), "The canonical URL ("+url+") cannot match the fullUrl ("+fullUrl+") unless on the canonical server itself"); rule(errors, IssueType.INVALID, entry.line(), entry.col(), stack.addToLiteralPath("entry", ":0"), !url.equals(fullUrl) || serverBase == null || (url.equals(Utilities.pathURL(serverBase, entry.getNamedChild("resource").fhirType(), id))), "The canonical URL ("+url+") cannot match the fullUrl ("+fullUrl+") unless on the canonical server itself");
} }
// todo: check specials
} }
} }
@ -4617,9 +4743,6 @@ private boolean isAnswerRequirementFulfilled(QuestionnaireItemComponent qItem, L
// time = System.nanoTime(); // time = System.nanoTime();
// check type invariants // check type invariants
if (definition.getId().equals("Composition.section:sectionResults")) {
System.out.println("!");
}
checkInvariants(hostContext, errors, profile, definition, resource, element, stack, false); checkInvariants(hostContext, errors, profile, definition, resource, element, stack, false);
if (definition.getFixed() != null) if (definition.getFixed() != null)
checkFixedValue(errors, stack.getLiteralPath(), element, definition.getFixed(), profile.getUrl(), definition.getSliceName(), null); checkFixedValue(errors, stack.getLiteralPath(), element, definition.getFixed(), profile.getUrl(), definition.getSliceName(), null);
@ -4630,16 +4753,14 @@ private boolean isAnswerRequirementFulfilled(QuestionnaireItemComponent qItem, L
if (childDefinitions.isEmpty()) { if (childDefinitions.isEmpty()) {
if (actualType == null) if (actualType == null)
return; // there'll be an error elsewhere in this case, and we're going to stop. return; // there'll be an error elsewhere in this case, and we're going to stop.
StructureDefinition dt = null; childDefinitions = getActualTypeChildren(hostContext, element, actualType);
if (isAbsolute(actualType)) } else if (definition.getType().size() > 1) {
dt = this.context.fetchResource(StructureDefinition.class, actualType); // this only happens when the profile constrains the abstract children but leaves th choice open.
else if (actualType == null)
dt = this.context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + actualType); return; // there'll be an error elsewhere in this case, and we're going to stop.
if (dt == null) List<ElementDefinition> typeChildDefinitions = getActualTypeChildren(hostContext, element, actualType);
throw new DefinitionException("Unable to resolve actual type " + actualType); // what were going to do is merge them - the type is not allowed to constrain things that the child definitions already do (well, if it does, it'll be ignored)
trackUsage(dt, hostContext, element); mergeChildLists(childDefinitions, typeChildDefinitions, definition.getPath(), actualType);
childDefinitions = ProfileUtilities.getChildMap(dt, dt.getSnapshot().getElement().get(0));
} }
List<ElementInfo> children = listChildren(element, stack); List<ElementInfo> children = listChildren(element, stack);
@ -4654,6 +4775,39 @@ private boolean isAnswerRequirementFulfilled(QuestionnaireItemComponent qItem, L
} }
} }
private void mergeChildLists(List<ElementDefinition> master, List<ElementDefinition> additional, String masterPath, String typePath) {
for (ElementDefinition ed : additional) {
boolean inMaster = false;
for (ElementDefinition t : master) {
String tp = masterPath + ed.getPath().substring(typePath.length());
if (t.getPath().equals(tp)) {
inMaster = true;
}
}
if (!inMaster) {
master.add(ed);
}
}
}
// todo: the element definition in context might assign a constrained profile for the type?
public List<ElementDefinition> getActualTypeChildren(ValidatorHostContext hostContext, Element element, String actualType) {
List<ElementDefinition> childDefinitions;
StructureDefinition dt = null;
if (isAbsolute(actualType))
dt = this.context.fetchResource(StructureDefinition.class, actualType);
else
dt = this.context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + actualType);
if (dt == null)
throw new DefinitionException("Unable to resolve actual type " + actualType);
trackUsage(dt, hostContext, element);
childDefinitions = ProfileUtilities.getChildMap(dt, dt.getSnapshot().getElement().get(0));
return childDefinitions;
}
public void checkChild(ValidatorHostContext hostContext, List<ValidationMessage> errors, StructureDefinition profile, ElementDefinition definition, public void checkChild(ValidatorHostContext hostContext, List<ValidationMessage> errors, StructureDefinition profile, ElementDefinition definition,
Element resource, Element element, String actualType, NodeStack stack, boolean inCodeableConcept, boolean checkDisplayInContext, ElementInfo ei, String extensionUrl) Element resource, Element element, String actualType, NodeStack stack, boolean inCodeableConcept, boolean checkDisplayInContext, ElementInfo ei, String extensionUrl)
throws FHIRException, DefinitionException { throws FHIRException, DefinitionException {
@ -5018,7 +5172,10 @@ private boolean isAnswerRequirementFulfilled(QuestionnaireItemComponent qItem, L
// if (process) { // if (process) {
for (ElementInfo ei : children) { for (ElementInfo ei : children) {
unsupportedSlicing = matchSlice(hostContext, errors, profile, stack, slicer, unsupportedSlicing, problematicPaths, sliceOffset, i, ed, childUnsupportedSlicing, ei); if (ei.sliceInfo == null) {
ei.sliceInfo = new ArrayList<>();
}
unsupportedSlicing = matchSlice(hostContext, errors, ei.sliceInfo, profile, stack, slicer, unsupportedSlicing, problematicPaths, sliceOffset, i, ed, childUnsupportedSlicing, ei);
} }
// } // }
} }
@ -5032,14 +5189,16 @@ private boolean isAnswerRequirementFulfilled(QuestionnaireItemComponent qItem, L
if (ei.additionalSlice && ei.definition != null) { if (ei.additionalSlice && ei.definition != null) {
if (ei.definition.getSlicing().getRules().equals(ElementDefinition.SlicingRules.OPEN) || if (ei.definition.getSlicing().getRules().equals(ElementDefinition.SlicingRules.OPEN) ||
ei.definition.getSlicing().getRules().equals(ElementDefinition.SlicingRules.OPENATEND) && true /* TODO: replace "true" with condition to check that this element is at "end" */) { ei.definition.getSlicing().getRules().equals(ElementDefinition.SlicingRules.OPENATEND) && true /* TODO: replace "true" with condition to check that this element is at "end" */) {
hint(errors, IssueType.INFORMATIONAL, ei.line(), ei.col(), ei.path, false, "This element does not match any known slice" + (profile == null ? "" : " for the profile " + profile.getUrl())); slicingHint(errors, IssueType.INFORMATIONAL, ei.line(), ei.col(), ei.path, false, "This element does not match any known slice" + (profile == null ? "" : " defined in the profile " + profile.getUrl()),
"This element does not match any known slice" + (profile == null ? "" : " defined in the profile " + profile.getUrl()+": "+errorSummaryForSlicingAsHtml(ei.sliceInfo)));
} else if (ei.definition.getSlicing().getRules().equals(ElementDefinition.SlicingRules.CLOSED)) { } else if (ei.definition.getSlicing().getRules().equals(ElementDefinition.SlicingRules.CLOSED)) {
rule(errors, IssueType.INVALID, ei.line(), ei.col(), ei.path, false, "This element does not match any known slice" + (profile == null ? "" : " for profile " + profile.getUrl() + " and slicing is CLOSED")); rule(errors, IssueType.INVALID, ei.line(), ei.col(), ei.path, false, "This element does not match any known slice " + (profile == null ? "" : " defined in the profile " + profile.getUrl() + " and slicing is CLOSED: "+errorSummaryForSlicing(ei.sliceInfo)),
"This element does not match any known slice " + (profile == null ? "" : " defined in the profile " + profile.getUrl() + " and slicing is CLOSED: "+errorSummaryForSlicingAsHtml(ei.sliceInfo)));
} }
} else { } else {
// Don't raise this if we're in an abstract profile, like Resource // Don't raise this if we're in an abstract profile, like Resource
if (!profile.getAbstract()) if (!profile.getAbstract())
hint(errors, IssueType.NOTSUPPORTED, ei.line(), ei.col(), ei.path, (ei.definition != null), "Could not verify slice for profile " + profile.getUrl()); rule(errors, IssueType.NOTSUPPORTED, ei.line(), ei.col(), ei.path, (ei.definition != null), "This element is not allowed by the profile "+profile.getUrl());
} }
// TODO: Should get the order of elements correct when parsing elements that are XML attributes vs. elements // TODO: Should get the order of elements correct when parsing elements that are XML attributes vs. elements
boolean isXmlAttr = false; boolean isXmlAttr = false;
@ -5081,7 +5240,7 @@ private boolean isAnswerRequirementFulfilled(QuestionnaireItemComponent qItem, L
checkInvariants(hostContext, errors, stack.getLiteralPath(), profile, definition, null, null, resource, element, onlyNonInherited); checkInvariants(hostContext, errors, stack.getLiteralPath(), profile, definition, null, null, resource, element, onlyNonInherited);
} }
public boolean matchSlice(ValidatorHostContext hostContext, List<ValidationMessage> errors, StructureDefinition profile, NodeStack stack, public boolean matchSlice(ValidatorHostContext hostContext, List<ValidationMessage> errors, List<ValidationMessage> sliceInfo, StructureDefinition profile, NodeStack stack,
ElementDefinition slicer, boolean unsupportedSlicing, List<String> problematicPaths, int sliceOffset, int i, ElementDefinition ed, ElementDefinition slicer, boolean unsupportedSlicing, List<String> problematicPaths, int sliceOffset, int i, ElementDefinition ed,
boolean childUnsupportedSlicing, ElementInfo ei) { boolean childUnsupportedSlicing, ElementInfo ei) {
boolean match = false; boolean match = false;
@ -5090,7 +5249,7 @@ private boolean isAnswerRequirementFulfilled(QuestionnaireItemComponent qItem, L
} else { } else {
if (nameMatches(ei.name, tail(ed.getPath()))) if (nameMatches(ei.name, tail(ed.getPath())))
try { try {
match = sliceMatches(hostContext, ei.element, ei.path, slicer, ed, profile, errors, stack); match = sliceMatches(hostContext, ei.element, ei.path, slicer, ed, profile, errors, sliceInfo, stack);
if (match) { if (match) {
ei.slice = slicer; ei.slice = slicer;
@ -5557,6 +5716,7 @@ private boolean isAnswerRequirementFulfilled(QuestionnaireItemComponent qItem, L
public class ElementInfo { public class ElementInfo {
public List<ValidationMessage> sliceInfo;
public int index; // order of definition in overall order. all slices get the index of the slicing definition public int index; // order of definition in overall order. all slices get the index of the slicing definition
public int sliceindex; // order of the definition in the slices (if slice != null) public int sliceindex; // order of the definition in the slices (if slice != null)
public int count; public int count;

View File

@ -241,6 +241,7 @@ public class ValidationEngine implements IValidatorResourceFetcher {
private boolean debug; private boolean debug;
private Set<String> loadedIgs = new HashSet<>(); private Set<String> loadedIgs = new HashSet<>();
private IValidatorResourceFetcher fetcher; private IValidatorResourceFetcher fetcher;
private boolean assumeValidRestReferences;
private class AsteriskFilter implements FilenameFilter { private class AsteriskFilter implements FilenameFilter {
String dir; String dir;
@ -432,7 +433,7 @@ public class ValidationEngine implements IValidatorResourceFetcher {
return fetchFromUrl(src+(v == null ? "" : "|"+v), explore); return fetchFromUrl(src+(v == null ? "" : "|"+v), explore);
} }
File f = new File(src); File f = new File(Utilities.path(src));
if (f.exists()) { if (f.exists()) {
if (f.isDirectory() && new File(Utilities.path(src, "package.tgz")).exists()) if (f.isDirectory() && new File(Utilities.path(src, "package.tgz")).exists())
return loadPackage(new FileInputStream(Utilities.path(src, "package.tgz")), Utilities.path(src, "package.tgz")); return loadPackage(new FileInputStream(Utilities.path(src, "package.tgz")), Utilities.path(src, "package.tgz"));
@ -760,8 +761,9 @@ public class ValidationEngine implements IValidatorResourceFetcher {
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) if (debug || ((e.getMessage() != null && e.getMessage().contains("cannot be cast")))) {
e.printStackTrace(); e.printStackTrace();
}
} }
return r; return r;
} }
@ -1270,6 +1272,7 @@ public class ValidationEngine implements IValidatorResourceFetcher {
validator.setAnyExtensionsAllowed(anyExtensionsAllowed); validator.setAnyExtensionsAllowed(anyExtensionsAllowed);
validator.setNoInvariantChecks(isNoInvariantChecks()); validator.setNoInvariantChecks(isNoInvariantChecks());
validator.setValidationLanguage(language); validator.setValidationLanguage(language);
validator.setAssumeValidRestReferences(assumeValidRestReferences);
validator.setFetcher(this); validator.setFetcher(this);
return validator; return validator;
} }
@ -1689,6 +1692,10 @@ public class ValidationEngine implements IValidatorResourceFetcher {
this.fetcher = fetcher; this.fetcher = fetcher;
} }
public void setAssumeValidRestReferences(boolean assumeValidRestReferences) {
this.assumeValidRestReferences = assumeValidRestReferences;
}
} }

View File

@ -217,6 +217,8 @@ public class Validator {
System.out.println(" referenced implementation guides or profiles as errors. (Default is to only raise information messages.)"); System.out.println(" referenced implementation guides or profiles as errors. (Default is to only raise information messages.)");
System.out.println("-hintAboutNonMustSupport: If present, raise hints if the instance contains data elements that are not"); System.out.println("-hintAboutNonMustSupport: If present, raise hints if the instance contains data elements that are not");
System.out.println(" marked as mustSupport=true. Useful to identify elements included that may be ignored by recipients"); System.out.println(" marked as mustSupport=true. Useful to identify elements included that may be ignored by recipients");
System.out.println("-assumeValidRestReferences: If present, assume that URLs that reference resources follow the RESTful URI pattern");
System.out.println(" and it is safe to infer the type from the URL");
System.out.println(""); System.out.println("");
System.out.println("The validator also supports the param -proxy=[address]:[port] for if you use a proxy"); System.out.println("The validator also supports the param -proxy=[address]:[port] for if you use a proxy");
System.out.println(""); System.out.println("");
@ -414,6 +416,7 @@ public class Validator {
String fhirpath = null; String fhirpath = null;
String snomedCT = "900000000000207008"; String snomedCT = "900000000000207008";
boolean doDebug = false; boolean doDebug = false;
boolean assumeValidRestReferences = false;
// load the parameters - so order doesn't matter // load the parameters - so order doesn't matter
for (int i = 0; i < args.length; i++) { for (int i = 0; i < args.length; i++) {
@ -447,6 +450,8 @@ public class Validator {
questionnaires.add(args[++i]); questionnaires.add(args[++i]);
} else if (args[i].equals("-native")) { } else if (args[i].equals("-native")) {
doNative = true; doNative = true;
} else if (args[i].equals("-assumeValidRestReferences")) {
assumeValidRestReferences = true;
} else if (args[i].equals("-debug")) { } else if (args[i].equals("-debug")) {
doDebug = true; doDebug = true;
} else if (args[i].equals("-sct")) { } else if (args[i].equals("-sct")) {
@ -577,6 +582,7 @@ public class Validator {
validator.setAnyExtensionsAllowed(anyExtensionsAllowed); validator.setAnyExtensionsAllowed(anyExtensionsAllowed);
validator.setLanguage(lang); validator.setLanguage(lang);
validator.setSnomedExtension(snomedCT); validator.setSnomedExtension(snomedCT);
validator.setAssumeValidRestReferences(assumeValidRestReferences);
IParser x; IParser x;
if (output != null && output.endsWith(".json")) if (output != null && output.endsWith(".json"))

View File

@ -177,6 +177,7 @@ public class ValidationTestSuite implements IEvaluationContext, IValidatorResour
} else { } else {
val.setAllowExamples(true); val.setAllowExamples(true);
} }
val.setAssumeValidRestReferences(content.has("assumeValidRestReferences") ? content.get("assumeValidRestReferences").getAsBoolean() : false);
if (name.endsWith(".json")) if (name.endsWith(".json"))
val.validate(null, errors, IOUtils.toInputStream(testCaseContent, Charsets.UTF_8), FhirFormat.JSON); val.validate(null, errors, IOUtils.toInputStream(testCaseContent, Charsets.UTF_8), FhirFormat.JSON);
else else
@ -200,6 +201,7 @@ public class ValidationTestSuite implements IEvaluationContext, IValidatorResour
v = content.has("version") ? content.get("version").getAsString() : Constants.VERSION; v = content.has("version") ? content.get("version").getAsString() : Constants.VERSION;
StructureDefinition sd = loadProfile(filename, contents, v, messages); StructureDefinition sd = loadProfile(filename, contents, v, messages);
val.getContext().cacheResource(sd); val.getContext().cacheResource(sd);
val.setAssumeValidRestReferences(profile.has("assumeValidRestReferences") ? profile.get("assumeValidRestReferences").getAsBoolean() : false);
List<ValidationMessage> errorsProfile = new ArrayList<ValidationMessage>(); List<ValidationMessage> errorsProfile = new ArrayList<ValidationMessage>();
if (name.endsWith(".json")) if (name.endsWith(".json"))
val.validate(null, errorsProfile, IOUtils.toInputStream(testCaseContent, Charsets.UTF_8), FhirFormat.JSON, asSdList(sd)); val.validate(null, errorsProfile, IOUtils.toInputStream(testCaseContent, Charsets.UTF_8), FhirFormat.JSON, asSdList(sd));

View File

@ -13,11 +13,11 @@
each other. It is fine to bump the point version of this POM without affecting each other. It is fine to bump the point version of this POM without affecting
HAPI FHIR. HAPI FHIR.
--> -->
<version>4.1.54-SNAPSHOT</version> <version>4.1.63-SNAPSHOT</version>
<properties> <properties>
<hapi_fhir_version>4.1.0</hapi_fhir_version> <hapi_fhir_version>4.1.0</hapi_fhir_version>
<validator_test_case_version>1.0.35-SNAPSHOT</validator_test_case_version> <validator_test_case_version>1.0.42-SNAPSHOT</validator_test_case_version>
</properties> </properties>
<artifactId>org.hl7.fhir.core</artifactId> <artifactId>org.hl7.fhir.core</artifactId>

View File

@ -1,7 +1,7 @@
@echo off @echo off
set oldver=4.1.53 set oldver=4.1.62
set newver=4.1.54 set newver=4.1.63
echo .. echo ..
echo ===================================================================== echo =====================================================================
@ -11,7 +11,8 @@ echo ..
call mvn versions:set -DnewVersion=%newver%-SNAPSHOT call mvn versions:set -DnewVersion=%newver%-SNAPSHOT
call git commit -a -m "Release new version" call git commit -t v%newver% -a -m "Release new version %newver%"
call git push origin master call git push origin master
call "C:\tools\fnr.exe" -dir "C:\work\org.hl7.fhir\build" -fileMask "*.xml" -find "%oldver%-SNAPSHOT" -replace "%newver%-SNAPSHOT" -count 8 call "C:\tools\fnr.exe" -dir "C:\work\org.hl7.fhir\build" -fileMask "*.xml" -find "%oldver%-SNAPSHOT" -replace "%newver%-SNAPSHOT" -count 8
call "C:\tools\fnr.exe" -dir "C:\work\org.hl7.fhir\fhir-ig-publisher" -fileMask "*.xml" -find "%oldver%-SNAPSHOT" -replace "%newver%-SNAPSHOT" -count 2 call "C:\tools\fnr.exe" -dir "C:\work\org.hl7.fhir\fhir-ig-publisher" -fileMask "*.xml" -find "%oldver%-SNAPSHOT" -replace "%newver%-SNAPSHOT" -count 2
@ -22,6 +23,9 @@ IF %ERRORLEVEL% NEQ 0 (
GOTO DONE GOTO DONE
) )
call "C:\tools\versionNotes.exe" -fileName C:\work\org.hl7.fhir\latest-ig-publisher\release-notes-validator.md -version %newver% -fileDest C:\temp\current-release-notes-validator.md -url https://fhir.github.io/latest-ig-publisher/org.hl7.fhir.validator.jar -maven https://oss.sonatype.org/service/local/artifact/maven/redirect?r=snapshots&g=ca.uhn.hapi.fhir&a=org.hl7.fhir.validation.cli&v=%newver%-SNAPSHOT&e=jar
copy org.hl7.fhir.validation.cli\target\org.hl7.fhir.validation.cli-%newver%-SNAPSHOT.jar ..\latest-ig-publisher\org.hl7.fhir.validator.jar copy org.hl7.fhir.validation.cli\target\org.hl7.fhir.validation.cli-%newver%-SNAPSHOT.jar ..\latest-ig-publisher\org.hl7.fhir.validator.jar
cd ..\latest-ig-publisher cd ..\latest-ig-publisher
call git commit -a -m "Release new version %newver%-SNAPSHOT" call git commit -a -m "Release new version %newver%-SNAPSHOT"
@ -29,7 +33,9 @@ call git push origin master
cd ..\org.hl7.fhir.core cd ..\org.hl7.fhir.core
call python c:\tools\zulip-api\zulip\zulip\send.py --stream committers/notification --subject "java core" -m "New Java Core v%newver%-SNAPSHOT released. New Validator at https://oss.sonatype.org/service/local/artifact/maven/redirect?r=snapshots&g=ca.uhn.hapi.fhir&a=org.hl7.fhir.validation.cli&v=%newver%-SNAPSHOT&e=jar, and also deployed at https://fhir.github.io/latest-ig-publisher/org.hl7.fhir.validator.jar" --config-file zuliprc call python c:\tools\zulip-api\zulip\zulip\send.py --stream committers/notification --subject "java core" -m "New Java Core v%newver%-SNAPSHOT released. New Validator at https://oss.sonatype.org/service/local/artifact/maven/redirect?r=snapshots&g=ca.uhn.hapi.fhir&a=org.hl7.fhir.validation.cli&v=%newver%-SNAPSHOT&e=jar, and also deployed at https://fhir.github.io/latest-ig-publisher/org.hl7.fhir.validator.jar" --config-file zuliprc
call python c:\tools\zulip-api\zulip\zulip\send.py --stream tooling/releases --subject "Validator" -m "New Validator @ https://fhir.github.io/latest-ig-publisher/org.hl7.fhir.validator.jar (v%newver%)" --config-file zuliprc call python c:\tools\zulip-api\zulip\zulip\send.py --stream tooling/releases --subject "Validator" --config-file zuliprc < C:\temp\current-release-notes-validator.md
del C:\temp\current-release-notes-validator.md
:DONE :DONE
echo =============================================================== echo ===============================================================