edu.duke.cabig.c3pr.domain.StudySubject.java Source code

Java tutorial

Introduction

Here is the source code for edu.duke.cabig.c3pr.domain.StudySubject.java

Source

/*******************************************************************************
 * Copyright Duke Comprehensive Cancer Center and SemanticBits
 * 
 * Distributed under the OSI-approved BSD 3-Clause License.
 * See http://ncip.github.com/c3pr/LICENSE.txt for details.
 ******************************************************************************/
package edu.duke.cabig.c3pr.domain;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.OrderBy;
import javax.persistence.Table;
import javax.persistence.Transient;

import org.apache.commons.collections15.functors.InstantiateFactory;
import org.apache.log4j.Logger;
import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.CascadeType;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.Parameter;
import org.hibernate.annotations.Where;
import org.springframework.context.MessageSource;
import org.springframework.context.support.ResourceBundleMessageSource;

import edu.duke.cabig.c3pr.constants.ConsentingMethod;
import edu.duke.cabig.c3pr.constants.CoordinatingCenterStudyStatus;
import edu.duke.cabig.c3pr.constants.EpochType;
import edu.duke.cabig.c3pr.constants.NotificationEmailSubstitutionVariablesEnum;
import edu.duke.cabig.c3pr.constants.OrganizationIdentifierTypeEnum;
import edu.duke.cabig.c3pr.constants.RegistrationDataEntryStatus;
import edu.duke.cabig.c3pr.constants.RegistrationWorkFlowStatus;
import edu.duke.cabig.c3pr.constants.ScheduledEpochDataEntryStatus;
import edu.duke.cabig.c3pr.constants.ScheduledEpochWorkFlowStatus;
import edu.duke.cabig.c3pr.constants.WorkFlowStatusType;
import edu.duke.cabig.c3pr.domain.customfield.CustomField;
import edu.duke.cabig.c3pr.domain.customfield.Customizable;
import edu.duke.cabig.c3pr.domain.factory.ParameterizedBiDirectionalInstantiateFactory;
import edu.duke.cabig.c3pr.domain.factory.ParameterizedInstantiateFactory;
import edu.duke.cabig.c3pr.exception.C3PRBaseException;
import edu.duke.cabig.c3pr.exception.C3PRBaseRuntimeException;
import edu.duke.cabig.c3pr.exception.C3PRExceptionHelper;
import edu.duke.cabig.c3pr.exception.C3PRInvalidDataEntryException;
import edu.duke.cabig.c3pr.utils.CommonUtils;
import edu.duke.cabig.c3pr.utils.DateUtil;
import edu.duke.cabig.c3pr.utils.ProjectedList;
import edu.duke.cabig.c3pr.utils.StringUtils;
import java.util.TreeSet;
import gov.nih.nci.cabig.ctms.collections.LazyListHelper;
import gov.nih.nci.cabig.ctms.domain.DomainObjectTools;

/**
 * The Class StudySubject.
 *
 * @author Ram Chilukuri
 */

/**
 * @author Kruttik Aggarwal
 *
 */
@Entity
@Table(name = "STUDY_SUBJECTS")
@GenericGenerator(name = "id-generator", strategy = "native", parameters = {
        @Parameter(name = "sequence", value = "STUDY_SUBJECTS_ID_SEQ") })
@Where(clause = "reg_workflow_status  != 'INVALID'")
public class StudySubject extends InteroperableAbstractMutableDeletableDomainObject
        implements Customizable, IdentifiableObject, CCTSBroadcastEnabledDomainObject {

    /** The lazy list helper. */
    private LazyListHelper lazyListHelper;

    /** The participant. */
    private StudySubjectDemographics studySubjectDemographics;

    /** The start date. */
    private Date startDate;

    /** The treating physician. */
    private StudyInvestigator treatingPhysician;

    /** The other treating physician. */
    private String otherTreatingPhysician;

    /** The reg data entry status. */
    private RegistrationDataEntryStatus regDataEntryStatus;

    /** The reg workflow status. */
    private RegistrationWorkFlowStatus regWorkflowStatus;

    /** The disease history. */
    private DiseaseHistory diseaseHistory;

    /** The identifiers. */
    private List<Identifier> identifiers = new ArrayList<Identifier>();

    /** The payment method. */
    private String paymentMethod;

    /** The child study subjects. */
    private List<StudySubject> childStudySubjects = new ArrayList<StudySubject>();

    /** The parent study subject. */
    private StudySubject parentStudySubject;

    /** The c3 pr exception helper. */
    private C3PRExceptionHelper c3PRExceptionHelper;

    /** The c3pr error messages. */
    private MessageSource c3prErrorMessages;

    private List<StudySubjectStudyVersion> studySubjectStudyVersions = new ArrayList<StudySubjectStudyVersion>();

    private StudySubjectStudyVersion studySubjectStudyVersion;

    private StudySite studySite;

    private String backDatedReasonText;

    private String invalidationReasonText;

    public String getInvalidationReasonText() {
        return invalidationReasonText;
    }

    public void setInvalidationReasonText(String invalidationReasonText) {
        this.invalidationReasonText = invalidationReasonText;
    }

    public static final String C3D_SYSTME_NAME = "C3D";

    public static final String C3D_IDENTIFIER_TYPE = "C3D Patient Position";

    public static final String MEDIDATA_SYSTME_NAME = "Medidata";

    public static final String MEDIDATA_IDENTIFIER_TYPE = "Medidata Patient Position";

    private List<StudySubjectRegistryStatus> studySubjectRegistryStatusHistory = new ArrayList<StudySubjectRegistryStatus>();

    private List<Correspondence> correspondences = new ArrayList<Correspondence>();

    private Integer studyId;

    private Identifier primaryStudyIdentifier;

    @Transient
    public Integer getStudySubjectVersionId() {
        return studySubjectVersionId;
    }

    public void setStudySubjectVersionId(Integer studySubjectVersionId) {
        this.studySubjectVersionId = studySubjectVersionId;
    }

    private Integer studySubjectVersionId;

    @Transient
    public Identifier getPrimaryStudyIdentifier() {
        return primaryStudyIdentifier;
    }

    public void setPrimaryStudyIdentifier(Identifier primaryStudyIdentifier) {
        this.primaryStudyIdentifier = primaryStudyIdentifier;
    }

    @Transient
    public List<Correspondence> getCorrespondences() {
        return lazyListHelper.getLazyList(Correspondence.class);
    }

    @Transient
    public Integer getStudyId() {
        return studyId;
    }

    public void setStudyId(Integer studyId) {
        this.studyId = studyId;
    }

    public String getBackDatedReasonText() {
        return backDatedReasonText;
    }

    @OneToMany(orphanRemoval = true)
    @Cascade({ CascadeType.ALL })
    @JoinColumn(name = "stu_sub_id", nullable = false)
    @Where(clause = "retired_indicator  = 'false'")
    @OrderBy("time desc")
    public List<Correspondence> getCorrespondencesInternal() {
        return lazyListHelper.getInternalList(Correspondence.class);
    }

    public void setCorrespondencesInternal(List<Correspondence> correspondences) {
        lazyListHelper.setInternalList(Correspondence.class, correspondences);
    }

    public void addCorrespondence(Correspondence correspondence) {
        lazyListHelper.getInternalList(Correspondence.class).add(correspondence);
    }

    public void setCorrespondences(List<Correspondence> correspondences) {
        setCorrespondencesInternal(correspondences);
    }

    public void setBackDatedReasonText(String backDatedReasonText) {
        this.backDatedReasonText = backDatedReasonText;
    }

    private Logger log = Logger.getLogger(StudySubject.class);

    /**
     * Instantiates a new study subject.
     */
    public StudySubject() {
        ResourceBundleMessageSource resourceBundleMessageSource = new ResourceBundleMessageSource();
        resourceBundleMessageSource.setBasename("error_messages_multisite");
        ResourceBundleMessageSource resourceBundleMessageSource1 = new ResourceBundleMessageSource();
        resourceBundleMessageSource1.setBasename("error_messages_c3pr");
        resourceBundleMessageSource1.setParentMessageSource(resourceBundleMessageSource);
        this.c3prErrorMessages = resourceBundleMessageSource1;
        this.c3PRExceptionHelper = new C3PRExceptionHelper(c3prErrorMessages);
        lazyListHelper = new LazyListHelper();
        this.regDataEntryStatus = RegistrationDataEntryStatus.INCOMPLETE;
        this.regWorkflowStatus = RegistrationWorkFlowStatus.PENDING;
        lazyListHelper.add(OrganizationAssignedIdentifier.class,
                new ParameterizedInstantiateFactory<OrganizationAssignedIdentifier>(
                        OrganizationAssignedIdentifier.class));
        lazyListHelper.add(SystemAssignedIdentifier.class,
                new ParameterizedInstantiateFactory<SystemAssignedIdentifier>(SystemAssignedIdentifier.class));
        setIdentifiers(new ArrayList<Identifier>());
        lazyListHelper.add(CustomField.class,
                new ParameterizedBiDirectionalInstantiateFactory<CustomField>(CustomField.class, this));
        lazyListHelper.add(Correspondence.class,
                new ParameterizedInstantiateFactory<Correspondence>(Correspondence.class));

        // mandatory, so that the lazy-projected list is managed properly.
    }

    // / BEAN PROPERTIES
    /**
     * Instantiates a new study subject.
     *
     * @param forExample the for example
     */
    public StudySubject(boolean forExample) {
        lazyListHelper = new LazyListHelper();
        lazyListHelper.add(ScheduledEpoch.class, new InstantiateFactory<ScheduledEpoch>(ScheduledEpoch.class));
        lazyListHelper.add(ScheduledEpoch.class, new InstantiateFactory<ScheduledEpoch>(ScheduledEpoch.class));
        lazyListHelper.add(OrganizationAssignedIdentifier.class,
                new ParameterizedInstantiateFactory<OrganizationAssignedIdentifier>(
                        OrganizationAssignedIdentifier.class));
        lazyListHelper.add(SystemAssignedIdentifier.class,
                new ParameterizedInstantiateFactory<SystemAssignedIdentifier>(SystemAssignedIdentifier.class));
        setIdentifiers(new ArrayList<Identifier>());
        lazyListHelper.add(CustomField.class,
                new ParameterizedBiDirectionalInstantiateFactory<CustomField>(CustomField.class, this));
        lazyListHelper.add(Correspondence.class,
                new ParameterizedInstantiateFactory<Correspondence>(Correspondence.class));

    }

    public StudySubject(Integer id, String paymentMethod, RegistrationDataEntryStatus regDataEntryStatus,
            Integer studySubjectVersionId, Integer studyId) {
        this.setId(id);
        this.setPaymentMethod(paymentMethod);
        this.setRegDataEntryStatus(regDataEntryStatus);
        this.setStudyId(studyId);
        this.setStudySubjectVersionId(studySubjectVersionId);
    }

    /**
     * Gets the scheduled epochs.
     *
     * @return the scheduled epochs
     */
    @Transient
    public List<ScheduledEpoch> getScheduledEpochs() {
        return getStudySubjectStudyVersion().getScheduledEpochs();
    }

    public void setStudySubjectStudyVersions(List<StudySubjectStudyVersion> studySubjectStudyVersions) {
        this.studySubjectStudyVersions = studySubjectStudyVersions;
    }

    @OneToMany(mappedBy = "studySubject", orphanRemoval = true)
    @Fetch(FetchMode.SUBSELECT)
    @Cascade(value = { CascadeType.ALL })
    public List<StudySubjectStudyVersion> getStudySubjectStudyVersions() {
        return studySubjectStudyVersions;
    }

    private void addStudySubjectStudyVersion(StudySubjectStudyVersion studySubjectStudyVersion) {
        studySubjectStudyVersion.setStudySubject(this);
        getStudySubjectStudyVersions().add(studySubjectStudyVersion);
    }

    @Transient
    private StudySubjectStudyVersion getLatestStudySubjectVersion() {
        if (getStudySubjectStudyVersions().size() == 0) {
            StudySubjectStudyVersion studySubjectStudyVersion = new StudySubjectStudyVersion();
            addStudySubjectStudyVersion(studySubjectStudyVersion);
            return studySubjectStudyVersion;
        } else {
            List<StudySubjectStudyVersion> sortedSubejctStudyVersions = new ArrayList<StudySubjectStudyVersion>();
            sortedSubejctStudyVersions.addAll(this.getStudySubjectStudyVersions());
            Collections.sort(sortedSubejctStudyVersions);
            return sortedSubejctStudyVersions.get(sortedSubejctStudyVersions.size() - 1);
        }
    }

    @Transient
    public StudySubjectStudyVersion getFirstStudySubjectVersion() {
        if (getStudySubjectStudyVersions().size() == 0) {
            StudySubjectStudyVersion studySubjectStudyVersion = new StudySubjectStudyVersion();
            addStudySubjectStudyVersion(studySubjectStudyVersion);
            return studySubjectStudyVersion;
        } else {
            List<StudySubjectStudyVersion> sortedSubejctStudyVersions = new ArrayList<StudySubjectStudyVersion>();
            sortedSubejctStudyVersions.addAll(this.getStudySubjectStudyVersions());
            Collections.sort(sortedSubejctStudyVersions);
            return sortedSubejctStudyVersions.get(0);
        }
    }

    // The scheduled epochs are always created and attached only to the 1st study subject study version.
    // The rest of the study subject study versions only capture consents
    @Transient
    public StudySubjectStudyVersion getStudySubjectStudyVersion() {
        if (studySubjectStudyVersion == null) {
            studySubjectStudyVersion = getFirstStudySubjectVersion();
        }
        return studySubjectStudyVersion;
    }

    public void clearAllAndAddStudySubjectStudyVersion(StudySubjectStudyVersion studySubjectStudyVersion) {
        this.studySubjectStudyVersions.clear();
        this.addStudySubjectStudyVersion(studySubjectStudyVersion);
    }

    @Transient
    public StudySiteStudyVersion getStudySiteVersion() {
        return getStudySubjectStudyVersion().getStudySiteStudyVersion();
    }

    /**
     * Adds the scheduled epoch.
     *
     * @param scheduledEpoch the scheduled epoch
     */
    public void addScheduledEpoch(ScheduledEpoch scheduledEpoch) {
        getStudySubjectStudyVersion().addScheduledEpoch(scheduledEpoch);
    }

    /**
     * Gets the scheduled epoch.
     *
     * @return the scheduled epoch
     */
    @Transient
    public ScheduledEpoch getScheduledEpoch() {
        return getStudySubjectStudyVersion().getCurrentScheduledEpoch();
    }

    /**
     * Gets the scheduled epoch.
     *
     * @return the scheduled epoch
     */
    @Transient
    public ScheduledEpoch getScheduledEpochByName(String epochName) {
        Epoch epoch = new Epoch();
        epoch.setName(epochName);
        return getStudySubjectStudyVersion().getScheduledEpoch(epoch);
    }

    /**
     * Gets the disease history.
     *
     * @return the disease history
     */
    @Transient
    public DiseaseHistory getDiseaseHistory() {
        if (this.diseaseHistory == null)
            this.diseaseHistory = new DiseaseHistory();
        return diseaseHistory;
    }

    /**
     * Sets the disease history.
     *
     * @param diseaseHistory the new disease history
     */
    public void setDiseaseHistory(DiseaseHistory diseaseHistory) {
        this.diseaseHistory = diseaseHistory;
    }

    /**
     * Gets the disease history internal.
     *
     * @return the disease history internal
     */
    @OneToOne(orphanRemoval = true)
    @Cascade({ CascadeType.ALL })
    @JoinColumn(name = "DISEASE_HISTORY_ID")
    public DiseaseHistory getDiseaseHistoryInternal() {
        return diseaseHistory;
    }

    /**
     * Sets the disease history internal.
     *
     * @param diseaseHistory the new disease history internal
     */
    public void setDiseaseHistoryInternal(DiseaseHistory diseaseHistory) {
        this.diseaseHistory = diseaseHistory;
    }

    /**
     * Sets the study site.
     *
     * @param studySite the new study site
     */
    public void setStudySite(StudySite studySite) {
        if (studySite != null) {
            this.getStudySubjectStudyVersion().setStudySiteStudyVersion(studySite.getStudySiteStudyVersion());
            this.studySite = studySite;
        }
    }

    /**
     * Gets the study site.
     *
     * @return the study site
     */
    @Transient
    public StudySite getStudySite() {
        StudySiteStudyVersion studySiteStudyVersion = getStudySubjectStudyVersion().getStudySiteStudyVersion();
        if (studySiteStudyVersion != null) {
            return studySiteStudyVersion.getStudySite();
        }
        return null;
    }

    /**
     * Sets the participant.
     *
     * @param participant the new participant
     */
    /*   public void setParticipant(Participant participant) {
          this.participant = participant;
       }*/

    /**
     * Gets the participant.
     *
     * @return the participant
     */
    /*@ManyToOne
    @JoinColumn(name = "PRT_ID", nullable = false)
    @Cascade( { CascadeType.LOCK })
    public Participant getParticipant() {
       return participant;
    }*/

    @ManyToOne
    @JoinColumn(name = "stu_sub_dmgphcs_id", nullable = false)
    @Cascade({ CascadeType.ALL })
    public StudySubjectDemographics getStudySubjectDemographics() {
        return studySubjectDemographics;
    }

    public void setStudySubjectDemographics(StudySubjectDemographics studySubjectDemographics) {
        this.studySubjectDemographics = studySubjectDemographics;
    }

    @Transient
    public Participant getParticipant() {
        return studySubjectDemographics != null ? studySubjectDemographics.getMasterSubject() : null;
    }

    @Transient
    public void setParticipant(Participant participant) {
        this.setStudySubjectDemographics(participant.createStudySubjectDemographics());
    }

    /**
     * Gets the start date.
     *
     * @return the start date
     */
    public Date getStartDate() {
        return startDate;
    }

    /**
     * Sets the start date.
     *
     * @param startDate the new start date
     */
    public void setStartDate(Date startDate) {
        this.startDate = startDate;
    }

    /* (non-Javadoc)
     * @see edu.duke.cabig.c3pr.domain.AbstractMutableDeletableDomainObject#equals(java.lang.Object)
     */
    @Override
    public boolean equals(Object o) {
        if (this == o)
            return true;
        if (o == null || getClass() != o.getClass())
            return false;

        final StudySubject that = (StudySubject) o;

        if (getStudySite() != null ? !getStudySite().equals(that.getStudySite()) : that.getStudySite() != null)
            return false;
        // Participant#equals calls this method, so we can't use it here
        if (!DomainObjectTools.equalById(studySubjectDemographics.getMasterSubject(),
                that.getStudySubjectDemographics().getMasterSubject()))
            return false;

        return true;
    }

    /* (non-Javadoc)
     * @see edu.duke.cabig.c3pr.domain.AbstractMutableDeletableDomainObject#hashCode()
     */
    @Override
    public int hashCode() {
        int result = 1;
        result = 29 * result + (getStudySite() != null ? getStudySite().hashCode() : 0);
        result = 29 * result + (studySubjectDemographics.getMasterSubject() != null
                ? studySubjectDemographics.getMasterSubject().hashCode()
                : 0);
        return result;
    }

    /**
     * Gets the off study date str.
     *
     * @return the off study date str
     */
    @Transient
    public String getOffStudyDateStr() {
        Date OffStudyDate = getOffStudyDate();
        if (OffStudyDate != null) {
            return DateUtil.formatDate(OffStudyDate, "MM/dd/yyyy");
        }
        return "";
    }

    /**
     * Gets the start date str.
     *
     * @return the start date str
     */
    @Transient
    public String getStartDateStr() {
        if (startDate != null) {
            return DateUtil.formatDate(startDate, "MM/dd/yyyy");
        }
        return "";
    }

    /**
     * Gets the identifiers.
     *
     * @return the identifiers
     */
    @OneToMany(orphanRemoval = true)
    @Fetch(FetchMode.SUBSELECT)
    @Cascade({ CascadeType.MERGE, CascadeType.ALL })
    @JoinColumn(name = "SPA_ID")
    @Where(clause = "retired_indicator  = 'false'")
    @OrderBy
    public List<Identifier> getIdentifiers() {
        return identifiers;
    }

    /**
     * Sets the identifiers.
     *
     * @param identifiers the new identifiers
     */
    private void setIdentifiers(List<Identifier> identifiers) {
        this.identifiers = identifiers;
        // initialize projected list for OrganizationAssigned and
        // SystemAssignedIdentifier
        lazyListHelper.setInternalList(OrganizationAssignedIdentifier.class,
                new ProjectedList<OrganizationAssignedIdentifier>(this.identifiers,
                        OrganizationAssignedIdentifier.class));
        lazyListHelper.setInternalList(SystemAssignedIdentifier.class,
                new ProjectedList<SystemAssignedIdentifier>(this.identifiers, SystemAssignedIdentifier.class));
    }

    /**
     * Gets the system assigned identifiers.
     *
     * @return the system assigned identifiers
     */
    @Transient
    public List<SystemAssignedIdentifier> getSystemAssignedIdentifiers() {
        return lazyListHelper.getLazyList(SystemAssignedIdentifier.class);
    }

    /**
     * Gets the organization assigned identifiers.
     *
     * @return the organization assigned identifiers
     */
    @Transient
    public List<OrganizationAssignedIdentifier> getOrganizationAssignedIdentifiers() {
        return lazyListHelper.getLazyList(OrganizationAssignedIdentifier.class);
    }

    /**
     * Adds the identifier.
     *
     * @param identifier the identifier
     */
    public void addIdentifier(Identifier identifier) {
        getIdentifiers().add(identifier);
    }

    /**
     * Removes the identifier.
     *
     * @param identifier the identifier
     */
    public void removeIdentifier(Identifier identifier) {
        getIdentifiers().remove(identifier);
    }

    /**
     * Gets the primary identifier.
     *
     * @return the primary identifier
     */
    @Transient
    public String getPrimaryIdentifier() {
        for (Identifier identifier : getIdentifiers()) {
            if (identifier.getPrimaryIndicator().booleanValue() == true) {
                return identifier.getValue();
            }
        }
        return null;
    }

    /**
     * Gets the primary identifier object. Currently used only in Subject Registry Code
     *
     * @return the primary identifier
     */
    @Transient
    public Identifier getPrimaryIdentifierObject() {
        for (Identifier identifier : getIdentifiers()) {
            if (identifier.getPrimaryIndicator().booleanValue() == true) {
                return identifier;
            }
        }
        return null;
    }

    /**
     * Gets the treating physician.
     *
     * @return the treating physician
     */
    @OneToOne
    @JoinColumn(name = "STI_ID")
    public StudyInvestigator getTreatingPhysician() {
        return treatingPhysician;
    }

    /**
     * Sets the treating physician.
     *
     * @param treatingPhysician the new treating physician
     */
    public void setTreatingPhysician(StudyInvestigator treatingPhysician) {
        this.treatingPhysician = treatingPhysician;
    }

    /**
     * Gets the other treating physician.
     *
     * @return the other treating physician
     */
    public String getOtherTreatingPhysician() {
        return otherTreatingPhysician;
    }

    /**
     * Sets the other treating physician.
     *
     * @param otherTreatingPhysician the new other treating physician
     */
    public void setOtherTreatingPhysician(String otherTreatingPhysician) {
        this.otherTreatingPhysician = otherTreatingPhysician;
    }

    /**
     * Gets the treating physician full name.
     *
     * @return the treating physician full name
     */
    @Transient
    public String getTreatingPhysicianFullName() {
        if (getTreatingPhysician() != null)
            return getTreatingPhysician().getHealthcareSiteInvestigator().getInvestigator().getFullName();
        return getOtherTreatingPhysician();
    }

    /**
     * Gets the reg workflow status.
     *
     * @return the reg workflow status
     */
    @Enumerated(EnumType.STRING)
    public RegistrationWorkFlowStatus getRegWorkflowStatus() {
        return regWorkflowStatus;
    }

    /**
     * Sets the reg workflow status.
     *
     * @param registrationWorkFlowStatus the new reg workflow status
     */
    public void setRegWorkflowStatus(RegistrationWorkFlowStatus registrationWorkFlowStatus) {
        this.regWorkflowStatus = registrationWorkFlowStatus;
    }

    /**
     * Gets the reg data entry status.
     *
     * @return the reg data entry status
     */
    @Enumerated(EnumType.STRING)
    public RegistrationDataEntryStatus getRegDataEntryStatus() {
        return regDataEntryStatus;
    }

    /**
     * Sets the reg data entry status.
     *
     * @param registrationDataEntryStatus the new reg data entry status
     */
    public void setRegDataEntryStatus(RegistrationDataEntryStatus registrationDataEntryStatus) {
        this.regDataEntryStatus = registrationDataEntryStatus;
    }

    /**
     * Gets the data entry status string.
     *
     * @return the data entry status string
     */
    @Transient
    public String getDataEntryStatusString() {
        return this.regDataEntryStatus == RegistrationDataEntryStatus.COMPLETE
                && this.getScheduledEpoch().getScEpochDataEntryStatus() == ScheduledEpochDataEntryStatus.COMPLETE
                        ? "Complete"
                        : "Incomplete";
    }

    /**
     * Gets the data entry status.
     *
     * @return the data entry status
     */
    @Transient
    public boolean getDataEntryStatus() {
        return this.regDataEntryStatus == RegistrationDataEntryStatus.COMPLETE
                && this.getScheduledEpoch().getScEpochDataEntryStatus() == ScheduledEpochDataEntryStatus.COMPLETE
                        ? true
                        : false;
    }

    /**
     * Gets the co ordinating center identifier.
     *
     * @return the co ordinating center identifier
     */
    @Transient
    public OrganizationAssignedIdentifier getCoOrdinatingCenterIdentifier() {
        for (OrganizationAssignedIdentifier organizationAssignedIdentifier : getOrganizationAssignedIdentifiers()) {
            if (organizationAssignedIdentifier.getType()
                    .equals(OrganizationIdentifierTypeEnum.COORDINATING_CENTER_ASSIGNED_STUDY_SUBJECT_IDENTIFIER)) {
                return organizationAssignedIdentifier;
            }
        }
        return null;
    }

    /**
     * Gets the C3PR assigned identifier.
     *
     * @return the C3PR assigned identifier
     */
    @Transient
    public SystemAssignedIdentifier getC3PRAssignedIdentifier() {
        for (SystemAssignedIdentifier systemAssignedIdentifier : getSystemAssignedIdentifiers()) {
            if (systemAssignedIdentifier.getSystemName().equals("C3PR")) {
                return systemAssignedIdentifier;
            }
        }
        return null;
    }

    /**
     * Sets the co ordinating center identifier.
     *
     * @param value the new co ordinating center identifier
     */
    public void setCoOrdinatingCenterIdentifier(String value) {
        OrganizationAssignedIdentifier identifier = getOrganizationAssignedIdentifiers().get(0);
        identifier.setHealthcareSite(
                this.getStudySite().getStudy().getStudyCoordinatingCenters().get(0).getHealthcareSite());
        identifier.setType(OrganizationIdentifierTypeEnum.COORDINATING_CENTER_ASSIGNED_STUDY_SUBJECT_IDENTIFIER);
        identifier.setValue(value);
    }

    /**
     * Gets the C3D identifier.
     *
     * @return the C3D identifier
     */
    @Transient
    public String getC3DIdentifier() {
        if (getSystemAssignedIdentifiers().size() == 0)
            return null;
        for (SystemAssignedIdentifier systemAssignedIdentifier : getSystemAssignedIdentifiers()) {
            if (systemAssignedIdentifier.getSystemName().equals(C3D_SYSTME_NAME))
                return systemAssignedIdentifier.getValue();
        }
        return null;
    }

    /**
     * Sets the C3D identifier.
     *
     * @param value the new c3 d identifier
     */
    public void setC3DIdentifier(String value) {
        SystemAssignedIdentifier identifier = new SystemAssignedIdentifier();
        identifier.setSystemName(C3D_SYSTME_NAME);
        identifier.setType(C3D_IDENTIFIER_TYPE);
        identifier.setValue(value);
        this.getSystemAssignedIdentifiers().add(identifier);
    }

    /**
     * Gets the medidata identifier.
     *
     * @return the medidata identifier
     */
    @Transient
    public String getMedidataIdentifier() {
        if (getSystemAssignedIdentifiers().size() == 0)
            return null;
        for (SystemAssignedIdentifier systemAssignedIdentifier : getSystemAssignedIdentifiers()) {
            if (systemAssignedIdentifier.getSystemName().equals(MEDIDATA_SYSTME_NAME))
                return systemAssignedIdentifier.getValue();
        }
        return null;
    }

    /**
     * Sets the Medidata identifier.
     *
     * @param value the new medidata identifier
     */
    public void setMedidataIdentifier(String value) {
        SystemAssignedIdentifier identifier = new SystemAssignedIdentifier();
        identifier.setSystemName(MEDIDATA_SYSTME_NAME);
        identifier.setType(MEDIDATA_IDENTIFIER_TYPE);
        identifier.setValue(value);
        this.getSystemAssignedIdentifiers().add(identifier);
    }

    /**
     * Gets the off study date.
     *
     * @return the off study date
     */
    @Transient
    public Date getOffStudyDate() {
        if (this.regWorkflowStatus == RegistrationWorkFlowStatus.OFF_STUDY) {
            return getScheduledEpoch().getOffEpochDate();
        }
        return null;
    }

    /**
     * Gets the of study reason text.
     *
     * @return the off study reason text
     */
    @Transient
    public List<OffEpochReason> getOffStudyReasons() {
        if (this.regWorkflowStatus == RegistrationWorkFlowStatus.OFF_STUDY) {
            return getScheduledEpoch().getOffEpochReasons();
        }
        return null;
    }

    /**
     * Evaluate registration data entry status.
     *
     * @return the registration data entry status
     */
    public RegistrationDataEntryStatus evaluateRegistrationDataEntryStatus() {
        List<Error> errors = new ArrayList<Error>();
        evaluateRegistrationDataEntryStatus(errors);
        return (errors.size() > 0) ? RegistrationDataEntryStatus.INCOMPLETE : RegistrationDataEntryStatus.COMPLETE;
    }

    // Adding refactored code
    /**
     * Evaluate registration data entry status.
     *
     * @param errors the errors
     */
    public void evaluateRegistrationDataEntryStatus(List<Error> errors) {

        switch (this.getStudySite().getStudy().getConsentRequired()) {
        case AS_MARKED_BELOW:
            for (StudySubjectConsentVersion studySubjectConsentVersion : this.getLatestStudySubjectVersion()
                    .getStudySubjectConsentVersions()) {
                if (studySubjectConsentVersion.getConsent().getMandatoryIndicator()
                        && StringUtils.isBlank(studySubjectConsentVersion.getInformedConsentSignedDateStr())) {
                    errors.add(new Error("Mandatory informed consent signed date for consent "
                            + studySubjectConsentVersion.getConsent().getName() + " is missing."));
                }
            }
            break;
        case ALL:
            for (StudySubjectConsentVersion studySubjectConsentVersion : this.getLatestStudySubjectVersion()
                    .getStudySubjectConsentVersions()) {
                if (StringUtils.isBlank(studySubjectConsentVersion.getInformedConsentSignedDateStr())) {
                    errors.add(new Error("Mandatory informed consent signed date for consent "
                            + studySubjectConsentVersion.getConsent().getName() + " is missing."));
                }
            }
            break;
        case ONE:
            boolean mandatoryConsentPresent = false;
            for (StudySubjectConsentVersion studySubjectConsentVersion : this.getLatestStudySubjectVersion()
                    .getStudySubjectConsentVersions()) {
                if (!StringUtils.isBlank(studySubjectConsentVersion.getInformedConsentSignedDateStr())) {
                    mandatoryConsentPresent = true;
                    break;
                }
            }
            if (!mandatoryConsentPresent) {
                errors.add(new Error("At least one consent needs to be signed"));
            }
            break;
        case NONE:
            break;
        }

        // register errors for child registrations 
        for (StudySubject childStudySubject : this.getChildStudySubjects()) {
            childStudySubject.evaluateRegistrationDataEntryStatus(errors);
        }
        if (this.getParentStudySubject() == null && getWorkPendingOnMandatoryCompanionRegistrations()) {
            errors.add(new Error("Mandatory companion is not registered"));
        }
    }

    /**
     * Evaluate scheduled epoch data entry status.
     *
     * @param errors the errors
     *
     * @return the scheduled epoch data entry status
     */
    public ScheduledEpochDataEntryStatus evaluateScheduledEpochDataEntryStatus(List<Error> errors) {
        return this.getScheduledEpoch().evaluateScheduledEpochDataEntryStatus(errors);
    }

    /**
     * Checks if is study site.
     *
     * @param nciCode the nci code
     *
     * @return true, if is study site
     */
    @Transient
    public boolean isStudySite(String nciCode) {
        return this.getStudySite().getHealthcareSite().getPrimaryIdentifier().equals(nciCode);
    }

    /**
     * Data Entry is considered complete if both Registrations and Scheduled
     * Epoch data entry status are complete.
     *
     * @return true, if checks if is data entry complete
     */
    @Transient
    public boolean isDataEntryComplete() {
        if (this.getRegDataEntryStatus() == RegistrationDataEntryStatus.COMPLETE
                && this.getScheduledEpoch().getScEpochDataEntryStatus() == ScheduledEpochDataEntryStatus.COMPLETE) {
            return true;
        }
        return false;
    }

    /**
     * Ready for randomization.
     *
     * @return true, if successful
     */
    public boolean readyForRandomization() {
        return regDataEntryStatus == RegistrationDataEntryStatus.COMPLETE
                && getScheduledEpoch().getScEpochDataEntryStatus() == ScheduledEpochDataEntryStatus.COMPLETE;
    }

    /**
     * Computes if co-ordinating center needs to approve a record for successful
     * registration. which is true if the study is multisite and the epoch is
     * enrolling.
     *
     * @return true, if requires coordinating center approval
     */
    public boolean requiresCoordinatingCenterApproval() {
        return this.getStudySite().getStudy().getMultiInstitutionIndicator()
                && this.getScheduledEpoch().getEpoch().isEnrolling();
    }

    /**
     * Checks if is co ordinating center.
     *
     * @param nciCode the nci code
     *
     * @return true, if is co ordinating center
     */
    @Transient
    public boolean isCoOrdinatingCenter(String nciCode) {
        return this.getStudySite().getStudy().isCoOrdinatingCenter(nciCode);
    }

    /*
     * public void updateDataEntryStatus(){
     * this.setRegDataEntryStatus(this.evaluateRegistrationDataEntryStatus());
     * this.getScheduledEpoch().setScEpochDataEntryStatus(
     * this.evaluateScheduledEpochDataEntryStatus()); }
     */

    /**
     * Update data entry status.
     *
     * @return the list< error>
     */
    public List<Error> updateDataEntryStatus() {
        List<Error> errors = new ArrayList<Error>();
        this.evaluateRegistrationDataEntryStatus(errors);
        this.setRegDataEntryStatus((errors.size() > 0) ? RegistrationDataEntryStatus.INCOMPLETE
                : RegistrationDataEntryStatus.COMPLETE);
        this.getScheduledEpoch().setScEpochDataEntryStatus(this.evaluateScheduledEpochDataEntryStatus(errors));
        return errors;
    }

    /**
     * Gets the payment method.
     *
     * @return the payment method
     */
    public String getPaymentMethod() {
        return paymentMethod;
    }

    /**
     * Sets the payment method.
     *
     * @param paymentMethod the new payment method
     */
    public void setPaymentMethod(String paymentMethod) {
        this.paymentMethod = paymentMethod;
    }

    /**
     * Requires affiliate site response.
     *
     * @return true, if successful
     */
    public boolean requiresAffiliateSiteResponse() {
        if (this.getMultisiteWorkflowStatus() == WorkFlowStatusType.MESSAGE_RECIEVED)
            return true;
        return false;
    }

    /**
     * Gets the child study subjects.
     *
     * @return the child study subjects
     */
    @OneToMany(mappedBy = "parentStudySubject", orphanRemoval = true)
    @Cascade(value = { CascadeType.ALL })
    public List<StudySubject> getChildStudySubjects() {
        return childStudySubjects;
    }

    /**
     * Removes the child study subject.
     *
     * @param studySubject the study subject
     */
    public void removeChildStudySubject(StudySubject studySubject) {
        getChildStudySubjects().remove(studySubject);
    }

    /**
     * Adds the child study subject.
     *
     * @param studySubject the study subject
     */
    public void addChildStudySubject(StudySubject studySubject) {
        getChildStudySubjects().add(studySubject);
    }

    /**
     * Sets the child study subjects.
     *
     * @param childStudySubjects the new child study subjects
     */
    public void setChildStudySubjects(List<StudySubject> childStudySubjects) {
        this.childStudySubjects = childStudySubjects;
    }

    /**
     * Gets the parent study subject.
     *
     * @return the parent study subject
     */
    @ManyToOne
    @Cascade(value = { CascadeType.LOCK })
    @JoinTable(name = "stu_sub_associations", joinColumns = @JoinColumn(name = "child_stu_sub_id"), inverseJoinColumns = @JoinColumn(name = "parent_stu_sub_id"))
    public StudySubject getParentStudySubject() {
        return parentStudySubject;
    }

    /**
     * Sets the parent study subject.
     *
     * @param parentStudySubject the new parent study subject
     */
    public void setParentStudySubject(StudySubject parentStudySubject) {
        this.parentStudySubject = parentStudySubject;
    }

    /**
     * Builds the map for notification.
     *
     * @return the map< object, object>
     */
    @SuppressWarnings("unused")
    @Transient
    /*
     * Used by the notifications use case to compose the email message by
     * replacing the sub vars.
     */
    public Map<Object, Object> buildMapForNotification() {
        StudySite studySite = getStudySite();
        Study study = studySite.getStudy();

        Map<Object, Object> map = new HashMap<Object, Object>();
        map.put(NotificationEmailSubstitutionVariablesEnum.PARTICIPANT_MRN.toString(),
                studySubjectDemographics.getMRN().getValue() == null ? "MRN"
                        : studySubjectDemographics.getMRN().getValue());
        map.put(NotificationEmailSubstitutionVariablesEnum.REGISTRATION_STATUS.toString(),
                getRegWorkflowStatus().getDisplayName() == null ? "site name"
                        : getRegWorkflowStatus().getDisplayName());

        map.put(NotificationEmailSubstitutionVariablesEnum.STUDY_SHORT_TITLE.toString(),
                study.getShortTitleText() == null ? "Short Title" : study.getShortTitleText());
        map.put(NotificationEmailSubstitutionVariablesEnum.STUDY_ID.toString(),
                study.getId() == null ? "Study Id" : study.getId().toString());
        map.put(NotificationEmailSubstitutionVariablesEnum.STUDY_ACCRUAL_THRESHOLD.toString(),
                study.getTargetAccrualNumber() == null ? "Study Target Accrual" : study.getTargetAccrualNumber());
        map.put(NotificationEmailSubstitutionVariablesEnum.STUDY_SITE_ACCRUAL_THRESHOLD.toString(),
                studySite.getTargetAccrualNumber() == null ? "Site Target Accrual"
                        : studySite.getTargetAccrualNumber());
        map.put(NotificationEmailSubstitutionVariablesEnum.STUDY_CURRENT_ACCRUAL.toString(),
                study.getCurrentAccrualCount() == null ? "Study Current Accrual" : study.getCurrentAccrualCount());
        map.put(NotificationEmailSubstitutionVariablesEnum.STUDY_SITE_CURRENT_ACCRUAL.toString(),
                studySite.getCurrentAccrualCount());
        return map;
    }

    /* (non-Javadoc)
     * @see edu.duke.cabig.c3pr.domain.InteroperableAbstractMutableDeletableDomainObject#getEndpoints()
     */
    @OneToMany(orphanRemoval = true)
    @Cascade(value = { CascadeType.ALL })
    @JoinColumn(name = "stu_sub_id")
    public List<EndPoint> getEndpoints() {
        return endpoints;
    }

    /**
     * Creates the scheduled epoch.
     *
     * @param epoch the epoch
     *
     * @return the scheduled epoch
     */
    public ScheduledEpoch createScheduledEpoch(Epoch epoch) {
        ScheduledEpoch scheduledEpoch = new ScheduledEpoch();
        scheduledEpoch.setEpoch(epoch);
        return scheduledEpoch;
    }

    /**
     * If scheduled epoch created for this epoch.
     *
     * @param epoch the epoch
     *
     * @return true, if successful
     */
    public boolean hasScheduledEpoch(Epoch epoch) {
        return getStudySubjectStudyVersion().hasScehduledEpoch(epoch);
    }

    //   /**
    //    * Gets the matching scheduled epoch.
    //    *
    //    * @param epoch the epoch
    //    *
    //    * @return the matching scheduled epoch
    //    */
    //   public ScheduledEpoch getMatchingScheduledEpoch(Epoch epoch) {
    //      return getStudySubjectStudyVersion().getScheduledEpoch(epoch);
    //   }

    // returns errors if cannot register.
    /**
     * Can register.
     *
     * @return the list< error>
     */
    public List<Error> canRegister() {
        return updateDataEntryStatus();
    }

    /**
     * Can reserve.
     *
     * @return the list< error>
     */
    public List<Error> canReserve() {
        return updateDataEntryStatus();
    }

    /**
     * Register.
     */
    public void register() {
        ScheduledEpoch scheduledEpoch = getScheduledEpoch();
        Epoch epoch = scheduledEpoch.getEpoch();
        if (scheduledEpoch.getScEpochWorkflowStatus() != ScheduledEpochWorkFlowStatus.PENDING_ON_EPOCH) {
            throw new C3PRBaseRuntimeException("StudySubject already registered on the epoch :" + epoch.getName());
        } else {
            // This returns errors
            List<Error> errors = new ArrayList<Error>();
            errors = canRegister();

            if (errors.size() == 0) {
                // if the epoch requires randomization set it status to
                // 'Registered But Not Randomized', else set it status to
                // 'Registered'
                if (epoch.getRandomizedIndicator()) {
                    scheduledEpoch
                            .setScEpochWorkflowStatus(ScheduledEpochWorkFlowStatus.PENDING_RANDOMIZATION_ON_EPOCH);
                    // only if the study subject is still unregistered(i.e. for
                    // the 1st epoch), we update it's status.
                    // else, the study subject continues to have his/her
                    // previous registration status.
                    if (this.getRegWorkflowStatus() == RegistrationWorkFlowStatus.PENDING
                            || this.getRegWorkflowStatus() == RegistrationWorkFlowStatus.RESERVED) {
                        this.setRegWorkflowStatus(RegistrationWorkFlowStatus.PENDING_ON_STUDY);
                    }

                } else {
                    if (!epoch.getEnrollmentIndicator()) {
                        scheduledEpoch.setScEpochWorkflowStatus(ScheduledEpochWorkFlowStatus.ON_EPOCH);
                    }
                    // only if the study subject is still unregistered(i.e. for
                    // the 1st epoch), we update it's status.
                    // else, the study subject continues to have his/her
                    // previous registration status.
                    if (this.getRegWorkflowStatus() == RegistrationWorkFlowStatus.PENDING
                            || this.getRegWorkflowStatus() == RegistrationWorkFlowStatus.RESERVED) {
                        this.setRegWorkflowStatus(RegistrationWorkFlowStatus.PENDING_ON_STUDY);
                        if (this.getParentStudySubject() != null) {
                            this.getScheduledEpoch()
                                    .setScEpochWorkflowStatus(ScheduledEpochWorkFlowStatus.ON_EPOCH);
                        }
                    }
                }
            } else {
                throw new C3PRInvalidDataEntryException(" Cannot register because data entry is not complete",
                        errors);
            }

        }
    }

    /**
     * Reserve.
     */
    public void reserve() {

        if (this.getRegWorkflowStatus() != RegistrationWorkFlowStatus.PENDING) {
            throw new C3PRBaseRuntimeException(
                    "The subject cannot be reserved a spot on the study site. The subject is already registered or enrolled on the study site.");
        } else {
            ScheduledEpoch scheduledEpoch = this.getScheduledEpoch();
            if (scheduledEpoch.getEpoch().getType() != EpochType.RESERVING) {
                throw new C3PRBaseRuntimeException(
                        "The epoch has to be reserving in order to reserve a spot for the subject");
            } else {
                List<Error> errors = new ArrayList<Error>();
                errors = canReserve();
                if (errors.size() > 0) {
                    throw new C3PRInvalidDataEntryException(
                            " Cannot reserve a spot because data entry is not complete", errors);
                } else {
                    scheduledEpoch.setScEpochWorkflowStatus(ScheduledEpochWorkFlowStatus.ON_EPOCH);
                    this.setRegWorkflowStatus(RegistrationWorkFlowStatus.RESERVED);
                }
            }
        }
    }

    /**
     * Checks if is randomized on scheduled epoch.
     *
     * @return true, if is randomized on scheduled epoch
     */
    @Transient
    public boolean isRandomizedOnScheduledEpoch() {
        return (getScheduledEpoch().getScheduledArm() != null
                && getScheduledEpoch().getScheduledArm().getArm() != null);
    }

    /**
     * Do local randomization.
     */
    private void doLocalRandomization() {
        // randomize subject
        switch (this.getStudySite().getStudy().getRandomizationType()) {
        case PHONE_CALL:
            doPhoneCallRandomization();
            break;
        case BOOK:
            doBookRandomization();
            break;
        case CALL_OUT:
            break;
        default:
            break;
        }
    }

    /**
     * Do book randomization.
     */
    private void doBookRandomization() {
        ScheduledArm sa = new ScheduledArm();
        ScheduledEpoch ste = getScheduledEpoch();
        if (getScheduledEpoch().getEpoch().getStratificationIndicator()) {
            try {
                sa.setArm(getScheduledEpoch().getStratumGroup().getNextArm());
            } catch (C3PRBaseException e) {
                throw new C3PRBaseRuntimeException(e.getMessage());
            }
            if (sa.getArm() != null) {
                ste.addScheduledArm(sa);
                // stratumGroupDao.merge(studySubject.getStratumGroup());
            }
        } else {
            sa.setArm(getNextArmForUnstratifiedStudy());
            if (sa.getArm() != null) {
                ste.addScheduledArm(sa);
            }
        }
    }

    /**
     * Do phone call randomization.
     */
    private void doPhoneCallRandomization() {
        ScheduledEpoch scheduledEpoch = this.getScheduledEpoch();
        if (scheduledEpoch.getScheduledArm() == null) {
            if (!this.getStudySite().getStudy().getBlindedIndicator())
                throw new C3PRBaseRuntimeException(
                        "The subject should have been already assigned to a Scheduled Arm for the Scheduled Epoch :"
                                + scheduledEpoch.getEpoch().getName());
        }
    }

    /**
     * Gets the next arm for unstratified study.
     *
     * @return the next arm for unstratified study
     */
    @Transient
    public Arm getNextArmForUnstratifiedStudy() {
        Arm arm = null;
        if ((getScheduledEpoch()).getEpoch().hasBookRandomizationEntry()) {
            Iterator<BookRandomizationEntry> iter = ((BookRandomization) (getScheduledEpoch()).getEpoch()
                    .getRandomization()).getBookRandomizationEntry().iterator();
            BookRandomizationEntry breTemp;

            while (iter.hasNext()) {
                breTemp = iter.next();
                if (breTemp.getPosition()
                        .equals((this.getScheduledEpoch().getEpoch().getCurrentBookRandomizationEntryPosition()))) {
                    synchronized (this) {
                        (this.getScheduledEpoch().getEpoch())
                                .setCurrentBookRandomizationEntryPosition(breTemp.getPosition() + 1);
                        arm = breTemp.getArm();
                        break;
                    }
                }
            }
        }

        if (arm == null) {
            throw getC3PRExceptionHelper().getRuntimeException(
                    getCode("C3PR.EXCEPTION.REGISTRATION.NO.ARM.AVAILABLE.BOOK.EXHAUSTED.CODE"));

        }
        return arm;
    }

    public void takeSubjectOffStudy(List<OffEpochReason> offStudyReasons, Date offStudyDate) {
        if (getRegWorkflowStatus() != RegistrationWorkFlowStatus.ON_STUDY) {
            throw new C3PRBaseRuntimeException("The subject has to be enrolled before being taken off study");
        }
        for (OffEpochReason offEpochReason : offStudyReasons) {
            if (!(offEpochReason.getReason() instanceof OffStudyReason)) {
                throw new C3PRBaseRuntimeException("Invalid reason type. Expected OffStudyReason but was "
                        + offEpochReason.getReason().getClass());
            }
        }
        this.getScheduledEpoch().takeSubjectOffEpoch(offStudyReasons, offStudyDate);
        this.setRegWorkflowStatus(RegistrationWorkFlowStatus.OFF_STUDY);
    }

    public void takeSubjectOffCurrentEpoch(List<OffEpochReason> offEpochReasons, Date offEpochDate) {
        if (this.getScheduledEpoch().getScEpochWorkflowStatus() != ScheduledEpochWorkFlowStatus.ON_EPOCH) {
            throw new C3PRBaseRuntimeException(
                    "The subject has to be successfully registered on the epoch before being taken off epoch");
        }
        this.getScheduledEpoch().takeSubjectOffEpoch(offEpochReasons, offEpochDate);
    }

    public void failScreening(List<OffEpochReason> offScreeningReasons, Date failScreeningDate) {
        ScheduledEpoch scheduledEpoch = getScheduledEpoch();
        if (canFailScreening()) {
            scheduledEpoch.takeSubjectOffEpoch(offScreeningReasons, failScreeningDate);
            setRegWorkflowStatus(RegistrationWorkFlowStatus.NOT_REGISTERED);
        } else {
            throw new C3PRBaseRuntimeException(
                    "Cannot fail screening. The subject is not registered successfully on a screening epoch.");
        }

    }

    /**
     * Transfer.
     *
     * @return the study subject
     */
    public StudySubject transfer() {
        if (getRegWorkflowStatus() != RegistrationWorkFlowStatus.ON_STUDY) {
            throw new C3PRBaseRuntimeException("The subject has to be enrolled before being transferred");
        }
        if (!isTransferrable()) {
            throw this.getC3PRExceptionHelper().getRuntimeException(
                    getCode("C3PR.EXCEPTION.REGISTRATION.TRANSFER.CANNOT_TO_LOWER_ORDER_EPOCH.CODE"));
        }

        if (getScheduledEpoch().getScEpochWorkflowStatus() != ScheduledEpochWorkFlowStatus.ON_EPOCH) {
            if (getScheduledEpoch().getScEpochWorkflowStatus() == ScheduledEpochWorkFlowStatus.PENDING_ON_EPOCH) {
                register();
            }
            if (getScheduledEpoch()
                    .getScEpochWorkflowStatus() == ScheduledEpochWorkFlowStatus.PENDING_RANDOMIZATION_ON_EPOCH) {
                if (!isRandomizedOnScheduledEpoch()) {
                    if (!getStudySite().getHostedMode()
                            && !this.getStudySite().getStudy().getStudyCoordinatingCenter().getHostedMode()
                            && (!getStudySite().getHealthcareSite().equals(this.getStudySite().getStudy()
                                    .getStudyCoordinatingCenter().getHealthcareSite()))) {
                        // doCoordinatingCenterRandomization(); // The
                        // coordinating
                        // center should
                        // do the
                        // randomization
                        // if not in
                        // hosted mode
                        // or not at the
                        // same site.
                    } else {
                        doLocalRandomization(); // If in hosted mode or if the
                        // study site is same as the
                        // coordinating center site, it
                        // happens locally.
                    }

                }
            }
            getScheduledEpoch().setScEpochWorkflowStatus(ScheduledEpochWorkFlowStatus.ON_EPOCH);
        }
        return this;
    }

    /**
     * Gets the matching companion study association.
     *
     * @param childStudySubject the child study subject
     *
     * @return the matching companion study association
     */
    public CompanionStudyAssociation getMatchingCompanionStudyAssociation(StudySubject childStudySubject) {
        for (CompanionStudyAssociation companionStudyAssociation : this.getStudySite().getStudy()
                .getCompanionStudyAssociations()) {
            if (companionStudyAssociation.getCompanionStudy().equals(childStudySubject.getStudySite().getStudy())) {
                return companionStudyAssociation;
            }
        }
        return null;
    }

    /**
     * Checks if is stand alone study subject.
     *
     * @return true, if is stand alone study subject
     */
    @Transient
    public boolean isStandAloneStudySubject() {
        return this.getStudySite().getStudy().getStandaloneIndicator();
    }

    /**
     * Checks for c3 pr system identifier.
     *
     * @return true, if successful
     */
    @Transient
    public boolean hasC3PRSystemIdentifier() {
        for (SystemAssignedIdentifier systemAssignedIdentfier : this.getSystemAssignedIdentifiers()) {
            if (systemAssignedIdentfier.getSystemName().equalsIgnoreCase("C3PR")) {
                return true;
            }
        }
        return false;
    }

    /**
     * Checks if is transferrable.
     *
     * @return true, if is transferrable
     */
    @Transient
    public boolean isTransferrable() {
        return getStudySubjectStudyVersion().isTransferrable();
    }

    /**
     * Gets the c3 pr exception helper.
     *
     * @return the c3 pr exception helper
     */
    @Transient
    public C3PRExceptionHelper getC3PRExceptionHelper() {
        return c3PRExceptionHelper;
    }

    /**
     * Sets the c3 pr exception helper.
     *
     * @param exceptionHelper the new c3 pr exception helper
     */
    public void setC3PRExceptionHelper(C3PRExceptionHelper exceptionHelper) {
        this.c3PRExceptionHelper = exceptionHelper;
    }

    /**
     * Gets the code.
     *
     * @param errortypeString the errortype string
     *
     * @return the code
     */
    @Transient
    public int getCode(String errortypeString) {
        return Integer.parseInt(this.c3prErrorMessages.getMessage(errortypeString, null, null));
    }

    /**
     * Gets the c3pr error messages.
     *
     * @return the c3pr error messages
     */
    @Transient
    public MessageSource getC3prErrorMessages() {
        return c3prErrorMessages;
    }

    /**
     * Sets the c3pr error messages.
     *
     * @param errorMessages the new c3pr error messages
     */
    public void setC3prErrorMessages(MessageSource errorMessages) {
        c3prErrorMessages = errorMessages;
    }

    /**
     * Prepare for enrollment.
     */
    public void prepareForEnrollment() {

        if (!this.getStudySite().getStudy().getStandaloneIndicator() && this.getParentStudySubject() != null
                && this.getParentStudySubject().regWorkflowStatus != RegistrationWorkFlowStatus.ON_STUDY) {
            throw new C3PRBaseRuntimeException(
                    " Cannot directly register on the embedded study. The registration can happen only through the parent");
        }

        if (getWorkPendingOnMandatoryCompanionRegistrations()) {
            throw new C3PRBaseRuntimeException(
                    " First register on the mandatory companions before enrolling on the parent");
        }
        for (StudySubject childStudySubject : this.getChildStudySubjects()) {
            CompanionStudyAssociation matchingCompanionStudyAssocation = null;
            matchingCompanionStudyAssocation = getMatchingCompanionStudyAssociation(childStudySubject);
            if (matchingCompanionStudyAssocation != null) {
                if (matchingCompanionStudyAssocation.getMandatoryIndicator()) {
                    if (!childStudySubject.getScheduledEpoch().getEpoch().getEnrollmentIndicator()) {
                        throw new C3PRBaseRuntimeException(
                                " First register the subject on the enrolling epoch of the mandatory companions before proceeding with enrollment");
                    }
                }
            }
        }

        List<Error> errors = new ArrayList<Error>();

        if (this.getStartDate() == null) {
            errors.add(new Error("Registration start date is missing"));
        }
        this.getScheduledEpoch().setStartDate(this.getStartDate());
        log.debug("Setting the registration start date to scheduled epoch start date");

        if (getScheduledEpoch().getScEpochWorkflowStatus() == ScheduledEpochWorkFlowStatus.PENDING_ON_EPOCH) {
            register();

            canEnroll(errors);
            if (errors.size() > 0) {
                throw new C3PRBaseRuntimeException(CommonUtils.listErrors(errors));
            }
        }

    }

    /**
     * Do local enrollment.
     */
    public void doLocalEnrollment() {
        ScheduledEpochWorkFlowStatus scEpochWorkflowStatus = getScheduledEpoch().getScEpochWorkflowStatus();
        if (scEpochWorkflowStatus != ScheduledEpochWorkFlowStatus.ON_EPOCH) {
            if (scEpochWorkflowStatus == ScheduledEpochWorkFlowStatus.PENDING_RANDOMIZATION_ON_EPOCH) {
                doLocalRandomization();
            }
            this.getScheduledEpoch().setScEpochWorkflowStatus(ScheduledEpochWorkFlowStatus.ON_EPOCH);
        }
        this.setRegWorkflowStatus(RegistrationWorkFlowStatus.ON_STUDY);
    }

    /**
     * Do muti site enrollment.
     *
     * @param coordinatingCenterReturnedScheduledEpoch the coordinating center returned scheduled epoch
     * @param coordinatingCenterAssignedIdentifier the coordinating center assigned identifier
     */
    public void doMutiSiteEnrollment(ScheduledEpoch coordinatingCenterReturnedScheduledEpoch,
            OrganizationAssignedIdentifier coordinatingCenterAssignedIdentifier) {
        ScheduledEpoch scheduledEpoch = this.getStudySubjectStudyVersion()
                .getScheduledEpoch(coordinatingCenterReturnedScheduledEpoch.getEpoch());
        if (scheduledEpoch.getRequiresArm()) {
            Arm arm = scheduledEpoch.getEpoch()
                    .getArmByName(coordinatingCenterReturnedScheduledEpoch.getScheduledArm().getArm().getName());
            ScheduledArm scheduledArm = scheduledEpoch.getScheduledArm() == null
                    ? scheduledEpoch.getScheduledArms().get(0)
                    : scheduledEpoch.getScheduledArm();
            scheduledArm.setArm(arm);
        }
        OrganizationAssignedIdentifier organizationAssignedIdentifier = new OrganizationAssignedIdentifier();
        organizationAssignedIdentifier
                .setHealthcareSite(this.getStudySite().getStudy().getStudyCoordinatingCenter().getHealthcareSite());
        organizationAssignedIdentifier.setGridId(coordinatingCenterAssignedIdentifier.getGridId());
        organizationAssignedIdentifier.setType(coordinatingCenterAssignedIdentifier.getType());
        organizationAssignedIdentifier.setValue(coordinatingCenterAssignedIdentifier.getValue());
        this.addIdentifier(organizationAssignedIdentifier);
        this.getScheduledEpoch().setScEpochWorkflowStatus(ScheduledEpochWorkFlowStatus.ON_EPOCH);
        this.setRegWorkflowStatus(RegistrationWorkFlowStatus.ON_STUDY);

        // TODO This should also set all the child studysubjects which are in
        // 'REGISTERED_BUT_NOT_ENROLLED' state to enrolled"

    }

    /**
     * Do muti site transfer.
     *
     * @param coordinatingCenterReturnedScheduledEpoch the coordinating center returned scheduled epoch
     */
    public void doMutiSiteTransfer(ScheduledEpoch coordinatingCenterReturnedScheduledEpoch) {
        ScheduledEpoch scheduledEpoch = this.getStudySubjectStudyVersion()
                .getScheduledEpoch(coordinatingCenterReturnedScheduledEpoch.getEpoch());
        if (scheduledEpoch.getRequiresArm()) {
            Arm arm = scheduledEpoch.getEpoch()
                    .getArmByName(coordinatingCenterReturnedScheduledEpoch.getScheduledArm().getArm().getName());
            ScheduledArm scheduledArm = scheduledEpoch.getScheduledArm() == null
                    ? scheduledEpoch.getScheduledArms().get(0)
                    : scheduledEpoch.getScheduledArm();
            scheduledArm.setArm(arm);
        }
        this.getScheduledEpoch().setScEpochWorkflowStatus(ScheduledEpochWorkFlowStatus.ON_EPOCH);
    }

    /**
     * Do local transfer.
     */
    public void doLocalTransfer() {
        if (getScheduledEpoch().getScEpochWorkflowStatus() != ScheduledEpochWorkFlowStatus.ON_EPOCH) {
            if (getScheduledEpoch()
                    .getScEpochWorkflowStatus() == ScheduledEpochWorkFlowStatus.PENDING_RANDOMIZATION_ON_EPOCH) {
                doLocalRandomization();
            }
            this.getScheduledEpoch().setScEpochWorkflowStatus(ScheduledEpochWorkFlowStatus.ON_EPOCH);
        }
        this.setRegWorkflowStatus(RegistrationWorkFlowStatus.ON_STUDY);
    }

    /**
     * Prepare for transfer.
     */
    public void prepareForTransfer() {
        if (getRegWorkflowStatus() != RegistrationWorkFlowStatus.ON_STUDY) {
            throw new C3PRBaseRuntimeException("The subject has to be enrolled before being transferred");
        }
        if (!isTransferrable()) {
            throw this.getC3PRExceptionHelper().getRuntimeException(
                    getCode("C3PR.EXCEPTION.REGISTRATION.TRANSFER.CANNOT_TO_LOWER_ORDER_EPOCH.CODE"));
        }

        if (getScheduledEpoch().getScEpochWorkflowStatus() == ScheduledEpochWorkFlowStatus.PENDING_ON_EPOCH) {
            register();
        }

    }

    /**
     * Can enroll.
     *
     * @param errors the errors
     *
     * @return the list< error>
     */
    public List<Error> canEnroll(List<Error> errors) {

        if (this.startDate == null) {
            errors.add(new Error("Registration start date is missing"));
        }

        StudySiteStudyVersion studySiteStudyVersion = this.getStudySubjectStudyVersion().getStudySiteStudyVersion();

        for (StudySubjectConsentVersion studySubjectConsentVersion : this.getStudySubjectStudyVersion()
                .getStudySubjectConsentVersions()) {
            if (studySubjectConsentVersion.getInformedConsentSignedDate() != null
                    && !studySiteStudyVersion.getStudySite().canEnroll(studySiteStudyVersion.getStudyVersion(),
                            studySubjectConsentVersion.getInformedConsentSignedDate())) {
                errors.add(new Error(
                        "The informed consent signed date does not correspond to the current study site study version. This seems to be a back dated registration"));
            }

            if (this.getStartDate() != null && studySubjectConsentVersion.getInformedConsentSignedDate() != null
                    && this.getStartDate().before(studySubjectConsentVersion.getInformedConsentSignedDate())) {
                errors.add(new Error("Enrollment cannot start before the subject signed the informed consent :"
                        + studySubjectConsentVersion.getConsent().getName()));
            }
        }

        for (StudySubject childStudySubject : this.getChildStudySubjects()) {
            CompanionStudyAssociation matchingCompanionStudyAssociation = null;
            matchingCompanionStudyAssociation = getMatchingCompanionStudyAssociation(childStudySubject);
            if (matchingCompanionStudyAssociation != null) {
                if (matchingCompanionStudyAssociation.getMandatoryIndicator()) {
                    childStudySubject.evaluateRegistrationDataEntryStatus(errors);
                    childStudySubject.evaluateScheduledEpochDataEntryStatus(errors);

                }
            }
        }
        return errors;
    }

    /**
     * Gets the custom fields internal.
     *
     * @return the custom fields internal
     */
    @OneToMany(mappedBy = "studySubject", fetch = FetchType.LAZY, orphanRemoval = true)
    @Cascade(value = { CascadeType.ALL })
    public List<CustomField> getCustomFieldsInternal() {
        return lazyListHelper.getInternalList(CustomField.class);
    }

    /* (non-Javadoc)
     * @see edu.duke.cabig.c3pr.domain.customfield.Customizable#getCustomFields()
     */
    @Transient
    public List<CustomField> getCustomFields() {
        return lazyListHelper.getLazyList(CustomField.class);
    }

    /**
     * Sets the custom fields internal.
     *
     * @param customFields the new custom fields internal
     */
    public void setCustomFieldsInternal(List<CustomField> customFields) {
        lazyListHelper.setInternalList(CustomField.class, customFields);
    }

    /**
     * Adds the custom field.
     *
     * @param customField the custom field
     */
    public void addCustomField(CustomField customField) {
        this.getCustomFields().add(customField);
        customField.setStudySubject(this);
    }

    /**
     * Gets the work pending on mandatory companion registrations.
     *
     * @return the work pending on mandatory companion registrations
     */
    @Transient
    public boolean getWorkPendingOnMandatoryCompanionRegistrations() {
        if (!this.getScheduledEpoch().getEpoch().getEnrollmentIndicator()) {
            return false;
        }
        for (CompanionStudyAssociation companionStudyAssociation : this.getStudySubjectStudyVersion()
                .getStudySiteStudyVersion().getStudyVersion().getCompanionStudyAssociations()) {
            if (companionStudyAssociation.getMandatoryIndicator()) {
                boolean hasCorrespondingStudySubject = false;
                for (StudySubject childStudySubject : this.getChildStudySubjects()) {
                    if (childStudySubject.getStudySite().getStudy()
                            .equals(companionStudyAssociation.getCompanionStudy())) {
                        hasCorrespondingStudySubject = true;
                    }
                }
                if (!hasCorrespondingStudySubject)
                    return true;
            }
        }
        for (StudySubject childStudySubject : this.getChildStudySubjects()) {
            if (!childStudySubject.getDataEntryStatus()) {
                return true;
            }
            if (childStudySubject.getRegWorkflowStatus() != RegistrationWorkFlowStatus.ON_STUDY) {
                CompanionStudyAssociation studyAssociation = getMatchingCompanionStudyAssociation(
                        childStudySubject);
                if (studyAssociation != null) {
                    if (studyAssociation.getMandatoryIndicator()) {
                        if (!childStudySubject.getScheduledEpoch().getEpoch().getEnrollmentIndicator()) {
                            return true;
                        }
                        if (childStudySubject.getRegWorkflowStatus() != RegistrationWorkFlowStatus.PENDING_ON_STUDY
                                || childStudySubject.getScheduledEpoch()
                                        .getScEpochWorkflowStatus() == ScheduledEpochWorkFlowStatus.PENDING_ON_EPOCH) {
                            return true;
                        }

                    }
                }
            }
        }
        return false;
    }

    /**
     * Checks for mandatory companions.
     *
     * @return true, if successful
     */
    @Transient
    public boolean hasMandatoryCompanions() {

        for (CompanionStudyAssociation companionStudyAssociation : this.getStudySite().getStudy()
                .getCompanionStudyAssociations()) {
            if (companionStudyAssociation.getMandatoryIndicator()) {
                return true;
            }
        }
        return false;
    }

    /**
     * Gets the checks if is direct arm assigment.
     *
     * @return the checks if is direct arm assigment
     */
    @Transient
    public boolean getIsDirectArmAssigment() {
        if (this.getScheduledEpoch().getRequiresArm() && !this.getScheduledEpoch().getRequiresRandomization()) {
            return true;
        }
        return false;
    }

    /**
     * Change study version.
     * Updates the registration to use the study version valid on a given date.
     * This method will remove the previous references and add a new valid study version.
     * @param date the date
     */
    public void changeStudyVersion(Date date) {
        if (this.getStudySubjectStudyVersions().size() > 1) {
            throw new RuntimeException(
                    "Subject registered on multiple study version. Study subject setup incorrectly.");
        }
        StudySiteStudyVersion studySiteStudyVersion = this.getStudySite().getStudySiteStudyVersion(date);
        if (studySiteStudyVersion == null) {
            throw this.getC3PRExceptionHelper().getRuntimeException(
                    getCode("C3PR.EXCEPTION.REGISTRATION.STUDYVERSION.NOTFOUND"),
                    new String[] { new SimpleDateFormat("MM/dd/yyyy").format(date) });
        }
        Epoch epoch = studySiteStudyVersion.getStudyVersion()
                .getEpochByName(this.getScheduledEpoch().getEpoch().getName());
        if (epoch == null) {
            throw this.getC3PRExceptionHelper().getRuntimeException(
                    getCode("C3PR.EXCEPTION.REGISTRATION.EPOCH.NOTFOUND"),
                    new String[] { this.getScheduledEpoch().getEpoch().getName(),
                            this.getStudySiteVersion().getStudyVersion().getName() });
        }
        ScheduledEpoch scheduledEpoch = new ScheduledEpoch();
        scheduledEpoch.setEpoch(epoch);
        StudySubjectStudyVersion studySubjectStudyVersion = new StudySubjectStudyVersion();
        studySubjectStudyVersion.setStudySiteStudyVersion(studySiteStudyVersion);
        studySubjectStudyVersion.addScheduledEpoch(scheduledEpoch);
        this.getStudySubjectStudyVersions().remove(this.getStudySubjectStudyVersion());
        this.addStudySubjectStudyVersion(studySubjectStudyVersion);
        this.clearAllAndAddStudySubjectStudyVersion(studySubjectStudyVersion);
        // adding informed consent question answers
        for (int i = 0; i < studySubjectStudyVersion.getStudySiteStudyVersion().getStudyVersion().getConsents()
                .size(); i++) {
            studySubjectStudyVersion.getStudySubjectConsentVersions().get(i).setConsent(
                    studySubjectStudyVersion.getStudySiteStudyVersion().getStudyVersion().getConsents().get(i));
            for (ConsentQuestion question : studySubjectStudyVersion.getStudySiteStudyVersion().getStudyVersion()
                    .getConsents().get(i).getQuestions()) {
                SubjectConsentQuestionAnswer subjectConsentQuestionAnswer = new SubjectConsentQuestionAnswer();
                subjectConsentQuestionAnswer.setConsentQuestion(question);
                studySubjectStudyVersion.getStudySubjectConsentVersions().get(i)
                        .addSubjectConsentAnswer(subjectConsentQuestionAnswer);
            }
        }
    }

    /**
     * Checks if passed in staff is assigned and active personnel on the site associated to the studySub or the coordinating center associated to the studySub.
     * Called by the StudySubjectSecurityFilter to enforce site level security on StudySubjects.
     *
     * @param researchStaff the research staff
     * @return true, if is assigned and active personnel
     */
    @Transient
    public boolean isAssignedAndActivePersonnel(PersonUser researchStaff) {
        //Checking if staff belongs to the site of registration
        for (StudyPersonnel studyPersonnel : getStudySite().getActiveStudyPersonnel()) {
            if (studyPersonnel.getPersonUser().equals(researchStaff)) {
                return true;
            }
        }

        //If the site of registration is not the coordinating center then checking if staff belongs to the coordinating center(CC) of the studySubject.
        if (!getStudySite().getIsCoordinatingCenter()) {
            //Note: The CC can just be a studyOrg or can be added to the study as a studySite 
            //and the user can add study personnel from either the CC as a studyOrg or the CC as a studySite.
            //Hence, we need to check for studyPersonnel under both the studySite and the studyOrg

            //Checking for CC under studySite
            StudySite coordinatingCenterStudySite = null;
            for (StudySite studySite : getStudySite().getStudy().getStudySites()) {
                if (studySite.getIsCoordinatingCenter()) {
                    coordinatingCenterStudySite = studySite;
                    break;
                }
            }
            if (coordinatingCenterStudySite != null) {
                for (StudyPersonnel studyPersonnel : coordinatingCenterStudySite.getActiveStudyPersonnel()) {
                    if (studyPersonnel.getPersonUser().equals(researchStaff)) {
                        return true;
                    }
                }
            }

            //Checking for CC under studyOrg
            StudyOrganization studyOrganizationCoordinatingCenter = getStudySite().getStudy()
                    .getStudyCoordinatingCenter();
            if (studyOrganizationCoordinatingCenter != null) {
                for (StudyPersonnel studyPersonnel : studyOrganizationCoordinatingCenter
                        .getActiveStudyPersonnel()) {
                    if (studyPersonnel.getPersonUser().equals(researchStaff)) {
                        return true;
                    }
                }
            }
        }
        return false;
    }

    @Transient
    public boolean isCurrentEpochWorkflowComplete() {
        return getScheduledEpoch().getScEpochWorkflowStatus() == ScheduledEpochWorkFlowStatus.ON_EPOCH
                || getScheduledEpoch().getScEpochWorkflowStatus() == ScheduledEpochWorkFlowStatus.OFF_EPOCH;
    }

    @Transient
    public boolean canChangeEpoch() {
        return isCurrentEpochWorkflowComplete()
                && getStudySite().getStudy()
                        .getCoordinatingCenterStudyStatus() == CoordinatingCenterStudyStatus.OPEN
                && getStudySite().getStudy().getIfHigherOrderEpochExists(getScheduledEpoch().getEpoch())
                && regWorkflowStatus != RegistrationWorkFlowStatus.OFF_STUDY
                && regWorkflowStatus != RegistrationWorkFlowStatus.NOT_REGISTERED;
    }

    @Transient
    public boolean canTakeSubjectOffStudy() {
        if (regWorkflowStatus == RegistrationWorkFlowStatus.ON_STUDY) {
            return true;
        }
        return false;
    }

    @Transient
    public boolean canFailScreening() {
        if (regWorkflowStatus != RegistrationWorkFlowStatus.ON_STUDY
                && regWorkflowStatus != RegistrationWorkFlowStatus.OFF_STUDY
                && getScheduledEpoch().getEpoch().getType() == EpochType.SCREENING
                && getScheduledEpoch().getScEpochWorkflowStatus() == ScheduledEpochWorkFlowStatus.ON_EPOCH) {
            return true;
        }
        return false;
    }

    public void addOriginalStudySubjectConsentVersion(StudySubjectConsentVersion studySubjectConsentVersion) {
        this.getStudySubjectStudyVersion().addStudySubjectConsentVersion(studySubjectConsentVersion);
    }

    public boolean canAllowEligibilityWaiver() {
        if (getScheduledEpoch().getScEpochWorkflowStatus() == ScheduledEpochWorkFlowStatus.PENDING_ON_EPOCH
                && getScheduledEpoch().hasWaivableEligibilityAnswers()) {
            return true;
        }
        return false;
    }

    public void allowEligibilityWaiver(List<EligibilityCriteria> eligibilityCriteriaList, PersonUser waivedBy) {
        if (!canAllowEligibilityWaiver()) {
            throw new C3PRBaseRuntimeException(
                    "Cannot allow waiver. Either there are no invalid eligibility answers or the scheduled epoch is not in pending state.");
        }
        if (waivedBy == null) {
            throw new C3PRBaseRuntimeException("Cannot allow waiver. Research staff is null.");
        }
        List<SubjectEligibilityAnswer> subjectEligibilityAnswers = getScheduledEpoch()
                .getSubjectEligibilityAnswers();
        for (EligibilityCriteria eligibilityCriteria : eligibilityCriteriaList) {
            for (SubjectEligibilityAnswer subjectEligibilityAnswer : subjectEligibilityAnswers) {
                if (subjectEligibilityAnswer.getEligibilityCriteria().equals(eligibilityCriteria)) {
                    if (!subjectEligibilityAnswer.canAllowWaiver()) {
                        throw new C3PRBaseRuntimeException("Eligibility criteria does not qualify for waiver.");
                    }
                    subjectEligibilityAnswer.setAllowWaiver(true);
                    subjectEligibilityAnswer.setWaivedBy(waivedBy);
                }
            }
        }
    }

    public void waiveEligibility(List<SubjectEligibilityAnswer> subjectEligibilityAnswersInput) {
        List<SubjectEligibilityAnswer> subjectEligibilityAnswers = getScheduledEpoch()
                .getSubjectEligibilityAnswers();
        for (SubjectEligibilityAnswer subjectEligibilityAnswerInput : subjectEligibilityAnswersInput) {
            for (SubjectEligibilityAnswer subjectEligibilityAnswer : subjectEligibilityAnswers) {
                if (subjectEligibilityAnswer.getEligibilityCriteria()
                        .equals(subjectEligibilityAnswerInput.getEligibilityCriteria())) {
                    if (!subjectEligibilityAnswer.getAllowWaiver()) {
                        throw new C3PRBaseRuntimeException(
                                "Eligibility criteria cannot be waved. Please contact the study coordinator to initiate the waiver process.");
                    }
                    if (StringUtils.isBlank(subjectEligibilityAnswerInput.getWaiverId())) {
                        throw new C3PRBaseRuntimeException("Cannot waive eligibility without a waiver id");
                    }
                    if (StringUtils.isBlank(subjectEligibilityAnswerInput.getWaiverReason())) {
                        throw new C3PRBaseRuntimeException("Cannot waive eligibility without a waiver reason");
                    }
                    subjectEligibilityAnswer.setWaiverId(subjectEligibilityAnswerInput.getWaiverId());
                    subjectEligibilityAnswer.setWaiverReason(subjectEligibilityAnswerInput.getWaiverReason());
                }
            }
        }
    }

    @Transient
    public Identifier getUniqueIdentifier() {
        return getSystemAssignedIdentifiers().get(0);
    }

    @Transient
    public StudyVersion getLastConsentedStudyVersion() {

        TreeSet uniqueStudyVersions = new TreeSet();
        List<StudySubjectConsentVersion> allStudySubjectConsentVersions = getAllConsents();

        for (StudySubjectConsentVersion studySubjectConsentVersion : allStudySubjectConsentVersions) {
            uniqueStudyVersions.add(studySubjectConsentVersion.getConsent().getStudyVersion());
        }

        return uniqueStudyVersions.isEmpty() ? null : (StudyVersion) uniqueStudyVersions.last();
    }

    @Transient
    public StudyVersion getFirstConsentedStudyVersion() {

        TreeSet uniqueStudyVersions = new TreeSet();
        List<StudySubjectConsentVersion> allStudySubjectConsentVersions = getAllConsents();
        for (StudySubjectConsentVersion studySubjectConsentVersion : allStudySubjectConsentVersions) {
            uniqueStudyVersions.add(studySubjectConsentVersion.getConsent().getStudyVersion());
        }

        return uniqueStudyVersions.isEmpty() ? null : (StudyVersion) uniqueStudyVersions.first();
    }

    // returns all signed study subject consent versions across different study subject versions
    @Transient
    public List<StudySubjectConsentVersion> getAllSignedConsents() {

        List<StudySubjectConsentVersion> allStudySubjectConsentVersions = new ArrayList<StudySubjectConsentVersion>();
        for (StudySubjectStudyVersion studySubjectStudyVersion : getStudySubjectStudyVersions()) {
            for (StudySubjectConsentVersion studySubjectConsentVersion : studySubjectStudyVersion
                    .getStudySubjectConsentVersions()) {
                if (studySubjectConsentVersion.getInformedConsentSignedDate() != null) {
                    allStudySubjectConsentVersions.add(studySubjectConsentVersion);
                }
            }
        }
        return allStudySubjectConsentVersions;
    }

    // returns all signed and unsigned study subject consent versions across different study subject versions (thought there will
    // be only 1 study subject version )
    @Transient
    public List<StudySubjectConsentVersion> getAllConsents() {

        List<StudySubjectConsentVersion> allStudySubjectConsentVersions = new ArrayList<StudySubjectConsentVersion>();
        for (StudySubjectStudyVersion studySubjectStudyVersion : getStudySubjectStudyVersions()) {
            allStudySubjectConsentVersions.addAll(studySubjectStudyVersion.getStudySubjectConsentVersions());
        }
        return allStudySubjectConsentVersions;
    }

    // ReConsent API
    // Begin

    // gets original consents signed by a subject i.e. first time he/she is registered on a study.
    @Transient
    public List<StudySubjectConsentVersion> getOriginalSignedConsents() {

        StudyVersion firstConsentedStudyVersion = getFirstConsentedStudyVersion();
        List<StudySubjectConsentVersion> originalStudySubjectConsnetVersions = new ArrayList<StudySubjectConsentVersion>();

        for (StudySubjectConsentVersion studySubjectConsentVersion : getAllSignedConsents()) {
            if (firstConsentedStudyVersion.equals(studySubjectConsentVersion.getConsent().getStudyVersion())) {
                originalStudySubjectConsnetVersions.add(studySubjectConsentVersion);
            }
        }

        return originalStudySubjectConsnetVersions;
    }

    // gets original signed and unsigned consents first time he/she is registered on a study.
    @Transient
    public List<StudySubjectConsentVersion> getOriginalConsents() {

        StudyVersion firstConsentedStudyVersion = getFirstConsentedStudyVersion();
        List<StudySubjectConsentVersion> originalStudySubjectConsnetVersions = new ArrayList<StudySubjectConsentVersion>();

        for (StudySubjectConsentVersion studySubjectConsentVersion : getAllConsents()) {
            if (firstConsentedStudyVersion.equals(studySubjectConsentVersion.getConsent().getStudyVersion())) {
                originalStudySubjectConsnetVersions.add(studySubjectConsentVersion);
            }
        }

        return originalStudySubjectConsnetVersions;
    }

    // returns the study subject signed consent versions on latest study version
    @Transient
    public List<StudySubjectConsentVersion> getLatestSignedConsents() {

        StudyVersion lastConsentedStudyVersion = getLastConsentedStudyVersion();
        List<StudySubjectConsentVersion> latestStudySubjectConsnetVersions = new ArrayList<StudySubjectConsentVersion>();

        for (StudySubjectConsentVersion studySubjectConsentVersion : getAllSignedConsents()) {
            if (lastConsentedStudyVersion.equals(studySubjectConsentVersion.getConsent().getStudyVersion())) {
                latestStudySubjectConsnetVersions.add(studySubjectConsentVersion);
            }
        }

        return latestStudySubjectConsnetVersions;
    }

    // returns the study subject signed and unsigned consent versions on latest study version
    @Transient
    public List<StudySubjectConsentVersion> getLatestConsents() {

        StudyVersion lastConsentedStudyVersion = getLastConsentedStudyVersion();
        List<StudySubjectConsentVersion> latestStudySubjectConsnetVersions = new ArrayList<StudySubjectConsentVersion>();

        for (StudySubjectConsentVersion studySubjectConsentVersion : getAllConsents()) {
            if (lastConsentedStudyVersion.equals(studySubjectConsentVersion.getConsent().getStudyVersion())) {
                latestStudySubjectConsnetVersions.add(studySubjectConsentVersion);
            }
        }

        return latestStudySubjectConsnetVersions;
    }

    // returns signed study subject consent versions on a given study version
    @Transient
    public List<StudySubjectConsentVersion> getSignedConsents(String studyVersionName) {
        StudyVersion studyVersion = getStudySite().getStudy().getStudyVersion(studyVersionName);

        // throw exception when no study version with the name is found
        if (studyVersion == null) {
            throw getC3PRExceptionHelper().getRuntimeException(
                    getCode("C3PR.EXCEPTION.STUDY.VERSION_WITH_NAME_NOT_FOUND.CODE"),
                    new String[] { studyVersionName });
        }

        List<Consent> consentsOnGivenStudyVersion = studyVersion.getConsents();
        List<StudySubjectConsentVersion> informedConsentsOnStudyVersion = new ArrayList<StudySubjectConsentVersion>();
        for (StudySubjectConsentVersion studySubjectConsentVersion : this.getAllSignedConsents()) {
            if (consentsOnGivenStudyVersion.contains(studySubjectConsentVersion.getConsent())) {
                informedConsentsOnStudyVersion.add(studySubjectConsentVersion);
            }
        }

        return informedConsentsOnStudyVersion;
    }

    // returns signed and unsigned study subject consent versions on a given study version
    @Transient
    public List<StudySubjectConsentVersion> getConsents(String studyVersionName) {
        StudyVersion studyVersion = getStudySite().getStudy().getStudyVersion(studyVersionName);

        // throw exception when no study version with the name is found
        if (studyVersion == null) {
            throw getC3PRExceptionHelper().getRuntimeException(
                    getCode("C3PR.EXCEPTION.STUDY.VERSION_WITH_NAME_NOT_FOUND.CODE"),
                    new String[] { studyVersionName });
        }

        List<Consent> consentsOnGivenStudyVersion = studyVersion.getConsents();
        List<StudySubjectConsentVersion> informedConsentsOnStudyVersion = new ArrayList<StudySubjectConsentVersion>();
        for (StudySubjectConsentVersion studySubjectConsentVersion : this.getAllConsents()) {
            if (consentsOnGivenStudyVersion.contains(studySubjectConsentVersion.getConsent())) {
                informedConsentsOnStudyVersion.add(studySubjectConsentVersion);
            }
        }

        return informedConsentsOnStudyVersion;
    }

    // returns null if a study version with the given name cannot be found in the study subject study version
    @Transient
    public Boolean hasSignedConsents(String studyVersionName) {

        return (getSignedConsents(studyVersionName).size() > 0);

    }

    @Transient
    public Boolean canReConsent(String studyVersionName) {

        // a subject can re consent only if his/her registration is in reserved, registered but not 
        //enrolled or enrolled status. 

        if (this.getAllSignedConsents().size() == 0) {
            return false;
        }

        if (getRegWorkflowStatus() == RegistrationWorkFlowStatus.OFF_STUDY
                || getRegWorkflowStatus() == RegistrationWorkFlowStatus.INVALID) {
            return false;
        }

        // obtain the study version object from study based on study version name
        StudyVersion reConsentingStudyVersion = this.getStudySite().getStudy().getStudyVersion(studyVersionName);

        // throw exception when no study version is found in study with given name
        if (reConsentingStudyVersion == null) {
            throw getC3PRExceptionHelper().getRuntimeException(
                    getCode("C3PR.EXCEPTION.STUDY.VERSION_WITH_NAME_NOT_FOUND.CODE"),
                    new String[] { studyVersionName });
        }

        // Subject cannot re consent on a study version if he/she already signed consent forms on this version.
        if (hasSignedConsents(studyVersionName)) {
            return false;
        }

        // Subject cannot re consent on a study version if he/she signed consent forms on a study version after this one.
        for (StudyVersion studyVersion : this.getStudySite().getStudy().getStudyVersions()) {
            if (studyVersion.getVersionDate().after(reConsentingStudyVersion.getVersionDate())
                    && hasSignedConsents(studyVersion.getName()))
                return false;
        }

        return true;

    }

    @Transient
    public void reConsent(String studyVersionName,
            List<StudySubjectConsentVersion> studySubjectConsentVersionsHolder) {

        log.debug("Calling reConsent API");
        if (studySubjectConsentVersionsHolder == null || studySubjectConsentVersionsHolder.size() == 0) {
            throw new C3PRBaseRuntimeException("Null consent(s) passed in the arguments of reConsent() API");
        }
        if (canReConsent(studyVersionName)) {

            // obtain the study version object from study based on study version name
            StudyVersion reConsentingStudyVersion = this.getStudySite().getStudy()
                    .getStudyVersion(studyVersionName);

            StudySubjectStudyVersion studySubjectStudyVersion = new StudySubjectStudyVersion();
            studySubjectStudyVersion.setStudySiteStudyVersion(
                    this.getStudySite().getStudySiteStudyVersionGivenStudyVersionName(studyVersionName));

            for (StudySubjectConsentVersion studySubjectConsentVersionHolder : studySubjectConsentVersionsHolder) {

                StudySubjectConsentVersion newStudySubjectConsentVersion = new StudySubjectConsentVersion();

                // retrieve consent in the study version based on name 
                if (studySubjectConsentVersionHolder.getConsent() == null
                        || StringUtils.isBlank(studySubjectConsentVersionHolder.getConsent().getName())) {
                    throw new C3PRBaseRuntimeException(
                            "Null consent or consent name passed in arguments of reConsent() API");
                }
                Consent newConsent = reConsentingStudyVersion
                        .getConsentByName(studySubjectConsentVersionHolder.getConsent().getName());

                // throw exception if no consent is found in study version with the given name
                if (newConsent == null) {
                    throw getC3PRExceptionHelper().getRuntimeException(
                            getCode("C3PR.EXCEPTION.STUDY.VERSION_CONSENT_WITH_NAME_NOT_FOUND.CODE"), new String[] {
                                    studySubjectConsentVersionHolder.getConsent().getName(), studyVersionName });
                }

                // set the retrieved consent to the new study subject consent version
                newStudySubjectConsentVersion.setConsent(newConsent);

                // validate consenting method, consent delivery and signed dates
                if (studySubjectConsentVersionHolder.getConsentingMethod() != null) {
                    validateConsentingMethod(reConsentingStudyVersion, newConsent,
                            studySubjectConsentVersionHolder.getConsentingMethod());
                }
                validateInformedConsentSignedDateAndDeliveryDate(reConsentingStudyVersion, newConsent,
                        studySubjectConsentVersionHolder.getConsentDeliveryDate(),
                        studySubjectConsentVersionHolder.getInformedConsentSignedDate());

                // copy other information into new study subject consent version from the consent data holder
                newStudySubjectConsentVersion
                        .setConsentingMethod(studySubjectConsentVersionHolder.getConsentingMethod());
                newStudySubjectConsentVersion
                        .setConsentPresenter(studySubjectConsentVersionHolder.getConsentPresenter());
                newStudySubjectConsentVersion
                        .setConsentDeliveryDate(studySubjectConsentVersionHolder.getConsentDeliveryDate());

                newStudySubjectConsentVersion.setInformedConsentSignedDate(
                        studySubjectConsentVersionHolder.getInformedConsentSignedDate());

                // Create new study subject study version and add study subject consent versions to it.

                studySubjectStudyVersion.addStudySubjectConsentVersion(newStudySubjectConsentVersion);
            }
            this.addStudySubjectStudyVersion(studySubjectStudyVersion);
            // validate mandatory indicator
            validateMandatoryInformedConsents(reConsentingStudyVersion);

        } else {

            // throw exception when study subject cannot consent on the given study version.
            throw getC3PRExceptionHelper().getRuntimeException(
                    getCode("C3PR.EXCEPTION.REGISTRATION.CANNOT_RECONSENT.CODE"),
                    new String[] { studyVersionName });
        }

    }

    @Transient
    private void validateConsentingMethod(StudyVersion studyVersion, Consent consent,
            ConsentingMethod consentingMethod) {
        if (!consent.getConsentingMethods().contains(consentingMethod)) {
            // throw exception when consenting method is not found in the consent from study version.
            throw getC3PRExceptionHelper().getRuntimeException(getCode(
                    "C3PR.EXCEPTION.REGISTRATION.CONSENTING_METHOD_NOT_FOUND_IN_STUDY_VERSION_CONSENT.CODE"),
                    new String[] { consentingMethod.getName(), consent.getName(), studyVersion.getName() });
        }

    }

    @Transient
    private void validateInformedConsentSignedDateAndDeliveryDate(StudyVersion studyVersion, Consent consent,
            Date consentDeliveryDate, Date consentSignedDate) {

        if (consentDeliveryDate != null && consentDeliveryDate.after(new Date())) {
            throw getC3PRExceptionHelper().getRuntimeException(
                    getCode("C3PR.EXCEPTION.REGISTRATION.CONSENT_DELIVERY_DATE_CANNOT_BE_IN_FUTURE.CODE"),
                    new String[] { consent.getName() });
        }

        if (consentSignedDate != null && consentSignedDate.after(new Date())) {
            throw getC3PRExceptionHelper().getRuntimeException(
                    getCode("C3PR.EXCEPTION.REGISTRATION.CONSENT_SIGNED_DATE_CANNOT_BE_IN_FUTURE.CODE"),
                    new String[] { consent.getName() });
        }
        if (consentSignedDate != null && consentDeliveryDate != null
                && consentDeliveryDate.after(consentSignedDate)) {
            throw getC3PRExceptionHelper().getRuntimeException(
                    getCode("C3PR.EXCEPTION.REGISTRATION.CONSENT_SIGNED_DATE_CANNOT_BE_BEFORE_DELIVERY_DATE.CODE"),
                    new String[] { consent.getName() });
        }

        if (consentSignedDate != null) {
            if (!getStudySite().canEnroll(studyVersion, consentSignedDate)) {
                throw getC3PRExceptionHelper().getRuntimeException(getCode(
                        "C3PR.EXCEPTION.REGISTRATION.CONSENT_SIGNED_DATE_DOES_NOT_BELONG_TO_STUDY_SITE_VERSION.CODE"),
                        new String[] { consent.getName() });
            }
        }

    }

    @Transient
    private void validateMandatoryInformedConsents(StudyVersion studyVersion) {
        StudySubjectStudyVersion studySubjectStudyVersionGivenStudyVersion = getStudySubjectStudyVersion(
                studyVersion);
        if (studySubjectStudyVersionGivenStudyVersion == null) {
            throw getC3PRExceptionHelper().getRuntimeException(
                    getCode("C3PR.EXCEPTION.REGISTRATION.STUDY_SUBJECT_VERSION_NOT_FOUND_FOR_STUDY_VERSION.CODE"),
                    new String[] { studyVersion.getName() });
        }
        for (Consent consent : studyVersion.getConsents()) {
            if (consent.getMandatoryIndicator()) {
                if (!studySubjectStudyVersionGivenStudyVersion.hasSignedConsent(consent)) {
                    throw getC3PRExceptionHelper().getRuntimeException(getCode(
                            "C3PR.EXCEPTION.REGISTRATION.MANDATORY_CONSENT_NOT_SIGNED_IN_STUDY_VERSION.CODE"),
                            new String[] { consent.getName() });
                }
            }
        }
    }

    // END
    // ReConsent API

    @Transient
    public boolean canDiscontinueEnrollment() {
        return (this.regWorkflowStatus == RegistrationWorkFlowStatus.PENDING
                || this.regWorkflowStatus == RegistrationWorkFlowStatus.RESERVED
                || this.regWorkflowStatus == RegistrationWorkFlowStatus.PENDING_ON_STUDY);
    }

    @Transient
    public void discontinueEnrollment(List<OffEpochReason> discontinueEpochReasons, Date discontinueEpochDate) {
        ScheduledEpoch scheduledEpoch = getScheduledEpoch();
        if (canDiscontinueEnrollment()) {
            scheduledEpoch.takeSubjectOffEpoch(discontinueEpochReasons, discontinueEpochDate);
            setRegWorkflowStatus(RegistrationWorkFlowStatus.NOT_REGISTERED);
        } else {
            throw new C3PRBaseRuntimeException(
                    "The subject is not on-study yet. The subject cannot discontinue registration but only be taken off study.");
        }

    }

    @OneToMany(orphanRemoval = true)
    @Fetch(FetchMode.SUBSELECT)
    @Cascade({ CascadeType.ALL })
    @JoinColumn(name = "stu_sub_id", nullable = false)
    public List<StudySubjectRegistryStatus> getStudySubjectRegistryStatusHistoryInternal() {
        return studySubjectRegistryStatusHistory;
    }

    public void setStudySubjectRegistryStatusHistoryInternal(
            List<StudySubjectRegistryStatus> studySubjectRegistryStatusHistory) {
        this.studySubjectRegistryStatusHistory = studySubjectRegistryStatusHistory;
    }

    @Transient
    public void updateRegistryStatus(String code, Date effectiveDate, String comment,
            List<RegistryStatusReason> reasons) {
        PermissibleStudySubjectRegistryStatus status = null;
        for (PermissibleStudySubjectRegistryStatus permissibleStudySubjectRegistryStatus : getStudySite().getStudy()
                .getPermissibleStudySubjectRegistryStatuses()) {
            if (permissibleStudySubjectRegistryStatus.getRegistryStatus().getCode().equals(code)) {
                status = permissibleStudySubjectRegistryStatus;
                break;
            }
        }
        if (status == null) {
            throw getC3PRExceptionHelper().getRuntimeException(
                    getCode("C3PR.EXCEPTION.REGISTRY.INVALID_STATUS.CODE"), new String[] { code });
        }
        if (reasons != null && reasons.size() > 0) {
            List<RegistryStatusReason> primaryReasons = new ArrayList<RegistryStatusReason>();
            List<RegistryStatusReason> secondaryReasons = new ArrayList<RegistryStatusReason>();
            for (RegistryStatusReason reason : reasons) {
                if (reason.getPrimaryIndicator()) {
                    primaryReasons.add(reason);
                } else {
                    secondaryReasons.add(reason);
                }
            }
            if (primaryReasons.size() != 0
                    && !status.getRegistryStatus().getPrimaryReasons().containsAll(primaryReasons)) {
                throw getC3PRExceptionHelper().getRuntimeException(
                        getCode("C3PR.EXCEPTION.REGISTRY.INVALID_STATUS_REASON.CODE"), new String[] { code });
            } else if (secondaryReasons.size() != 0
                    && !status.getSecondaryReasons().containsAll(secondaryReasons)) {
                throw getC3PRExceptionHelper().getRuntimeException(
                        getCode("C3PR.EXCEPTION.REGISTRY.INVALID_STATUS_REASON.CODE"), new String[] { code });
            }
            studySubjectRegistryStatusHistory
                    .add(new StudySubjectRegistryStatus(effectiveDate, status, comment, reasons));
        } else {
            studySubjectRegistryStatusHistory.add(new StudySubjectRegistryStatus(effectiveDate, status, comment));
        }
    }

    @Transient
    public StudySubjectRegistryStatus getStudySubjectRegistryStatus() {
        return getStudySubjectRegistryStatusHistory().get(0);
    }

    @Transient
    public List<StudySubjectRegistryStatus> getStudySubjectRegistryStatusHistory() {
        List<StudySubjectRegistryStatus> sorted = new ArrayList<StudySubjectRegistryStatus>(
                studySubjectRegistryStatusHistory);
        Collections.sort(sorted, new Comparator<StudySubjectRegistryStatus>() {
            public int compare(StudySubjectRegistryStatus o1, StudySubjectRegistryStatus o2) {
                return o1.getEffectiveDate().compareTo(o2.getEffectiveDate());
            }
        });
        Collections.reverse(sorted);
        return sorted;
    }

    @Transient
    public StudySubjectStudyVersion getStudySubjectStudyVersion(StudyVersion studyVersion) {
        for (StudySubjectStudyVersion localStudySubjectStudyVersion : this.getStudySubjectStudyVersions()) {
            if (localStudySubjectStudyVersion.getStudySiteStudyVersion().getStudyVersion().getName()
                    .equals(studyVersion.getName())) {
                return localStudySubjectStudyVersion;
            }
        }
        return null;
    }

    @Transient
    public Correspondence getByCorrespondenceId(int correspondenceId) {
        for (Correspondence correspondence : this.getCorrespondences()) {
            if (correspondence.getId() != null && correspondence.getId().equals(correspondenceId)) {
                return correspondence;
            }
        }
        return null;
    }

}