us.mn.state.health.lims.patient.saving.Accessioner.java Source code

Java tutorial

Introduction

Here is the source code for us.mn.state.health.lims.patient.saving.Accessioner.java

Source

/*
 * The contents of this file are subject to the Mozilla Public License
 * Version 1.1 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
 * License for the specific language governing rights and limitations under
 * the License.
 *
 * The Original Code is OpenELIS code.
 *
 * Copyright (C) The Minnesota Department of Health.  All Rights Reserved.
 *
 * Contributor(s): CIRG, University of Washington, Seattle WA.
 */

/**
 * Cte d'Ivoire
 * @author pahill
 * @since 2010-06-15
 **/
package us.mn.state.health.lims.patient.saving;

import org.apache.commons.beanutils.DynaBean;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.validator.GenericValidator;
import org.apache.struts.action.ActionErrors;
import org.apache.struts.action.ActionMessages;
import org.hibernate.Transaction;
import us.mn.state.health.lims.analysis.dao.AnalysisDAO;
import us.mn.state.health.lims.analysis.daoimpl.AnalysisDAOImpl;
import us.mn.state.health.lims.analysis.valueholder.Analysis;
import us.mn.state.health.lims.common.action.BaseActionForm;
import us.mn.state.health.lims.common.action.IActionConstants;
import us.mn.state.health.lims.common.exception.LIMSInvalidConfigurationException;
import us.mn.state.health.lims.common.exception.LIMSRuntimeException;
import us.mn.state.health.lims.common.log.LogEvent;
import us.mn.state.health.lims.common.services.NoteService;
import us.mn.state.health.lims.common.services.StatusService;
import us.mn.state.health.lims.common.services.StatusService.AnalysisStatus;
import us.mn.state.health.lims.common.services.StatusService.OrderStatus;
import us.mn.state.health.lims.common.services.StatusService.RecordStatus;
import us.mn.state.health.lims.common.services.StatusService.SampleStatus;
import us.mn.state.health.lims.common.services.StatusSet;
import us.mn.state.health.lims.common.util.DateUtil;
import us.mn.state.health.lims.common.util.StringUtil;
import us.mn.state.health.lims.common.util.SystemConfiguration;
import us.mn.state.health.lims.common.util.validator.ActionError;
import us.mn.state.health.lims.hibernate.HibernateUtil;
import us.mn.state.health.lims.note.dao.NoteDAO;
import us.mn.state.health.lims.note.daoimpl.NoteDAOImpl;
import us.mn.state.health.lims.note.valueholder.Note;
import us.mn.state.health.lims.observationhistory.dao.ObservationHistoryDAO;
import us.mn.state.health.lims.observationhistory.daoimpl.ObservationHistoryDAOImpl;
import us.mn.state.health.lims.observationhistory.valueholder.ObservationHistory;
import us.mn.state.health.lims.observationhistorytype.ObservationHistoryTypeMap;
import us.mn.state.health.lims.organization.dao.OrganizationDAO;
import us.mn.state.health.lims.organization.daoimpl.OrganizationDAOImpl;
import us.mn.state.health.lims.organization.valueholder.Organization;
import us.mn.state.health.lims.patient.dao.PatientDAO;
import us.mn.state.health.lims.patient.daoimpl.PatientDAOImpl;
import us.mn.state.health.lims.patient.util.PatientUtil;
import us.mn.state.health.lims.patient.valueholder.ObservationData;
import us.mn.state.health.lims.patient.valueholder.Patient;
import us.mn.state.health.lims.patientidentity.dao.PatientIdentityDAO;
import us.mn.state.health.lims.patientidentity.daoimpl.PatientIdentityDAOImpl;
import us.mn.state.health.lims.patientidentity.valueholder.PatientIdentity;
import us.mn.state.health.lims.patientidentitytype.util.PatientIdentityTypeMap;
import us.mn.state.health.lims.person.dao.PersonDAO;
import us.mn.state.health.lims.person.daoimpl.PersonDAOImpl;
import us.mn.state.health.lims.person.valueholder.Person;
import us.mn.state.health.lims.project.dao.ProjectDAO;
import us.mn.state.health.lims.project.daoimpl.ProjectDAOImpl;
import us.mn.state.health.lims.project.valueholder.Project;
import us.mn.state.health.lims.referencetables.daoimpl.ReferenceTablesDAOImpl;
import us.mn.state.health.lims.sample.dao.SampleDAO;
import us.mn.state.health.lims.sample.daoimpl.SampleDAOImpl;
import us.mn.state.health.lims.sample.form.ProjectData;
import us.mn.state.health.lims.sample.util.CI.BaseProjectFormMapper;
import us.mn.state.health.lims.sample.util.CI.BaseProjectFormMapper.TypeOfSampleTests;
import us.mn.state.health.lims.sample.util.CI.IProjectFormMapper;
import us.mn.state.health.lims.sample.util.CI.ProjectForm;
import us.mn.state.health.lims.sample.util.CI.ProjectFormMapperFactory;
import us.mn.state.health.lims.sample.valueholder.Sample;
import us.mn.state.health.lims.samplehuman.dao.SampleHumanDAO;
import us.mn.state.health.lims.samplehuman.daoimpl.SampleHumanDAOImpl;
import us.mn.state.health.lims.samplehuman.valueholder.SampleHuman;
import us.mn.state.health.lims.sampleitem.dao.SampleItemDAO;
import us.mn.state.health.lims.sampleitem.daoimpl.SampleItemDAOImpl;
import us.mn.state.health.lims.sampleitem.valueholder.SampleItem;
import us.mn.state.health.lims.sampleorganization.dao.SampleOrganizationDAO;
import us.mn.state.health.lims.sampleorganization.daoimpl.SampleOrganizationDAOImpl;
import us.mn.state.health.lims.sampleorganization.valueholder.SampleOrganization;
import us.mn.state.health.lims.sampleproject.dao.SampleProjectDAO;
import us.mn.state.health.lims.sampleproject.daoimpl.SampleProjectDAOImpl;
import us.mn.state.health.lims.sampleproject.valueholder.SampleProject;
import us.mn.state.health.lims.test.dao.TestDAO;
import us.mn.state.health.lims.test.daoimpl.TestDAOImpl;
import us.mn.state.health.lims.test.valueholder.Test;
import us.mn.state.health.lims.typeofsample.valueholder.TypeOfSample;

import java.lang.reflect.InvocationTargetException;
import java.sql.Timestamp;
import java.util.*;
import java.util.Map.Entry;

import static us.mn.state.health.lims.sample.util.CI.ProjectForm.EID;
import static us.mn.state.health.lims.sample.util.CI.ProjectForm.SPECIAL_REQUEST;

/**
 * Update/Creates, as needed, a Sample and Patient and all associated parts in
 * order to enter that patient in the system. To use one of these to accession,
 * you should:
 * <nl>
 * <li>Provide a constructor (which probably includes whatever structure comes
 * from your UI form, so it can be used below).
 * <li>set the expected new RecordStatus for the patient and sample (maybe in
 * the constructor), but you can change that later.
 * <li>implement canAccession() to decide if this is the right time to try this
 * accession algorithm.
 * <li>implement validate* and match* methods to make sure all the entities
 * created and found hang together.
 * <li>implement the missing populate* methods (including providing any empty
 * implementation).
 * <li>possibly override the persist* methods, if there is more to do than
 * simply saving what has been built (delete any old ones? Update instead?).
 * </nl>
 * Use:<br/>
 * if (myAccessioner1.canAccession()) { if (!myAccession1.accession(...)) {
 * errors = myAccession1.getMessages(); saveErrors(request, errors);
 * request.setAttribute(Globals.ERROR_KEY, errors); return
 * mapping.findForward(FWD_FAIL); } else { return
 * mapping.findForward(FWD_SUCCESS); } } else (myAccession2.canAccession() { ...
 * }
 * 
 * PAH 07/2010 This object is still a work in progress. For example, we have a
 * member for projectFormMapper, but all its use in the subclasses at this time
 * . Maybe the projectFormMapper should just be injected after creation? It is
 * also the case that there are form field property names listed in these
 * various acccesioning classes when the formFieldMapper class is really the
 * class that should know the right place to find values on the submitted form.
 * 
 * @author pahill
 */

public abstract class Accessioner {

    /**
     * a set of possible analysis status that means an analysis is done
     */
    private Set<String> analysisDone = new HashSet<String>();
    {
        analysisDone.add(StatusService.getInstance().getStatusID(StatusService.AnalysisStatus.Finalized));
        analysisDone.add(
                StatusService.getInstance().getStatusID(StatusService.AnalysisStatus.NonConforming_depricated));
        analysisDone.add(StatusService.getInstance().getStatusID(StatusService.AnalysisStatus.Canceled));
    }

    /**
     * Sample or Patient Entry always mark the type of an analysis as a MANUAL
     * type.
     */
    private static final String DEFAULT_ANALYSIS_TYPE = IActionConstants.ANALYSIS_TYPE_MANUAL;
    // private static String OBSERVATION_HISTORY_YES_ID = null;
    private static String SAMPLE_TABLE_ID = null;

    static {
        SAMPLE_TABLE_ID = new ReferenceTablesDAOImpl().getReferenceTableByName("sample").getId();
        //      OBSERVATION_HISTORY_YES_ID = new DictionaryDAOImpl().getDictionaryByDictEntry("Demographic Response Yes (in Yes or No)").getId();
    }

    /**
     * Find the projectFormName where ever we normally store it. This is for
     * that could which needs this information before creating a
     * projectFormMapper
     * 
     * @param form
     * @return the current projectFormName
     * @throws IllegalAccessException
     * @throws InvocationTargetException
     * @throws NoSuchMethodException
     */
    public static String findProjectFormName(DynaBean form)
            throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
        String projectFormName = ((ObservationData) (PropertyUtils.getProperty(form, "observations")))
                .getProjectFormName();
        return projectFormName;
    }

    /**
     * @param dynaBean
     * @return
     * @throws NoSuchMethodException
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     * @throws LIMSRuntimeException
     */
    public static IProjectFormMapper getProjectFormMapper(DynaBean dynaBean)
            throws LIMSRuntimeException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
        return new ProjectFormMapperFactory().getProjectInitializer(findProjectFormName(dynaBean),
                (BaseActionForm) dynaBean);
    }

    /**
     * @param dynaBean
     * @return
     */
    public static IProjectFormMapper getProjectFormMapper(String projectFormName, DynaBean dynaBean) {
        return new ProjectFormMapperFactory().getProjectInitializer(projectFormName, (BaseActionForm) dynaBean);
    }

    protected String accessionNumber;
    protected String patientIdentifier;
    protected String patientSiteSubjectNo;
    protected StatusSet statusSet;
    ActionMessages messages = new ActionMessages();

    protected java.sql.Date today;
    protected String todayAsText;
    protected String sysUserId;
    protected ProjectForm projectForm;

    protected Patient patientInDB;
    private List<PatientIdentity> patientIdentities = new ArrayList<PatientIdentity>();
    protected List<ObservationHistory> observationHistories = new ArrayList<ObservationHistory>();

    Map<String, List<ObservationHistory>> observationHistoryLists = null;

    protected Sample sample;
    protected List<SampleItemAnalysisCollection> sampleItemsAnalysis = new ArrayList<SampleItemAnalysisCollection>();
    protected List<SampleOrganization> sampleOrganizations = new ArrayList<SampleOrganization>();

    protected SampleHuman sampleHuman;
    protected SampleProject sampleProject;

    protected RecordStatus newPatientStatus;
    protected RecordStatus newSampleStatus;

    protected PatientDAO patientDAO = new PatientDAOImpl();
    protected PersonDAO personDAO = new PersonDAOImpl();
    protected PatientIdentityDAO identityDAO = new PatientIdentityDAOImpl();
    protected SampleDAO sampleDAO = new SampleDAOImpl();
    protected SampleHumanDAO sampleHumanDAO = new SampleHumanDAOImpl();
    protected SampleProjectDAO sampleProjectDAO = new SampleProjectDAOImpl();
    protected ObservationHistoryDAO observationHistoryDAO = new ObservationHistoryDAOImpl();
    protected ProjectDAO projectDAO = new ProjectDAOImpl();
    protected OrganizationDAO organizationDAO = new OrganizationDAOImpl();
    protected SampleOrganizationDAO sampleOrganizationDAO = new SampleOrganizationDAOImpl();
    protected SampleItemDAO sampleItemDAO = new SampleItemDAOImpl();
    protected TestDAO testDAO = new TestDAOImpl();
    protected AnalysisDAO analysisDAO = new AnalysisDAOImpl();
    private NoteDAO noteDAO = new NoteDAOImpl();

    protected boolean existingSample;
    protected boolean existingPatient;
    protected IProjectFormMapper projectFormMapper;

    protected Patient patientToDelete;
    protected List<Analysis> analysisToUpdate = new ArrayList<Analysis>();
    protected List<Analysis> analysisToDelete = new ArrayList<Analysis>();
    protected List<SampleItem> sampleItemsToDelete = new ArrayList<SampleItem>();
    private boolean aDifferentPatientRecord = false;
    private ProjectData projectData;
    private ObservationData observationData;

    protected Accessioner(String sampleIdentifier, String patientIdentifier, String siteSubjectNo,
            String sysUserId) {
        this();
        this.accessionNumber = sampleIdentifier;
        this.patientIdentifier = patientIdentifier;
        this.patientSiteSubjectNo = siteSubjectNo;
        this.sysUserId = sysUserId;
    }

    public Accessioner() {
        today = new java.sql.Date(System.currentTimeMillis());
        todayAsText = DateUtil.formatDateAsText(today);
    }

    /**
     * Primary entry point for processing a patient/sample combination Check the
     * messages, on true there may have been errors
     * 
     * @return TRUE => errors FALSE => did not do it, had a problem doing it.
     * @throws Exception
     * @throws Exception
     */
    public String save() throws Exception {
        Transaction tx = null;
        try {
            if (!canAccession()) {
                return null;
            }
            existingPatient = findPatient();
            if (existingPatient && !validateFoundPatient()) {
                return IActionConstants.FWD_FAIL;
            }
            existingSample = findSample();
            if (existingSample && !validateFoundSample()) {
                return IActionConstants.FWD_FAIL;
            }
            if (!matchPatientAndSample()) {
                return IActionConstants.FWD_FAIL;
            }

            populatePatientData();
            populateSampleData();
            populateSampleHuman();
            populateObservationHistory();

            tx = HibernateUtil.getSession().beginTransaction();
            // all of the following methods are assumed to only write when
            // necessary
            persistPatient();
            persistIdentityTypes();

            persistSampleData();

            persistSampleHuman();

            persistObservationHistory();
            persistObservationHistoryLists();
            persistRecordStatus();
            deleteOldPatient();
            populateAndPersistUnderInvestigationNote();
            tx.commit();
            return IActionConstants.FWD_SUCCESS;
        } catch (Exception e) {
            if (null != tx) {
                tx.rollback();
            }
            logAndAddMessage("save()", "errors.InsertException", e);
            return IActionConstants.FWD_FAIL;
        } finally {
            HibernateUtil.closeSession();
        }
    }

    private void populateAndPersistUnderInvestigationNote() {
        // N.B. The notes is being attached to the Sample table rather than the
        // observation history because the observation history table
        // is being updated by inserting new observation histories and then
        // deleting the old ones. Any references to the old observation history
        // under investigation row are being lost. Until we fix that the notes
        // will be attached to the sample with note type of "UnderInvestigation"
        if (//OBSERVATION_HISTORY_YES_ID.equals(observationData.getUnderInvestigation()) && <-- not sure of the business rules around this
        !GenericValidator.isBlankOrNull(projectData.getUnderInvestigationNote())) {

            Note note = new Note();
            note.setNoteType(Note.EXTERNAL);
            note.setReferenceId(sample.getId());
            note.setReferenceTableId(SAMPLE_TABLE_ID);

            List<Note> noteList = noteDAO.getNotesByNoteTypeRefIdRefTable(note);

            if (noteList != null && !noteList.isEmpty()) {
                note = noteList.get(0);
                note.setText(note.getText() + "<br/>" + getActionLabel() + ": "
                        + projectData.getUnderInvestigationNote());
            } else {
                note.setText(getActionLabel() + ": " + projectData.getUnderInvestigationNote());
                note.setSubject("UnderInvestigation");
            }

            note.setSysUserId(sysUserId);
            note.setSystemUser(NoteService.createSystemUser(sysUserId));

            if (note.getId() == null) {
                noteDAO.insertData(note);
            } else {
                noteDAO.updateData(note);
            }
        }
    }

    protected void persistSampleData() throws Exception {
        persistSample();
        persistSampleProject();
        persistSampleOrganization();
        persisteSampleItemsChanged();
        persistSampleItemsAndAnalysis();
    }

    /**
     * This is probably the result of Sample(Second)Entry, so nothing to due by
     * default
     */
    private void persisteSampleItemsChanged() {
        analysisDAO.deleteData(analysisToDelete);
        sampleItemDAO.deleteData(sampleItemsToDelete);
        for (Analysis analysis : this.analysisToUpdate) {
            analysisDAO.updateData(analysis);
        }
    }

    /**
     * This method is called when this object contains a patient from the
     * database found by ID. Sometimes we need to validate some of the fields
     * with the values from the input. Place an error in the messages list to
     * return an error.
     * 
     * @return TRUE => all is well with the patient we have found in the
     *         database vs. the data coming from the input.
     */
    protected boolean validateFoundPatient() {
        return true;
    }

    /**
     * This method is called when this object contains a sample from the
     * database found by ID. Sometimes we need to validate some of the fields
     * with the values from the input. Place an error in the messages list to
     * return an error.
     * 
     * @return TRUE => all is well with the SAMPLE we have found in the database
     *         vs. the data coming from the input.
     */
    protected boolean validateFoundSample() {
        return true;
    }

    /**
     * This method is called when there is something to check to make sure the
     * patient and sample go together correctly.
     * 
     * @return TRUE => all looks good.
     */
    protected boolean matchPatientAndSample() {
        return true;
    }

    /**
     * Use appropriate means to come up with a patient and its person record.
     * 
     * @return TRUE => patient was found in the database, FALSE => patient not
     *         found, simply created.
     */
    protected boolean findPatient() {
        String patientId = this.statusSet.getPatientId();
        String sampleId = this.statusSet.getSampleId();
        String unknownPatient = PatientUtil.getUnknownPatient().getId();
        if (sampleId == null) {
            // no sample => nothing to leverage so build up from what we have
            return createPatientByIdentifiers();
        }
        if (patientId.equals(unknownPatient)) {
            // the UNKNOWN patient, so we'll create a new one
            setADifferentPatient(true);
            return createNewPatient();
        }
        // the patient is already associated with the existing sample and its
        // not the unknown one
        patientInDB = patientDAO.readPatient(patientId);
        if (patientIdentifiersDoNotMatch()) {
            Patient otherPatient = findPatientByIndentifiers();
            if (otherPatient == null) { // unknown
                int otherSamplesOnCurrentPatient = sampleHumanDAO.getSamplesForPatient(patientId).size() - 1;
                if (otherSamplesOnCurrentPatient == 0) {
                    // stick with the existing patient of the sample, update it
                    return true;
                } else {
                    // it's a new patient, so we might have to move some old
                    // pointers around.
                    setADifferentPatient(true);
                    return createNewPatient();
                }
            } else {
                // it's another existing patient, so use that and don't forget
                // we're doing that.
                patientInDB = otherPatient;
                setADifferentPatient(false);
                return true;
            }
        }
        return true;
    }

    private boolean patientIdentifiersDoNotMatch() {

        String existingSubjectNo = StringUtil.replaceNullWithEmptyString(patientInDB.getNationalId()).trim();

        if (!GenericValidator.isBlankOrNull(this.patientIdentifier)
                && this.patientIdentifier.equals(existingSubjectNo)) {
            return false;
        }

        String existingSiteSubjectNo = StringUtil.replaceNullWithEmptyString(patientInDB.getExternalId()).trim();

        if (!GenericValidator.isBlankOrNull(this.patientSiteSubjectNo)
                && this.patientSiteSubjectNo.equals(existingSiteSubjectNo)) {
            return false;
        }

        return true;
    }

    /**
     * Either find it by the primary or secondary identifier or return a new one
     */
    private boolean createPatientByIdentifiers() {
        patientInDB = findPatientByIndentifiers();
        if (patientInDB == null) {
            return createNewPatient();
        } else {
            return true;
        }
    }

    private Patient findPatientByIndentifiers() {
        Patient aPatient;
        if (this.patientIdentifier != "") {
            aPatient = patientDAO.getPatientByNationalId(this.patientIdentifier);
        } else {
            String externalId = this.projectFormMapper.getSiteSubjectNumber();
            aPatient = patientDAO.getPatientByExternalId(externalId);
        }
        return aPatient;
    }

    /**
     * @return FALSE since it doesn't already exist
     */
    protected boolean createNewPatient() {
        patientInDB = new Patient();
        patientInDB.setPerson(new Person());
        return false;
    }

    /**
     * 
     * @return true = existing sample
     * @throws LIMSRuntimeException
     * @throws LIMSInvalidConfigurationException
     */
    protected boolean findSample() throws LIMSRuntimeException, LIMSInvalidConfigurationException {
        sample = sampleDAO.getSampleByAccessionNumber(accessionNumber);
        String sampleId = this.statusSet.getSampleId();
        // if there is sample for the given acc. number is an existing sample it
        // had better be same one we found when we loading the sampleSet
        if (sample != null && !isNewSample()) {
            if (!sample.getId().equals(sampleId)) {
                messages.add(ActionErrors.GLOBAL_MESSAGE,
                        new ActionError("errors.may_not_reuse_accession_number", accessionNumber));
                throw new RuntimeException("You can not re-use an existing accessionNumber " + accessionNumber);
            }
            return true;
        } else {
            // the accession number is unknown, so load/build the sample using
            // the sampleStatus ID
            sample = new Sample();
            if (sampleId == null) {
                return false; // it brand new, no existing accessionNumber in
                              // use, not known sampleId provided
            } else {
                sample = knownSampleTemplate();
                sampleDAO.getData(sample);
                sample.setAccessionNumber(accessionNumber);
                return true; // it existed previously, but we have a new
                             // (edited) accesionNumber
            }
        }
    }

    private Sample knownSampleTemplate() {
        Sample sample = new Sample();
        sample.setId(this.statusSet.getSampleId());
        return sample;
    }

    /**
     * @return if the sample we are working with is NOT in the database this
     *         object is about to create it.
     */
    protected boolean isNewSample() {
        return sample.getId() == null;
    }

    /**
     * @return TRUE if the patient is not the known patient already associated
     *         with the known sample.
     */
    protected boolean isADifferentPatient() {
        return this.aDifferentPatientRecord;
    }

    /**
     * Call this to record that when we started, the patient record associated
     * with the original sample record is NOT the record we are now working
     * with. We record this because, once we updating records to write, we can
     * longer compare IDs to come up with the answer.
     */
    protected void setADifferentPatient(boolean force) {
        //TODO 'force' was added as a safety because aDifferentPatientRecourd was not being set to true when it should
        // have.  The original test for aDifferentPatientRecord may still be needed, in the two places force is set
        // to true
        aDifferentPatientRecord = (force || patientInDB == null || statusSet.getPatientId() != patientInDB.getId());
    }

    /**
     * Test this object to see if we should even begin. This is intended for
     * checking existing patient/sample record status.
     * 
     * @return TRUE => all is well; FALSE (Default) => this particular version
     *         of the accession process is not appropriate for the status
     *         combination.
     */
    abstract public boolean canAccession();

    /**
     * load up this object with any new observation history records, including
     * lists
     */
    protected void populateObservationHistory() {
        projectFormMapper.getDynaBean();
        observationData = (ObservationData) (projectFormMapper.getDynaBean().get("observations"));
        this.observationHistories = projectFormMapper.readObservationHistories(observationData);
        this.observationHistoryLists = projectFormMapper.readObservationHistoryLists(observationData);
    }

    public StatusSet findStatusSet() {
        if (statusSet == null) {
            String sampleId = projectFormMapper.getSampleId();
            if (GenericValidator.isBlankOrNull(sampleId)) {
                statusSet = StatusService.getInstance().getStatusSetForAccessionNumber(accessionNumber);
            } else {
                statusSet = StatusService.getInstance().getStatusSetForSampleId(sampleId);
            }
        }
        return statusSet;
    }

    /**
     * Move data from the form to the sample and sample related objects, include
     * SampleOrganization but not to any of the entities which tie a patient to
     * a sample; don't include ObservationHistory and SampleHuman
     * 
     * @throws Exception
     *             if things go wrong.
     */
    abstract protected void populateSampleData() throws Exception;

    /**
     * Create any appropriate sample human entity
     * 
     * @throws Exception
     */
    protected void populateSampleHuman() throws Exception {
        if (isNewSample()) {
            sample.setStatusId(StatusService.getInstance().getStatusID(OrderStatus.Entered));
            sampleHuman = new SampleHuman();
        } else {
            if (isNewPatient() || isADifferentPatient()) {
                sampleHuman = new SampleHuman();
                sampleHuman.setSampleId(sample.getId());
                sampleHumanDAO.getDataBySample(sampleHuman);
            }
        }
        if (isADifferentPatient()) {
            int size = sampleHumanDAO.getSamplesForPatient(statusSet.getPatientId()).size();
            if (size == 1) {
                // if there is only one sample on the OLD patient of this sample
                // we can delete it.
                Patient patient = knownPatientTemplate();
                patientToDelete = patient;
                patientDAO.getData(patientToDelete);
            }
        }
    }

    private boolean isNewPatient() {
        return patientInDB.getId() == null;
    }

    /**
     * Build an example Patient record with the ID only for the current patient
     * of the sample
     * 
     * @return
     */
    private Patient knownPatientTemplate() {
        Patient patient = new Patient();
        patient.setId(statusSet.getPatientId());
        return patient;
    }

    protected void populateSample(Timestamp receivedDateForDisplay, Timestamp collectionDateForDisplay)
            throws Exception {
        sample.setAccessionNumber(accessionNumber);
        sample.setReceivedTimestamp(receivedDateForDisplay);
        sample.setCollectionDate(collectionDateForDisplay);

        // and all the administration fields.
        if (isNewSample()) {
            sample.setEnteredDateForDisplay(todayAsText);
            sample.setEnteredDate(today);
        }
        sample.setDomain(SystemConfiguration.getInstance().getHumanDomain());
    }

    protected void populateSampleProject() {
        if (!isSampleInProject()) {
            Project project = projectForm.getProject();
            sampleProject = new SampleProject();
            sampleProject.setProject(project);
        }
    }

    /**
     * @return TRUE only if the sample is already associated with a project.
     */
    private boolean isSampleInProject() {
        if (isNewSample()) {
            return false;
        }
        SampleProject oldSampleProject = sampleProjectDAO.getSampleProjectBySampleId(this.sample.getId());
        return (oldSampleProject == null) ? false : true;
    }

    /**
     * create a sampleOrganization record, finding any old one to delete. While
     * there can be more than one organization per sample, this code only
     * supports one.
     * 
     * @param organizationId
     *            if none null, use it; otherwise do nothing
     */
    protected void populateSampleOrganization(String organizationId) {
        if (GenericValidator.isBlankOrNull(organizationId)
                || organizationId.equals(BaseProjectFormMapper.ORGANIZATION_ID_NONE)) {
            return;
        }
        Organization org = new Organization();
        org.setId(organizationId);
        organizationDAO.getData(org);

        if (null == org.getId()) {
            throw new LIMSRuntimeException("Undefined organization name = " + organizationId
                    + ". Unable to create sample to organization mapping.");
        }

        SampleOrganization oldSampleOrg = null;
        if (!isNewSample()) {
            oldSampleOrg = new SampleOrganization();
            oldSampleOrg.setSampleId(sample.getId());
            sampleOrganizationDAO.getDataBySample(oldSampleOrg);
            if (oldSampleOrg.getOrganization() != null) { // there may not be an
                // organiztion
                // attached yet
                String oldOrganizationId = oldSampleOrg.getOrganization().getId();
                if (oldOrganizationId.equals(organizationId)) {
                    return; // we have an existing sample with the right
                            // organization already, so don't bother with delete
                            // or insert
                }
                oldSampleOrg.setSysUserId(sysUserId);
                oldSampleOrg.setOrganization(org);
                sampleOrganizations.add(oldSampleOrg);
            }
        }
        SampleOrganization so = new SampleOrganization();
        so.setOrganization(org);
        // sampleOrganization.setSampleOrganizationType("?"); // nothing
        // important
        so.setSysUserId(sysUserId);
        sampleOrganizations.add(so);
    }

    /**
     * Add to the list of patient identities which will be saved.
     * 
     * @param patientIdentityTypeName
     * @param paramValue
     *            a value for a particular patient identity; if null, do
     *            nothing.
     */
    protected void addPatientIdentity(String patientIdentityTypeName, String paramValue) {
        if (paramValue == null) {
            return;
        }
        String typeID = PatientIdentityTypeMap.getInstance().getIDForType(patientIdentityTypeName);
        PatientIdentity identity = new PatientIdentity();
        identity.setPatientId(patientInDB.getId());
        identity.setIdentityTypeId(typeID);
        identity.setIdentityData(paramValue);
        patientIdentities.add(identity);
    }

    /**
     * Assume all the fields on the dynaForm have the right names (see code) and
     * @throws IllegalAccessException
     * @throws InvocationTargetException
     * @throws NoSuchMethodException
     */
    protected void populatePatientData()
            throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
        DynaBean dynaForm = projectFormMapper.getDynaBean();
        Timestamp lastupdated1 = patientInDB.getLastupdated();
        PropertyUtils.copyProperties(patientInDB, dynaForm);
        patientInDB.setLastupdated(lastupdated1);
        Person person = patientInDB.getPerson();
        PropertyUtils.copyProperties(person, dynaForm);

        patientInDB.setNationalId(convertEmptyToNull((String) dynaForm.get("subjectNumber")));
        patientInDB.setExternalId(convertEmptyToNull((String) dynaForm.get("siteSubjectNumber")));
        populatePatientBirthDate((String) dynaForm.get("birthDateForDisplay"));

        projectData = (ProjectData) dynaForm.get("ProjectData");
        if (projectData != null) {
            person.setHomePhone(convertEmptyToNull(projectData.getPhoneNumber()));
            person.setFax(convertEmptyToNull(projectData.getFaxNumber()));
            person.setEmail(convertEmptyToNull(projectData.getEmail()));
            person.setStreetAddress(convertEmptyToNull(projectData.getAddress()));
        }
    }

    /**
     * @param value
     *            - string to test
     * @return if the string is null or empty "", then return null
     */
    private String convertEmptyToNull(String value) {
        return (GenericValidator.isBlankOrNull(value)) ? null : value;
    }

    /**
     * Given a possibly ambiguous birthday date string, get the right values
     * into both (1) the patient birthdate and (2) make sure we create a
     * patient_identity for any ambiguous value.
     */
    protected void populatePatientBirthDate(String birthDateForDisplay) {
        patientInDB.setBirthDateForDisplay(birthDateForDisplay);
    }

    protected void populateSampleItems(List<TypeOfSampleTests> typeofSampleTestList, Timestamp collectionDate)
            throws Exception {
        sampleItemsAnalysis = new ArrayList<SampleItemAnalysisCollection>();

        if (typeofSampleTestList.size() == 0) {
            messages.add(ActionErrors.GLOBAL_MESSAGE, new ActionError("errors.no.tests"));
            throw new Exception("No tests selected.");
        }

        for (TypeOfSampleTests typeofSampleTest : typeofSampleTestList) {
            TypeOfSample toSample = typeofSampleTest.typeOfSample;
            List<Test> tests = typeofSampleTest.tests;
            SampleItem item = buildSampleItem(sample, toSample, collectionDate);

            String collectionDateTime = DateUtil.convertTimestampToStringDateAndTime(collectionDate);
            sampleItemsAnalysis.add(new SampleItemAnalysisCollection(item, tests, collectionDateTime));
        }
    }

    protected SampleItem buildSampleItem(Sample sample, TypeOfSample typeofsample, Timestamp collectionDate) {
        SampleItem item = new SampleItem();
        item.setTypeOfSample(typeofsample);
        item.setSortOrder(Integer.toString(0));
        item.setStatusId(StatusService.getInstance().getStatusID(SampleStatus.Entered));
        item.setCollectionDate(collectionDate);
        return item;
    }

    protected final class SampleItemAnalysisCollection {
        public SampleItem item;
        public List<Test> tests;
        public String collectionDate;

        public SampleItemAnalysisCollection(SampleItem item, List<Test> tests, String collectionDate)
                throws Exception {
            // Currently we allow a sampleItem w/o any tests requested,
            // elsewhere we check that at least one test is ordered somewhere.
            // if (tests.size() == 0) {
            // messages.add(ActionErrors.GLOBAL_MESSAGE, new
            // ActionError("errors.samples.with.no.tests"));
            // throw new
            // Exception("A least one sample was defined without any corresponding test selected.");
            // }
            this.item = item;
            this.tests = tests;
        }
    }

    /**
     * Delete any new ones and remove any old ones.
     */
    protected void persistSampleOrganization() {
        for (SampleOrganization so : sampleOrganizations) {
            sampleDAO.getData(sample);
            so.setSample(sample);
            if (so.getId() == null) {
                sampleOrganizationDAO.insertData(so);
            } else {
                sampleOrganizationDAO.updateData(so);
            }
        }
    }

    protected void persistSampleItemsAndAnalysis() throws Exception {
        if (0 == sampleItemsAnalysis.size()) {
            return;
        }

        // Find all the already created sample items for this sample
        Map<String, SampleItem> itemsByType = findExistingSampleTypeItems();
        int nextSortOrder = calcLastSortOrder(itemsByType) + 1;

        String analysisRevision = SystemConfiguration.getInstance().getAnalysisDefaultRevision();
        boolean newAnalysis = false;
        for (SampleItemAnalysisCollection sampleTestPair : sampleItemsAnalysis) {

            // create new or find existing sample item for testing.
            SampleItem item = sampleTestPair.item;
            SampleItem existingSampleItem = itemsByType.get(item.getTypeOfSampleId());
            if (existingSampleItem == null) {
                item.setSample(sample);
                item.setSortOrder(Integer.toString(nextSortOrder++));
                item.setSysUserId(sysUserId);

                sampleItemDAO.insertData(item);
            } else {
                sampleTestPair.item = item = existingSampleItem;
            }

            List<String> existingTests = findDefinedTestsForItem(item);
            // insert any missing analysis
            for (Test newTest : sampleTestPair.tests) {
                testDAO.getData(newTest);
                if (!existingTests.contains(newTest.getId())) {
                    Analysis analysis = buildAnalysis(analysisRevision, sampleTestPair, newTest);
                    analysisDAO.insertData(analysis, false);
                    newAnalysis = true;
                }
            }
        }
        // if we didn't create any analysis, we need to check if the sample is
        // completely done.
        if (!newAnalysis) {
            completeSample();
        }
    }

    /**
     * @param itemsByType
     * @return the maximum value which has already been used in the set of
     *         SampleItems
     */
    private int calcLastSortOrder(Map<String, SampleItem> itemsByType) {
        int max = 0;
        for (Entry<String, SampleItem> entry : itemsByType.entrySet()) {
            SampleItem sampleItem = entry.getValue();
            int curr = Integer.valueOf(sampleItem.getSortOrder());
            if (max < curr) {
                max = curr;
            }
        }
        return max;
    }

    /**
     * Find all tests of the item which have had analysis already ordered
     * (defined).
     * 
     * @param item
     * @return a list of test IDs
     */
    private List<String> findDefinedTestsForItem(SampleItem item) {
        List<Analysis> analysesForItem = analysisDAO.getAnalysesBySampleItem(item);
        List<String> testIds = new ArrayList<String>();
        for (Analysis analysis : analysesForItem) {
            Test test = analysis.getTest();
            testIds.add(test.getId());
        }
        return testIds;
    }

    private Map<String, SampleItem> findExistingSampleTypeItems() {
        List<SampleItem> itemsForSample = sampleItemDAO.getSampleItemsBySampleId(sample.getId());
        Map<String, SampleItem> itemsByType = new HashMap<String, SampleItem>();
        for (SampleItem sampleItem : itemsForSample) {
            String id = sampleItem.getTypeOfSampleId();
            itemsByType.put(id, sampleItem);
        }
        return itemsByType;
    }

    /**
     * If during entry we find (1) all the analysis are finished, and (2) the
     * sample is not already been declared bad, then we're ready to mark the
     * sample as done.
     * 
     * @throws Exception
     */
    public void completeSample() throws Exception {
        if (isAllAnalysisDone() && !StatusService.getInstance().getStatusID(OrderStatus.NonConforming_depricated)
                .equals(sample.getStatus())) {
            sample.setStatusId(StatusService.getInstance().getStatusID(OrderStatus.Finished));
            sample.setSysUserId(sysUserId);
            sampleDAO.updateData(sample);
        }
    }

    /**
     * The question is whether we are ready to update the sample status. TODO
     * Pahill maybe we could move this to StatusService.getInstance()?
     */
    private boolean isAllAnalysisDone() {
        List<Analysis> analyses = analysisDAO.getAnalysesBySampleId(sample.getId());
        for (Analysis analysis : analyses) {
            if (!analysisDone.contains(analysis.getStatusId())) {
                return false;
            }
        }
        return true;
    }

    private Analysis buildAnalysis(String analysisRevision, SampleItemAnalysisCollection sampleTestCollection,
            Test test) {
        java.sql.Date collectionDateTime = DateUtil.convertStringDateToSqlDate(sampleTestCollection.collectionDate);

        Analysis analysis = new Analysis();
        analysis.setTest(test);
        analysis.setIsReportable(test.getIsReportable());
        analysis.setAnalysisType(DEFAULT_ANALYSIS_TYPE);
        analysis.setSampleItem(sampleTestCollection.item);
        analysis.setRevision(analysisRevision);
        analysis.setStartedDate(collectionDateTime);
        analysis.setStatusId(StatusService.getInstance().getStatusID(AnalysisStatus.NotStarted));
        analysis.setTestSection(test.getTestSection());
        analysis.setSysUserId(sysUserId);
        return analysis;
    }

    /**
     * save patient
     */
    protected void persistPatient() {
        if (patientInDB != null) {
            Person person = patientInDB.getPerson();
            person.setSysUserId(sysUserId);
            patientInDB.setSysUserId(sysUserId);
            if (patientInDB.getId() == null) {
                personDAO.insertData(person);
                patientDAO.insertData(patientInDB);
            } else {
                // The reason for the explicit person update is to capture the
                // history.
                personDAO.updateData(person);
                patientDAO.updateData(patientInDB);
            }
        }
    }

    /**
     * Save whatever patient identities have been created.
     * 
     * @throws LIMSRuntimeException
     */
    public void persistIdentityTypes() throws LIMSRuntimeException {

        if (0 == patientIdentities.size()) {
            return;
        }
        for (PatientIdentity identity : patientIdentities) {
            identity.setPatientId(patientInDB.getId());
            identity.setSysUserId(sysUserId);
            identityDAO.insertData(identity);
        }
    }

    protected void persistSample() throws LIMSRuntimeException, Exception {
        if (null != sample) {
            sample.setSysUserId(sysUserId);
            if (sample.getId() != null) {
                sampleDAO.updateData(sample);
                HibernateUtil.getSession().evict(sample);
            } else {
                sampleDAO.insertDataWithAccessionNumber(sample);
            }
        }
    }

    protected void persistSampleProject() throws LIMSRuntimeException {
        if (null != sampleProject) {
            sampleProject.setSample(sample);
            sampleProject.setSysUserId(sysUserId);
            sampleProjectDAO.insertData(sampleProject);
        }
    }

    /**
     * Persist any old or new sampleHuman we're currently holding.
     */
    protected void persistSampleHuman() {
        if (sampleHuman != null) {
            sampleHuman.setPatientId(patientInDB.getId());
            sampleHuman.setSampleId(sample.getId());
            // we do not store any doctor name as a provider in SampleHuman
            sampleHuman.setSysUserId(sysUserId);
            if (null == sampleHuman.getId()) {
                sampleHumanDAO.insertData(sampleHuman);
            } else {
                sampleHumanDAO.updateData(sampleHuman);
            }
        }
    }

    /**
     * Persist any simple observations histories in this accession object.
     */
    protected void persistObservationHistory() {
        if (isADifferentPatient()) {
            List<ObservationHistory> oldOHes = observationHistoryDAO.getAll(knownPatientTemplate(),
                    knownSampleTemplate());
            for (ObservationHistory oldOh : oldOHes) {
                oldOh.setSampleId(sample.getId());
                oldOh.setPatientId(patientInDB.getId());
                oldOh.setSysUserId(sysUserId);
                observationHistoryDAO.updateData(oldOh);
            }
        }

        for (ObservationHistory newOh : observationHistories) {
            List<ObservationHistory> existingTypeOHes = observationHistoryDAO.getAll(patientInDB, sample,
                    newOh.getObservationHistoryTypeId());
            List<ObservationHistory> deleteTypeOHes = new ArrayList<ObservationHistory>();
            // delete any matching old ones, but only when there are both same
            // Ob. History type AND the same value type. Why because the
            // database allows more than one of the same type,
            // and we store e.g. nationality twice as two types, D=dictionary
            // for dropdown value, L=value used when there the menu value is
            // other, and there is a literal with text for other.
            String newValueType = newOh.getValueType();
            for (ObservationHistory oh : existingTypeOHes) {
                if (oh.getValueType().equals(newValueType)) {
                    oh.setSysUserId(sysUserId);
                    deleteTypeOHes.add(oh);
                }
            }
            if (deleteTypeOHes.size() > 0) {
                observationHistoryDAO.delete(deleteTypeOHes);
            }

            newOh.setSampleId(sample.getId());
            newOh.setPatientId(patientInDB.getId());
            newOh.setSysUserId(sysUserId);
            observationHistoryDAO.insertData(newOh);
        }
    }

    //Note -- this version does not do the delete insert model of updates but we are putting off
    // rolling it out because of the cost of testing
    /*   protected void persistObservationHistory() {
          if (isADifferentPatient()) {
     List<ObservationHistory> oldOHes = observationHistoryDAO.getAll(knownPatientTemplate(), knownSampleTemplate());
     for (ObservationHistory oldOh : oldOHes) {
        oldOh.setSampleId(sample.getId());
        oldOh.setPatientId(patientInDB.getId());
        oldOh.setSysUserId(sysUserId);
        observationHistoryDAO.updateData(oldOh);
     }
          }
        
          for (ObservationHistory newOh : observationHistories) {
     boolean machedInDB = false;
     List<ObservationHistory> existingTypeOHes = observationHistoryDAO.getAll(patientInDB, sample,
           newOh.getObservationHistoryTypeId());
     List<ObservationHistory> deleteTypeOHes = new ArrayList<ObservationHistory>();
     // update any matching old ones, but only when there are both same
     // Ob. History type AND the same value type. Why because the
     // database allows more than one of the same type,
     // and we store e.g. nationality twice as two types, D=dictionary
     // for dropdown value, L=value used when there the menu value is
     // other, and there is a literal with text for other.
     String newValueType = newOh.getValueType();
     for (ObservationHistory oh : existingTypeOHes) {
        if (oh.getValueType().equals(newValueType)) {
           machedInDB = true;
           if (oh.getValue() != null && !oh.getValue().equals(newOh.getValue())) {
              oh.setSysUserId(sysUserId);
              oh.setValue(newOh.getValue());
              observationHistoryDAO.updateData(oh);
        
           }
        }
     }
     // if (deleteTypeOHes.size() > 0) {
     // observationHistoryDAO.delete(deleteTypeOHes);
     // }
        
     if (!machedInDB) {
        newOh.setSampleId(sample.getId());
        newOh.setPatientId(patientInDB.getId());
        newOh.setSysUserId(sysUserId);
        observationHistoryDAO.insertData(newOh);
     }
          }
       }
    */
    /**
      *
      */
    protected void persistObservationHistoryLists() {
        if (observationHistoryLists == null) {
            return;
        }

        for (String listType : this.observationHistoryLists.keySet()) {
            // throw away the old list
            Map<String, ObservationHistory> oldOHes = findExistingObservationHistories(listType);
            // System.out.println();
            // System.out.println(listType + " oldOHes.size = "
            // +oldOHes.size());
            for (ObservationHistory oh : oldOHes.values()) {
                oh.setSysUserId(sysUserId);
            }
            observationHistoryDAO.delete(new ArrayList<ObservationHistory>(oldOHes.values()));

            // insert the new
            List<ObservationHistory> newOHes = observationHistoryLists.get(listType);
            // System.out.println(listType + " newOHes.size = "
            // +newOHes.size());
            for (ObservationHistory newOH : newOHes) {
                newOH.setSysUserId(sysUserId);
                newOH.setPatientId(patientInDB.getId());
                newOH.setSampleId(sample.getId());
                observationHistoryDAO.insertData(newOH);
            }
        }
    }

    private Map<String, ObservationHistory> findExistingObservationHistories(String nameKey) {
        Map<String, ObservationHistory> existing = new HashMap<String, ObservationHistory>();

        String ohTypeId = ObservationHistoryTypeMap.getInstance().getIDForType(nameKey);
        List<ObservationHistory> existingOHes = observationHistoryDAO.getAll(patientInDB, sample, ohTypeId);
        for (ObservationHistory oh : existingOHes) {
            existing.put(oh.getValue(), oh);
        }
        return existing;
    }

    protected void persistRecordStatus() {
        // Special Request and EID don't have a patient entry form, so we move
        // the patient record status when we move the sample record status.
        if (projectForm == SPECIAL_REQUEST || projectForm == EID) {
            newPatientStatus = newSampleStatus;
        }
        StatusService.getInstance().persistRecordStatusForSample(sample, newSampleStatus, patientInDB,
                newPatientStatus, sysUserId);
    }

    protected void deleteOldPatient() {
        if (patientToDelete != null) {
            List<PatientIdentity> oldIdentities = identityDAO
                    .getPatientIdentitiesForPatient(patientToDelete.getId());
            for (PatientIdentity listIdentity : oldIdentities) {
                identityDAO.delete(listIdentity.getId(), sysUserId);
            }
            Person personToDelete = patientToDelete.getPerson();
            patientToDelete.setSysUserId(sysUserId);
            patientDAO.deleteData(Arrays.asList(patientToDelete));
            personToDelete.setSysUserId(sysUserId);
            personDAO.deleteData(Arrays.asList(personToDelete));
        }
    }

    protected List<ObservationHistory> getObservationHistories() {
        return observationHistories;
    }

    protected SampleDAO getSimpleSampleDAO() {
        return new SampleDAOImpl();
    }

    public ActionMessages getMessages() {
        return messages;
    }

    public void setMessages(ActionMessages messages) {
        this.messages = messages;
    }

    /****
     * Record a thrown exception
     * 
     * @param methodName
     *            where it happened.
     * @param messageKey
     *            default message if there is not already a error recorded.
     * @param e
     *            the thrown exception of which to print the stack trace.
     */
    public void logAndAddMessage(String methodName, String messageKey, Exception e) {
        e.printStackTrace();
        LogEvent.logError(this.getClass().getSimpleName(), methodName, e.toString());
        if (messages.size() == 0) {
            ActionError error = new ActionError(messageKey, null, null);
            messages.add(ActionMessages.GLOBAL_MESSAGE, error);
        }
    }

    protected abstract String getActionLabel();
}