org.openhim.mediator.denormalization.ATNAAuditingActor.java Source code

Java tutorial

Introduction

Here is the source code for org.openhim.mediator.denormalization.ATNAAuditingActor.java

Source

/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

package org.openhim.mediator.denormalization;

import akka.actor.ActorRef;
import akka.actor.ActorSelection;
import akka.actor.UntypedActor;
import akka.dispatch.OnComplete;
import akka.event.Logging;
import akka.event.LoggingAdapter;
import ihe.iti.atna.AuditMessage;
import ihe.iti.atna.EventIdentificationType;
import org.apache.commons.io.IOUtils;
import org.openhim.mediator.ATNAUtil;
import org.openhim.mediator.datatypes.Identifier;
import org.openhim.mediator.engine.MediatorConfig;
import org.openhim.mediator.engine.messages.MediatorSocketRequest;
import org.openhim.mediator.messages.ATNAAudit;
import scala.concurrent.ExecutionContext;
import scala.concurrent.Future;

import javax.net.ssl.SSLSocketFactory;
import javax.xml.bind.JAXBException;
import java.io.DataOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.Callable;

import static akka.dispatch.Futures.future;

/**
 * An actor for sending out audit messages to an audit repository.
 * <br/><br/>
 * Messages supported:
 * <ul>
 *     <li>ATNAAudit - fire-and-forget</li>
 * </ul>
 */
public class ATNAAuditingActor extends UntypedActor {

    LoggingAdapter log = Logging.getLogger(getContext().system(), this);

    private MediatorConfig config;

    public ATNAAuditingActor(MediatorConfig config) {
        this.config = config;
    }

    protected String generateForPIXRequest(ATNAAudit audit) throws JAXBException {
        AuditMessage res = new AuditMessage();

        EventIdentificationType eid = new EventIdentificationType();
        eid.setEventID(ATNAUtil.buildCodedValueType("DCM", "110112", "Query"));
        eid.setEventActionCode("E");
        eid.setEventDateTime(ATNAUtil.newXMLGregorianCalendar());
        eid.getEventTypeCode().add(ATNAUtil.buildCodedValueType("IHE Transactions", "ITI-9", "PIX Query"));
        eid.setEventOutcomeIndicator(audit.getOutcome() ? BigInteger.ZERO : new BigInteger("4"));
        res.setEventIdentification(eid);

        res.getActiveParticipant()
                .add(ATNAUtil.buildActiveParticipant(
                        config.getProperty("pix.sendingFacility") + "|"
                                + config.getProperty("pix.sendingApplication"),
                        ATNAUtil.getProcessID(), true, ATNAUtil.getHostIP(), (short) 2, "DCM", "110153", "Source"));
        res.getActiveParticipant()
                .add(ATNAUtil.buildActiveParticipant(
                        config.getProperty("pix.receivingFacility") + "|"
                                + config.getProperty("pix.receivingApplication"),
                        "2100", false, config.getProperty("pix.manager.host"), (short) 1, "DCM", "110152",
                        "Destination"));

        res.getAuditSourceIdentification().add(ATNAUtil.buildAuditSource("openhim"));

        // Max of 1 patient is allowed
        Identifier id = audit.getParticipantIdentifiers().get(0);
        if (id != null) {
            res.getParticipantObjectIdentification().add(ATNAUtil.buildParticipantObjectIdentificationType(
                    id.toCX(), (short) 1, (short) 1, "RFC-3881", "2", "PatientNumber", null));
        }

        res.getParticipantObjectIdentification()
                .add(ATNAUtil.buildParticipantObjectIdentificationType(UUID.randomUUID().toString(), (short) 2,
                        (short) 24, "IHE Transactions", "ITI-9", "PIX Query", audit.getMessage(),
                        new ATNAUtil.ParticipantObjectDetail("MSH-10", audit.getUniqueId().getBytes())));

        return ATNAUtil.marshallATNAObject(res);
    }

    protected String generateForPIXIdentityFeed(ATNAAudit audit) throws JAXBException {
        AuditMessage res = new AuditMessage();

        EventIdentificationType eid = new EventIdentificationType();
        eid.setEventID(ATNAUtil.buildCodedValueType("DCM", "110110", "Patient Record"));
        eid.setEventActionCode("C");
        eid.setEventDateTime(ATNAUtil.newXMLGregorianCalendar());
        eid.getEventTypeCode()
                .add(ATNAUtil.buildCodedValueType("IHE Transactions", "ITI-8", "Patient Identity Feed"));
        eid.setEventOutcomeIndicator(audit.getOutcome() ? BigInteger.ZERO : new BigInteger("4"));
        res.setEventIdentification(eid);

        res.getActiveParticipant()
                .add(ATNAUtil.buildActiveParticipant(
                        config.getProperty("pix.sendingFacility") + "|"
                                + config.getProperty("pix.sendingApplication"),
                        ATNAUtil.getProcessID(), true, ATNAUtil.getHostIP(), (short) 2, "DCM", "110153", "Source"));
        res.getActiveParticipant()
                .add(ATNAUtil.buildActiveParticipant(
                        config.getProperty("pix.receivingFacility") + "|"
                                + config.getProperty("pix.receivingApplication"),
                        "2100", false, config.getProperty("pix.manager.host"), (short) 1, "DCM", "110152",
                        "Destination"));

        res.getAuditSourceIdentification().add(ATNAUtil.buildAuditSource("openhim"));

        // Max of 1 patient is allowed
        Identifier id = audit.getParticipantIdentifiers().get(0);
        if (id != null) {
            res.getParticipantObjectIdentification().add(ATNAUtil.buildParticipantObjectIdentificationType(
                    id.toCX(), (short) 1, (short) 1, "RFC-3881", "2", "PatientNumber", null));
        }

        res.getParticipantObjectIdentification()
                .add(ATNAUtil.buildParticipantObjectIdentificationType(UUID.randomUUID().toString(), (short) 2,
                        (short) 24, "IHE Transactions", "ITI-9", "PIX Query", audit.getMessage(),
                        new ATNAUtil.ParticipantObjectDetail("MSH-10", audit.getUniqueId().getBytes())));

        return ATNAUtil.marshallATNAObject(res);
    }

    protected String generateForRegistryQueryReceived(ATNAAudit audit) throws JAXBException {
        AuditMessage res = new AuditMessage();

        EventIdentificationType eid = new EventIdentificationType();
        eid.setEventID(ATNAUtil.buildCodedValueType("DCM", "110112", "Query"));
        eid.setEventActionCode("E");
        eid.setEventDateTime(ATNAUtil.newXMLGregorianCalendar());
        eid.getEventTypeCode()
                .add(ATNAUtil.buildCodedValueType("IHE Transactions", "ITI-18", "Registry Stored Query"));
        eid.setEventOutcomeIndicator(audit.getOutcome() ? BigInteger.ZERO : new BigInteger("4"));
        res.setEventIdentification(eid);

        res.getActiveParticipant().add(ATNAUtil.buildActiveParticipant(ATNAUtil.WSA_REPLYTO_ANON, "client", true,
                audit.getSourceIP(), (short) 2, "DCM", "110153", "Source"));
        res.getActiveParticipant().add(ATNAUtil.buildActiveParticipant(ATNAUtil.WSA_REPLYTO_ANON,
                ATNAUtil.getProcessID(), false, ATNAUtil.getHostIP(), (short) 2, "DCM", "110152", "Destination"));

        res.getAuditSourceIdentification().add(ATNAUtil.buildAuditSource("openhim"));

        // Max of 1 patient is allowed
        Identifier id = audit.getParticipantIdentifiers().get(0);
        if (id != null) {
            res.getParticipantObjectIdentification().add(ATNAUtil.buildParticipantObjectIdentificationType(
                    id.toCX(), (short) 1, (short) 1, "RFC-3881", "2", "PatientNumber", null));
        }

        List<ATNAUtil.ParticipantObjectDetail> pod = new ArrayList<>();
        pod.add(new ATNAUtil.ParticipantObjectDetail("QueryEncoding", "UTF-8".getBytes()));
        if (audit.getHomeCommunityId() != null)
            pod.add(new ATNAUtil.ParticipantObjectDetail("urn:ihe:iti:xca:2010:homeCommunityId",
                    audit.getHomeCommunityId().getBytes()));

        res.getParticipantObjectIdentification()
                .add(ATNAUtil.buildParticipantObjectIdentificationType(audit.getUniqueId(), (short) 2, (short) 24,
                        "IHE Transactions", "ITI-18", "Registry Stored Query", audit.getMessage(), pod));

        return ATNAUtil.marshallATNAObject(res);
    }

    protected String generateForRegistryQueryResponse(ATNAAudit audit) throws JAXBException {
        AuditMessage res = new AuditMessage();

        EventIdentificationType eid = new EventIdentificationType();
        eid.setEventID(ATNAUtil.buildCodedValueType("DCM", "110112", "Query"));
        eid.setEventActionCode("E");
        eid.setEventDateTime(ATNAUtil.newXMLGregorianCalendar());
        eid.getEventTypeCode()
                .add(ATNAUtil.buildCodedValueType("IHE Transactions", "ITI-18", "Registry Stored Query"));
        eid.setEventOutcomeIndicator(audit.getOutcome() ? BigInteger.ZERO : new BigInteger("4"));
        res.setEventIdentification(eid);

        String xdsRegistryHost = config.getProperty("xds.registry.host");
        res.getActiveParticipant().add(ATNAUtil.buildActiveParticipant(ATNAUtil.WSA_REPLYTO_ANON,
                ATNAUtil.getProcessID(), true, ATNAUtil.getHostIP(), (short) 2, "DCM", "110153", "Source"));
        res.getActiveParticipant().add(ATNAUtil.buildActiveParticipant(buildRegistryPath(), xdsRegistryHost, false,
                xdsRegistryHost, (short) 1, "DCM", "110152", "Destination"));

        res.getAuditSourceIdentification().add(ATNAUtil.buildAuditSource("openhim"));

        // Max of 1 patient is allowed
        Identifier id = audit.getParticipantIdentifiers().get(0);
        if (id != null) {
            res.getParticipantObjectIdentification().add(ATNAUtil.buildParticipantObjectIdentificationType(
                    id.toCX(), (short) 1, (short) 1, "RFC-3881", "2", "PatientNumber", null));
        }

        List<ATNAUtil.ParticipantObjectDetail> pod = new ArrayList<>();
        pod.add(new ATNAUtil.ParticipantObjectDetail("QueryEncoding", "UTF-8".getBytes()));
        if (audit.getHomeCommunityId() != null)
            pod.add(new ATNAUtil.ParticipantObjectDetail("urn:ihe:iti:xca:2010:homeCommunityId",
                    audit.getHomeCommunityId().getBytes()));

        res.getParticipantObjectIdentification()
                .add(ATNAUtil.buildParticipantObjectIdentificationType(audit.getUniqueId(), (short) 2, (short) 24,
                        "IHE Transactions", "ITI-18", "Registry Stored Query", audit.getMessage(), pod));

        return ATNAUtil.marshallATNAObject(res);
    }

    private String buildRegistryPath() {
        return String.format("%s:%s/%s", config.getProperty("xds.registry.host"),
                ((config.getProperty("xds.registry.secure").equalsIgnoreCase("true"))
                        ? config.getProperty("xds.registry.securePort")
                        : config.getProperty("xds.registry.port")),
                config.getProperty("xds.registry.path"));
    }

    protected String generateForPNRReceived(ATNAAudit audit) throws JAXBException {
        AuditMessage res = new AuditMessage();

        EventIdentificationType eid = new EventIdentificationType();
        eid.setEventID(ATNAUtil.buildCodedValueType("DCM", "110107", "Import"));
        eid.setEventActionCode("C");
        eid.setEventDateTime(ATNAUtil.newXMLGregorianCalendar());
        eid.getEventTypeCode().add(
                ATNAUtil.buildCodedValueType("IHE Transactions", "ITI-41", "Provide and Register Document Set-b"));
        eid.setEventOutcomeIndicator(audit.getOutcome() ? BigInteger.ZERO : new BigInteger("4"));
        res.setEventIdentification(eid);

        res.getActiveParticipant().add(ATNAUtil.buildActiveParticipant(ATNAUtil.WSA_REPLYTO_ANON, "client", true,
                audit.getSourceIP(), (short) 2, "DCM", "110153", "Source"));
        res.getActiveParticipant().add(ATNAUtil.buildActiveParticipant(ATNAUtil.WSA_REPLYTO_ANON,
                ATNAUtil.getProcessID(), false, ATNAUtil.getHostIP(), (short) 2, "DCM", "110152", "Destination"));

        res.getAuditSourceIdentification().add(ATNAUtil.buildAuditSource("openhim"));

        // Only one is allowed
        Identifier id = audit.getParticipantIdentifiers().get(0);
        if (id != null) {
            res.getParticipantObjectIdentification().add(ATNAUtil.buildParticipantObjectIdentificationType(
                    id.toCX(), (short) 1, (short) 1, "RFC-3881", "2", "PatientNumber", null));
        }

        List<ATNAUtil.ParticipantObjectDetail> pod = new ArrayList<>();
        pod.add(new ATNAUtil.ParticipantObjectDetail("QueryEncoding", "UTF-8".getBytes()));
        if (audit.getHomeCommunityId() != null)
            pod.add(new ATNAUtil.ParticipantObjectDetail("urn:ihe:iti:xca:2010:homeCommunityId",
                    audit.getHomeCommunityId().getBytes()));

        res.getParticipantObjectIdentification()
                .add(ATNAUtil.buildParticipantObjectIdentificationType(audit.getUniqueId(), (short) 2, (short) 20,
                        "IHE XDS Metadata", "urn:uuid:a54d6aa5-d40d-43f9-88c5-b4633d873bdd",
                        "submission set classificationNode", audit.getMessage(), pod));

        return ATNAUtil.marshallATNAObject(res);
    }

    protected String generateForPNRResponse(ATNAAudit audit) throws JAXBException {
        AuditMessage res = new AuditMessage();

        EventIdentificationType eid = new EventIdentificationType();
        eid.setEventID(ATNAUtil.buildCodedValueType("DCM", "110106", "Export"));
        eid.setEventActionCode("R");
        eid.setEventDateTime(ATNAUtil.newXMLGregorianCalendar());
        eid.getEventTypeCode().add(
                ATNAUtil.buildCodedValueType("IHE Transactions", "ITI-41", "Provide and Register Document Set-b"));
        eid.setEventOutcomeIndicator(audit.getOutcome() ? BigInteger.ZERO : new BigInteger("4"));
        res.setEventIdentification(eid);

        String xdsRepositoryHost = config.getProperty("xds.repository.host");
        res.getActiveParticipant().add(ATNAUtil.buildActiveParticipant(ATNAUtil.WSA_REPLYTO_ANON,
                ATNAUtil.getProcessID(), true, ATNAUtil.getHostIP(), (short) 2, "DCM", "110153", "Source"));
        res.getActiveParticipant().add(ATNAUtil.buildActiveParticipant(xdsRepositoryHost, false, xdsRepositoryHost,
                (short) 1, "DCM", "110152", "Destination"));

        res.getAuditSourceIdentification().add(ATNAUtil.buildAuditSource("openhim"));

        // Only one is allowed
        Identifier id = audit.getParticipantIdentifiers().get(0);
        if (id != null) {
            res.getParticipantObjectIdentification().add(ATNAUtil.buildParticipantObjectIdentificationType(
                    id.toCX(), (short) 1, (short) 1, "RFC-3881", "2", "PatientNumber", null));
        }

        res.getParticipantObjectIdentification()
                .add(ATNAUtil.buildParticipantObjectIdentificationType(audit.getUniqueId(), (short) 2, (short) 20,
                        "IHE XDS Metadata", "urn:uuid:a54d6aa5-d40d-43f9-88c5-b4633d873bdd",
                        "submission set classificationNode", audit.getMessage()));

        return ATNAUtil.marshallATNAObject(res);
    }

    private void sendUsingUDP(MediatorSocketRequest request) {
        ActorSelection udpConnector = getContext().actorSelection(config.userPathFor("udp-fire-forget-connector"));
        udpConnector.tell(request, getSelf());
    }

    private Socket getSocket(final MediatorSocketRequest req) throws IOException {
        if (req.isSecure()) {
            SSLSocketFactory factory = (SSLSocketFactory) SSLSocketFactory.getDefault();
            return factory.createSocket(req.getHost(), req.getPort());
        } else {
            return new Socket(req.getHost(), req.getPort());
        }
    }

    private void sendUsingTCP(final MediatorSocketRequest request) throws IOException {
        final Socket socket = getSocket(request);

        ExecutionContext ec = getContext().dispatcher();
        Future<Boolean> f = future(new Callable<Boolean>() {
            public Boolean call() throws IOException {
                DataOutputStream out = new DataOutputStream(socket.getOutputStream());
                out.writeBytes(request.getBody());
                return Boolean.TRUE;
            }
        }, ec);
        f.onComplete(new OnComplete<Boolean>() {
            @Override
            public void onComplete(Throwable ex, Boolean result) throws Throwable {
                IOUtils.closeQuietly(socket);

                if (ex != null) {
                    log.error(ex, "Exception during TCP send");
                }
            }
        }, ec);
    }

    private String generateMesage(ATNAAudit audit) throws JAXBException {
        switch (audit.getType()) {
        case PIX_REQUEST:
            return generateForPIXRequest(audit);
        case PIX_IDENTITY_FEED:
            return generateForPIXIdentityFeed(audit);
        case REGISTRY_QUERY_RECEIVED:
            return generateForRegistryQueryReceived(audit);
        case REGISTRY_QUERY_ENRICHED:
            return generateForRegistryQueryResponse(audit);
        case PROVIDE_AND_REGISTER_RECEIVED:
            return generateForPNRReceived(audit);
        case PROVIDE_AND_REGISTER_ENRICHED:
            return generateForPNRResponse(audit);
        }

        //shouldn't happen as we cover all the enum cases
        return "";
    }

    private void sendAuditMessage(ATNAAudit audit) throws Exception { //Just die if something goes wrong, akka will restart

        String message = generateMesage(audit);

        message = ATNAUtil.build_TCP_Msg_header() + message;
        boolean useTCP;
        int port;

        if (config.getProperty("atna.useTcp").equalsIgnoreCase("true")) {
            port = Integer.parseInt(config.getProperty("atna.tcpPort"));
            useTCP = true;
            message = message.length() + " " + message; // Required by RFC5425
        } else {
            port = Integer.parseInt(config.getProperty("atna.udpPort"));
            useTCP = false;
            message = message + "\r\n"; // to make OpenATNA happy
        }

        MediatorSocketRequest request = new MediatorSocketRequest(ActorRef.noSender(), getSelf(), "ATNA Audit",
                null, config.getProperty("atna.host"), port, message,
                config.getProperty("atna.secure").equalsIgnoreCase("true"));

        if (useTCP) {
            log.info("Sending ATNA " + audit.getType() + " audit message using TCP");
            sendUsingTCP(request);
        } else {
            log.info("Sending ATNA " + audit.getType() + " audit message using UDP");
            sendUsingUDP(request);
        }
    }

    @Override
    public void onReceive(Object msg) throws Exception {
        if (msg instanceof ATNAAudit) {
            sendAuditMessage((ATNAAudit) msg);
        } else {
            unhandled(msg);
        }
    }
}