uk.org.openeyes.diagnostics.FieldProcessor.java Source code

Java tutorial

Introduction

Here is the source code for uk.org.openeyes.diagnostics.FieldProcessor.java

Source

/**
 * OpenEyes
 *
 * (C) Moorfields Eye Hospital NHS Foundation Trust, 2008-2011
 * (C) OpenEyes Foundation, 2011-2013
 * This file is part of OpenEyes.
 * OpenEyes is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
 * OpenEyes is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License along with OpenEyes in a file titled COPYING. If not, see <http://www.gnu.org/licenses/>.
 *
 * @package OpenEyes
 * @link http://www.openeyes.org.uk
 * @author OpenEyes <info@openeyes.org.uk>
 * @copyright Copyright (c) 2008-2011, Moorfields Eye Hospital NHS Foundation Trust
 * @copyright Copyright (c) 2011-2013, OpenEyes Foundation
 * @license http://www.gnu.org/licenses/gpl-3.0.html The GNU General Public License V3.0
 */
/**
 * OpenEyes
 *
 * (C) Moorfields Eye Hospital NHS Foundation Trust, 2008-2011
 * (C) OpenEyes Foundation, 2011-2013
 * This file is part of OpenEyes.
 * OpenEyes is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
 * OpenEyes is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License along with OpenEyes in a file titled COPYING. If not, see <http://www.gnu.org/licenses/>.
 *
 * @package OpenEyes
 * @link http://www.openeyes.org.uk
 * @author OpenEyes <info@openeyes.org.uk>
 * @copyright Copyright (c) 2008-2011, Moorfields Eye Hospital NHS Foundation Trust
 * @copyright Copyright (c) 2011-2013, OpenEyes Foundation
 * @license http://www.gnu.org/licenses/gpl-3.0.html The GNU General Public License V3.0
 */
package uk.org.openeyes.diagnostics;

import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.net.ConnectException;
import java.util.Calendar;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.hibernate.Query;
import org.hl7.fhir.Patient;
import sun.misc.BASE64Encoder;
import uk.org.openeyes.diagnostics.db.DbUtils;
import uk.org.openeyes.diagnostics.db.FieldError;
import uk.org.openeyes.diagnostics.db.FieldErrorReport;
import uk.org.openeyes.diagnostics.db.FieldReport;
import uk.org.openeyes.diagnostics.db.HibernateUtil;

/**
 * Temporary class to watch for humphrey field images.
 */
public class FieldProcessor extends AbstractFieldProcessor {

    private final static Logger log = Logger.getLogger(FieldProcessor.class.getName());
    /**
     * Host to send reports to.
     */
    private String host = "localhost";
    /**
     * Username.
     */
    private String authenticationUsername;
    /**
     * Password for authentication.
     */
    private String authenticationPassword;
    /**
     * Port number to send reports on.
     */
    private int port = 80;
    /**
     * Which directory to place files that failed to send.
     */
    private File outgoingDir;
    /**
     * Time, in minutes for when to send old unsent files.
     */
    private static final int[] BACKOFF_TIMES = { 5, 10, 20, 60, 120, 240, 480, 960, 86400 };

    /**
     * Main thread for checking files.
     */
    public void run() {
        while (true) {
            try {
                this.checkDir();
                // check which files need to be sent (again):
                this.checkOutgoing();
                Thread.sleep(this.getInterval() * 1000);
            } catch (InterruptedException iex) {
                iex.printStackTrace();
            }
        }
    }

    /**
     * Checks the outgoing directory and see if there are any files that
     * need re-sending.
     */
    private void checkOutgoing() {
        // get file list -  all XML files
        File[] files = this.outgoingDir.listFiles(new FileFilter() {

            public boolean accept(File pathname) {
                return pathname.getName().toLowerCase().endsWith(".xml");
            }
        });
        for (File file : files) {
            this.checkFile(file);
        }
    }

    /**
     * Check specified file to see if it needs sending again.
     * 
     * @param f the non-null file to send. 
     */
    private void checkFile(File f) {

        session = HibernateUtil.getSessionFactory().getCurrentSession();

        session.getTransaction().begin();
        Query query = session.createQuery("from FieldReport where file_name=:file_name");
        query.setParameter("file_name", f.getName());
        List list = query.list();
        FieldReport report = null;
        if (!list.isEmpty()) {
            report = (FieldReport) list.get(0);
        }
        if (report != null) {
            long time = ((long) ((Calendar.getInstance().getTimeInMillis() - f.lastModified()) / 1000));
            query = session.createQuery("from FieldErrorReport err where field_report_id= :report_id");
            query.setParameter("report_id", report.getId());
            int results = query.list().size();
            if (results > 0) {
                int timeDiff = 0;
                if (results < FieldProcessor.BACKOFF_TIMES.length) {
                    timeDiff = FieldProcessor.BACKOFF_TIMES[results - 1];
                } else {
                    timeDiff = FieldProcessor.BACKOFF_TIMES[FieldProcessor.BACKOFF_TIMES.length - 1];
                }
                if (time > timeDiff) {
                    // move the file back to import directory for re-processing:
                    File imageFile = new File(this.outgoingDir, report.getFileReference());
                    File xmlFile = new File(this.outgoingDir, report.getFileName());
                    HumphreyFieldMetaData metaData = new HumphreyFieldMetaData(this.regex);
                    // only need certain patient-identifiable fields:
                    metaData.setDob(report.getDob());
                    metaData.setFamilyName(report.getLastName());
                    metaData.setGivenName(report.getFirstName());
                    metaData.setFileReference(report.getFileReference());
                    metaData.setPatientId(report.getPatientId());
                    try {
                        this.send(metaData, xmlFile, imageFile, report);
                    } catch (IOException ioex) {
                        log.warning("IO Error processing " + f.getName() + ": " + ioex.getMessage());
                    }
                }
            }

        }
    }

    /**
     *
     * @param patient
     * @return
     */
    private String getPatientId(Patient patient) {
        String id = null;
        try {
            id = patient.getIdentifierArray(0).getValue().getValue();
        } catch (NullPointerException npex) {
        }
        return id;
    }

    /**
     *
     * @param patient
     * @return
     */
    private String getPatientFamilyName(Patient patient) {
        String name = null;
        try {
            name = patient.getNameArray(0).getFamilyArray(0).getValue();
        } catch (NullPointerException npex) {
        }
        return name;
    }

    /**
     *
     * @param patient
     * @return
     */
    private String getPatientDoB(Patient patient) {
        String dob = null;
        try {
            dob = patient.getBirthDate().getValue().toString();
        } catch (NullPointerException npex) {
        }
        return dob;
    }

    /**
     *
     * @param patient
     * @return
     */
    private String getPatientGender(Patient patient) {
        String gender = null;
        try {
            gender = patient.getGender().getCodingArray(0).getCode().getValue();
        } catch (NullPointerException npex) {
        }
        return gender;
    }

    /**
     * Processes the file and attempts to move it to a correct location.
     * 
     * @param file a non-null already existing file.
     */
    @Override
    public void processFile(File file) {

        this.generateDirectories(new File[] { this.dir, this.archiveDir, this.errDir });
        log.log(Level.FINE, "Processing {0}", file.getName());
        try {
            // parse XML file:
            if (!this.validate(file)) {
                file.renameTo(new File(this.errDir, file.getName()));
                // if the file is invalid, that means we can't get the file name -
                // though it might still exist. Check anyway:
                String basename = FilenameUtils.getBaseName(file.getName());
                File imageFile = new File(file.getParentFile(), basename + ".tif");
                if (imageFile.exists()) {
                    imageFile.renameTo(new File(this.errDir, imageFile.getName()));
                }
                return;
            }
            HumphreyFieldMetaData metaData = this.parseFields(file);
            // find out what the values in the report are:
            FieldReport report = this.generateReport(file, metaData, true);

            /*
            // TODO: these are reliant on the PAS being in place
            String familyName = this.getPatientFamilyName(patient);
            String id = this.getPatientId(patient);
            String birthDate = this.getPatientDoB(patient);
            if (birthDate != null && !birthDate.equals(metaData.getDob())) {
            report.setFieldError(DbUtils.getError(DbUtils.ERROR_NO_DOB_MATCH));
            continue;
            }
            if (familyName != null && !familyName.equals(metaData.getFamilyName())) {
            report.setFieldError(DbUtils.getError(DbUtils.ERROR_NO_SURNAME_MATCH));
            continue;
            }*/
            // we expect to see the named file reference in the same directory
            // as the XML file:

            File imageFile = new File(this.dir, metaData.getFileReference());

            if (imageFile.isDirectory() || !imageFile.exists()) {
                this.moveFile(metaData, report, file);
                return;
            }
            this.send(metaData, file, imageFile, report);

            if (!report.getFieldErrorReports().isEmpty()) {
                // if it is an unknown patient, try a resend in case the patient
                // has been picked up by the PAS at this point:
                boolean outgoing = false;
                for (Iterator<FieldErrorReport> it = report.getFieldErrorReports().iterator(); it.hasNext();) {
                    if (it.next().getFieldError().getId() == DbUtils.ERROR_UNKOWN_OE_PATIENT) {
                        outgoing = true;
                    }
                }
                if (outgoing) {

                    File dest = new File(this.getOutgoingDir(), file.getName());
                    File imgDest = new File(this.getOutgoingDir(), report.getFileReference());
                    file.renameTo(dest);
                    imageFile.renameTo(imgDest);
                    log.log(Level.WARNING, "Failed to send; moving {0} to {1}",
                            new Object[] { file.getName(), this.outgoingDir });
                    return;
                } else {
                    // it's a general error, so just move it to the error dir:
                    this.moveFile(metaData, report, file);
                    log.log(Level.WARNING, "Error in record; moving {0} to {1}",
                            new Object[] { file.getName(), this.errDir });
                    return;
                }
            }

            // move the image file to the archive directory:
            File moveToFile = new File(this.archiveDir, imageFile.getName());
            imageFile.renameTo(moveToFile);
            // don't use boolean result, not always consistent, just check if new file exists:
            if (!moveToFile.exists()) {
                log.log(Level.WARNING, "Unable to move {0}", imageFile.getAbsolutePath());
                // TODO clean up - mark file as ignored?
            }
            // now move the XML:
            moveToFile = new File(this.archiveDir, file.getName());
            if (file.renameTo(moveToFile)) {
                log.info("Moved " + file.getName() + " to " + moveToFile.getParentFile().getAbsolutePath());
            }

            // don't use boolean result, not always consistent, just check if new file exists:
            if (!moveToFile.exists()) {
                log.log(Level.WARNING, "Unable to move {0}", file.getAbsolutePath());
                // TODO clean up - mark file as ignored?
            }
        } catch (FileNotFoundException fnfex) {
            fnfex.printStackTrace();
        } catch (IOException fnfex) {
            fnfex.printStackTrace();
        }
    }

    /**
     * Attempts to send the details of the scan to the OE server. This involves
     * contacting the server to see if the patient exists, and if they do,
     * sending it on.
     * 
     * @param metaData meta data containing patient information.
     * @param file the XML file being processed.
     * @param imageFile the non-null image file.
     * @param report non-null field report.
     * @throws IOException 
     */
    private void send(HumphreyFieldMetaData metaData, File file, File imageFile, FieldReport report)
            throws IOException {
        String reportText = null;
        try {
            // get the report's patient id and find out if they exist:
            Patient patient = new FhirUtils().readPatient(this.getHost(), this.getPort(), metaData,
                    this.getAuthenticationUsername(), this.getAuthenticationPassword());
            if (patient == null) { // not found
                this.setUnknownOEPatient(report);
            } else {
                reportText = this.generateMeasurementText(patient.getId(), file, imageFile, report);
                this.transferHumphreyVisualField(reportText, report, file);
            }
        } catch (IllegalArgumentException | ConnectException iaex) {
            // the illegal argument exception occurs when (for example) a patient hos num
            // contains no leading zeros, but should - e.g. 0123456 vs. 123456
            // in the report, pid is 123456 but /should/ be 0123456
            // This results in an illegal argument exception and thinks the patient can't be found
            this.setUnknownOEPatient(report);
            if (reportText == null) {
                // mark the patient ID (or lack of) in the report text:
                reportText = this.generateMeasurementText("__OE_PATIENT_ID_" + report.getPatientId() + "__", file,
                        imageFile, report);
            }
            // mark the message as not having been sent and re-try at a later date:
            File measurementFile = new File(this.getOutgoingDir(),
                    FilenameUtils.getBaseName(file.getName()) + ".fmes");
            measurementFile.createNewFile();
            IOUtils.write(reportText, new FileWriter(measurementFile));
        }
    }

    /**
     * Mark a patient as unknown. This happens when a lookup is performed
     * for a patient, but they are not yet present in the PAS.
     * 
     * @param report the non-null report to check to see if the patient
     * exists.
     */
    private void setUnknownOEPatient(FieldReport report) {
        session = HibernateUtil.getSessionFactory().getCurrentSession();
        session.getTransaction().begin();
        // TODO the patient is not found; move to error directory
        FieldError err = DbUtils.getError(DbUtils.ERROR_UNKOWN_OE_PATIENT);
        FieldErrorReport errReport = new FieldErrorReport();
        errReport.setFieldError(err);
        errReport.setFieldReport(report);
        session.save(errReport);

        session.refresh(report);
        session.update(report);
        session.getTransaction().commit();
    }

    /**
     * Create the main measurement text that will be understood by the server.
     * 
     * @param patientRef the patient's reference/hos num.
     * @param xmlFile non-null existent file containing humphrey XML.
     * @param file the non-null image file.
     * @param fieldReport field report containing 
     * 
     * @return String measurement text.
     * 
     * @throws IOException if the 
     */
    private String generateMeasurementText(String patientRef, File xmlFile, File file, FieldReport fieldReport)
            throws IOException {

        // execute the operation
        File imageConverted = new File(file.getParentFile(), FilenameUtils.getBaseName(file.getName()) + ".gif");
        File imageCropped = new File(file.getParentFile(),
                FilenameUtils.getBaseName(file.getName()) + "-cropped.gif");
        this.transformImages(file, imageConverted, imageCropped);
        BASE64Encoder encoder = new BASE64Encoder();
        FileInputStream fis = new FileInputStream(imageConverted);
        String encodedData = encoder.encode(IOUtils.toByteArray(fis, fis.available()));

        encoder = new BASE64Encoder();
        fis = new FileInputStream(imageCropped);
        String encodedDataThumb = encoder.encode(IOUtils.toByteArray(fis, fis.available()));

        String reportText = null;

        reportText = this.getHumphreyMeasurement(xmlFile, String.format("%07d", new Integer(patientRef)),
                fieldReport, encodedData, encodedDataThumb);

        imageConverted.delete();
        imageCropped.delete();
        return reportText;
    }

    /**
     * Transfer the specified report to the server.
     *
     * @param reportText actual report text to send to the server.
     * @param fieldReport non-null report containing field exam information.
     * @param file non-null existent file containing CZM XML.
     */
    private void transferHumphreyVisualField(String reportText, FieldReport fieldReport, File file)
            throws IOException {
        HttpTransfer sender = new HttpTransfer();
        sender.setHost(this.getHost());
        sender.setPort(this.getPort());
        int code = sender.send("MeasurementVisualFieldHumphrey", reportText, this.authenticationUsername,
                this.authenticationPassword);
        if (code > -1) {
            this.generateCommsLog(code, DbUtils.FHIR_RESOURCE_TYPE_DIAGNOSTIC_REPORT, fieldReport,
                    sender.getResponse());
            if (code == 200) {
                boolean moved = file.renameTo(new File(this.archiveDir, file.getName()));
                if (!moved) {
                    log.log(Level.WARNING, "Unable to move {0}", file.getAbsolutePath());
                }
            }
        }

    }

    public String getHost() {
        return host;
    }

    public void setHost(String host) {
        this.host = host;
    }

    public File getOutgoingDir() {
        return outgoingDir;
    }

    public void setOutgoingDir(String outgoingDir) {
        this.outgoingDir = new File(outgoingDir);
    }

    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }

    public String getAuthenticationUsername() {
        return authenticationUsername;
    }

    public void setAuthenticationUsername(String authenticationUsername) {
        this.authenticationUsername = authenticationUsername;
    }

    public String getAuthenticationPassword() {
        return authenticationPassword;
    }

    public void setAuthenticationPassword(String authenticationPassword) {
        this.authenticationPassword = authenticationPassword;
    }
}