package at.medevit.elexis.ehc.vacdoc.service.internal;
import static ch.elexis.core.constants.XidConstants.DOMAIN_AHV;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.KeyStore;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import org.ehealth_connector.cda.ch.vacd.CdaChVacd;
import org.ehealth_connector.common.Identificator;
import org.ehealth_connector.common.Name;
import org.ehealth_connector.common.enums.CodeSystems;
import org.ehealth_connector.common.enums.LanguageCode;
import org.ehealth_connector.common.utils.DateUtil;
import org.ehealth_connector.communication.AffinityDomain;
import org.ehealth_connector.communication.AtnaConfig;
import org.ehealth_connector.communication.ConvenienceMasterPatientIndexV3;
import org.ehealth_connector.communication.Destination;
import org.ehealth_connector.communication.DocumentRequest;
import org.ehealth_connector.communication.MasterPatientIndexQuery;
import org.ehealth_connector.communication.MasterPatientIndexQueryResponse;
import org.ehealth_connector.communication.ch.ConvenienceCommunicationCh;
import org.ehealth_connector.communication.ch.DocumentMetadataCh;
import org.ehealth_connector.communication.ch.enums.AvailabilityStatus;
import org.ehealth_connector.communication.ch.enums.ClassCode;
import org.ehealth_connector.communication.ch.enums.ConfidentialityCode;
import org.ehealth_connector.communication.ch.enums.FormatCode;
import org.ehealth_connector.communication.ch.enums.HealthcareFacilityTypeCode;
import org.ehealth_connector.communication.ch.enums.MimeType;
import org.ehealth_connector.communication.ch.enums.PracticeSettingCode;
import org.ehealth_connector.communication.ch.enums.TypeCode;
import org.ehealth_connector.communication.ch.xd.storedquery.FindDocumentsQuery;
import org.openhealthtools.ihe.atna.auditor.XDSSourceAuditor;
import org.openhealthtools.ihe.atna.auditor.context.AuditorModuleConfig;
import org.openhealthtools.ihe.atna.auditor.context.AuditorModuleContext;
import org.openhealthtools.ihe.atna.nodeauth.SecurityDomainException;
import org.openhealthtools.ihe.xds.document.DocumentDescriptor;
import org.openhealthtools.ihe.xds.metadata.AvailabilityStatusType;
import org.openhealthtools.ihe.xds.metadata.DocumentEntryType;
import org.openhealthtools.ihe.xds.response.DocumentEntryResponseType;
import org.openhealthtools.ihe.xds.response.XDSQueryResponseType;
import org.openhealthtools.ihe.xds.response.XDSResponseType;
import org.openhealthtools.ihe.xds.response.XDSRetrieveResponseType;
import org.openhealthtools.ihe.xds.response.XDSStatusType;
import org.openhealthtools.mdht.uml.cda.util.CDAUtil;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import at.medevit.elexis.ehc.vacdoc.service.MeineImpfungenService;
import at.medevit.elexis.ehc.vacdoc.service.VacdocService;
import ch.elexis.core.data.activator.CoreHub;
import ch.elexis.core.data.events.ElexisEvent;
import ch.elexis.core.data.events.ElexisEventDispatcher;
import ch.elexis.core.data.events.ElexisEventListener;
import ch.elexis.core.data.events.ElexisEventListenerImpl;
import ch.elexis.core.services.ISSLStoreService;
import ch.elexis.data.Mandant;
import ch.elexis.data.Patient;
@Component
public class MeineImpfungenServiceImpl implements MeineImpfungenService {
// use dummy sub id of ehc dev OID for now -> TODO get an Elexis OID
public static final String ORGANIZATIONAL_ID = "2.16.756.5.30.1.139.1.1.3.9999";
private static final String PDQ_REQUEST_URL =
"https://test.suisse-open-exchange.healthcare/services/mpi/services/PDQSupplier_Port_Soap12";
private static final String XDS_REGISTRY_URL =
"https://test.suisse-open-exchange.healthcare/services/registry/services/DocumentRegistry";
private static final String XDS_REPOSITORY_URL =
"https://test.meineimpfungen.ch/ihe/xds/DocumentRepository";
private static final String ATNA_URL = "tls://test.suisse-open-exchange.healthcare:5544";
private AffinityDomain affinityDomain;
private static final Logger logger = LoggerFactory.getLogger(MeineImpfungenServiceImpl.class);
private VacdocService vacdocService;
private ISSLStoreService sslStoreService;
private ElexisEventListener mandantListener;
private Optional<KeyStore> currentTrustStore = Optional.empty();
private Optional<KeyStore> currentKeyStore = Optional.empty();
@Reference
public void setVacdocService(VacdocService vacdocService){
this.vacdocService = vacdocService;
}
public void unsetVacdocService(VacdocService vacdocService){
this.vacdocService = null;
}
@Reference
public void setSSLService(ISSLStoreService sslStoreService){
this.sslStoreService = sslStoreService;
}
public void unsetSSLService(ISSLStoreService sslStoreService){
this.sslStoreService = null;
}
@Activate
public void activate(){
updateConfiguration();
if (mandantListener == null) {
mandantListener = new MandantChangedListener();
ElexisEventDispatcher.getInstance().addListeners(mandantListener);
}
}
@Deactivate
public void deactivate(){
if (mandantListener != null) {
ElexisEventDispatcher.getInstance().removeListeners(mandantListener);
mandantListener = null;
}
}
private void updateAffinityDomain()
throws URISyntaxException, SecurityDomainException{
affinityDomain = getMeineImpfungenAffinityDomain();
AuditorModuleContext ctx = AuditorModuleContext.getContext();
AuditorModuleConfig auditorConfig = ctx.getAuditor(XDSSourceAuditor.class).getConfig();
try {
auditorConfig
.setAuditRepositoryUri(affinityDomain.getAtnaConfig().getAuditRepositoryUri());
} catch (Exception e) {
logger.error("Audit configuration problem", e);
}
}
private AffinityDomain getMeineImpfungenAffinityDomain() throws URISyntaxException{
// set secure destinations
Destination pdqDestination = new Destination(ORGANIZATIONAL_ID, new URI(PDQ_REQUEST_URL));
pdqDestination.setSenderApplicationOid(ORGANIZATIONAL_ID);
pdqDestination.setReceiverApplicationOid(PDQ_REQUEST_PATID_OID);
pdqDestination.setReceiverFacilityOid(PDQ_REQUEST_PATID_OID);
Destination xdsRegistryDestination = new Destination(ORGANIZATIONAL_ID,
new URI(XDS_REGISTRY_URL));
Destination xdsRepositoryDestination = new Destination(ORGANIZATIONAL_ID,
new URI(XDS_REPOSITORY_URL));
xdsRegistryDestination.setReceiverApplicationOid(XDS_REPOSITORY_OID);
xdsRegistryDestination.setReceiverFacilityOid(XDS_REPOSITORY_OID);
AffinityDomain ret = new AffinityDomain();
ret.setPdqDestination(pdqDestination);
ret.setRegistryDestination(xdsRegistryDestination);
ret.addRepository(xdsRepositoryDestination);
ret.setPixDestination(pdqDestination);
AtnaConfig atnaConfig = new AtnaConfig();
atnaConfig.setAuditRepositoryUri(ATNA_URL);
atnaConfig.setAuditSourceId("EHC-Elexis");
ret.setAtnaConfig(atnaConfig);
return ret;
}
@Override
public synchronized boolean updateConfiguration(){
affinityDomain = null;
// read the configuration
String truststorePath = CoreHub.mandantCfg.get(CONFIG_TRUSTSTORE_PATH, null);
String truststorePass = CoreHub.mandantCfg.get(CONFIG_TRUSTSTORE_PASS, null);
String keystorePath = CoreHub.mandantCfg.get(CONFIG_KEYSTORE_PATH, null);
String keystorePass = CoreHub.mandantCfg.get(CONFIG_KEYSTORE_PASS, null);
if (truststorePass != null && truststorePath != null && keystorePass != null
&& keystorePath != null) {
try {
if(!currentTrustStore.isPresent()) {
currentTrustStore = sslStoreService.loadKeyStore(truststorePath, truststorePass, "JKS");
currentTrustStore.ifPresent(store -> sslStoreService.addTrustStore(store));
}
// remove previous key store and add new key store
currentKeyStore.ifPresent(store -> sslStoreService.removeKeyStore(store));
currentKeyStore = sslStoreService.loadKeyStore(keystorePath, keystorePass, "PKCS12");
currentKeyStore
.ifPresent(store -> sslStoreService.addKeyStore(store, keystorePass));
updateAffinityDomain();
} catch (SecurityDomainException | URISyntaxException e) {
logger.error("Could not update affinity domain.", e);
return false;
}
} else {
// remove previous key store
currentKeyStore.ifPresent(store -> sslStoreService.removeKeyStore(store));
currentKeyStore = Optional.empty();
}
return true;
}
@Override
public boolean isVaild(){
return affinityDomain != null && affinityDomain.getPdqDestination() != null;
}
@Override
public List<CdaChVacd> getDocuments(org.ehealth_connector.common.Patient ehcPatient){
List<CdaChVacd> ret = new ArrayList<>();
List<DocumentEntryType> entryTypes = getAllPatientDocumentEntryTypes(ehcPatient);
try {
for (DocumentEntryType documentEntryType : entryTypes) {
if (documentEntryType.getAvailabilityStatus() != null && documentEntryType
.getAvailabilityStatus() == AvailabilityStatusType.APPROVED_LITERAL) {
if ("text/xml".equals(documentEntryType.getMimeType())) {
InputStream documentStream = getDocumentAsInputStream(documentEntryType);
if (documentStream != null) {
Optional<CdaChVacd> vacdocOpt =
vacdocService.loadVacdocDocument(documentStream);
vacdocOpt.ifPresent(d -> ret.add(d));
}
}
}
}
} catch (Exception e) {
logger.error("Could not load CdaChVacd", e);
e.printStackTrace(System.err);
}
return ret;
}
/**
* Gets the document entry types.
*
* @param aMyPatientId
* the a my patient id
* @return the document entry types
*/
private List<DocumentEntryType> getAllPatientDocumentEntryTypes(
org.ehealth_connector.common.Patient ehcPatient){
List<DocumentEntryType> ret = new ArrayList<DocumentEntryType>();
List<Identificator> ids = ehcPatient.getIds();
if (ids != null && !ids.isEmpty()) {
FindDocumentsQuery fdq = new FindDocumentsQuery(ids.get(0), null, null, null, null,
null, null, null, AvailabilityStatus.APPROVED);
logger.debug("getDocumentEntryTypes");
final ConvenienceCommunicationCh convComm =
new ConvenienceCommunicationCh(affinityDomain);
logger.debug("queryDocuments");
final XDSQueryResponseType regDocQuery = convComm.queryDocuments(fdq);
if (regDocQuery != null) {
final List<DocumentEntryResponseType> docEntrieResponses =
regDocQuery.getDocumentEntryResponses();
logger.info("Document Entries found: " + docEntrieResponses.size());
for (final DocumentEntryResponseType docEntryResponse : docEntrieResponses) {
final DocumentEntryType docEntry = docEntryResponse.getDocumentEntry();
ret.add(docEntry);
}
}
convComm.clearDocuments();
}
return ret;
}
private String getDocumentAsString(DocumentEntryType docEntry) throws IOException{
InputStream inputStream = getDocumentAsInputStream(docEntry);
try (BufferedReader buffer =
new BufferedReader(new InputStreamReader(inputStream, "UTF-8"))) {
return buffer.lines().collect(Collectors.joining("\n"));
}
}
/**
* Gets the document as input stream.
*
* @param docEntry
* the doc entry
* @return the document as input stream
*/
private InputStream getDocumentAsInputStream(DocumentEntryType docEntry){
DocumentRequest documentRequest = new DocumentRequest(docEntry.getRepositoryUniqueId(),
affinityDomain.getRepositoryDestination().getUri(), docEntry.getEntryUUID());
ConvenienceCommunicationCh convComm = new ConvenienceCommunicationCh(affinityDomain);
XDSRetrieveResponseType rrt = convComm.retrieveDocument(documentRequest);
try {
if ((rrt.getErrorList() != null)) {
logger.error("error retriveing doc " + docEntry.getEntryUUID() + " - "
+ rrt.getErrorList().getHighestSeverity().getName());
}
if ((rrt.getAttachments() == null) || rrt.getAttachments().size() != 1) {
logger.error("document not downloaded or more than one");
return null;
}
return rrt.getAttachments().get(0).getStream();
} finally {
convComm.clearDocuments();
}
}
@Override
public List<org.ehealth_connector.common.Patient> getPatients(Patient elexisPatient){
MasterPatientIndexQuery mpiQuery =
new MasterPatientIndexQuery(affinityDomain.getPdqDestination());
mpiQuery.addDomainToReturn(PDQ_REQUEST_PATID_OID);
Name name = new Name(elexisPatient.getVorname(), elexisPatient.getName());
mpiQuery.addPatientName(true, name);
String birthDate = elexisPatient.getGeburtsdatum();
if (birthDate != null && !birthDate.isEmpty()) {
mpiQuery.setPatientDateOfBirth(DateUtil.date(birthDate));
}
MasterPatientIndexQueryResponse ret =
ConvenienceMasterPatientIndexV3.queryPatientDemographics(mpiQuery, affinityDomain);
if (!ret.getSuccess()) {
throw new IllegalStateException("Error contacting meineimpfungen Service");
}
return ret.getPatients();
}
private Identificator getPatientId(Patient elexisPatient){
// patient AHV
String socialSecurityNumber = elexisPatient.getXid(DOMAIN_AHV);
if (socialSecurityNumber != null) {
socialSecurityNumber = socialSecurityNumber.trim();
socialSecurityNumber = socialSecurityNumber.replaceAll("\\.", "");
if (socialSecurityNumber.length() == 11) {
return new Identificator(CodeSystems.SwissSSNDeprecated.getCodeSystemId(),
socialSecurityNumber);
} else if (socialSecurityNumber.length() == 13) {
return new Identificator(CodeSystems.SwissSSN.getCodeSystemId(),
socialSecurityNumber);
}
}
return null;
}
@Override
public String getBaseUrl(){
return "https://test.meineimpfungen.ch/";
}
@Override
public boolean uploadDocument(CdaChVacd document){
XDSResponseType response = null;
try {
ConvenienceCommunicationCh convComm =
new ConvenienceCommunicationCh(affinityDomain);
DocumentMetadataCh metaData = convComm.addChDocument(DocumentDescriptor.CDA_R2,
getDocumentAsInputStream(document));
setMetadataCdaCh(metaData, document);
response = convComm.submit();
} catch (final Exception e) {
logger.error("Error uploading document", e);
return false;
}
if (response.getStatus() != null) {
return XDSStatusType.SUCCESS == response.getStatus().getValue();
}
return false;
}
private boolean setMetadataCdaCh(DocumentMetadataCh metaData, CdaChVacd document){
metaData.addAuthor(document.getAuthor());
metaData.setMimeType(MimeType.XML_TEXT);
Optional<Identificator> patId = getMeineImpfungenPatientId(document.getPatient());
if (patId.isPresent()) {
metaData.setDestinationPatientId(patId.get());
metaData.setSourcePatientId(patId.get());
} else {
return false;
}
metaData.setCodedLanguage(LanguageCode.GERMAN);
metaData.setTypeCode(TypeCode.ELEKTRONISCHER_IMPFAUSWEIS);
metaData.setFormatCode(FormatCode.EIMPFDOSSIER);
metaData.setClassCode(ClassCode.ALERTS);
metaData.setHealthcareFacilityTypeCode(
HealthcareFacilityTypeCode.AMBULANTE_EINRICHTUNG_INKL_AMBULATORIUM);
metaData.setPracticeSettingCode(PracticeSettingCode.ALLERGOLOGIE);
metaData.addConfidentialityCode(ConfidentialityCode.ADMINISTRATIVE_DATEN);
return true;
}
private Optional<Identificator> getMeineImpfungenPatientId(
org.ehealth_connector.common.Patient patient){
List<Identificator> ids = patient.getIds();
if (ids != null && !ids.isEmpty()) {
for (Identificator identificator : ids) {
if (MeineImpfungenService.PDQ_REQUEST_PATID_OID.equals(identificator.getRoot())) {
return Optional.of(identificator);
}
}
}
return Optional.empty();
}
private InputStream getDocumentAsInputStream(CdaChVacd document) throws Exception{
ByteArrayOutputStream out = new ByteArrayOutputStream();
CDAUtil.save(document.getMdht(), out);
return new ByteArrayInputStream(out.toByteArray());
}
private class MandantChangedListener extends ElexisEventListenerImpl {
public MandantChangedListener(){
super(Mandant.class, ElexisEvent.EVENT_MANDATOR_CHANGED);
}
@Override
public void run(ElexisEvent ev) {
updateConfiguration();
}
}
}