![JAR search and dependency download from the Maven repository](/logo.png)
com.alextherapeutics.diga.implementation.DigaXmlJaxbRequestReader Maven / Gradle / Ivy
/*
* Copyright 2021-2021 Alex Therapeutics AB and individual contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*
*/
package com.alextherapeutics.diga.implementation;
import com.alextherapeutics.diga.DigaXmlReaderException;
import com.alextherapeutics.diga.DigaXmlRequestReader;
import com.alextherapeutics.diga.model.*;
import com.alextherapeutics.diga.model.generatedxml.billingreport.MessageType;
import com.alextherapeutics.diga.model.generatedxml.billingreport.Report;
import com.alextherapeutics.diga.model.generatedxml.billingreport.ResourceType;
import com.alextherapeutics.diga.model.generatedxml.billingreport.ValidationStepResultType;
import com.alextherapeutics.diga.model.generatedxml.codevalidation.NachrichtentypStp;
import com.alextherapeutics.diga.model.generatedxml.codevalidation.PruefungFreischaltcode;
import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.JAXBException;
import jakarta.xml.bind.Unmarshaller;
import java.io.*;
import java.nio.charset.Charset;
import java.util.Collections;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
/** A XML reader using JAXB. Depends on XML Schemas (.xsd) located in main/resources/*-xsd/ */
@Slf4j
public class DigaXmlJaxbRequestReader implements DigaXmlRequestReader {
private final JAXBContext codeValidationContext;
private final Unmarshaller codeValidationUnmarshaller;
private final JAXBContext billingReportContext;
private final Unmarshaller billingReportUnmarshaller;
public DigaXmlJaxbRequestReader() throws JAXBException {
codeValidationContext = JAXBContext.newInstance(PruefungFreischaltcode.class);
codeValidationUnmarshaller = codeValidationContext.createUnmarshaller();
billingReportContext = JAXBContext.newInstance(Report.class);
billingReportUnmarshaller = billingReportContext.createUnmarshaller();
}
@Override
public DigaInvoiceResponse readBillingReport(InputStream decryptedReport)
throws DigaXmlReaderException {
try {
XMLStreamReader xmlStream =
XMLInputFactory.newInstance().createXMLStreamReader(decryptedReport);
var encoding = xmlStream.getEncoding();
var charset = Charset.forName(encoding);
// xml is not always properly encoded despite the stated encoding in the xml
// therefore we re-encode based on the specified encoding
var bytes = new String(decryptedReport.readAllBytes(), charset).getBytes();
var report = (Report) billingReportUnmarshaller.unmarshal(new ByteArrayInputStream(bytes));
return DigaInvoiceResponse.builder()
.hasError(!report.isValid())
.errors(getInvoiceErrors(report))
.rawXmlResponseBody(bytes)
.generatedInvoice(IOUtils.toString(bytes, "UTF-8"))
.build();
} catch (JAXBException | IOException | XMLStreamException e) {
throw new DigaXmlReaderException(e);
}
}
@Override
public DigaCodeValidationResponse readCodeValidationResponse(InputStream decryptedResponse)
throws DigaXmlReaderException {
try {
var bytes = decryptedResponse.readAllBytes();
var response =
(PruefungFreischaltcode)
codeValidationUnmarshaller.unmarshal(new ByteArrayInputStream(bytes));
validateCodeValidationResponse(response);
// appendix 4 at
// https://www.gkv-datenaustausch.de/leistungserbringer/digitale_gesundheitsanwendungen/digitale_gesundheitsanwendungen.jsp
// seems to differ from the current xsd schema definition. watch for changes here
return DigaCodeValidationResponse.builder()
.rawXmlResponseBody(bytes)
.hasError(response.getNachrichtentyp().equals(NachrichtentypStp.FEH))
.errors(getCodeValidationErrors(response))
.validatedDigaCode(
response.getAntwort() == null ? null : response.getAntwort().getFreischaltcode())
.dayOfServiceProvision(
response.getAntwort() == null
? null
: response
.getAntwort()
.getTagDerLeistungserbringung()
.toGregorianCalendar()
.getTime())
.validatedDigaveid(
response.getAntwort() == null ? null : response.getAntwort().getDiGAVEID())
.build();
} catch (JAXBException | IOException e) {
throw new DigaXmlReaderException(e);
}
}
private List getCodeValidationErrors(PruefungFreischaltcode request) {
var errors = request.getFehlerinformation();
return errors == null
? Collections.emptyList()
: errors.stream()
.map(
fehlerinformation ->
new DigaCodeValidationResponseError(
DigaCodeValidationErrorCode.fromCode(
fehlerinformation.getFehlernummer().intValue()),
fehlerinformation.getFehlertext()))
.collect(Collectors.toList());
}
// log errors if the response looks strange, for manual inspection (it is difficult to know what
// can go wrong at this point)
// dont throw exception because the process may work anyway if we are lucky
private void validateCodeValidationResponse(PruefungFreischaltcode response) {
if (response.getNachrichtentyp().equals(NachrichtentypStp.ANF)) {
log.error(
"Received ANF (request) type in a response from the DiGA API. Should be ANT or FEH. Response: {}",
response);
}
if (!response
.getVersion()
.equals(DigaSupportedXsdVersion.DIGA_CODE_VALIDATION_VERSION.getValue())) {
log.error(
"Received code validation response with version mismatch. Supported version: {), Response version: {}",
DigaSupportedXsdVersion.DIGA_CODE_VALIDATION_VERSION.getValue(),
response.getVersion());
}
}
private List getInvoiceErrors(Report report) {
return report.isValid() ? Collections.emptyList() : buildErrorResponsesFromReport(report);
}
private List buildErrorResponsesFromReport(Report report) {
if (report.getScenarioMatched() == null && report.getNoScenarioMatched() == null) {
return List.of(
DigaInvoiceResponseError.builder()
.validationStepId(null)
.messages(
"Validator "
+ getEngineNameFromReport(report)
+ " returned neither scenarioMatched or noScenarioMatched for the invoice.")
.build());
}
if (report.getScenarioMatched() == null) {
return List.of(
DigaInvoiceResponseError.builder()
.messages(
"Validator "
+ getEngineNameFromReport(report)
+ " returned 'noScenarioMatched' for the invoice.")
.build());
}
return report.getScenarioMatched().getValidationStepResult().stream()
.filter(Predicate.not(ValidationStepResultType::isValid))
.map(
validationStepResult ->
DigaInvoiceResponseError.builder()
.validationStepId(validationStepResult.getId())
.resources(createResourceInfoFromError(validationStepResult.getResource()))
.messages(createMessagesFromError(validationStepResult.getMessage()))
.build())
.collect(Collectors.toList());
}
private String getEngineNameFromReport(Report report) {
return report.getEngine() == null ? "null" : report.getEngine().getName();
}
private String createMessagesFromError(List messages) {
var sb = new StringBuilder();
sb.append("Messages:\n");
messages.stream()
.forEach(
message -> {
sb.append("Message: " + message.getValue());
sb.append(", with code: ");
sb.append(message.getCode());
sb.append(", at xPathLocation: ");
sb.append(message.getXpathLocation());
sb.append("\n");
});
return sb.toString();
}
private String createResourceInfoFromError(List resources) {
var sb = new StringBuilder();
sb.append("Resources:\n");
resources.stream()
.forEach(
resource -> {
sb.append("Name: " + resource.getName());
sb.append(", ");
sb.append("Location: " + resource.getLocation());
sb.append("\n");
});
return sb.toString();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy