org.tdl.vireo.model.jpa.JpaSubmissionImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.tdl.vireo.model.jpa.JpaSubmissionImpl.java

Source

package org.tdl.vireo.model.jpa;

import java.io.File;
import java.io.IOException;
import java.text.DateFormatSymbols;
import java.text.SimpleDateFormat;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;

import javax.persistence.CascadeType;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.OrderBy;
import javax.persistence.OrderColumn;
import javax.persistence.PostLoad;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Transient;

import org.apache.commons.lang.LocaleUtils;
import org.tdl.vireo.model.ActionLog;
import org.tdl.vireo.model.Attachment;
import org.tdl.vireo.model.AttachmentType;
import org.tdl.vireo.model.CommitteeMember;
import org.tdl.vireo.model.CustomActionDefinition;
import org.tdl.vireo.model.CustomActionValue;
import org.tdl.vireo.model.DegreeLevel;
import org.tdl.vireo.model.EmbargoType;
import org.tdl.vireo.model.Language;
import org.tdl.vireo.model.NameFormat;
import org.tdl.vireo.model.Person;
import org.tdl.vireo.model.Submission;
import org.tdl.vireo.proquest.ProquestLanguage;
import org.tdl.vireo.security.SecurityContext;
import org.tdl.vireo.services.Utilities;
import org.tdl.vireo.state.State;
import org.tdl.vireo.state.StateManager;

import play.modules.spring.Spring;

/**
 * JPA specific implementation of Vireo's Submission interface.
 * 
 * @author <a href="http://www.scottphillips.com">Scott Phillips</a>
 */
@Entity
@Table(name = "submission")
public class JpaSubmissionImpl extends JpaAbstractModel<JpaSubmissionImpl> implements Submission {

    @ManyToOne(optional = false, targetEntity = JpaPersonImpl.class)
    public Person submitter;

    @Column(length = 255)
    public String studentFirstName;
    @Column(length = 255)
    public String studentLastName;
    @Column(length = 255)
    public String studentMiddleName;
    public Integer studentBirthYear;

    @Column(length = 326768) // 2^15
    public String documentTitle;
    @Column(length = 326768) // 2^15
    public String documentAbstract;
    @Column(length = 326768) // 2^15
    public String documentKeywords;
    @ElementCollection
    @OrderColumn
    @CollectionTable(name = "submission_subjects")
    public List<String> documentSubjects;

    @Column(length = 255)
    public String documentLanguage;

    @Column(length = 326768) // 2^15
    public String publishedMaterial;

    @OneToOne(targetEntity = JpaEmbargoTypeImpl.class)
    public EmbargoType embargoType;

    @OneToMany(targetEntity = JpaAttachmentImpl.class, mappedBy = "submission", cascade = CascadeType.ALL)
    public List<Attachment> attachments;

    @OneToMany(targetEntity = JpaCommitteeMemberImpl.class, mappedBy = "submission", cascade = CascadeType.ALL)
    @OrderBy("displayOrder")
    public List<CommitteeMember> committeeMembers;
    @Column(length = 255)
    public String committeeContactEmail;

    @Column(unique = true, length = 255)
    public String committeeEmailHash;

    @Temporal(TemporalType.TIMESTAMP)
    public Date committeeApprovalDate;
    @Temporal(TemporalType.TIMESTAMP)
    public Date committeeEmbargoApprovalDate;

    @Temporal(TemporalType.TIMESTAMP)
    public Date submissionDate;
    @Temporal(TemporalType.TIMESTAMP)
    public Date approvalDate;
    @Temporal(TemporalType.TIMESTAMP)
    public Date licenseAgreementDate;
    @Temporal(TemporalType.TIMESTAMP)
    public Date defenseDate;

    @Column(length = 255)
    public String degree;
    public DegreeLevel degreeLevel;
    @Column(length = 255)
    public String department;
    @Column(length = 255)
    public String college;
    @Column(length = 255)
    public String program;
    @Column(length = 255)
    public String major;
    @Column(length = 255)
    public String documentType;

    public Integer graduationYear;
    public Integer graduationMonth;

    public String stateName;

    @OneToOne(targetEntity = JpaPersonImpl.class)
    public Person assignee;
    public Boolean UMIRelease;

    @OneToMany(targetEntity = JpaCustomActionValueImpl.class, mappedBy = "submission", cascade = CascadeType.ALL)
    public List<CustomActionValue> customActions;

    @Column(length = 1024)
    public String depositId;
    @Temporal(TemporalType.TIMESTAMP)
    public Date depositDate;

    @Column(length = 326768) // 2^15
    public String reviewerNotes;

    @Column(length = 326768) // 2^15
    public String lastActionLogEntry;
    @Temporal(TemporalType.TIMESTAMP)
    public Date lastActionLogDate;

    // This is not publicly available, only present for queries.
    @OneToMany(targetEntity = JpaActionLogImpl.class, mappedBy = "submission")
    public List<ActionLog> actionLogs;

    // List of log items pending a save.
    @Transient
    protected List<ActionLog> pendingLogs = new ArrayList<ActionLog>();

    // Flag whether the subjects list has changed.
    @Transient
    protected boolean documentSubjectsChanged = false;

    /**
     * Insure that the pendingLogs array is initialized when loading the object
     * from the database.
     */
    @PostLoad
    private void onPostLoad() {
        pendingLogs = new ArrayList<ActionLog>();
    }

    /**
     * Construct a new JpaSubmissionImpl
     * 
     * @param submitter
     *            The student submitting this submission.
     */
    protected JpaSubmissionImpl(Person submitter) {

        if (submitter == null)
            throw new IllegalArgumentException("Submissions require a submitter");

        assertReviewerOrOwner(submitter);

        this.submitter = submitter;
        this.documentSubjects = new ArrayList<String>();
        this.attachments = new ArrayList<Attachment>();
        this.committeeMembers = new ArrayList<CommitteeMember>();
        this.customActions = new ArrayList<CustomActionValue>();
        this.actionLogs = new ArrayList<ActionLog>();
        this.stateName = (Spring.getBeanOfType(StateManager.class).getInitialState()).getBeanName();

        generateLog("Submission created", true);
    }

    @Override
    public JpaSubmissionImpl save() {
        assertReviewerOrOwner(submitter);

        if (documentSubjectsChanged) {
            // Generate a note that the subjects have been updated.
            String entry = "";
            if (documentSubjects.size() == 0)
                entry = "Document subjects cleared";
            else {
                for (String subject : documentSubjects) {
                    if (entry.length() != 0)
                        entry += "; ";
                    entry += "'" + subject + "'";
                }
                entry = "Document subjects changed to " + entry;
            }

            generateLog(entry, false);
            documentSubjectsChanged = false;
        }

        if (pendingLogs.size() > 0) {
            lastActionLogEntry = pendingLogs.get(pendingLogs.size() - 1).getEntry();
            lastActionLogDate = pendingLogs.get(pendingLogs.size() - 1).getActionDate();
        }

        // Scrub all user-exposed String fields of Unicode control stuff   
        this.documentTitle = Utilities.scrubControl(this.documentTitle, "");
        this.documentAbstract = Utilities.scrubControl(this.documentAbstract, " ");
        this.documentKeywords = Utilities.scrubControl(this.documentKeywords, " ");
        this.publishedMaterial = Utilities.scrubControl(this.publishedMaterial, " ");

        super.save();

        // After saving save all pending actionlogs
        for (ActionLog log : pendingLogs) {
            log.save();
        }
        pendingLogs.clear();

        return this;
    }

    @Override
    public JpaSubmissionImpl delete() {

        assertReviewerOrOwner(submitter);

        // Don't rely on the cascade for deleting attachments because the files
        // need to be deleted on disk.
        List<Attachment> attachmentsCopy = new ArrayList<Attachment>(attachments);
        for (Attachment attachment : attachmentsCopy) {
            attachment.delete();
        }

        // Explicitly delete the committee members because their roles will not
        // delete on cascade.
        List<CommitteeMember> membersCopy = new ArrayList<CommitteeMember>(committeeMembers);
        for (CommitteeMember member : membersCopy) {
            member.delete();
        }

        // Delete all action logs associated with this submission
        em().createQuery("DELETE FROM JpaActionLogImpl " + "WHERE Submission_Id = ? ").setParameter(1, this.getId())
                .executeUpdate();

        return super.delete();
    }

    @Override
    public JpaSubmissionImpl detach() {
        submitter.detach();
        if (assignee != null)
            assignee.detach();
        if (embargoType != null)
            embargoType.detach();
        return super.detach();
    }

    @Override
    public Person getSubmitter() {
        return submitter;
    }

    @Override
    public String getStudentFirstName() {
        return studentFirstName;
    }

    @Override
    public void setStudentFirstName(String firstName) {

        assertReviewerOrOwner(submitter);

        if (firstName != null && firstName.trim().length() == 0)
            firstName = null;

        if (!equals(this.studentFirstName, firstName)) {
            this.studentFirstName = firstName;
            generateChangeLog("Student first name", firstName, false);

            updatePrimaryDocumentName();
        }
    }

    @Override
    public String getStudentLastName() {
        return studentLastName;
    }

    @Override
    public void setStudentLastName(String lastName) {

        assertReviewerOrOwner(submitter);

        if (lastName != null && lastName.trim().length() == 0)
            lastName = null;

        if (!equals(this.studentLastName, lastName)) {
            this.studentLastName = lastName;
            generateChangeLog("Student last name", lastName, false);

            updatePrimaryDocumentName();
        }
    }

    @Override
    public String getStudentMiddleName() {
        return studentMiddleName;
    }

    public void setStudentMiddleName(String middleName) {

        assertReviewerOrOwner(submitter);

        if (middleName != null && middleName.trim().length() == 0)
            middleName = null;

        if (!equals(this.studentMiddleName, middleName)) {
            this.studentMiddleName = middleName;
            generateChangeLog("Student middle name", middleName, false);
        }
    }

    @Override
    public Integer getStudentBirthYear() {
        return studentBirthYear;
    }

    @Override
    public void setStudentBirthYear(Integer year) {

        assertReviewerOrOwner(submitter);

        if (!equals(this.studentBirthYear, year)) {
            this.studentBirthYear = year;

            if (year == null)
                generateChangeLog("Student birth year", null, false);
            else
                generateChangeLog("Student birth year", String.valueOf(year), false);
        }
    }

    @Override
    public String getStudentFormattedName(NameFormat format) {

        return NameFormat.format(format, studentFirstName, studentMiddleName, studentLastName, studentBirthYear);
    }

    @Override
    public String getDocumentTitle() {
        return documentTitle;
    }

    @Override
    public void setDocumentTitle(String title) {

        assertReviewerOrOwner(submitter);
        if (!equals(this.documentTitle, title)) {
            this.documentTitle = title;
            generateChangeLog("Document title", title, false);
        }
    }

    @Override
    public String getDocumentAbstract() {
        return documentAbstract;
    }

    @Override
    public void setDocumentAbstract(String docAbstract) {

        assertReviewerOrOwner(submitter);

        if (!equals(this.documentAbstract, docAbstract)) {
            this.documentAbstract = docAbstract;
            generateChangeLog("Document abstract", docAbstract, false);
        }
    }

    @Override
    public String getDocumentKeywords() {
        return documentKeywords;
    }

    @Override
    public void setDocumentKeywords(String keywords) {

        assertReviewerOrOwner(submitter);

        if (!equals(this.documentKeywords, keywords)) {
            this.documentKeywords = keywords;
            generateChangeLog("Document keywords", keywords, false);
        }
    }

    @Override
    public List<String> getDocumentSubjects() {
        return documentSubjects;
    }

    @Override
    public void addDocumentSubject(String subject) {
        documentSubjects.add(subject);
        documentSubjectsChanged = true;
    }

    @Override
    public void removeDocumentSubject(String subject) {
        documentSubjects.remove(subject);
        documentSubjectsChanged = true;
    }

    @Override
    public void setDocumentLanguage(String language) {

        if (language != null) {
            if (language.isEmpty())
                language = null;
            else if (JpaLanguageImpl._toLocale(language) == null)
                throw new IllegalArgumentException("Language is an invalid locale");
        }

        assertReviewerOrOwner(submitter);

        if (!equals(this.documentLanguage, language)) {
            this.documentLanguage = language;
            if (language != null)
                generateChangeLog("Document language", JpaLanguageImpl._toLocale(language).getDisplayName(), false);
            else
                generateChangeLog("Document language", null, false);
        }
    }

    @Override
    public String getDocumentLanguage() {
        return documentLanguage;
    }

    @Override
    public Locale getDocumentLanguageLocale() {
        return JpaLanguageImpl._toLocale(documentLanguage);
    }

    @Override
    public String getPublishedMaterial() {
        return publishedMaterial;
    }

    @Override
    public void setPublishedMaterial(String material) {

        assertReviewerOrOwner(submitter);

        if (!equals(this.publishedMaterial, material)) {
            this.publishedMaterial = material;
            if (material != null)
                generateChangeLog("Published material", material, false);
            else
                generateChangeLog("Published material", null, false);
        }
    }

    @Override
    public EmbargoType getEmbargoType() {
        return embargoType;
    }

    @Override
    public void setEmbargoType(EmbargoType embargo) {

        assertReviewerOrOwner(submitter);

        if (!equals(this.embargoType, embargo)) {
            this.embargoType = embargo;
            generateChangeLog("Embargo type", embargo.getName(), false);
        }
    }

    @Override
    public Attachment getPrimaryDocument() {
        for (Attachment attachment : attachments) {
            if (AttachmentType.PRIMARY == attachment.getType())
                return attachment;
        }

        return null;
    }

    @Override
    public List<Attachment> getSupplementalDocuments() {

        return getAttachmentsByType(AttachmentType.SUPPLEMENTAL);

    }

    @Override
    public List<Attachment> getAttachmentsByType(AttachmentType... types) {

        List<Attachment> filteredAttachments = new ArrayList<Attachment>();
        for (AttachmentType type : types) {
            for (Attachment attachment : attachments) {
                if (type == attachment.getType())
                    filteredAttachments.add(attachment);
            }
        }

        return filteredAttachments;
    }

    @Override
    public List<Attachment> getAttachments() {
        return attachments;
    }

    /**
     * Internal call back method when an attachment has been deleted.
     * 
     * @param attachment
     *            The attachment to remove.
     */
    protected void removeAttachment(Attachment attachment) {

        attachments.remove(attachment);
    }

    @Override
    public Attachment addAttachment(File file, AttachmentType type) throws IOException {

        Attachment attachment = new JpaAttachmentImpl(this, type, file);
        attachments.add(attachment);
        return attachment;
    }

    @Override
    public Attachment addAttachment(byte[] content, String filename, AttachmentType type) throws IOException {

        Attachment attachment = new JpaAttachmentImpl(this, type, filename, content);
        attachments.add(attachment);
        return attachment;
    }

    @Override
    public Attachment findAttachmentById(Long id) {
        for (Attachment attachment : attachments) {
            if (id.compareTo(attachment.getId()) == 0)
                return attachment;
        }
        return null;
    }

    @Override
    public Attachment findAttachmentByName(String name) {
        if (name == null)
            return null;

        for (Attachment attachment : attachments) {
            if (name.equals(attachment.getName()))
                return attachment;
        }
        return null;
    }

    @Override
    public List<CommitteeMember> getCommitteeMembers() {
        return committeeMembers;
    }

    @Override
    public CommitteeMember addCommitteeMember(String firstName, String lastName, String middleName) {
        CommitteeMember member = new JpaCommitteeMemberImpl(this, firstName, lastName, middleName);
        committeeMembers.add(member);
        return member;
    }

    /**
     * Internal call back for when a committee member has been deleted, so that
     * it will be removed from the list.
     * 
     * @param member
     *            The member to remove.
     */
    protected void removeCommitteeMember(CommitteeMember member) {
        this.committeeMembers.remove(member);
    }

    @Override
    public String getCommitteeContactEmail() {
        return committeeContactEmail;
    }

    @Override
    public void setCommitteeContactEmail(String email) {
        assertReviewerOrOwner(submitter);

        if (!equals(this.committeeContactEmail, email)) {
            this.committeeContactEmail = email;
            generateChangeLog("Committee contact email address", email, false);
        }
    }

    @Override
    public String getCommitteeEmailHash() {
        return committeeEmailHash;
    }

    @Override
    public void setCommitteeEmailHash(String hash) {
        assertReviewerOrOwner(submitter);

        if (!equals(this.committeeEmailHash, hash)) {
            this.committeeEmailHash = hash;
            generateLog("New committee email hash generated", false);
        }
    }

    @Override
    public Date getCommitteeApprovalDate() {
        return committeeApprovalDate;
    }

    @Override
    public void setCommitteeApprovalDate(Date date) {
        assertReviewerOrOwner(submitter);

        if (!equals(this.committeeApprovalDate, date)) {
            this.committeeApprovalDate = date;

            if (date == null)
                generateLog("Committee approval of submission cleared", false);
            else
                generateLog("Committee approval of submission set", false);
        }
    }

    @Override
    public Date getCommitteeEmbargoApprovalDate() {
        return committeeEmbargoApprovalDate;
    }

    @Override
    public void setCommitteeEmbargoApprovalDate(Date date) {
        assertReviewerOrOwner(submitter);

        if (!equals(this.committeeEmbargoApprovalDate, date)) {
            this.committeeEmbargoApprovalDate = date;

            if (date == null)
                generateLog("Committee approval of embargo cleared", false);
            else
                generateLog("Committee approval of embargo set", false);
        }
    }

    @Override
    public Date getSubmissionDate() {
        return submissionDate;
    }

    @Override
    public void setSubmissionDate(Date date) {
        assertReviewerOrOwner(submitter);

        if (!equals(this.submissionDate, date)) {
            this.submissionDate = date;

            if (date == null) {
                generateLog("Submission date cleared", true);
            } else {
                DateFormat format = new SimpleDateFormat("MM/dd/yyyy");
                String formattedDate = format.format(date);
                generateLog("Submission date set to " + formattedDate, true);
            }
        }
    }

    @Override
    public Date getApprovalDate() {
        return approvalDate;
    }

    @Override
    public void setApprovalDate(Date date) {
        assertReviewerOrOwner(submitter);

        if (!equals(this.approvalDate, date)) {
            this.approvalDate = date;

            if (date == null)
                generateLog("Submission approval cleared", true);
            else
                generateLog("Submission approval set", true);
        }
    }

    @Override
    public Date getLicenseAgreementDate() {
        return licenseAgreementDate;
    }

    @Override
    public void setLicenseAgreementDate(Date date) {
        assertReviewerOrOwner(submitter);

        if (!equals(this.licenseAgreementDate, date)) {
            this.licenseAgreementDate = date;

            if (date == null)
                generateLog("Submission license agreement cleared", true);
            else
                generateLog("Submission license agreement set", true);
        }
    }

    @Override
    public Date getDefenseDate() {
        return defenseDate;
    }

    @Override
    public void setDefenseDate(Date date) {
        assertReviewerOrOwner(submitter);

        if (!equals(this.defenseDate, date)) {
            this.defenseDate = date;

            if (date == null) {
                generateLog("Defense date cleared", true);
            } else {
                DateFormat formatter = new SimpleDateFormat("MM/dd/yyyy");
                generateChangeLog("Defense date", formatter.format(date), true);
            }
        }
    }

    @Override
    public String getDegree() {
        return degree;
    }

    @Override
    public void setDegree(String degree) {
        assertReviewerOrOwner(submitter);

        if (!equals(this.degree, degree)) {
            this.degree = degree;
            generateChangeLog("Degree", degree, false);
        }
    }

    @Override
    public DegreeLevel getDegreeLevel() {
        return degreeLevel;
    }

    @Override
    public void setDegreeLevel(DegreeLevel level) {
        assertReviewerOrOwner(submitter);

        if (!equals(this.degreeLevel, level)) {
            this.degreeLevel = level;
            generateChangeLog("Degree level", (degreeLevel == null) ? null : degreeLevel.name(), false);
        }
    }

    @Override
    public String getDepartment() {
        return department;
    }

    @Override
    public void setDepartment(String department) {
        assertReviewerOrOwner(submitter);

        if (!equals(this.department, department)) {
            this.department = department;
            generateChangeLog("Department", department, false);
        }
    }

    @Override
    public String getCollege() {
        return college;
    }

    @Override
    public void setCollege(String college) {
        assertReviewerOrOwner(submitter);

        if (!equals(this.college, college)) {
            this.college = college;
            generateChangeLog("College", college, false);
        }
    }

    @Override
    public String getProgram() {
        return program;
    }

    @Override
    public void setProgram(String program) {
        assertReviewerOrOwner(submitter);

        if (!equals(this.program, program)) {
            this.program = program;
            generateChangeLog("Program", program, false);
        }
    }

    @Override
    public String getMajor() {
        return major;
    }

    @Override
    public void setMajor(String major) {
        assertReviewerOrOwner(submitter);

        if (!equals(this.major, major)) {
            this.major = major;
            generateChangeLog("Major", major, false);
        }
    }

    @Override
    public String getDocumentType() {
        return documentType;

    }

    @Override
    public void setDocumentType(String documentType) {
        assertReviewerOrOwner(submitter);

        if (!equals(this.documentType, documentType)) {
            this.documentType = documentType;
            generateChangeLog("Document type", documentType, false);

            updatePrimaryDocumentName();
        }
    }

    @Override
    public Integer getGraduationYear() {
        return graduationYear;
    }

    @Override
    public void setGraduationYear(Integer year) {
        assertReviewerOrOwner(submitter);

        if (!equals(this.graduationYear, year)) {
            this.graduationYear = year;

            if (year == null)
                generateChangeLog("Graduation year", null, false);
            else
                generateChangeLog("Graduation year", String.valueOf(year), false);

            updatePrimaryDocumentName();
        }
    }

    @Override
    public Integer getGraduationMonth() {
        return graduationMonth;
    }

    @Override
    public void setGraduationMonth(Integer month) {

        if (month != null && (month < 0 || month > 11))
            throw new IllegalArgumentException("Month is out of bounds.");

        assertReviewerOrOwner(submitter);

        if (!equals(this.graduationMonth, month)) {
            this.graduationMonth = month;

            if (month == null)
                generateChangeLog("Graduation month", null, false);
            else
                generateChangeLog("Graduation month", new DateFormatSymbols().getMonths()[month], false);
        }
    }

    @Override
    public State getState() {
        return Spring.getBeanOfType(StateManager.class).getState(stateName);
    }

    @Override
    public void setState(State state) {

        if (state == null)
            throw new IllegalArgumentException("State is required");

        assertReviewer();

        if (!equals(this.stateName, state.getBeanName())) {
            this.stateName = state.getBeanName();
            generateChangeLog("Submission status", state.getDisplayName(), true);

            // Check if this state is approved
            if (this.approvalDate == null && state.isApproved())
                this.setApprovalDate(new Date());
        }
    }

    @Override
    public Person getAssignee() {
        return assignee;
    }

    @Override
    public void setAssignee(Person assignee) {

        assertReviewer();

        this.assignee = assignee;

        if (assignee == null)
            generateChangeLog("Assignee", null, true);
        else
            generateChangeLog("Assignee", assignee.getFormattedName(NameFormat.FIRST_LAST), true);
    }

    @Override
    public Boolean getUMIRelease() {
        return UMIRelease;
    }

    @Override
    public void setUMIRelease(Boolean umiRelease) {

        assertReviewerOrOwner(submitter);

        if (!equals(this.UMIRelease, umiRelease)) {
            this.UMIRelease = umiRelease;

            if (umiRelease == null)
                generateLog("UMI Release cleared", false);
            else if (umiRelease == true)
                generateChangeLog("UMI Release", "Yes", false);
            else
                generateChangeLog("UMI Release", "No", false);
        }
    }

    @Override
    public List<CustomActionValue> getCustomActions() {
        return customActions;
    }

    @Override
    public CustomActionValue getCustomAction(CustomActionDefinition definition) {

        Iterator<CustomActionValue> valueItr = customActions.iterator();
        while (valueItr.hasNext()) {
            CustomActionValue value = valueItr.next();
            if (value.getDefinition().equals(definition))
                return value;
        }

        return null;
    }

    /**
     * Internal call back to notify the parent submission when a custom action
     * value has been deleted.
     * 
     * @param value
     *            The value being deleted.
     */
    protected void removeCustomAction(CustomActionValue value) {
        customActions.remove(value);
    }

    @Override
    public CustomActionValue addCustomAction(CustomActionDefinition definition, Boolean value) {
        CustomActionValue customAction = new JpaCustomActionValueImpl(this, definition, value);
        customActions.add(customAction);
        return customAction;
    }

    @Override
    public String getDepositId() {
        return depositId;
    }

    @Override
    public void setDepositId(String depositId) {
        assertReviewerOrOwner(submitter);

        if (!equals(this.depositId, depositId)) {
            this.depositId = depositId;
            generateChangeLog("Repository deposit ID", depositId, false);
        }
    }

    @Override
    public Date getDepositDate() {
        return depositDate;
    }

    @Override
    public void setDepositDate(Date depositDate) {
        assertReviewerOrOwner(submitter);

        this.depositDate = depositDate;
    }

    @Override
    public String getReviewerNotes() {
        return this.reviewerNotes;
    }

    @Override
    public void setReviewerNotes(String notes) {

        assertReviewer();

        if (!equals(this.reviewerNotes, notes)) {

            this.reviewerNotes = notes;

            // Generate a private note
            String entry;
            if (notes == null)
                entry = "Reviewer notes cleared";
            else
                entry = String.format("Reviewer notes changed to '%s'", notes);

            ActionLog log = logAction(entry);
            log.setPrivate(true);
        }

    }

    @Override
    public String getLastLogEntry() {
        return lastActionLogEntry;
    }

    @Override
    public Date getLastLogDate() {
        return lastActionLogDate;
    }

    @Override
    public ActionLog logAction(String entry) {
        return logAction(entry, null);
    }

    /**
     * Create an action log entry about this submission. This is method is not
     * included in the public interface at the present time. Maybe in the future
     * we will move it up there. This method operates exactly the same as the
     * "logAction" method, which is defined in the public interface, but accepts
     * an attachment object. The action log object generated will be associated
     * with the attachment object present.
     * 
     * @param entry
     *            The entry text to be saved, note " by User" will be appended
     *            to the end of this entry text recording who made the action.
     * @param attachment
     *            The attachment this action log item is associated with (may be
     *            null).
     * @return The unsaved action log object.
     */
    public ActionLog logAction(String entry, Attachment attachment) {

        SecurityContext context = Spring.getBeanOfType(SecurityContext.class);

        Person actor = context.getPerson();
        // The "By whom ever" is duplicating data and just uneeded.
        //if (actor != null)
        //   entry += " by " + actor.getFormattedName(NameFormat.FIRST_LAST);

        ActionLog log = new JpaActionLogImpl(this, this.getState(), actor, new Date(), attachment, entry, false);
        pendingLogs.add(log);
        return log;
    }

    /**
     * Internal method for generating log events.
     * 
     * This will create an ActionLog entry and append it to a list of pending
     * changes. Then when this object is saved() those logs will be persisted in
     * the database.
     * 
     * @param entry
     *            The log entry (note, "by User" will be appended to the end of
     *            the log entry)
     * @param always
     *            This log message should be generated no matter what state the submission is currently in.
     */
    protected void generateLog(String entry, boolean always) {
        if (!always) {
            // Ignore if the submission is in the initial state.
            StateManager manager = Spring.getBeanOfType(StateManager.class);
            if (manager.getInitialState() == this.getState())
                return;
        }

        pendingLogs.add(logAction(entry));
    }

    /**
     * Internal method for generating log events.
     * 
     * This will create a preformatted action log entry for a field change. The
     * log will then be appended to the list of pending changes which will be
     * persisted when the submission object is saved.
     * 
     * @param fieldName
     *            The name of the field being updated.
     * @param newValue
     *            The new value of the field. (may be null)
     * @param always
     *            This log message should be generated no matter what state the submission is currently in.
     */
    protected void generateChangeLog(String fieldName, String newValue, boolean always) {
        String entry;
        if (newValue == null)
            entry = fieldName + " cleared";
        else
            entry = String.format("%s changed to '%s'", fieldName, newValue);

        generateLog(entry, always);
    }

    /**
     * This is called when information that could potentially effect the name of
     * the primary document has been modified. This method is called to see if
     * the attachment should be updated.
     */
    protected void updatePrimaryDocumentName() {

        Attachment primary = this.getPrimaryDocument();
        if (primary != null) {
            ((JpaAttachmentImpl) primary).renamePrimaryDocument();
            primary.save();
        }
    }

    /**
     * Return true if the two objects are equals, accounting for nulls.
     * 
     * @param a An object
     * @param b An object
     * @return True if a is the same as b, otherwise false.
     */
    private boolean equals(Object a, Object b) {

        if (a == null) {
            if (b == null)
                return true;
            else
                return false;
        } else {
            return a.equals(b);
        }
    }

}