All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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