package gov.nist.registry.ws; import gov.nist.registry.common2.MetadataTypes; import gov.nist.registry.common2.exception.MetadataException; import gov.nist.registry.common2.exception.MetadataValidationException; import gov.nist.registry.common2.exception.SchemaValidationException; import gov.nist.registry.common2.exception.XDSMissingDocumentException; import gov.nist.registry.common2.exception.XDSMissingDocumentMetadataException; import gov.nist.registry.common2.exception.XDSRepositoryMetadataException; import gov.nist.registry.common2.exception.XdsConfigurationException; import gov.nist.registry.common2.exception.XdsException; import gov.nist.registry.common2.exception.XdsFormatException; import gov.nist.registry.common2.exception.XdsIOException; import gov.nist.registry.common2.exception.XdsInternalException; import gov.nist.registry.common2.io.Sha1Bean; import gov.nist.registry.common2.registry.Metadata; import gov.nist.registry.common2.registry.MetadataSupport; import gov.nist.registry.common2.registry.RegistryResponse; import gov.nist.registry.common2.registry.RegistryUtility; import gov.nist.registry.common2.registry.Response; import gov.nist.registry.common2.registry.XdsCommon; import gov.nist.registry.common2.soap.Soap; import gov.nist.registry.ws.config.Repository; import java.io.IOException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import javax.activation.DataHandler; import javax.xml.namespace.QName; import org.apache.axiom.om.OMAttribute; import org.apache.axiom.om.OMElement; import org.apache.axiom.om.OMNode; import org.apache.axiom.om.OMText; import org.apache.axis2.context.MessageContext; import org.apache.commons.codec.binary.Base64; import org.apache.commons.httpclient.protocol.Protocol; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.openhealthtools.common.ihe.IheActor; import org.openhealthtools.common.utils.ConnectionUtil; import org.openhealthtools.common.ws.server.IheHTTPServer; import org.openhealthtools.openexchange.actorconfig.net.IConnectionDescription; import org.openhealthtools.openexchange.audit.ActiveParticipant; import org.openhealthtools.openexchange.audit.AuditCodeMappings; import org.openhealthtools.openexchange.audit.IheAuditTrail; import org.openhealthtools.openexchange.audit.ParticipantObject; import org.openhealthtools.openexchange.audit.AuditCodeMappings.AuditTypeCodes; import org.openhealthtools.openexchange.config.PropertyFacade; import org.openhealthtools.openxds.XdsFactory; import org.openhealthtools.openxds.log.LogMessage; import org.openhealthtools.openxds.log.LoggerException; import org.openhealthtools.openxds.repository.api.RepositoryException; import org.openhealthtools.openxds.repository.api.RepositoryRequestContext; import org.openhealthtools.openxds.repository.api.XdsRepository; import org.openhealthtools.openxds.repository.api.XdsRepositoryItem; import org.openhealthtools.openxds.repository.api.XdsRepositoryService; import org.openhealthtools.openxua.api.XuaException; public class ProvideAndRegisterDocumentSet extends XdsCommon { ContentValidationService validater; String registry_endpoint = null; MessageContext messageContext; boolean accept_xop = true; IConnectionDescription connection = null; IConnectionDescription registryClientConnection = null; /* The IHE Audit Trail for this actor. */ private IheAuditTrail auditLog = null; private final static Log logger = LogFactory.getLog(ProvideAndRegisterDocumentSet.class); // static { // BasicConfigurator.configure(); // } public ProvideAndRegisterDocumentSet(LogMessage log_message, short xds_version, MessageContext messageContext) { this.log_message = log_message; this.messageContext = messageContext; this.xds_version = xds_version; transaction_type = PR_transaction; try { if (messageContext == null) { throw new XdsInternalException("Cannot find MessageContext"); } IheHTTPServer httpServer = (IheHTTPServer)messageContext.getTransportIn().getReceiver(); IheActor actor = httpServer.getIheActor(); if (actor == null) { throw new XdsInternalException("Cannot find XdsRepository actor configuration."); } connection = actor.getConnection(); if (connection == null) { throw new XdsInternalException("Cannot find Server connection configuration."); } registryClientConnection = ((XdsRepository)actor).getRegistryClientConnection(); if (registryClientConnection == null) { throw new XdsInternalException("Cannot find XdsRepository Registry connection configuration."); } auditLog = actor.getAuditTrail(); init(new RegistryResponse( (xds_version == xds_a) ? Response.version_2 : Response.version_3), xds_version, messageContext); } catch (XdsInternalException e) { logger.fatal("Internal Error creating RegistryResponse: " + e.getMessage()); } } public OMElement provideAndRegisterDocumentSet(OMElement pnr, ContentValidationService validater) { this.validater = validater; // Call X-Service Provider Actor to validate X-User Assertion with X-Assertion Provider try { boolean validateUserAssertion = PropertyFacade.getBoolean("validate.userassertion"); if(validateUserAssertion){ SoapHeader header = new SoapHeader(messageContext); boolean status = validateAssertion(header); if (!status) throw new XdsException("Invalid Identity Assertion"); } pnr.build(); provide_and_register(pnr); } catch (XDSRepositoryMetadataException e) { response.add_error("XDSRepositoryMetadataError", e.getMessage(), RegistryUtility.exception_trace(e), log_message); } catch (XdsFormatException e) { response.add_error("XDSRepositoryError", "SOAP Format Error: " + e.getMessage(), RegistryUtility.exception_trace(e), log_message); } catch (XDSMissingDocumentException e) { response.add_error("XDSMissingDocument", e.getMessage(), RegistryUtility.exception_details(e), log_message); logger.warn(logger_exception_details(e)); } catch (XDSMissingDocumentMetadataException e) { response.add_error("XDSMissingDocumentMetadata", e.getMessage(), RegistryUtility.exception_details(e), log_message); logger.warn(logger_exception_details(e)); } catch (XdsInternalException e) { response.add_error("XDSRepositoryError", "XDS Internal Error:\n " + e.getMessage(), RegistryUtility.exception_details(e), log_message); logger.fatal(logger_exception_details(e)); } catch (XdsIOException e) { response.add_error("XDSRepositoryError", "XDS IO Error:\n " + e.getMessage(), RegistryUtility.exception_details(e), log_message); logger.fatal(logger_exception_details(e)); } catch (XdsConfigurationException e) { response.add_error("XDSRepositoryError", "XDS Configuration Error:\n " + e.getMessage(), RegistryUtility.exception_details(e), log_message); logger.fatal(logger_exception_details(e)); } catch (MetadataValidationException e) { response.add_error(MetadataSupport.XDSRepositoryMetadataError, "Test input incorrect:\n " + e.getMessage(), RegistryUtility.exception_details(e), log_message); } catch (MetadataException e) { response.add_error("XDSRepositoryError", "Metadata Validation Errors:\n " + e.getMessage(), RegistryUtility.exception_details(e), log_message); } catch (LoggerException e) { response.add_error("XDSRegistryError", "Internal Logging error: LoggerException: " + e.getMessage(), RegistryUtility.exception_details(e), log_message); logger.fatal(logger_exception_details(e)); } catch (SchemaValidationException e) { response.add_error("XDSRepositoryError", "Schema Validation Errors:\n" + e.getMessage(), RegistryUtility.exception_details(e), log_message); } catch (XdsException e) { response.add_error("XDSRepositoryError", "XDS Internal Error:\n " + e.getMessage(), RegistryUtility.exception_details(e), log_message); logger.warn(logger_exception_details(e)); } catch (XuaException e) { response.add_error("XDSRepositoryError", "XUA Error" + e.getMessage(), RegistryUtility.exception_details(e), log_message); } catch (Exception e) { response.add_error("XDSRepositoryError", "Input Error - no SOAP Body:\n " + e.getMessage(), RegistryUtility.exception_details(e), log_message); logger.fatal(logger_exception_details(e)); } this.log_response(); OMElement res = null; try { res = response.getResponse(); if (logger.isDebugEnabled()) { logger.debug("Response from the Repository: "); logger.debug(res.toString()); } } catch (XdsInternalException e) { logger.error("Error generating response"); try { log_message.addErrorParam("Internal Error", "Error generating response from PnR"); } catch (LoggerException e1) { } } return res; } void validate_docs_and_metadata_b(OMElement pnr, Metadata m) throws XDSMissingDocumentException, XDSMissingDocumentMetadataException { List<OMElement> docs = MetadataSupport.childrenWithLocalName(pnr, "Document"); List<String> doc_ids = new ArrayList<String>(); for (OMElement doc : docs) { String id = doc.getAttributeValue(MetadataSupport.id_qname); // if id == null or id =="" doc_ids.add(id); } List<String> eo_ids = m.getExtrinsicObjectIds(); for (String id : eo_ids) { if ( ! doc_ids.contains(id)) throw new XDSMissingDocumentException("Document with id " + id + " is missing"); } for (String id : doc_ids) { if ( ! eo_ids.contains(id)) throw new XDSMissingDocumentMetadataException("XDSDocumentEntry with id " + id + " is missing"); } } void provide_and_register(OMElement pnr) throws MetadataValidationException, SchemaValidationException, XdsInternalException, MetadataException, XdsConfigurationException, XdsIOException, LoggerException, XdsException, IOException, XDSRepositoryMetadataException { RegistryUtility.schema_validate_local( pnr, (xds_version == xds_a) ? MetadataTypes.METADATA_TYPE_R : MetadataTypes.METADATA_TYPE_RET); OMElement sor = find_sor(pnr); if (logger.isDebugEnabled()) { logger.debug("Request from the Source:"); logger.debug(sor.toString()); } Metadata m = new Metadata(sor); generateAuditLog(m); log_message.addOtherParam("SSuid", m.getSubmissionSetUniqueId()); log_message.addOtherParam("Structure", m.structure()); if (xds_version == xds_b) this.validate_docs_and_metadata_b(pnr, m); // Validator val = new Validator(m, response.registryErrorList, true, xds_version == xds_b, log_message, true, connection); // val.run(); if (this.validater != null && !this.validater.runContentValidationService(m, response)) return; int eo_count = m.getExtrinsicObjectIds().size(); int doc_count = 0; if (this.xds_version == xds_b) { for (OMElement document : MetadataSupport.childrenWithLocalName(pnr, "Document")) { doc_count++; String id = document.getAttributeValue(MetadataSupport.id_qname); OMText binaryNode = getBinaryNode(document); if (logger.isDebugEnabled()) { logger.debug("isOptimized: " + binaryNode.isOptimized()); } if ( ! accept_xop ) { if (binaryNode.isOptimized() == true) { throw new XdsIOException("Submission uses XOP for optimized encoding - not acceptable on this endpoint"); } } boolean optimized = false; javax.activation.DataHandler datahandler = null; try { datahandler = (javax.activation.DataHandler) binaryNode.getDataHandler(); optimized = true; } catch (Exception e) { // good - message not optimized } if ( ! accept_xop ) { if (optimized) throw new XdsIOException("Submission uses XOP for optimized encoding - this is not acceptable on this endpoint"); } if (optimized) { store_document_swa_xop(m, id, datahandler, datahandler.getContentType(), false /* validate_mime_type */); } else { String base64 = binaryNode.getText(); byte[] ba = Base64.decodeBase64(base64.getBytes()); store_document_mtom(m, id, ba); } } } else { // xds.a // need message context to implement if (messageContext == null) throw new XdsInternalException("XDS.a: PnR: no MessageContext"); doc_count = messageContext.getAttachmentMap().getContentIDSet().size() - 1; // metadata counts too if (doc_count == -1) doc_count = 0; for (OMElement eo : m.getExtrinsicObjects()) { String id = eo.getAttributeValue(MetadataSupport.id_qname); DataHandler dh = messageContext.getAttachment(id); if (dh == null) throw new XDSMissingDocumentException("Cannot find attachment for id " + id); store_document_swa_xop(m, id, dh, dh.getContentType(), true /* validate_mime_type */); } } if (eo_count != doc_count) throw new XDSMissingDocumentMetadataException("Submission contained " + doc_count + " documents but " + eo_count + " ExtrinsicObjects in metadata - they must match"); setRepositoryUniqueId(m); OMElement register_transaction = (this.xds_version == xds_a) ? m.getV2SubmitObjectsRequest() : m.getV3SubmitObjectsRequest(); String epr = registry_endpoint(); Protocol protocol = ConnectionUtil.getProtocol(registryClientConnection); log_message.addOtherParam("Register transaction endpoint", epr); log_message.addOtherParam("Register transaction", register_transaction.toString()); boolean success = false; Soap soap = new Soap(); try { OMElement result; try { if (this.xds_version == XdsCommon.xds_b) { soap.soapCall(register_transaction, protocol, epr, false, true, true, "urn:ihe:iti:2007:RegisterDocumentSet-b", null); } else { soap.soapCall(register_transaction, protocol, epr, false, true, false, "urn:anonOutInOp" ,null); } } catch (XdsException e) { response.add_error(MetadataSupport.XDSRepositoryError, e.getMessage(), RegistryUtility.exception_details(e), log_message); } result = soap.getResult(); if (result != null) { QName testlogid = new QName("testLogId"); String registryTestLogId = result.getAttributeValue(testlogid); if (registryTestLogId != null) { log_message.addOtherParam("Registry Test Log ID", registryTestLogId); // remove attribute - just private communitication OMAttribute tlidA = result.getAttribute(testlogid); if (tlidA != null) result.removeAttribute(tlidA); } } log_headers(soap); if (result == null) { response.add_error(MetadataSupport.XDSRepositoryError, "Null response message from Registry", "ProvideAndRegistryDocumentSet.java", log_message); log_message.addOtherParam("Register transaction response", "null"); } else { log_message.addOtherParam("Register transaction response", result.toString()); String status = result.getAttributeValue(MetadataSupport.status_qname); if (status == null) { response.add_error(MetadataSupport.XDSRepositoryError, "Null status from Registry", "ProvideAndRegistryDocumentSet.java", log_message); } else { status = m.stripNamespace(status); if ( !status.equals("Success")) { OMElement registry_error_list = MetadataSupport.firstChildWithLocalName(result, "RegistryErrorList"); if (registry_error_list != null) response.addRegistryErrorList(registry_error_list, log_message); else response.add_error(MetadataSupport.XDSRepositoryError, "Registry returned Failure but no error list", "ProvideAndRegistryDocumentSet.java", log_message); } else { success = true; } } } } catch (Exception e) { response.add_error(MetadataSupport.XDSRepositoryError, e.getMessage(), "ProvideAndRegistryDocumentSet.java", log_message); } if (success) { //ITI-42 Succeed, log a success message auditLog(m, AuditTypeCodes.RegisterDocumentSet_b, false); } else { List<String> rollbackDocs = new ArrayList<String>(); for (OMElement document : MetadataSupport.childrenWithLocalName(pnr, "Document")) { doc_count++; String id = document.getAttributeValue(MetadataSupport.id_qname); String uid = m.getExternalIdentifierValue(id, "urn:uuid:2e82c1f6-a085-4c72-9da3-8640a32e42ab"); // doc uniqueid if (uid != null) { rollbackDocs.add( uid ); } } rollbackDocument( rollbackDocs ); } } static OMText getBinaryNode(OMElement document) { Iterator<OMNode> childrenIterator = document.getChildren(); while (childrenIterator.hasNext()) { OMNode container = childrenIterator.next(); if (container instanceof OMText && StringUtils.isNotBlank(((OMText)container).getText())) { return (OMText)container; } } return null; } private void rollbackDocument(List<String> docs) { try { XdsRepositoryService rs = XdsFactory.getXdsRepositoryService(); rs.delete(docs, new RepositoryRequestContext()); }catch(RepositoryException e) { logger.error("Error rolling back document from the repository - " + e.getMessage(), e); } } String registry_endpoint() { return (registry_endpoint == null) ? ConnectionUtil.getTransactionEndpoint(registryClientConnection) : registry_endpoint; } void log_headers(Soap soap) throws LoggerException, XdsInternalException { OMElement in_hdr = soap.getInHeader(); OMElement out_hdr = soap.getOutHeader(); log_message.addSoapParam("Header sent to Registry", (out_hdr == null) ? "Null" : out_hdr.toString()); log_message.addSoapParam("Header received from Registry", (in_hdr == null) ? "Null" : in_hdr.toString()); } private OMElement find_sor(OMElement pnr) throws MetadataValidationException { // OMElement first = pnr.getFirstElement(); OMElement sor; if (pnr.getLocalName().equals("SubmitObjectsRequest")) sor = pnr; // xds.a else { // xds.b sor = pnr.getFirstElement(); if (sor == null || !sor.getLocalName().equals("SubmitObjectsRequest")) throw new MetadataValidationException("Cannot find SubmitObjectsRequest element in submission - top level element is " + pnr.getLocalName()); } return sor; } public void setRegistryEndPoint(String endpoint) { this.registry_endpoint = endpoint; } private void store_document_swa_xop(Metadata m, String id, DataHandler dataHandler, String content_type, boolean validate_content_type) throws MetadataException, XdsIOException, XdsInternalException, XdsConfigurationException, XdsException, XDSRepositoryMetadataException { OMElement extrinsic_object = m.getObjectById(id); String actualDocSize = null; String actualDocHash = null; // content type is not guaranteed by the standard so this validation has been removed validate_content_type = false; if (extrinsic_object == null) throw new MetadataException("Document submitted with id of " + id + " but no ExtrinsicObject exists in metadata with same id"); String uid = m.getExternalIdentifierValue(id, "urn:uuid:2e82c1f6-a085-4c72-9da3-8640a32e42ab"); // doc uniqueid if (uid == null) throw new MetadataException("Document " + id + " does not have a Unique ID"); XdsRepositoryService rm = XdsFactory.getXdsRepositoryService(); XdsRepositoryItem item = XdsFactory.getXdsReposiotryItem(); item.setDocumentUniqueId(uid); item.setDataHandler(dataHandler); String mime_type = extrinsic_object.getAttributeValue(MetadataSupport.mime_type_qname); int isize = -1; try{ isize = item.getSize(); actualDocSize = Integer.toString(isize); }catch (Exception e) {throw new XdsInternalException("Error calculating size on repository file");} try{ actualDocHash = (new Sha1Bean()).getSha1(item.getDataHandler(), isize ); } catch (Exception e) { throw new XdsInternalException("Error calculating hash on repository file");} validate_size_and_hash(m, extrinsic_object, actualDocSize, actualDocHash); if (mime_type == null || mime_type.equals("")) throw new MetadataException("ExtrinsicObject " + id + " does not have a mimeType"); if ( validate_content_type && !mime_type.equals(content_type)) throw new MetadataException("ExtrinsicObject " + id + " metadata has mimeType is " + mime_type + " but document content type is " + content_type); item.setMimeType(mime_type); try { RepositoryRequestContext context = new RepositoryRequestContext(); context.setConnection(connection); rm.insert(item, context); }catch(RepositoryException e) { throw new XdsException("Error saving document to the repository - " + e.getMessage(), e); } auditLog(m, AuditTypeCodes.ProvideAndRegisterDocumentSet_b, true); // set size, hash, URI into metadata m.setSlot(extrinsic_object, "size", actualDocSize); m.setSlot(extrinsic_object, "hash", actualDocHash); //m.setURIAttribute(extrinsic_object, document_uri (uid, mime_type)); } private void validate_size_and_hash(Metadata m, OMElement extrinsic_object, String size_str, String hash_value) throws XDSRepositoryMetadataException { // if size already in metadata, must match data or error String pnr_size = m.getSlotValue(extrinsic_object, "size", 0); if (pnr_size != null) { if (!pnr_size.equals(size_str)) { throw new XDSRepositoryMetadataException("Size attribute in Provide and Register metadata does not match supplied document: Metadata has " + pnr_size +" and contents has " + size_str); } } // if hash already in metadata, must match data or error String pnr_hash = m.getSlotValue(extrinsic_object, "hash", 0); if (pnr_hash != null) { if (!pnr_hash.equalsIgnoreCase(hash_value)) { throw new XDSRepositoryMetadataException("Hash attribute in Provide and Register metadata does not match supplied document: Metadata has " + pnr_hash +" and contents has " + hash_value); } } } private void store_document_mtom(Metadata m, String id, byte[] bytes) throws MetadataException, XdsIOException, XdsInternalException, XdsConfigurationException, XdsException, XDSRepositoryMetadataException { OMElement extrinsic_object = m.getObjectById(id); if (extrinsic_object == null) throw new MetadataException("Document submitted with id of " + id + " but no ExtrinsicObject exists in metadata with same id"); String uid = m.getExternalIdentifierValue(id, "urn:uuid:2e82c1f6-a085-4c72-9da3-8640a32e42ab"); // doc uniqueid if (uid == null) throw new MetadataException("Document " + id + " does not have a Unique ID"); String mime_type = extrinsic_object.getAttributeValue(MetadataSupport.mime_type_qname); if (mime_type == null || mime_type.equals("")) throw new MetadataException("ExtrinsicObject " + id + " does not have a mimeType"); DataHandler dataHandler = new DataHandler(new String(bytes), mime_type); String size_str = Integer.toString(bytes.length); String hash_value = null; try { hash_value = (new Sha1Bean()).getSha1(dataHandler, bytes.length); } catch (Exception e) { throw new XdsInternalException("Error calculating hash on repository file"); } validate_size_and_hash(m, extrinsic_object, size_str, hash_value); XdsRepositoryService rm = XdsFactory.getXdsRepositoryService(); XdsRepositoryItem item = XdsFactory.getXdsReposiotryItem(); item.setDocumentUniqueId(uid); item.setDataHandler(dataHandler); item.setMimeType(mime_type); try { RepositoryRequestContext context = new RepositoryRequestContext(); context.setConnection(connection); rm.insert(item, context); }catch(RepositoryException e) { throw new XdsException("Error saving document to the repository - " + e.getMessage(), e); } auditLog(m, AuditTypeCodes.ProvideAndRegisterDocumentSet_b, true); m.setSlot(extrinsic_object, "size", size_str); m.setSlot(extrinsic_object, "hash", hash_value); //m.setURIAttribute(extrinsic_object, document_uri (uid, mime_type)); } void setRepositoryUniqueId(Metadata m) throws MetadataException { for (OMElement eo : m.getExtrinsicObjects()) { m.setSlot(eo, "repositoryUniqueId", Repository.getRepositoryUniqueId()); } } String document_uri(String uid, String mime_type)throws MetadataException, XdsConfigurationException, XdsException { return Repository.getBaseUri() + uid + "." + (new DocumentTypes(connection)).fileExtension(mime_type); } /** * Audit Logging of ProvodeAndRegisterDocumentSet message. * * @throws MetadataException */ private void auditLog(Metadata meatdata, AuditCodeMappings.AuditTypeCodes typeCode, boolean isITI41) throws MetadataException { if (auditLog == null) return; String replyto = getMessageContext().getReplyTo().getAddress(); String remoteIP = (String)getMessageContext().getProperty(MessageContext.REMOTE_ADDR); String localIP = (String)getMessageContext().getProperty(MessageContext.TRANSPORT_ADDR); ParticipantObject set = new ParticipantObject("SubmissionSet", meatdata.getSubmissionSetUniqueId()); ParticipantObject patientObj = new ParticipantObject("PatientIdentifier", meatdata.getSubmissionSetPatientId()); if(isITI41){ ActiveParticipant source = new ActiveParticipant(); source.setUserId(replyto); source.setAccessPointId(remoteIP); ActiveParticipant dest = new ActiveParticipant(); //TODO: Needs to be improved String userid = "http://"+connection.getHostname()+":"+connection.getPort()+"/axis2/services/xdsrepositoryb"; dest.setUserId(userid); dest.setAccessPointId(localIP); auditLog.logDocumentImport(source, dest, patientObj, set, typeCode); }else{ ActiveParticipant source = new ActiveParticipant(); source.setUserId(replyto); source.setAccessPointId(localIP); ActiveParticipant dest = new ActiveParticipant(); dest.setUserId(registry_endpoint()); dest.setAccessPointId(localIP); auditLog.logDocumentExport(source, dest, patientObj, set, typeCode); } } }