org.projecthusky.communication.ConvenienceCommunication Maven / Gradle / Ivy
/*
* This code is made available under the terms of the Eclipse Public License v1.0
* in the github project https://github.com/project-husky/husky there you also
* find a list of the contributors and the license information.
*
* This project has been developed further and modified by the joined working group Husky
* on the basis of the eHealth Connector opensource project from June 28, 2021,
* whereas medshare GmbH is the initial and main contributor/author of the eHealth Connector.
*
*/
package org.projecthusky.communication;
import org.apache.camel.CamelContext;
import org.apache.commons.text.StringEscapeUtils;
import org.openehealth.ipf.commons.core.OidGenerator;
import org.openehealth.ipf.commons.ihe.xds.XDS;
import org.openehealth.ipf.commons.ihe.xds.core.metadata.*;
import org.openehealth.ipf.commons.ihe.xds.core.metadata.Timestamp.Precision;
import org.openehealth.ipf.commons.ihe.xds.core.requests.ProvideAndRegisterDocumentSet;
import org.openehealth.ipf.commons.ihe.xds.core.requests.QueryRegistry;
import org.openehealth.ipf.commons.ihe.xds.core.requests.RetrieveDocumentSet;
import org.openehealth.ipf.commons.ihe.xds.core.requests.query.QueryReturnType;
import org.openehealth.ipf.commons.ihe.xds.core.responses.QueryResponse;
import org.openehealth.ipf.commons.ihe.xds.core.responses.Response;
import org.openehealth.ipf.commons.ihe.xds.core.responses.RetrievedDocumentSet;
import org.projecthusky.common.communication.*;
import org.projecthusky.common.communication.AtnaConfig.AtnaConfigMode;
import org.projecthusky.common.communication.DocumentMetadata.DocumentMetadataExtractionMode;
import org.projecthusky.common.communication.SubmissionSetMetadata.SubmissionSetMetadataExtractionMode;
import org.projecthusky.common.enums.DocumentDescriptor;
import org.projecthusky.common.enums.EhcVersions;
import org.projecthusky.common.model.Code;
import org.projecthusky.common.utils.XdsMetadataUtil;
import org.projecthusky.common.utils.xml.XmlFactories;
import org.projecthusky.communication.utils.HuskyUtils;
import org.projecthusky.communication.xd.storedquery.AbstractStoredQuery;
import org.projecthusky.communication.xd.storedquery.FindFoldersStoredQuery;
import org.projecthusky.communication.xd.xdm.IndexHtm;
import org.projecthusky.communication.xd.xdm.ReadmeTxt;
import org.projecthusky.communication.xd.xdm.XdmContents;
import org.projecthusky.xua.core.SecurityHeaderElement;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;
import javax.activation.DataHandler;
import javax.mail.util.ByteArrayDataSource;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.*;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.UUID;
import java.util.zip.ZipFile;
/**
* The ConvenienceCommunication class provides a convenience API for
* transactions to different destinations such as registries and repositories
* over media.
*
* It implements the following IHE actors:
*
* - IHE ITI Document Consumer
* - IHE ITI Document Source
* - IHE ITI Portable Media Creator
* - IHE ITI Portable Media Importer
*
*
* It implements the following IHE transactions:
*
* - [ITI-18] Registry Stored Query
* - [ITI-32] Distribute Document Set on Media
* - [ITI-41] Provide and Register Document Set – b
* - [ITI-43] Retrieve Document Set
*
*/
@Component
public class ConvenienceCommunication extends CamelService {
private static final String LOG_SEND_REQUEST = "Sending request to '{}' endpoint";
/**
* The SLF4J logger instance.
*/
private static Logger log = LoggerFactory.getLogger(ConvenienceCommunication.class);
@Autowired
private CamelContext context;
/**
* The affinity domain set-up
*/
private final ThreadLocal affinityDomain = new ThreadLocal<>();
/**
* The ATNA config mode (secure or unsecure)
*/
private AtnaConfig.AtnaConfigMode atnaConfigMode = AtnaConfigMode.UNSECURE;
/**
* Determines if XDS document metadata will be extracted automatically (e.g.
* from CDA documents)
*/
private DocumentMetadataExtractionMode documentMetadataExtractionMode = DocumentMetadataExtractionMode.DEFAULT_EXTRACTION;
/**
* Determines if SubmissionSet metadata will be extracted automatically (e.g.
* from CDA documents)
*/
private SubmissionSetMetadataExtractionMode submissionSetMetadataExtractionMode = SubmissionSetMetadataExtractionMode.DEFAULT_EXTRACTION;
/**
* The IPF transaction data to send XDS Documents
*/
private final ThreadLocal txnData = new ThreadLocal<>();
/**
* Instantiates a new convenience communication without affinity domain set-up.
* ATNA audit is disabled (unsecure)
*/
public ConvenienceCommunication() {
super();
this.atnaConfigMode = AtnaConfigMode.UNSECURE;
}
/**
* Instantiates a new convenience communication with the given affinity domain
* set-up. ATNA audit is disabled (unsecure)
*
* @param affinityDomain the affinity domain configuration
*/
public ConvenienceCommunication(AffinityDomain affinityDomain) {
this.affinityDomain.set(affinityDomain);
this.atnaConfigMode = AtnaConfigMode.UNSECURE;
}
/**
* Instantiates a new convenience communication with the given affinity domain
* set-up.
*
* @param affinityDomain the affinity domain configuration
* @param atnaConfigMode the ATNA config mode (secure or
* unsecure)
* @param documentMetadataExtractionMode determines, if and how document
* metadata should be extracted
* automatically. Extracted metadata
* attributes will not overwrite
* attributes that have been set,
* manually.
* @param submissionSetMetadataExtractionMode determines, if and how submission
* set metadata should be extracted,
* automatically. Extracted metadata
* attributes will not overwrite
* attributes that have been set,
* manually.
*/
public ConvenienceCommunication(AffinityDomain affinityDomain, AtnaConfigMode atnaConfigMode,
DocumentMetadataExtractionMode documentMetadataExtractionMode,
SubmissionSetMetadataExtractionMode submissionSetMetadataExtractionMode) {
this.affinityDomain.set(affinityDomain);
this.atnaConfigMode = atnaConfigMode;
this.documentMetadataExtractionMode = documentMetadataExtractionMode;
this.submissionSetMetadataExtractionMode = submissionSetMetadataExtractionMode;
}
/**
* Adds a document to the XDS Submission set.
*
* @param desc the document descriptor (which kind of document do you
* want to transfer? e.g. PDF, CDA,...)
* @param inputStream The input stream to the document
* @return the document metadata (which have to be completed)
*/
public DocumentMetadata addDocument(DocumentDescriptor desc, InputStream inputStream) {
return addDocument(desc, inputStream, null);
}
/**
* Adds a document to the XDS Submission set.
*
* @param desc the document descriptor (which kind of document
* do you want to transfer? e.g. PDF, CDA,...)
* @param inputStream The input stream to the document
* @param inputStream4Metadata the input stream that is only used to get the
* metadata from (it's texts will be ascii conform
* for registry purposes)
* @return the document metadata (which have to be completed)
*/
public DocumentMetadata addDocument(DocumentDescriptor desc, InputStream inputStream,
InputStream inputStream4Metadata) {
DocumentMetadata retVal = null;
var doc = new Document();
var doc4Metadata = new Document();
doc4Metadata.setDocumentEntry(new DocumentEntry());
InputStream unicodeStream = null;
try {
if (inputStream4Metadata != null) {
unicodeStream = convertNonAsciiText2Unicode(inputStream4Metadata);
var dataSource = new ByteArrayDataSource(unicodeStream, desc.getMimeType());
doc4Metadata.setDataHandler(new DataHandler(dataSource));
}
var dataSource = new ByteArrayDataSource(inputStream, desc.getMimeType());
doc.setDataHandler(new DataHandler(dataSource));
retVal = addXdsDocument(doc, desc, doc4Metadata);
} catch (final IOException e) {
log.error("Error adding document from inputstream.", e);
log.error(e.getMessage(), e);
} finally {
if (unicodeStream != null) {
try {
unicodeStream.close();
} catch (IOException e) {
log.error("Error adding document from inputstream.", e);
log.error(e.getMessage(), e);
}
}
}
if (retVal != null)
retVal.setDocumentDescriptor(desc);
return retVal;
}
/**
* Escapes all non java character in the inputsream that is expected as XML.
*
* @param inputStream the input stream to be escaped
* @return the input stream
*/
protected InputStream convertNonAsciiText2Unicode(InputStream inputStream) {
InputStream retVal = null;
DocumentBuilder docBuilder;
try (var outputStream = new ByteArrayOutputStream()) {
docBuilder = XmlFactories.newSafeDocumentBuilder();
var document = docBuilder.parse(inputStream);
convertNonAsciiText2Unicode(document.getDocumentElement());
Source xmlSource = new DOMSource(document);
Result outputTarget = new StreamResult(outputStream);
var transformer = XmlFactories.newTransformer();
transformer.transform(xmlSource, outputTarget);
retVal = new ByteArrayInputStream(outputStream.toByteArray());
} catch (ParserConfigurationException | SAXException | IOException | TransformerException
| TransformerFactoryConfigurationError e) {
// Do nothing
}
return retVal;
}
/**
* Escapes all non java character in the node text.
*
* @param node the node to be escaped
*/
protected void convertNonAsciiText2Unicode(Node node) {
if (node.getFirstChild() != null) {
String nodeValue = node.getFirstChild().getNodeValue();
if (nodeValue != null) {
nodeValue = nodeValue.replace("\n", "").replace("\t", "");
node.getFirstChild().setNodeValue(StringEscapeUtils.escapeJava(nodeValue));
}
}
var nodeList = node.getChildNodes();
for (var i = 0; i < nodeList.getLength(); i++) {
var currentNode = nodeList.item(i);
if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
// calls this method for all the children which is Element
convertNonAsciiText2Unicode(currentNode);
}
}
}
/**
* Adds a document to the XDS Submission set.
*
* @param desc the document descriptor (which kind of document do you want
* to transfer? e.g. PDF, CDA,...)
* @param filePath the file path
* @return the document metadata (which have to be completed)
* @throws IOException
*/
public DocumentMetadata addDocument(DocumentDescriptor desc, String filePath) throws IOException {
return addDocument(desc, filePath, null);
}
/**
* Adds a document to the XDS Submission set.
*
* @param desc the document descriptor (which kind of document do
* you want to transfer? e.g. PDF, CDA,...)
* @param filePath the file path
* @param filePathMetadata the file path metadata
* @return the document metadata (which have to be completed)
* @throws IOException
*/
public DocumentMetadata addDocument(DocumentDescriptor desc, String filePath, String filePathMetadata)
throws IOException {
try (InputStream is = new FileInputStream(new File(filePath))) {
return addDocument(desc, is);
}
}
/**
* Add a document to a folder by theirs ids
*
* @param documentEntryUUID the entry uuid of the document
* @param folderEntryUUID the entry uuid of the folder
*/
public void addDocumentToFolder(String documentEntryUUID, String folderEntryUUID) {
var association = new Association(AssociationType.HAS_MEMBER, UUID.randomUUID().toString(), folderEntryUUID,
documentEntryUUID);
getTxnData().getAssociations().add(association);
}
/**
* Adds a xds folder.
*
* @param submissionSetContentType the content type code for submission set
* @return the metadata of the new folder
*/
public FolderMetadata addFolder(Code submissionSetContentType) {
if (getTxnData() == null) {
this.txnData.set(new ProvideAndRegisterDocumentSet());
}
ProvideAndRegisterDocumentSet txnData = getTxnData();
var folder = new Folder();
folder.assignEntryUuid();
if (folder.getUniqueId() == null) {
folder.assignUniqueId();
}
txnData.getFolders().add(folder);
if (txnData.getSubmissionSet() == null) {
txnData.setSubmissionSet(new SubmissionSet());
}
txnData.getSubmissionSet().setContentTypeCode(XdsMetadataUtil.convertEhcCodeToCode(submissionSetContentType));
return new FolderMetadata(folder);
}
/**
* Adds an XDSDocument to the Transaction data
*
* @param doc the document
* @param desc the Document descriptor
* @return the DocumentMetadata
*/
protected DocumentMetadata addXdsDocument(Document doc, DocumentDescriptor desc) {
return addXdsDocument(doc, desc, null);
}
/**
* Adds an XDSDocument to the Transaction data.
*
* @param doc the document
* @param desc the Document descriptor
* @param metadataDoc the metadata doc
* @return the doc to get the metadata from
*/
protected DocumentMetadata addXdsDocument(Document doc, DocumentDescriptor desc, Document metadataDoc) {
if (getTxnData() == null) {
this.txnData.set(new ProvideAndRegisterDocumentSet());
}
ProvideAndRegisterDocumentSet txnData = getTxnData();
DocumentMetadata docMetadata = null;
if (metadataDoc != null) {
docMetadata = new DocumentMetadata(metadataDoc.getDocumentEntry());
if (doc.getDocumentEntry() == null) {
doc.setDocumentEntry(metadataDoc.getDocumentEntry());
}
} else {
docMetadata = new DocumentMetadata(doc.getDocumentEntry());
}
txnData.getDocuments().add(doc);
if (documentMetadataExtractionMode == DocumentMetadataExtractionMode.DEFAULT_EXTRACTION) {
if (DocumentDescriptor.CDA_R2.equals(desc)) {
cdaExtractionFixes(docMetadata);
}
generateDefaultDocEntryAttributes(docMetadata, doc, desc);
} else {
docMetadata.clear();
}
return docMetadata;
}
/**
* Cda fixes of CDAExtraction bugs and extraction methods, which are unsafe,
* because an XDS registry might use another value set.
*
* @param docMetadata the doc metadata
*/
private void cdaExtractionFixes(DocumentMetadata docMetadata) {
// Fix the OHT CDAExtraction behavior, that uses the confidentiality
// code from the cda for the XDS metadata. This leads to an error in the
// swiss repository, where the value set is different. As precaution we
// clean the list.
docMetadata.clearExtracted();
// Checks if the unique ID is longer than 64 or if no unique ID is set a new
// unique ID should be generated
if (docMetadata.getUniqueId() == null
|| (docMetadata.getUniqueId() != null && docMetadata.getUniqueId().length() > 64)) {
docMetadata.setUniqueId(OidGenerator.uniqueOid().toString());
}
}
/**
* Resets the transaction data (SubmissionSet and DocumentMetadata)
*/
public void clearDocuments() {
txnData.set(new ProvideAndRegisterDocumentSet());
}
/**
* creates an XDM volume with default values. You have to add a document to this
* class first.
*
* @param outputStream The outputStream object where the contents will be
* written to.
* @return the XdmContents object
*/
public XdmContents createXdmContents(OutputStream outputStream) {
if (submissionSetMetadataExtractionMode == SubmissionSetMetadataExtractionMode.DEFAULT_EXTRACTION) {
getTxnData().setSubmissionSet(generateDefaultSubmissionSetAttributes());
linkDocumentEntryWithSubmissionSet();
}
ProvideAndRegisterDocumentSet txnData = getTxnData();
final var xdmContents = new XdmContents(new IndexHtm(txnData), new ReadmeTxt(txnData));
xdmContents.createZip(outputStream, txnData);
return xdmContents;
}
/**
* creates an XDM volume with a given XdmContents object. This method will be
* used, if you want to create your own INDEX.HTM and README.TXT for your XDM
* volume. You have to add a document to this class first.
*
* @param outputStream The outputStream object where the contents will be
* written to.
* @param xdmContents The xdmContents object containing your own INDEX.HTM and
* README.TXT
* @return the XdmContents object
*/
public XdmContents createXdmContents(OutputStream outputStream, XdmContents xdmContents) {
ProvideAndRegisterDocumentSet txnData = getTxnData();
if (submissionSetMetadataExtractionMode == SubmissionSetMetadataExtractionMode.DEFAULT_EXTRACTION) {
txnData.setSubmissionSet(generateDefaultSubmissionSetAttributes());
linkDocumentEntryWithSubmissionSet();
}
xdmContents.createZip(outputStream, txnData);
return xdmContents;
}
/**
* creates an XDM volume with default values. You have to add a document to this
* class first.
*
* @param filePath The filePath where the contents will be written to.
* @return the XdmContents object
*/
public XdmContents createXdmContents(String filePath) {
ProvideAndRegisterDocumentSet txnData = getTxnData();
if (submissionSetMetadataExtractionMode == SubmissionSetMetadataExtractionMode.DEFAULT_EXTRACTION) {
txnData.setSubmissionSet(generateDefaultSubmissionSetAttributes());
linkDocumentEntryWithSubmissionSet();
}
final var xdmContents = new XdmContents(new IndexHtm(txnData), new ReadmeTxt(txnData));
xdmContents.createZip(filePath, txnData);
return xdmContents;
}
/**
* creates an XDM volume with default values. You have to add a document to this
* class first.
*
* @param filePath The filePath where the contents will be written to.
* @param xdmContents The xdmContents object containing your own INDEX.HTM and
* README.TXT
* @return the XdmContents object
*/
public XdmContents createXdmContents(String filePath, XdmContents xdmContents) {
ProvideAndRegisterDocumentSet txnData = getTxnData();
if (submissionSetMetadataExtractionMode == SubmissionSetMetadataExtractionMode.DEFAULT_EXTRACTION) {
txnData.setSubmissionSet(generateDefaultSubmissionSetAttributes());
linkDocumentEntryWithSubmissionSet();
}
xdmContents.createZip(filePath, txnData);
return xdmContents;
}
/**
* creates an XDM volume with the given submission set metadata. You have to add
* a document to this class first.
*
* @param submissionSetMetadata The metadata of the submission set
* @param outputStream The outputStream object where the contents will
* be written to.
* @return the XdmContents object
*/
public XdmContents createXdmContents(SubmissionSetMetadata submissionSetMetadata, OutputStream outputStream) {
ProvideAndRegisterDocumentSet txnData = getTxnData();
if (txnData.getSubmissionSet() == null) {
txnData.setSubmissionSet(new SubmissionSet());
}
submissionSetMetadata.toOhtSubmissionSetType(txnData.getSubmissionSet());
final var xdmContents = new XdmContents(new IndexHtm(txnData), new ReadmeTxt(txnData));
xdmContents.createZip(outputStream, txnData);
return xdmContents;
}
/**
* Generate missing doc entry attributes.
*
* @param docMetadata
* @param document
* @param documentDescriptor
*/
private void generateDefaultDocEntryAttributes(DocumentMetadata docMetadata, Document document,
DocumentDescriptor documentDescriptor) {
// Derive MimeType from DocumentDescriptor
if (docMetadata.getDocumentEntry().getMimeType() == null) {
docMetadata.setMimeType(documentDescriptor.getMimeType());
document.getDocumentEntry().setMimeType(documentDescriptor.getMimeType());
}
// Generate the unique ID
if (docMetadata.getDocumentEntry().getUniqueId() == null) {
document.getDocumentEntry().assignUniqueId();
docMetadata.setUniqueId(document.getDocumentEntry().getUniqueId());
}
// Generate the UUID
if (docMetadata.getDocumentEntry().getEntryUuid() == null) {
document.getDocumentEntry().assignEntryUuid();
docMetadata.setEntryUUID(document.getDocumentEntry().getEntryUuid());
}
// Generate Creation Time with the current time
if (docMetadata.getDocumentEntry().getCreationTime() == null) {
docMetadata.setCreationTime(ZonedDateTime.now());
var timestamp = new org.openehealth.ipf.commons.ihe.xds.core.metadata.Timestamp(
docMetadata.getCreationTime(), Precision.SECOND);
document.getDocumentEntry().setCreationTime(timestamp);
}
}
private void linkDocumentEntryWithSubmissionSet() {
ProvideAndRegisterDocumentSet txnData = getTxnData();
for (Document document : txnData.getDocuments()) {
// link document entry to submission set
var association = new Association();
association.setAssociationType(AssociationType.HAS_MEMBER);
association.setSourceUuid(txnData.getSubmissionSet().getEntryUuid());
association.setTargetUuid(document.getDocumentEntry().getEntryUuid());
association.setLabel(AssociationLabel.ORIGINAL);
association.assignEntryUuid();
txnData.getAssociations().add(association);
}
for (Folder folder : txnData.getFolders()) {
// link folder to submission set
var association = new Association(AssociationType.HAS_MEMBER, txnData.getSubmissionSet().getEntryUuid(),
txnData.getSubmissionSet().getEntryUuid(), folder.getEntryUuid());
association.assignEntryUuid();
txnData.getAssociations().add(association);
}
}
/**
* Generate missing Submission Set attributes.
*
* @return the submission set
*/
public SubmissionSet generateDefaultSubmissionSetAttributes() {
ProvideAndRegisterDocumentSet txnData = getTxnData();
if (txnData.getSubmissionSet() == null) {
txnData.setSubmissionSet(new SubmissionSet());
}
// Create SubmissionSet
final var subSet = txnData.getSubmissionSet();
if (txnData.getDocuments() != null && !txnData.getDocuments().isEmpty()) {
setSubSetDetailsFromDocument(subSet);
} else if (txnData.getFolders() != null && !txnData.getFolders().isEmpty()) {
setSubSetDetailsFromFolder(subSet);
}
return subSet;
}
private void setSubSetDetailsFromDocument(SubmissionSet subSet) {
ProvideAndRegisterDocumentSet txnData = getTxnData();
log.info("count of documents {}", txnData.getDocuments().size());
for (Document document : txnData.getDocuments()) {
final var docEntry = document.getDocumentEntry();
if (docEntry.getPatientId() == null) {
log.warn("Missing destination patient ID in DocumentMetadata of document.");
}
// set ContentTypeCode
if (subSet.getContentTypeCode() == null && docEntry.getTypeCode() != null) {
subSet.setContentTypeCode(docEntry.getTypeCode());
}
setGeneralSubSetDetails(subSet, docEntry.getPatientId());
}
}
private void setSubSetDetailsFromFolder(SubmissionSet subSet) {
ProvideAndRegisterDocumentSet txnData = getTxnData();
for (Folder folder : txnData.getFolders()) {
if (folder.getPatientId() == null) {
throw new IllegalStateException(
"Missing destination patient ID in DocumentMetadata of first document.");
}
if (subSet.getContentTypeCode() == null && folder.getCodeList() != null
&& folder.getCodeList().get(0) != null) {
subSet.setContentTypeCode(folder.getCodeList().get(0));
}
setGeneralSubSetDetails(subSet, folder.getPatientId());
}
}
protected void setGeneralSubSetDetails(SubmissionSet subSet, Identifiable patientId) {
// set submission time
if (subSet.getSubmissionTime() == null) {
subSet.setSubmissionTime(new Timestamp(ZonedDateTime.now(), Precision.SECOND));
}
if (subSet.getEntryUuid() == null) {
subSet.setEntryUuid(UUID.randomUUID().toString());
}
if ((subSet.getUniqueId() == null) || (subSet.getSourceId() == null)) {
if (subSet.getUniqueId() == null) {
subSet.assignUniqueId();
}
// set submission set source id
if (subSet.getSourceId() == null) {
subSet.setSourceId(getSourceId(patientId));
}
}
// Use the PatientId of the first Document for the Submission set ID
if (subSet.getPatientId() == null) {
subSet.setPatientId(patientId);
}
}
protected String getSourceId(Identifiable patientId) {
if (patientId != null) {
return patientId.getAssigningAuthority().getUniversalId();
} else {
return EhcVersions.getCurrentVersion().getOid();
}
}
/**
* Returns the current affinity domain
*
* @return the affinity domain
*/
public AffinityDomain getAffinityDomain() {
if (affinityDomain.get() == null) {
affinityDomain.set(new AffinityDomain(null, null, new ArrayList<>()));
}
return affinityDomain.get();
}
/**
* Sets the affinity domain set-up
*
* @param affinityDomain the affinity domain set-up
*/
public void setAffinityDomain(AffinityDomain affinityDomain) {
this.affinityDomain.set(affinityDomain);
}
/**
* Gets the status of the automatic metadata extraction
*
* @return true, if metadata will be extracted as far as possible)
* automatically, false otherwise
*/
public DocumentMetadataExtractionMode getAutomaticExtractionEnabled() {
return documentMetadataExtractionMode;
}
/**
* Sets the status of the automatic metadata extraction
*
* @param automaticExtractionEnabled true, if metadata will be extracted as far
* as possible) automatically, false otherwise
*/
public void setAutomaticExtractionEnabled(DocumentMetadataExtractionMode automaticExtractionEnabled) {
this.documentMetadataExtractionMode = automaticExtractionEnabled;
}
/**
* Sets the status of the automatic metadata extraction
*
* @param automaticExtractionEnabled true, if metadata will be extracted as far
* as possible) automatically, false otherwise
*/
public void setAutomaticExtractionEnabled(SubmissionSetMetadataExtractionMode automaticExtractionEnabled) {
this.submissionSetMetadataExtractionMode = automaticExtractionEnabled;
}
/**
* Gets the IPF transaction data (SubmissionSet and DocumentMetadata)
*
* @return the transaction data object
*/
public ProvideAndRegisterDocumentSet getTxnData() {
return this.txnData.get();
}
/**
* Returns the contents of an existing XDM volume.
*
* @param filePath the XDM volume as ZipFile
* @return the XDMContents
*/
public XdmContents getXdmContents(String filePath) {
return new XdmContents(filePath);
}
/**
* Returns the contents of an existing XDM volume.
*
* @param zipFile the XDM volume as ZipFile
* @return the XDMContents
*/
public XdmContents getXdmContents(ZipFile zipFile) {
return new XdmContents(zipFile);
}
/**
* Queries the document registry of the affinity domain for documents, using a
* find documents query. This is useful if the number of results is limited in
* the registry and your query would exceed this limit. In this case, precise
* your query or do a query for references first, choose the possible matches
* (e.g. the last 10 results) and then query for metadata.
*
* @param queryParameter a findDocumentsQuery object filled with your query
* parameters
* @param securityHeader a security header element for example an assertion
* @return the IPF QueryResponse containing references instead of the complete
* document metadata
* @throws Exception
*/
public QueryResponse queryDocumentReferencesOnly(AbstractStoredQuery queryParameter,
SecurityHeaderElement securityHeader, String messageId) throws Exception {
return queryDocumentQuery(queryParameter, securityHeader, QueryReturnType.OBJECT_REF, messageId);
}
/**
* Queries the document registry of the affinity domain for documents, using a
* find documents query.
*
* @param queryParameter a findDocumentsQuery object filled with your query
* parameters
* @param securityHeader a security header element for example an assertion
* @return the IPF QueryResponse containing full document metadata
* @throws Exception
*/
public QueryResponse queryDocuments(AbstractStoredQuery queryParameter, SecurityHeaderElement securityHeader,
String messageId) throws Exception {
return queryDocumentQuery(queryParameter, securityHeader, QueryReturnType.LEAF_CLASS, messageId);
}
/**
* Queries the registry of the affinity domain for all documents satisfying the
* given query parameters.
*
* @param query one of the given queries (@see
* org.projecthusky.communication.storedquery and
* org.projecthusky.communication.storedquery.ch)
* @param securityHeader a security header element for example an assertion
* @param returnType return type for XDS query
* @return the IPF QueryResponse containing full document metadata
* @throws Exception
*/
protected QueryResponse queryDocumentQuery(AbstractStoredQuery query, SecurityHeaderElement securityHeader,
QueryReturnType returnType, String messageId) throws Exception {
AffinityDomain affinityDomain = getAffinityDomain();
final var queryRegistry = new QueryRegistry(query.getIpfQuery());
queryRegistry.setReturnType(returnType);
final String endpoint = HuskyUtils.createEndpoint(XDS.Interactions.ITI_18.getWsTransactionConfiguration().getName(),
affinityDomain.getRepositoryDestination().getUri(), //
atnaConfigMode.equals(AtnaConfigMode.SECURE));
log.info(LOG_SEND_REQUEST, endpoint);
final var exchange = send(endpoint, queryRegistry, securityHeader, messageId, null);
return exchange.getMessage().getBody(QueryResponse.class);
}
/**
* Queries the document registry of the affinity domain for documents, using a
* find documents query.
*
* @param queryParameter a findFoldersQuery object filled with your query
* parameters
* @param security a security header element for example an assertion
* @return the IPF QueryResponse containing full folder metadata
* @throws Exception
*/
public QueryResponse queryFolders(FindFoldersStoredQuery queryParameter, SecurityHeaderElement security,
String messageId) throws Exception {
return queryDocumentQuery(queryParameter, security, QueryReturnType.LEAF_CLASS, messageId);
}
/**
* Retrieves a document from a Repository
*
* @param docReq the document request
* @param security a security header element for example an assertion
* @return the IPF RetrievedDocumentSet
* @throws Exception
*/
public RetrievedDocumentSet retrieveDocument(DocumentRequest docReq, SecurityHeaderElement security,
String messageId) throws Exception {
return retrieveDocuments(new DocumentRequest[] { docReq }, security, messageId);
}
/**
* Retrieves multiple documents from one or more Repositories
*
* @param docReq an array of document requests
* @param security a security header element for example an assertion
* @return the IPF RetrievedDocumentSet
* @throws Exception
*/
public RetrievedDocumentSet retrieveDocuments(DocumentRequest[] docReq, SecurityHeaderElement security,
String messageId) throws Exception {
AffinityDomain affinityDomain = getAffinityDomain();
final var retrieveDocumentSet = new RetrieveDocumentSet();
for (final DocumentRequest element : docReq) {
if (element != null) {
retrieveDocumentSet.addReferenceTo(element.getIpfDocumentEntry());
}
}
final String endpoint = HuskyUtils.createEndpoint(XDS.Interactions.ITI_43.getWsTransactionConfiguration().getName(),
affinityDomain.getRepositoryDestination().getUri(), //
atnaConfigMode.equals(AtnaConfigMode.SECURE));
log.info(LOG_SEND_REQUEST, endpoint);
final var exchange = send(endpoint, retrieveDocumentSet, security, messageId, null);
return exchange.getMessage().getBody(RetrievedDocumentSet.class);
}
/**
* Sets the atna config
*
* @param atnaConfigMode secure or unsecure config
*/
public void setAtnaConfig(AtnaConfigMode atnaConfigMode) {
this.atnaConfigMode = atnaConfigMode;
}
/**
* Sets the key- and truststore for the default security domain
*
* @param dest the Destination Object
*/
private void setDefaultKeystoreTruststore(Destination dest) {
if (dest.getKeyStore() == null) {
System.clearProperty("javax.net.ssl.keyStore");
System.clearProperty("javax.net.ssl.keyStorePassword");
System.clearProperty("javax.net.ssl.keyStoreType");
System.clearProperty("javax.net.ssl.trustStore");
System.clearProperty("javax.net.ssl.trustStorePassword");
System.clearProperty("javax.net.ssl.trustStoreType");
} else {
System.setProperty("javax.net.ssl.keyStore", dest.getKeyStore());
System.setProperty("javax.net.ssl.keyStorePassword", dest.getKeyStorePassword());
System.setProperty("javax.net.ssl.keyStoreType", dest.getKeyStoreType());
System.setProperty("javax.net.ssl.trustStore", dest.getTrustStore());
System.setProperty("javax.net.ssl.trustStorePassword", dest.getTrustStorePassword());
System.setProperty("javax.net.ssl.trustStoreType", dest.getTrustStoreType());
}
}
/**
* Submission of the previously prepared document(s) to the repository
* IHE [ITI-41] Provide and Register Document Set – b in the role of the IHE ITI
* Document Source actor
*
* @param security a security header element for example an assertion
* @return the IPF Response
* @throws Exception if the transfer is not successful
*/
public Response submit(SecurityHeaderElement security, String messageId) throws Exception {
return submit(security, null, messageId);
}
/**
* Submission of the previously prepared document(s) to the repository
* IHE [ITI-41] Provide and Register Document Set – b in the role of the IHE ITI
* Document Source actor
*
* @param submissionSetMetadata The information in this object will be used to
* create comprehensive meta data about this
* submission (e.g. with AuthorRole,
* AuthorInstitution, ContentType and Title).
* Although, some of this information can be
* derived automatically, some may be required in
* your country (e.g. AuthorRole in Switzerland)
* @param security a security header element for example an
* assertion
* @return the IPF Response
* @throws Exception if the transfer is not successful
*/
public Response submit(SubmissionSetMetadata submissionSetMetadata, SecurityHeaderElement security,
String messageId) throws Exception {
ProvideAndRegisterDocumentSet txnData = getTxnData();
if (txnData.getSubmissionSet() == null) {
txnData.setSubmissionSet(new SubmissionSet());
}
submissionSetMetadata.toOhtSubmissionSetType(txnData.getSubmissionSet());
return submit(security, messageId);
}
/**
* Submission of the previously prepared document(s) to the repository to
* replace another document. The restriction of this method is that only one
* document could be replaced
* IHE [ITI-41] Provide and Register Document Set – b in the role of the IHE ITI
* Document Source actor
*
* @param submissionSetMetadata The information in this object will be used to
* create comprehensive meta data about this
* submission (e.g. with AuthorRole,
* AuthorInstitution, ContentType and Title).
* Although, some of this information can be
* derived automatically, some may be required in
* your country (e.g. AuthorRole in Switzerland)
* @param idOfOriginDocument ID of the document, which should be replaced
* @param security a security header element for example an
* assertion
* @return the IPF Response
* @throws Exception if the transfer is not successful
*/
public Response submitReplacement(SubmissionSetMetadata submissionSetMetadata, String idOfOriginDocument,
SecurityHeaderElement security, String messageId) throws Exception {
ProvideAndRegisterDocumentSet txnData = getTxnData();
if (txnData.getSubmissionSet() == null) {
txnData.setSubmissionSet(new SubmissionSet());
}
var association = new Association();
association.setAssociationType(AssociationType.REPLACE);
association.setAvailabilityStatus(AvailabilityStatus.APPROVED);
association.setTargetUuid(idOfOriginDocument);
association.assignEntryUuid();
txnData.getAssociations().add(association);
submissionSetMetadata.toOhtSubmissionSetType(txnData.getSubmissionSet());
return submit(security, association, messageId);
}
/**
* Submission of the previously prepared document(s) to the repository
* IHE [ITI-41] Provide and Register Document Set – b in the role of the IHE ITI
* Document Source actor
*
* @param security a security header element for example an assertion
* @return the IPF Response
* @throws Exception if the transfer is not successful
*/
private Response submit(SecurityHeaderElement security, Association association, String messageId)
throws Exception {
log.debug("submit document");
AffinityDomain affinityDomain = getAffinityDomain();
ProvideAndRegisterDocumentSet txnData = getTxnData();
setDefaultKeystoreTruststore(affinityDomain.getRepositoryDestination());
if (submissionSetMetadataExtractionMode == SubmissionSetMetadataExtractionMode.DEFAULT_EXTRACTION) {
log.debug("extract submission set metadata");
txnData.setSubmissionSet(generateDefaultSubmissionSetAttributes());
linkDocumentEntryWithSubmissionSet();
}
if (association != null) {
log.debug("set association data");
if (txnData.getDocuments() != null && !txnData.getDocuments().isEmpty()
&& txnData.getDocuments().get(0) != null) {
association.setSourceUuid(txnData.getDocuments().get(0).getDocumentEntry().getEntryUuid());
} else if (txnData.getFolders() != null && !txnData.getFolders().isEmpty()
&& txnData.getFolders().get(0) != null) {
association.setSourceUuid(txnData.getFolders().get(0).getEntryUuid());
}
}
log.debug("prepare submit of document");
final String endpoint = HuskyUtils.createEndpoint(XDS.Interactions.ITI_41.getWsTransactionConfiguration().getName(),
affinityDomain.getRepositoryDestination().getUri(), atnaConfigMode.equals(AtnaConfigMode.SECURE));
log.debug(LOG_SEND_REQUEST, endpoint);
final var exchange = send(endpoint, txnData, security, messageId, null);
return exchange.getMessage().getBody(Response.class);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy