package gov.nist.registry.ws; import gov.nist.registry.common2.MetadataTypes; import gov.nist.registry.common2.datatypes.Hl7Date; 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.XMLParserException; import gov.nist.registry.common2.exception.XdsDeprecatedException; import gov.nist.registry.common2.exception.XdsException; import gov.nist.registry.common2.exception.XdsFormatException; import gov.nist.registry.common2.exception.XdsInternalException; import gov.nist.registry.common2.exception.XdsNonIdenticalHashException; import gov.nist.registry.common2.exception.XdsPatientIdDoesNotMatchException; import gov.nist.registry.common2.exception.XdsUnknownPatientIdException; import gov.nist.registry.common2.registry.BackendRegistry; import gov.nist.registry.common2.registry.IdParser; import gov.nist.registry.common2.registry.Metadata; import gov.nist.registry.common2.registry.MetadataParser; import gov.nist.registry.common2.registry.MetadataSupport; import gov.nist.registry.common2.registry.Properties; 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.registry.storedquery.StoredQuerySupport; import gov.nist.registry.common2.registry.validation.Structure; import gov.nist.registry.common2.registry.validation.Validator; import gov.nist.registry.common2.util.PidHelper; import gov.nist.registry.ws.config.Registry; import gov.nist.registry.ws.sq.RegistryObjectValidator; import gov.nist.registry.ws.sq.RegistryValidations; import gov.nist.registry.ws.sq.SQFactory; import java.sql.SQLException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.xml.stream.XMLStreamException; import javax.xml.transform.TransformerConfigurationException; import org.apache.axiom.om.OMElement; import org.apache.axis2.context.MessageContext; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.openhealthexchange.openpixpdq.data.PatientIdentifier; import org.openhealthtools.common.ihe.IheActor; import org.openhealthtools.common.utils.HL7; import org.openhealthtools.common.utils.OMUtil; 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.registry.api.RegistryLifeCycleContext; import org.openhealthtools.openxds.registry.api.RegistryLifeCycleException; import org.openhealthtools.openxds.registry.api.RegistryPatientException; import org.openhealthtools.openxds.registry.api.XdsRegistryLifeCycleService; import org.openhealthtools.openxds.registry.api.XdsRegistryPatientService; import com.misyshealthcare.connect.net.Identifier; public class SubmitObjectsRequest extends XdsCommon { boolean submit_raw = false; ContentValidationService validater; short xds_version; private final static Log logger = LogFactory.getLog(SubmitObjectsRequest.class); private IConnectionDescription connection = null; static ArrayList<String> sourceIds = null; String clientIPAddress; /* The IHE Audit Trail for this actor. */ private IheAuditTrail auditLog = null; public void setClientIPAddress(String addr) { clientIPAddress = addr; } boolean asBoolean(String value) { if (value == null) return false; if (value.equals("true")) return true; return false; } boolean returnTestLogId() { //return asBoolean(properties.getString("returnTestLogId")); return false; } public SubmitObjectsRequest(LogMessage log_message, short xds_version, MessageContext messageContext) { this.log_message = log_message; this.xds_version = xds_version; this.clientIPAddress = null; transaction_type = R_transaction; try { IheHTTPServer httpServer = (IheHTTPServer)messageContext.getTransportIn().getReceiver(); IheActor actor = httpServer.getIheActor(); if (actor == null) { throw new XdsInternalException("Cannot find XdsRegistry actor configuration."); } connection = actor.getConnection(); if (connection == null) { throw new XdsInternalException("Cannot find XdsRegistry connection configuration."); } auditLog = actor.getAuditTrail(); init(new RegistryResponse( (xds_version == xds_a) ? Response.version_2 : Response.version_3), xds_version, messageContext); loadSourceIds(); } catch (XdsInternalException e) { logger.fatal(logger_exception_details(e)); } } public SubmitObjectsRequest() { // TODO Auto-generated constructor stub } void loadSourceIds() throws XdsInternalException { if (sourceIds != null) return; String sids = connection.getProperty("sourceIds"); if (sids == null || sids.equals("")) throw new XdsInternalException("Registry: sourceIds not configured"); String[] parts = sids.split(","); sourceIds = new ArrayList<String>(); for (int i=0; i<parts.length; i++) { sourceIds.add(parts[i].trim()); } } public void setContentValidationService(ContentValidationService validater) { this.validater = validater; } public OMElement submitObjectsRequest(OMElement sor) { this.validater = validater; if (logger.isDebugEnabled()) { logger.debug("Request from the Repository:"); logger.debug(sor.toString()); } try { sor.build(); SubmitObjectsRequestInternal(sor); } catch (XdsFormatException e) { response.add_error("XDSRegistryError", "SOAP Format Error: " + e.getMessage(), RegistryUtility.exception_trace(e), log_message); } catch (XdsNonIdenticalHashException e) { response.add_error("XDSNonIdenticalHash", e.getMessage(), RegistryUtility.exception_trace(e), log_message); } catch (XdsDeprecatedException e) { response.add_error("XDSRegistryDeprecatedDocumentError", "XDS Deprecated Document Error:\n " + e.getMessage(), RegistryUtility.exception_trace(e), log_message); logger.warn(logger_exception_details(e)); } catch (XdsUnknownPatientIdException e) { response.add_error("XDSUnknownPatientId", "XDS Unknown Patient Id:\n " + e.getMessage(), RegistryUtility.exception_trace(e), log_message); logger.warn(logger_exception_details(e)); } catch (XdsPatientIdDoesNotMatchException e) { response.add_error("XDSPatientIdDoesNotMatch", "Patient ID does not match:\n " + e.getMessage(), RegistryUtility.exception_trace(e), log_message); logger.warn(logger_exception_details(e)); } catch (XdsInternalException e) { response.add_error("XDSRegistryError", "XDS Internal Error:\n " + e.getMessage(), RegistryUtility.exception_trace(e), log_message); logger.fatal(RegistryUtility.exception_trace(e)); } catch (MetadataValidationException e) { response.add_error("XDSRegistryMetadataError", "Metadata Validation Errors:\n " + e.getMessage(), RegistryUtility.exception_trace(e), log_message); } catch (LoggerException e) { response.add_error("XDSRegistryError", "Internal Logging error: LoggerException: " + e.getMessage(), RegistryUtility.exception_trace(e), log_message); logger.fatal(logger_exception_details(e)); } catch (SchemaValidationException e) { response.add_error("XDSRegistryMetadataError", "Schema Validation Errors:\n" + e.getMessage(), RegistryUtility.exception_trace(e), log_message); } catch (XdsException e) { response.add_error("XDSRegistryError", "Exception:\n " + e.getMessage(), RegistryUtility.exception_trace(e), log_message); logger.warn(logger_exception_details(e)); } catch (TransformerConfigurationException e) { response.add_error("XDSRegistryError", "Internal Error: Transformer Configuration Error: " + e.getMessage(), RegistryUtility.exception_trace(e), log_message); logger.fatal(logger_exception_details(e)); } catch (SQLException e) { response.add_error("XDSRegistryError", "Internal Logging error: SQLException: " + e.getMessage(), RegistryUtility.exception_trace(e), log_message); logger.fatal(logger_exception_details(e)); } catch (Exception e) { response.add_error("XDSRegistryError", "XDS General Error: " + e.getMessage(), RegistryUtility.exception_trace(e), log_message); logger.fatal(logger_exception_details(e)); } log_response(); OMElement res = null; try { res = response.getResponse(); if (logger.isDebugEnabled()) { logger.debug("Response from the Registry"); logger.debug(res.toString()); } } catch (XdsInternalException e) { } // return test log message id only if request from internal Repository if (returnTestLogId() && "127.0.0.1".equals(clientIPAddress)) { logger.info("Adding testLogId"); res.addAttribute("testLogId", log_message.getMessageID(), null); } if (logger.isInfoEnabled()){ logger.info("response is " + res.toString()); } return res; } void SubmitObjectsRequestInternal(OMElement sor) throws SQLException, SchemaValidationException, MetadataValidationException, XdsInternalException, TransformerConfigurationException, LoggerException, MetadataValidationException, XdsException { boolean status; //String sor_string = sor.toString(); if (submit_raw ) { status = submit_to_backend_registry(sor.toString()); return; } if (xds_version == xds_b) RegistryUtility.schema_validate_local(sor, MetadataTypes.METADATA_TYPE_Rb); else RegistryUtility.schema_validate_local(sor, MetadataTypes.METADATA_TYPE_R); // try { Metadata m = new Metadata(sor); StoredQuerySupport sqs = new StoredQuerySupport(response, log_message); RegistryObjectValidator rov = Registry.getRegistryObjectValidator(sqs); generateAuditLog(m); logIds(m); Validator val = new Validator(m, response.registryErrorList, true, xds_version == xds_b, log_message, false, connection); val.run(); RegistryValidations vals = null; if (PropertyFacade.getBoolean("validate.metadata")) vals = Registry.getRegistryValidations(response, log_message); if (vals != null) vals.validateProperUids(m); if (response.has_errors()) logger.error("metadata validator failed"); if (response.has_errors()) return; if (this.validater != null && !this.validater.runContentValidationService(m, response)) return; String patient_id = m.getSubmissionSetPatientId(); log_message.addOtherParam("Patient ID", patient_id); validate_patient_id(patient_id); if (vals != null) validateSourceId(m); // check for references to registry contents List<String> referenced_objects = m.getIdsOfReferencedObjects(); if (referenced_objects.size() > 0 && vals != null) { List<String> missing = vals.validateApproved(referenced_objects); if (missing != null) throw new XdsDeprecatedException("The following registry objects were referenced by this submission but are not present, as Approved documents, in the registry: " + missing); // make allowance for by reference inclusion missing = rov.validateSamePatientId(m.getReferencedObjectsThatMustHaveSamePatientId(), patient_id); if (missing != null) throw new XdsPatientIdDoesNotMatchException("The following registry objects were referenced by this submission but do not reference the same patient ID: " + missing); } //Get SSUID before UUID allocation String ssUid = m.getSubmissionSetUniqueId(); // allocate uuids for symbolic ids IdParser ra = new IdParser(m); ra.compileSymbolicNamesIntoUuids(); // check that submission does not include any object ids that are already in registry List<String> ids_in_submission = m.getAllDefinedIds(); List<String> ids_already_in_registry = rov.validateNotExists(ids_in_submission); if ( ids_already_in_registry.size() != 0) response.add_error(MetadataSupport.XDSRegistryMetadataError, "The following UUIDs which are present in the submission are already present in registry: " + ids_already_in_registry, "SubmitObjectsRequest.java", log_message); // Set XDSFolder.lastUpdateTime // this will set time on empty folders too. This is not required nor forbidden by XDS if (m.getFolders().size() != 0) { String timestamp = new Hl7Date().now(); for (OMElement fol : m.getFolders()) { m.setSlot(fol, "lastUpdateTime", timestamp); } } // If this submission includes a DocumentEntry replace and the original DocumentEntry is in a folder // then the replacement document must be put into the folder as well. This must happen here // so the following logic to update folder lastUpdateTime can be triggered. addRPLCToFolder(m); // if this submission adds a document to a folder then update that folder's lastUpdateTime Slot updateFolderTimes(m); // submit to backend registry String to_backend = m.getV3SubmitObjectsRequest().toString(); log_message.addOtherParam("From Registry Adaptor", to_backend); status = submit_to_backend_registry(to_backend); if (!status) { return; } auditLog(patient_id, ssUid, AuditTypeCodes.RegisterDocumentSet_b); // Approve List<String> approvable_object_ids = ra.approvable_object_ids(m); if (approvable_object_ids.size() > 0) { OMElement approve = ra.getApproveObjectsRequest(approvable_object_ids); log_message.addOtherParam("Approve", approve.toString()); submit_to_backend_registry(approve.toString()); } // Deprecate List<String> deprecatable_object_ids = m.getObjectIdsToDeprecate(); // add to the list of things to deprecate, any XFRM or APND documents hanging off documents // in the deprecatable_object_ids list deprecatable_object_ids.addAll(rov.getXFRMandAPNDDocuments(deprecatable_object_ids)); if (deprecatable_object_ids.size() > 0) { // validate that these are documents first List<String> missing = rov.validateDocuments(deprecatable_object_ids); if (missing != null) throw new XdsException("The following documents were referenced by this submission but are not present in the registry: " + missing); OMElement deprecate = ra.getDeprecateObjectsRequest(deprecatable_object_ids); log_message.addOtherParam("Deprecate", deprecate.toString()); submit_to_backend_registry(deprecate.toString()); } log_response(); // } // catch (MetadataException e) { // response.add_error("XDSRegistryError", e.getMessage(), RegistryUtility.exception_details(e), log_message); // return; // } // catch (ParserConfigurationException e) { // response.add_error("XDSRegistryError", e.getMessage(), exception_details(e), log_message); // return; // } // catch (ATNAException e) { // response.add_error("XDSRegistryError", e.getMessage(), exception_details(e), log_message); // return; // } } // this applies only to folders in the registry. folders in the submission are handled separately private void updateFolderTimes(Metadata m) throws MetadataException, LoggerException, XdsException, XdsInternalException, XMLParserException, MetadataValidationException { log_message.addOtherParam("start update folder",""); for (OMElement assoc : m.getAssociations()) { log_message.addOtherParam("assoc type is ", m.getSimpleAssocType(assoc)); if ( !m.getSimpleAssocType(assoc).equals("HasMember")) continue; String sourceId = m.getAssocSource(assoc); log_message.addOtherParam("sourceid ", sourceId); if (m.getSubmissionSetId().equals(sourceId)) continue; // sourceObject is SS if (m.getFolderIds().contains(sourceId)) continue; // sourceObject is folder in submission - handled elsewhere // sourceObject must be folder in registry, no other possibilities log_message.addOtherParam("Adding to Registry Folder ", sourceId); Metadata fm = fetchFolderbyId(sourceId); if (fm.getFolders().size() == 0) throw new XdsException("Adding to folder, sourceObject, " + sourceId + ", is not a folder in submission or in Registry"); // Set XDSFolder.lastUpdateTime String timestamp = new Hl7Date().now(); for (OMElement fol : fm.getFolders()) { fm.setSlot(fol, "lastUpdateTime", timestamp); } m.addMetadata(fm); // add to metadata collection, will get submitted back along with new metadata } } private Metadata fetchFolderbyId(String sourceId) throws XMLParserException, LoggerException, XdsInternalException, MetadataException, MetadataValidationException { BackendRegistry reg = new BackendRegistry(response,log_message); OMElement res = reg.basic_query("SELECT * from RegistryPackage rp WHERE rp.id='" + sourceId + "'", true /* leaf_class */); Metadata fm = MetadataParser.parseNonSubmission(res); return fm; } private boolean folderInRegistry(String sourceId) throws LoggerException, XdsException { StoredQuerySupport sqs = new StoredQuerySupport(response, log_message); RegistryObjectValidator rov = Registry.getRegistryObjectValidator(sqs); List<String> ids = new ArrayList<String>(); ids.add(sourceId); List<String> arenot = rov.validateAreFolders(ids); return ! arenot.contains(sourceId); } private boolean folderInSubmission(String sourceId) throws LoggerException, XdsException, XdsInternalException { return new Structure(new Metadata(), false).isFolder(sourceId); } private void addRPLCToFolder(Metadata m) throws MetadataException, LoggerException, XdsException { Map<String, String> rplcToOrigIds = new HashMap<String, String>(); for (OMElement assoc : m.getAssociations()) { if (m.getSimpleAssocType(assoc).equals("RPLC")) { rplcToOrigIds.put(m.getAssocSource(assoc), m.getAssocTarget(assoc)); } } log_message.addOtherParam("RPLC assocs", rplcToOrigIds.toString()); for (String replacementDocumentId : rplcToOrigIds.keySet()) { String originalDocumentId = rplcToOrigIds.get(replacementDocumentId); // for each original document, find the collection of folders it belongs to Metadata me = new SQFactory(this).findFoldersForDocumentByUuid(originalDocumentId, false /*LeafClass*/); List<String> folderIds = me.getObjectIds(me.getObjectRefs()); log_message.addOtherParam("RPLC containing folders=", folderIds.toString()); // for each folder, add an association placing replacement in that folder for (String fid : folderIds) { OMElement assoc = m.add_association(m.mkAssociation("HasMember", fid, replacementDocumentId)); m.add_association(m.mkAssociation("HasMember", m.getSubmissionSetId(), assoc.getAttributeValue(MetadataSupport.id_qname))); } } } private void logIds(Metadata m) throws LoggerException, MetadataException { log_message.addOtherParam("SSuid", m.getSubmissionSetUniqueId()); List<String> doc_uids = new ArrayList<String>(); for (String id : m.getExtrinsicObjectIds()) { String uid = m.getUniqueIdValue(id); if (uid != null && !uid.equals("")) doc_uids.add(uid); } log_message.addOtherParam("DOCuids", doc_uids.toString()); List<String> fol_uids = new ArrayList<String>(); for (String id : m.getFolderIds()) { String uid = m.getUniqueIdValue(id); if (uid != null && !uid.equals("")) fol_uids.add(uid); } log_message.addOtherParam("FOLuids", fol_uids.toString()); log_message.addOtherParam("Structure", m.structure()); } private void validateSourceId(Metadata m) throws MetadataException, MetadataValidationException { String sourceId = m.getExternalIdentifierValue(m.id(m.getSubmissionSet()), MetadataSupport.XDSSubmissionSet_sourceid_uuid); boolean sourceIdValid = false; for (int i=0; i<sourceIds.size(); i++) { if (sourceId.startsWith(sourceIds.get(i))) sourceIdValid = true; } if ( !sourceIdValid) throw new MetadataValidationException("sourceId " + sourceId + " is not acceptable to this Document Registry." + " It must have one of these prefixes: " + sourceIds.toString()); } private void validate_patient_id(String patient_id) throws SQLException, XdsException, XdsInternalException { if (PropertyFacade.getBoolean("validate.patient.id")) { try { XdsRegistryPatientService patientMan = XdsFactory.getXdsRegistryPatientService(); PatientIdentifier pid = getPatientIdentifier(patient_id); boolean known_patient_id = patientMan.isValidPatient(pid, null); if ( !known_patient_id) { String aa = "unknown"; if (pid.getAssigningAuthority() != null) { aa = "'" + pid.getAssigningAuthority().getAuthorityNameString() + "'"; } throw new XdsUnknownPatientIdException("PatientId " + patient_id + " (Assigning Authority " + aa + ") is not known to the Registry"); } } catch (RegistryPatientException e) { throw new XdsInternalException("RegistryPatientException: " + e.getMessage(), e); } } } /** * Converts a patientId from CX format to an {@link PatientIdentifier} object. * * @param patientId the patient id, exp. 12321^^^&1.3.6.1.4.1.21367.2009.1.2.300&ISO * @return the {@link PatientIdentifier} */ private PatientIdentifier getPatientIdentifier(String patientId){ String patId = HL7.getIdFromCX(patientId); Identifier assigningAuthority = HL7.getAssigningAuthorityFromCX(patientId); Identifier aa = reconcileIdentifier(assigningAuthority, connection); PatientIdentifier pid = new PatientIdentifier(); pid.setId(patId); pid.setAssigningAuthority(aa); return pid; } // private void validate_assigning_authority(String patient_id, Validator val) // throws XdsException { // int ups = patient_id.indexOf("^^^"); // if (ups == -1) // throw new XdsException("Cannot parse patient id " + patient_id); // String assigning_authority = patient_id.substring(ups + 3); //// String config_assigning_authority = Properties.loader().getString("assigning_authority"); // String config_assigning_authority = val.getAssigningAuthority(); // if (config_assigning_authority != null) // if ( !config_assigning_authority.equals(assigning_authority)) // throw new XdsException("Unknown Patient ID Assigning Authority " + // assigning_authority + " found in submission " + // " this Affinity Domain requires " + config_assigning_authority); // } private boolean submit_to_backend_registry(String sor_string) throws XdsInternalException { boolean status = true; XdsRegistryLifeCycleService lcm = XdsFactory.getXdsRegistryLifeCycleService(); OMElement result = null; try { OMElement request = OMUtil.xmlStringToOM(sor_string); if(request.getLocalName().equalsIgnoreCase("SubmitObjectsRequest")){ result = lcm.submitObjects(request, new RegistryLifeCycleContext()); }else if(request.getLocalName().equalsIgnoreCase("ApproveObjectsRequest")){ result = lcm.approveObjects(request, new RegistryLifeCycleContext()); }else if(request.getLocalName().equalsIgnoreCase("DeprecateObjectsRequest")){ result = lcm.deprecateObjects(request, new RegistryLifeCycleContext()); } }catch(XMLStreamException e) { response.add_error("XDSRegistryError", e.getMessage(), RegistryUtility.exception_details(e),log_message); status = false; }catch(RegistryLifeCycleException e) { response.add_error("XDSRegistryError", e.getMessage(), RegistryUtility.exception_details(e),log_message); status = false; } if (!status) { return status; } String statusCode = result.getAttributeValue(MetadataSupport.status_qname); if (!statusCode.equals(MetadataSupport.response_status_type_namespace + "Success")) { OMElement errorList = MetadataSupport.firstChildWithLocalName(result, "RegistryErrorList") ; response.addRegistryErrorList(errorList, log_message); status = false; } return status; } public void setSubmitRaw(boolean val) { submit_raw = val; } /** * Audit Logging of Register Document Set message. */ private void auditLog(String patientId, String submissionSetUid, AuditCodeMappings.AuditTypeCodes typeCode) { 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", submissionSetUid); ParticipantObject patientObj = new ParticipantObject("PatientIdentifier", patientId); ActiveParticipant source = new ActiveParticipant(); source.setUserId(replyto); source.setAccessPointId(remoteIP); //TODO: Needs to be improved String userid = "http://"+connection.getHostname()+":"+connection.getPort()+"/axis2/services/xdsregistryb"; ActiveParticipant dest = new ActiveParticipant(); dest.setUserId(userid); // the Alternative User ID should be set to our Process ID, see // section TF-2b section 3.42.7.1.2 dest.setAltUserId(PidHelper.getPid()); dest.setAccessPointId(localIP); auditLog.logDocumentImport(source, dest, patientObj, set, typeCode); } }