Skip to content

Commit

Permalink
Release 0.5.0
Browse files Browse the repository at this point in the history
  • Loading branch information
Gematik-Entwicklung committed May 3, 2023
1 parent 8187304 commit d7129e4
Show file tree
Hide file tree
Showing 183 changed files with 8,030 additions and 149 deletions.
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,20 @@ Die eingebundenen Packages, unterstützte Profile und Versionen findet man [hier

Die eingebundenen Packages, unterstützte Profile und Versionen findet man [hier](supported-profiles.md).

### ISIK2-Modul

Abweichend vom allgemeinen Prüfumfang verhält sich das ISIK2-Modul wie folgt:
- Codes aus den CodeSystemen `http://snomed.info/sct`, `http://fhir.de/CodeSystem/bfarm/icd-10-gm`, `http://fhir.de/CodeSystem/bfarm/atc` und `http://fhir.de/CodeSystem/bfarm/ops` werden nicht validiert
- Folgende ValueSets werden nicht validiert: `https://gematik.de/fhir/isik/v2/Basismodul/ValueSet/ProzedurenCodesSCT`, `https://gematik.de/fhir/isik/v2/Basismodul/ValueSet/DiagnosesSCT`, `https://gematik.de/fhir/isik/v2/Basismodul/ValueSet/ProzedurenKategorieSCT`, `https://gematik.de/fhir/isik/v2/Terminplanung/ValueSet/ISiKTerminPriority`, `https://gematik.de/fhir/isik/v2/Medikation/ValueSet/SctRouteOfAdministration` und `http://fhir.de/ValueSet/bfarm/ops`

### ISIK1-Modul

Abweichend vom allgemeinen Prüfumfang verhält sich das ISIK1-Modul wie folgt:
- Codes aus den CodeSystemen `http://snomed.info/sct`, `http://fhir.de/CodeSystem/bfarm/icd-10-gm` und `http://fhir.de/CodeSystem/bfarm/ops` werden nicht validiert
- Folgende ValueSets werden nicht validiert: `https://gematik.de/fhir/isik/v2/Basismodul/ValueSet/ProzedurenCodesSCT`, `https://gematik.de/fhir/isik/v2/Basismodul/ValueSet/DiagnosesSCT`, `https://gematik.de/fhir/isik/v2/Basismodul/ValueSet/ProzedurenKategorieSCT` und `http://fhir.de/ValueSet/bfarm/ops`

Die eingebundenen Packages, unterstützte Profile und Versionen findet man [hier](supported-profiles.md).

## Erste Schritte

### Voraussetzungen
Expand Down Expand Up @@ -115,6 +129,8 @@ Unterstützte Modulnamen:
- `erp` (E-Rezept)
- `eau` (Elektronische Arbeitsunfähigkeitsbescheinigung)
- `isip1` (Informationstechnische Systeme in der Pflege Stufe 1)
- `isik2` (Informationstechnische Systeme in Krankenhäusern Stufe 2)
- `isik1` (Informationstechnische Systeme in Krankenhäusern Stufe 1)

### Java-Bibliothek

Expand Down
12 changes: 12 additions & 0 deletions ReleaseNotes.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,18 @@

# Release Notes Gematik Referenzvalidator

## Release 0.5.0

### added
- support for the 1.2 version of profiles in de.abda.erezeptabgabedatenpkv (package de.abda.erezeptabgabedatenpkv-1.2.0)
- New validation module: isik2 (Informationstechnische Systeme in Krankenhäusern Stufe 2)
- New validation module: isik1 (Informationstechnische Systeme in Krankenhäusern Stufe 1)

### changed
- removed packages de.abda.erezeptabgabedaten-1.3.0, de.abda.erezeptabgabedatenpkv-1.1.0
- added packages (erp): de.abda.erezeptabgabedaten-1.3.1, de.abda.erezeptabgabedatenbasis-1.3.1
- KBV_CS_SFHIR_KBV_DARREICHUNGSFORM_V1.11.xml integrated

## Release 0.4.1

### fixed
Expand Down
2 changes: 1 addition & 1 deletion cli/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<artifactId>referencevalidator</artifactId>
<groupId>de.gematik.refv</groupId>
<version>0.4.1</version>
<version>0.5.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

Expand Down
2 changes: 1 addition & 1 deletion commons/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<artifactId>referencevalidator</artifactId>
<groupId>de.gematik.refv</groupId>
<version>0.4.1</version>
<version>0.5.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import com.ctc.wstx.stax.WstxInputFactory;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser;
import de.gematik.refv.commons.configuration.ValidationModuleConfiguration;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -32,6 +33,8 @@
import javax.xml.stream.events.XMLEvent;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

/**
Expand All @@ -44,22 +47,23 @@ public class ReferencedProfileLocator {
private static final WstxInputFactory inputFactory = new WstxInputFactory();

private static final JsonFactory jsonfactory = new JsonFactory();
private static final String PROFILE_STRING = "profile";

public Optional<Profile> locate(String resourceBody) throws IllegalArgumentException {
public Optional<Profile> locate(String resourceBody, ValidationModuleConfiguration configuration) throws IllegalArgumentException {
EncodingEnum detectedEncoding = EncodingEnum.detectEncoding(resourceBody);

if(detectedEncoding.equals(EncodingEnum.JSON)) {
try {
return locateInJson(resourceBody);
return locateInJson(resourceBody, configuration);
} catch (IOException e) {
throw new IllegalArgumentException("Could not parse resource", e);
}
}
else
return locateInXml(resourceBody);
return locateInXml(resourceBody, configuration);
}

private Optional<Profile> locateInXml(String resource) throws IllegalArgumentException {
private Optional<Profile> locateInXml(String resource, ValidationModuleConfiguration configuration) throws IllegalArgumentException {
try {
final XMLEventReader xmlEventReader;
xmlEventReader = inputFactory.createXMLEventReader(new StringReader(resource));
Expand All @@ -70,10 +74,9 @@ private Optional<Profile> locateInXml(String resource) throws IllegalArgumentExc
StartElement nextTag = event.asStartElement();
if (nextTag.getName().getLocalPart().equalsIgnoreCase("meta")) {

return locateProfileInMetaTagXml(xmlEventReader);
return locateProfileInMetaTagXml(xmlEventReader, configuration);
}
}

}
} catch (Exception e) {
throw new IllegalArgumentException("Could not parse resource", e);
Expand All @@ -82,15 +85,15 @@ private Optional<Profile> locateInXml(String resource) throws IllegalArgumentExc
return Optional.empty();
}

private Optional<Profile> locateProfileInMetaTagXml(XMLEventReader xmlEventReader) throws XMLStreamException {
private Optional<Profile> locateProfileInMetaTagXml(XMLEventReader xmlEventReader, ValidationModuleConfiguration configuration) throws XMLStreamException {
StartElement nextTag;
XMLEvent event;
while (xmlEventReader.hasNext()) {
event = xmlEventReader.nextEvent();
if (event.isStartElement()) {
nextTag = event.asStartElement();
if (nextTag.getName().getLocalPart().equalsIgnoreCase("profile")) {
Attribute valueAttribute = nextTag.asStartElement().getAttributeByName(new QName("", "value", ""));
if (nextTag.getName().getLocalPart().equalsIgnoreCase(PROFILE_STRING)) {
Attribute valueAttribute = checkForMultipleProfilesXml(xmlEventReader, nextTag, configuration);
if (valueAttribute == null || StringUtils.isEmpty(valueAttribute.getValue())) {
logger.debug("Profile element has no value");
return Optional.empty();
Expand All @@ -104,41 +107,101 @@ private Optional<Profile> locateProfileInMetaTagXml(XMLEventReader xmlEventReade
return Optional.empty();
}

private Optional<Profile> locateInJson(String resource) throws IOException {
private Attribute checkForMultipleProfilesXml(XMLEventReader xmlEventReader, StartElement nextTag, ValidationModuleConfiguration configuration) throws XMLStreamException {
Attribute valueAttribute = nextTag.asStartElement().getAttributeByName(new QName("", "value", ""));
List<Attribute> profilesFound = getProfiles(xmlEventReader, valueAttribute);
List<Attribute> supportedProfilesFound = getSupportedProfiles(profilesFound, configuration);
if(!supportedProfilesFound.isEmpty())
return supportedProfilesFound.get(0);
else
return valueAttribute;
}

private List<Attribute> getProfiles(XMLEventReader xmlEventReader, Attribute valueAttribute) throws XMLStreamException {
XMLEvent event;
StartElement nextTag;
List<Attribute> profilesFound = new ArrayList<>();
profilesFound.add(valueAttribute);
while(xmlEventReader.hasNext()) {
event = xmlEventReader.nextEvent();
if(event.isEndElement() && event.asEndElement().getName().getLocalPart().equalsIgnoreCase("meta"))
break;
if(event.isStartElement()) {
nextTag = event.asStartElement();
if(nextTag.getName().getLocalPart().equalsIgnoreCase(PROFILE_STRING) &&
!nextTag.getName().getLocalPart().equalsIgnoreCase("meta")) {
valueAttribute = nextTag.asStartElement().getAttributeByName(new QName("", "value", ""));
profilesFound.add(valueAttribute);
}
}
}
return profilesFound;
}

private List<Attribute> getSupportedProfiles(List<Attribute> profilesFound, ValidationModuleConfiguration configuration) {
List<Attribute> supportedProfilesFound = new ArrayList<>();
for(Attribute attribute : profilesFound) {
if(attribute != null && !"".equals(attribute.getValue()) &&
configuration.getSupportedProfiles().containsKey(attribute.getValue())){
supportedProfilesFound.add(attribute);
}
}
return supportedProfilesFound;
}

private Optional<Profile> locateInJson(String resource, ValidationModuleConfiguration configuration) throws IOException {
try (JsonParser jsonParser = jsonfactory.createParser(resource)) {
jsonParser.nextToken();
while (jsonParser.hasCurrentToken()) {
String fieldName = jsonParser.getCurrentName();
jsonParser.nextToken();
if ("meta".equals(fieldName)) {

return locateProfileInMetaJson(jsonParser);
return locateProfileInMetaJson(jsonParser, configuration);
}
}
}
logger.debug("No meta element found");
return Optional.empty();
}

private Optional<Profile> locateProfileInMetaJson(JsonParser jsonParser) throws IOException {
private Optional<Profile> locateProfileInMetaJson(JsonParser jsonParser, ValidationModuleConfiguration configuration) throws IOException {
while(jsonParser.hasCurrentToken()) {
String fieldName = jsonParser.getCurrentName();
jsonParser.nextToken();
if("profile".equals(fieldName)) {
if(PROFILE_STRING.equals(fieldName)) {
jsonParser.nextToken();
return readProfileValueAsStringJson(jsonParser);
return readProfileValueAsStringJson(jsonParser, configuration);
}
}
logger.debug("No profile element found");
return Optional.empty();
}

private Optional<Profile> readProfileValueAsStringJson(JsonParser jsonParser) throws IOException {
String profileValueAsString = jsonParser.getText();
if (StringUtils.isEmpty(profileValueAsString) || "]".equals(profileValueAsString) || "}".equals(profileValueAsString)) {
logger.debug("Profile element has no value");
return Optional.empty();
private Optional<Profile> readProfileValueAsStringJson(JsonParser jsonParser, ValidationModuleConfiguration configuration) throws IOException {
String profileValueAsString = checkForMultipleProfilesJson(jsonParser, configuration);
if (StringUtils.isEmpty(profileValueAsString) || "]".equals(profileValueAsString) || "}".equals(profileValueAsString)) {
logger.debug("Profile element has no value");
return Optional.empty();
}
return Optional.of(Profile.parse(profileValueAsString));
}

private String checkForMultipleProfilesJson(JsonParser jsonParser, ValidationModuleConfiguration configuration) throws IOException {
String profileValueAsString = jsonParser.getText();
List<String> supportedProfilesFound = new ArrayList<>();
while (!"]".equals(jsonParser.getText()) && !"}".equals(jsonParser.getText()) ){
String currentProfile = jsonParser.getText();
if(!StringUtils.isEmpty(currentProfile) &&
!"}".equals(currentProfile) &&
configuration.getSupportedProfiles().containsKey(currentProfile)){
supportedProfilesFound.add(currentProfile);
}
return Optional.of(Profile.parse(profileValueAsString));
jsonParser.nextToken();
}
if(!supportedProfilesFound.isEmpty())
return supportedProfilesFound.get(0);
else
return profileValueAsString;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,5 @@ public class ValidationMessageTransformation {
String severityLevelFrom;
String severityLevelTo;
String locatorString;
String messageId;
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ public class ValidationModuleConfiguration {

private List<String> ignoredCodeSystems = new LinkedList<>();

private List<String> ignoredValueSets = new LinkedList<>();

private boolean errorOnUnknownProfile;
private boolean anyExtensionsAllowed;

public Collection<String> getPatchesForPackageAndItsDependencies(PackageDefinition packageDefinition) {
var result = new HashSet<String>();
result.addAll(packageDefinition.getPatches().stream().map(p -> getPathToPatch(packageDefinition.getFilename(), p)).collect(Collectors.toList()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public ValidationResult validate(
@NonNull
ValidationModuleConfiguration configuration) throws IllegalArgumentException {

Profile profileInResource = getProfileInResource(resourceBody);
Profile profileInResource = getProfileInResource(resourceBody, configuration);

logger.info("Validating against {}...", profileInResource);

Expand Down Expand Up @@ -84,7 +84,10 @@ private FhirValidator getOrCreateCachedFhirValidatorFor(Profile profileInResourc
fhirContext,
configuration.listPackageNamesToLoad(packageDefinition),
configuration.getPatchesForPackageAndItsDependencies(packageDefinition),
configuration.getIgnoredCodeSystems()
configuration.getIgnoredCodeSystems(),
configuration.getIgnoredValueSets(),
configuration.isErrorOnUnknownProfile(),
configuration.isAnyExtensionsAllowed()
));
return hapiValidatorsCache.get(profileInResource);
}
Expand All @@ -93,7 +96,10 @@ private FhirValidator getOrCreateCachedFhirValidatorFor(Profile profileInResourc
fhirContext,
configuration.listPackageNamesToLoad(packageDefinition),
configuration.getPatchesForPackageAndItsDependencies(packageDefinition),
configuration.getIgnoredCodeSystems()
configuration.getIgnoredCodeSystems(),
configuration.getIgnoredValueSets(),
configuration.isErrorOnUnknownProfile(),
configuration.isAnyExtensionsAllowed()
);
}

Expand All @@ -104,10 +110,10 @@ private PackageDefinition getPackageDefinitionForProfile(Profile profileInResour
return packageDefinition.get();
}

private Profile getProfileInResource(String resourceBody) {
private Profile getProfileInResource(String resourceBody, ValidationModuleConfiguration configuration) {
// Use custom performance-optimized profile extraction due to issues with HAPI XML Parser
// Parsed XML files are transformed to JSON internally by HAPI and some elements such as XML comments are processed wrongly
Optional<Profile> profileOrEmpty = referencedProfileLocator.locate(resourceBody);
Optional<Profile> profileOrEmpty = referencedProfileLocator.locate(resourceBody, configuration);
if (profileOrEmpty.isEmpty())
throw new IllegalArgumentException("FHIR resources without a referenced profile are currently unsupported");
return profileOrEmpty.get();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.util.ClasspathUtil;
import ca.uhn.fhir.validation.FhirValidator;
import de.gematik.refv.commons.validation.support.IgnoreMissingValueSetValidationSupport;
import de.gematik.refv.commons.validation.support.IgnoreCodeSystemValidationSupport;
import de.gematik.refv.commons.validation.support.IgnoreValueSetValidationSupport;
import de.gematik.refv.commons.validation.support.PipedCanonicalCoreResourcesValidationSupport;
import lombok.SneakyThrows;
import org.hl7.fhir.common.hapi.validation.support.NpmPackageValidationSupport;
Expand All @@ -42,7 +43,11 @@ public FhirValidator createInstance(
FhirContext ctx,
Collection<String> packageFilenames,
Collection<String> patches,
Collection<String> codeSystemsToIgnore) {
Collection<String> codeSystemsToIgnore,
Collection<String> valueSetsToIgnore,
boolean errorOnUnknownProfile,
boolean anyExtensionsAllowed
) {

var npmPackageSupport = new NpmPackageValidationSupport(ctx);
for (String packagePath : packageFilenames) {
Expand All @@ -61,18 +66,19 @@ public FhirValidator createInstance(

IValidationSupport validationSupport = ctx.getValidationSupport();

var validationSupportChain = new ValidationSupportChain(
npmPackageSupport,
validationSupport,
new IgnoreMissingValueSetValidationSupport(ctx, codeSystemsToIgnore),
new PipedCanonicalCoreResourcesValidationSupport(ctx)
);
var validationSupportChain = new ValidationSupportChain(
new IgnoreCodeSystemValidationSupport(ctx, codeSystemsToIgnore),
new IgnoreValueSetValidationSupport(ctx, valueSetsToIgnore),
npmPackageSupport,
validationSupport,
new PipedCanonicalCoreResourcesValidationSupport(ctx)
);

FhirInstanceValidator hapiValidatorModule = new FhirInstanceValidator(
validationSupportChain);
hapiValidatorModule.setErrorForUnknownProfiles(true);
hapiValidatorModule.setErrorForUnknownProfiles(errorOnUnknownProfile);
hapiValidatorModule.setNoExtensibleWarnings(true);
hapiValidatorModule.setAnyExtensionsAllowed(false);
hapiValidatorModule.setAnyExtensionsAllowed(anyExtensionsAllowed);
FhirValidator fhirValidator = ctx.newValidator();
fhirValidator.registerValidatorModule(hapiValidatorModule);
return fhirValidator;
Expand Down
Loading

0 comments on commit d7129e4

Please sign in to comment.