org.sipfoundry.voicemail.VmMessage.java Source code

Java tutorial

Introduction

Here is the source code for org.sipfoundry.voicemail.VmMessage.java

Source

/*
 * 
 * 
 * Copyright (C) 2009 Pingtel Corp., certain elements licensed under a Contributor Agreement.  
 * Contributors retain copyright to elements licensed under a Contributor Agreement.
 * Licensed to the User under the LGPL license.
 * 
 */
package org.sipfoundry.voicemail;

import java.io.File;
import java.io.IOException;
import java.io.SequenceInputStream;
import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.mail.MessagingException;
import javax.mail.Flags.Flag;
import javax.sound.sampled.AudioFileFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;

import org.apache.commons.io.FileUtils;
import org.apache.log4j.Logger;
import org.sipfoundry.commons.userdb.User;
import org.sipfoundry.commons.userdb.ValidUsersXML;
import org.sipfoundry.sipxivr.Mailbox;
import org.sipfoundry.voicemail.MessageDescriptor.Priority;

import com.sun.mail.imap.IMAPMessage;

/**
 * Represents the message in a mailbox
 */
public class VmMessage {
    static final Logger LOG = Logger.getLogger("org.sipfoundry.sipxivr");
    static Pattern namePattern = Pattern.compile("^(\\d+)-(00|01|FW)\\.(xml|wav|sta)$");

    String m_messageId; // A machine wide unique incremented "number"
    File m_audioFile;
    File m_originalAudioFile;
    File m_combinedAudioFile;
    File m_descriptorFile;
    File m_originalDescriptorFile;
    File m_statusFile;
    File m_urgentFile;
    MessageDescriptor m_messageDescriptor;
    boolean m_unHeard;
    boolean m_urgent;

    /**
     * Private constructor for factory method to call
     */
    private VmMessage() {

    }

    /**
     * LEGEND INTO FILE DIRECTORY ========================================= 
     * For a message in a voicemail directory
     *
     * Message id = "00000018"
     * 
     * 00000018-00.sta zero length file, exists only if message is unheard
     *
     * 00000018-00.wav if forwarded represent the comment, NOTE: can be zero if no comment was left
     * else voicemail message media
     *
     * 00000018-00.xml message or comment details
     *
     * 00000018-01.wav original message without comment NOTE: only exists for forwarded messages
     *
     * 00000018-01.xml original message details NOTE: only exists for forwarded messages
     *
     * 00000018-FW.wav original message plus comment NOTE: only exists for forwarded messages
     *
     */

    /**
     * Factory method to Load a message with the existing files from the directory
     * @param directory
     * @param id
     */
    public static VmMessage loadMessage(File directory, String id) {
        VmMessage me = new VmMessage();
        me.m_messageId = id;
        me.m_descriptorFile = new File(directory, id + "-00.xml");
        if (!me.m_descriptorFile.exists()) {
            LOG.error(String.format("Voice message descriptor file %s does not exist",
                    me.m_descriptorFile.getPath()));
            return null;
        }

        // Check for -00.wav
        File audio = new File(directory, id + "-00.wav");

        me.m_audioFile = audio;

        // Check for -01.wav
        File audio1 = new File(directory, id + "-01.wav");
        if (audio1.exists()) {
            me.m_originalAudioFile = audio1;
        }

        // Check for -01.xml
        File descriptor1 = new File(directory, id + "-01.xml");
        if (descriptor1.exists()) {
            me.m_originalDescriptorFile = descriptor1;
        }

        // See if -FW.wav exists 
        File audioFw = new File(directory, id + "-FW.wav");
        if (audioFw.exists()) {
            me.m_combinedAudioFile = audioFw;
        }

        // Check for -00.sta
        File status = new File(directory, id + "-00.sta");
        me.m_statusFile = status;
        if (status.exists()) {
            me.m_unHeard = true;
        }

        // Check for -00.urg
        File urgent = new File(directory, id + "-00.urg");
        me.m_urgentFile = urgent;
        if (urgent.exists()) {
            me.m_urgent = true;
        }

        return me;
    }

    public static VmMessage newMessageFromMime(Mailbox mailbox, IMAPMessage msg) {
        VmMessage me = new VmMessage();

        // Generate the next message ID
        me.m_messageId = ExtMailStore.GetMsgId(msg);
        if (me.m_messageId == null) {
            me.m_messageId = nextMessageId(mailbox.getMailstoreDirectory() + "/..");
        }

        // Generate the MessageDescriptor;
        me.m_messageDescriptor = new MessageDescriptor();
        me.m_messageDescriptor.setId(mailbox.getUser().getIdentity());

        try {
            String str = msg.getHeader("X-SIPX-FROMURI", ";");
            me.m_messageDescriptor.setFromUri(str);

            me.m_messageDescriptor.setDurationSecs(msg.getSize());
            me.m_unHeard = !msg.isSet(Flag.SEEN);
            me.m_messageDescriptor.setTimestamp(msg.getReceivedDate().getTime());
            me.m_messageDescriptor.setSubject(msg.getSubject());

            Priority pr = Priority.NORMAL;

            if (msg.isSet(Flag.FLAGGED)) {
                pr = Priority.URGENT;
                me.m_urgent = true;

            }
            me.m_messageDescriptor.setPriority(pr);

        } catch (MessagingException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }

        String baseName = mailbox.getInboxDirectory() + me.m_messageId + "-00";
        me.m_audioFile = new File(baseName + ".wav");
        me.m_descriptorFile = new File(baseName + ".xml");
        me.m_statusFile = new File(baseName + ".sta");
        me.m_urgentFile = new File(baseName + ".urg");
        String operation = "storing stuff";
        try {
            if (me.m_unHeard) {
                operation = "creating status file " + me.m_statusFile.getPath();
                LOG.debug("VmMessage::newMessage " + operation);
                FileUtils.touch(me.m_statusFile);
            }

            if (me.m_urgent) {
                operation = "creating urgent file " + me.m_urgentFile.getPath();
                LOG.debug("VmMessage::newMessage " + operation);
                FileUtils.touch(me.m_urgentFile);
            }

            operation = "creating messageDescriptor " + me.m_descriptorFile.getPath();
            LOG.debug("VmMessage::newMessage " + operation);
            new MessageDescriptorWriter().writeObject(me.m_messageDescriptor, me.m_descriptorFile);
        } catch (IOException e) {
            LOG.error("VmMessage::newMessage error while " + operation, e);
            return null;
        }
        LOG.info("VmMessage::newMessage created message " + me.m_descriptorFile.getPath());
        if (me.m_unHeard) {
            Mwi.sendMWI(mailbox);
        }

        return me;
    }

    public static VmMessage newMessage(Mailbox mailbox, Message recording) {
        VmMessage me = new VmMessage();

        // Generate the next message ID
        me.m_messageId = nextMessageId(mailbox.getMailstoreDirectory() + "/..");
        me.m_unHeard = true;

        // Generate the MessageDescriptor;
        me.m_messageDescriptor = new MessageDescriptor();
        me.m_messageDescriptor.setId(mailbox.getUser().getIdentity());
        me.m_messageDescriptor.setFromUri(recording.getFromUri());
        me.m_messageDescriptor.setDurationSecs(recording.getDuration());
        me.m_messageDescriptor.setTimestamp(recording.getTimestamp());
        me.m_messageDescriptor.setSubject("Voice Message " + me.m_messageId);
        me.m_messageDescriptor.setPriority(recording.getPriority());

        if (recording.getPriority() == Priority.URGENT) {
            me.m_urgent = true;
        }

        Vector<User> otherRecipients = recording.getOtherRecipeints();
        if (otherRecipients != null) {
            for (User recipient : otherRecipients) {
                if (!recipient.getUserName().equals(mailbox.getUser().getUserName())) {
                    me.m_messageDescriptor.addOtherRecipient(recipient.getUserName());
                }
            }
        }

        File tempFile = recording.getWavFile();
        String baseName = mailbox.getInboxDirectory() + me.m_messageId + "-00";
        me.m_audioFile = new File(baseName + ".wav");
        me.m_descriptorFile = new File(baseName + ".xml");
        me.m_statusFile = new File(baseName + ".sta");
        me.m_urgentFile = new File(baseName + ".urg");
        String operation = "storing stuff";
        try {
            operation = "creating status file " + me.m_statusFile.getPath();
            LOG.debug("VmMessage::newMessage " + operation);
            FileUtils.touch(me.m_statusFile);

            if (me.m_urgent) {
                operation = "creating urgent file " + me.m_urgentFile.getPath();
                LOG.debug("VmMessage::newMessage " + operation);
                FileUtils.touch(me.m_urgentFile);
            }

            operation = "copying recording .wav file to " + me.m_audioFile.getPath();
            LOG.debug("VmMessage::newMessage " + operation);
            FileUtils.copyFile(tempFile, me.m_audioFile, true);

            operation = "creating messageDescriptor " + me.m_descriptorFile.getPath();
            LOG.debug("VmMessage::newMessage " + operation);
            new MessageDescriptorWriter().writeObject(me.m_messageDescriptor, me.m_descriptorFile);
        } catch (IOException e) {
            LOG.error("VmMessage::newMessage error while " + operation, e);
            return null;
        }
        LOG.info("VmMessage::newMessage created message " + me.m_descriptorFile.getPath());
        Mwi.sendMWI(mailbox);
        me.sendToEmail(mailbox);

        return me;
    }

    /**
     * Copy this message into the inbox of a Mailbox, with a new message ID.
     * @param directory
     * @return newly copied message
     */
    public VmMessage copy(Mailbox mailbox) {
        VmMessage me = new VmMessage();

        // Generate the next message ID
        me.m_messageId = nextMessageId(mailbox.getMailstoreDirectory() + "/..");

        // Copy into the inbox of the mailbox
        File directory = new File(mailbox.getInboxDirectory());
        String baseName = mailbox.getInboxDirectory() + me.m_messageId + "-00";
        me.m_statusFile = new File(baseName + ".sta");
        me.m_urgentFile = new File(baseName + ".urg");

        // Copy (with the new message ID in the names) all the files
        String operation = "copying stuff";
        try {
            me.m_unHeard = true;
            operation = "creating status file " + me.m_statusFile.getPath();
            FileUtils.touch(me.m_statusFile);

            if (m_urgent) {
                operation = "creating urgent file " + me.m_urgentFile.getPath();
                LOG.debug("VmMessage::newMessage " + operation);
                FileUtils.touch(me.m_urgentFile);
            }

            operation = "copying audio file " + m_audioFile.getPath();
            me.m_audioFile = CopyMessageFileToDirectory(m_audioFile, me.m_messageId, directory);
            if (m_originalAudioFile != null) {
                // Deal with the forwarded stuff
                operation = "copying original audio file " + m_originalAudioFile.getPath();
                me.m_originalAudioFile = CopyMessageFileToDirectory(m_originalAudioFile, me.m_messageId, directory);
                operation = "copying original descriptor file " + m_originalDescriptorFile.getPath();
                me.m_originalDescriptorFile = CopyMessageFileToDirectory(m_originalDescriptorFile, me.m_messageId,
                        directory);
                operation = "copying combined audio file " + m_combinedAudioFile.getPath();
                me.m_combinedAudioFile = CopyMessageFileToDirectory(m_combinedAudioFile, me.m_messageId, directory);
            }
            // Including the descriptor file (which will be wrong for a moment, but it gives us the new name)
            operation = "copying descriptor file " + m_descriptorFile.getPath();
            me.m_descriptorFile = CopyMessageFileToDirectory(m_descriptorFile, me.m_messageId, directory);

        } catch (IOException e) {
            LOG.error("VmMessage::copy error while " + operation, e);
            return null;
        }

        // Read the new (but wrong) message descriptor file
        me.m_messageDescriptor = new MessageDescriptorReader().readObject(me.m_descriptorFile);

        // correct the other recipient list
        if (me.m_messageDescriptor.getOtherRecipients() != null) {
            me.m_messageDescriptor.addOtherRecipient(ValidUsersXML.getUserPart(me.m_messageDescriptor.getId()));
            me.m_messageDescriptor.removeOtherRecipient(mailbox.getUser().getUserName());
        }

        // Correct the identity in the descriptor file to the correct user
        me.m_messageDescriptor.setId(mailbox.getUser().getIdentity());
        // Correct the subject to the correct subject
        me.m_messageDescriptor.setSubject("Voice Message " + me.m_messageId);
        // Write the new file out.
        new MessageDescriptorWriter().writeObject(me.m_messageDescriptor, me.m_descriptorFile);

        LOG.info("VmMessage::copy created message " + me.m_descriptorFile.getPath());
        Mwi.sendMWI(mailbox);
        me.sendToEmail(mailbox);

        return me;
    }

    /**
     * Forward this message (with optional recorded comments) to the inbox of a mailbox
     * 
     * @param mailbox
     * @param recording
     * @return
     */
    public VmMessage forward(Mailbox mailbox, Message recording) {
        VmMessage me = new VmMessage();

        // Generate the next message ID
        me.m_messageId = nextMessageId(mailbox.getMailstoreDirectory() + "/..");

        // Generate the MessageDescriptor;
        me.m_messageDescriptor = new MessageDescriptor();
        me.m_messageDescriptor.setId(mailbox.getUser().getIdentity());
        me.m_messageDescriptor.setFromUri(recording.getFromUri());
        me.m_messageDescriptor.setDurationSecs(recording.getDuration() + getDuration());
        me.m_messageDescriptor.setTimestamp(recording.getTimestamp());
        me.m_messageDescriptor.setSubject("Fwd:Voice Message " + m_messageId);
        me.m_messageDescriptor.setPriority(recording.getPriority());

        Vector<User> otherRecipients = recording.getOtherRecipeints();
        if (otherRecipients != null) {
            for (User recip : otherRecipients) {
                if (!recip.getUserName().equals(mailbox.getUser().getUserName())) {
                    me.m_messageDescriptor.addOtherRecipient(recip.getUserName());
                }
            }
        }

        if (recording.getPriority() == Priority.URGENT) {
            me.m_urgent = true;
        }

        // Copy into the inbox of the mailbox
        File directory = new File(mailbox.getInboxDirectory());
        String baseName = mailbox.getInboxDirectory() + me.m_messageId;
        me.m_audioFile = new File(baseName + "-00.wav");
        me.m_descriptorFile = new File(baseName + "-00.xml");
        me.m_statusFile = new File(baseName + "-00.sta");
        me.m_urgentFile = new File(baseName + "-00.urg");
        me.m_combinedAudioFile = new File(baseName + "-FW.wav");

        // Copy (with the new message ID in the names) all the files
        String operation = "copying stuff";
        try {
            me.m_unHeard = true;
            operation = "creating status file " + me.m_statusFile.getPath();
            FileUtils.touch(me.m_statusFile);

            if (me.m_urgent) {
                operation = "creating urgent file " + me.m_urgentFile.getPath();
                LOG.debug("VmMessage::newMessage " + operation);
                FileUtils.touch(me.m_urgentFile);
            }

            operation = "copying audio file " + m_audioFile.getPath();
            // The old audio file becomes "originalAudioFile" as NewID-01.wav
            String newName = String.format("%s-%s.%s", me.m_messageId, "01", "wav");
            me.m_originalAudioFile = new File(directory, newName);
            FileUtils.copyFile(m_audioFile, me.m_originalAudioFile, true);

            operation = "copying descriptor file " + m_audioFile.getPath();
            // The old descriptor file becomes "originalDescriptorFile" as NewID-01.xml
            newName = String.format("%s-%s.%s", me.m_messageId, "01", "xml");
            me.m_originalDescriptorFile = new File(directory, newName);
            FileUtils.copyFile(m_descriptorFile, me.m_originalDescriptorFile, true);

            // The recording (if any) becomes the audioFile as NewID-00.wav
            if (recording.getWavFile() != null) {
                File tempFile = recording.getWavFile();
                operation = "copying comments .wav file to " + me.m_audioFile.getPath();
                FileUtils.copyFile(tempFile, me.m_audioFile, true);

                // Combine the new and the old file into one bigger .wav as NewID-FW.wav
                operation = "combining " + me.m_audioFile.getPath() + " with " + me.m_originalAudioFile.getPath();
                concatAudio(me.m_combinedAudioFile, me.m_audioFile, me.m_originalAudioFile);
            } else {
                FileUtils.touch(me.m_audioFile); // Create an empty NewID-00.wav file
                // Copy the old audiofile by itself to NewID-FW.wav
                FileUtils.copyFile(me.m_originalAudioFile, me.m_combinedAudioFile, true);
            }

            operation = "creating messageDescriptor " + me.m_descriptorFile.getPath();
            new MessageDescriptorWriter().writeObject(me.m_messageDescriptor, me.m_descriptorFile);

        } catch (Exception e) {
            LOG.error("VmMessage::copyMessage error while " + operation, e);
            return null;
        }
        LOG.info("VmMessage::forward created message " + me.m_descriptorFile.getPath());
        Mwi.sendMWI(mailbox);
        me.sendToEmail(mailbox);

        return me;

    }

    /**
     * Factory method for testing (no actual files involved)
     * @param id
     * @param unHeard
     * @param timestamp
     * @return
     */
    public static VmMessage testMessage(String id, boolean unHeard, long timestamp) {
        VmMessage me = new VmMessage();
        me.m_messageId = id;
        me.m_unHeard = unHeard;
        me.m_messageDescriptor = new MessageDescriptor();
        me.m_messageDescriptor.setTimestamp(timestamp);
        return me;
    }

    /**
     * Copy a file from where it is into a new directory with a new message id
     * @param file existing file
     * @param newMessageId
     * @param newDirectory
     * @throws IOException
     */
    private static File CopyMessageFileToDirectory(File file, String newMessageId, File newDirectory)
            throws IOException {
        if (file != null) {
            Matcher m = namePattern.matcher(file.getName());
            if (m.matches()) {
                String newName = String.format("%s-%s.%s", newMessageId, m.group(2), m.group(3));
                File newFile = new File(newDirectory, newName);
                FileUtils.copyFile(file, newFile);
                return new File(newDirectory, newFile.getName());
            }
        }
        return null;
    }

    /**
     * Move a file from where it is into a new directory
     * @param file
     * @param newDirectory
     * @throws IOException
     */
    private static File moveFileToDirectory(File file, File newDirectory) throws IOException {
        if (file != null) {
            if (file.exists()) {
                FileUtils.moveFileToDirectory(file, newDirectory, false);
            }
            return new File(newDirectory, file.getName());
        }
        return null;
    }

    /**
     * Combine two wav files into one bigger one
     * 
     * @param newFile
     * @param orig1
     * @param orig2
     * @throws Exception
     */
    static void concatAudio(File newFile, File orig1, File orig2) throws Exception {
        String operation = "dunno";
        try {
            operation = "getting AudioInputStream from " + orig1.getPath();
            AudioInputStream clip1 = AudioSystem.getAudioInputStream(orig1);
            operation = "getting AudioInputStream from " + orig2.getPath();
            AudioInputStream clip2 = AudioSystem.getAudioInputStream(orig2);

            operation = "building SequnceInputStream";
            AudioInputStream concatStream = new AudioInputStream(new SequenceInputStream(clip1, clip2),
                    clip1.getFormat(), clip1.getFrameLength() + clip2.getFrameLength());

            operation = "writing SequnceInputStream to " + newFile.getPath();
            AudioSystem.write(concatStream, AudioFileFormat.Type.WAVE, newFile);
            LOG.info("VmMessage::concatAudio created combined file " + newFile.getPath());
        } catch (Exception e) {
            String trouble = "VmMessage::concatAudio Problem while " + operation;
            //           LOG.error(trouble, e);
            throw new Exception(trouble, e);
        }
    }

    /**
     * Moves all the files associated with this message to the specified directory
     * @param newDirectory
     */
    public void moveToDirectory(File newDirectory) {

        try {
            m_audioFile = moveFileToDirectory(m_audioFile, newDirectory);
            m_originalAudioFile = moveFileToDirectory(m_originalAudioFile, newDirectory);
            m_combinedAudioFile = moveFileToDirectory(m_combinedAudioFile, newDirectory);
            m_originalDescriptorFile = moveFileToDirectory(m_originalDescriptorFile, newDirectory);
            m_statusFile = moveFileToDirectory(m_statusFile, newDirectory);
            m_urgentFile = moveFileToDirectory(m_urgentFile, newDirectory);
            m_descriptorFile = moveFileToDirectory(m_descriptorFile, newDirectory);
        } catch (IOException e) {
            LOG.error(
                    String.format("Failed to move message %s to directory %s", m_messageId, newDirectory.getPath()),
                    e);
        }
    }

    /**
     * Send this message to the e-mail addrs specified in the mailbox
     * @param mailbox
     */
    void sendToEmail(Mailbox mailbox) {
        // Queue it in a background thread
        Emailer.queueVm2Email(mailbox, this);
    }

    /**
     * Generate the next message Id
     * static synchronized as it's machine wide
     * @param directory which holds the messageid.txt file
     */
    static synchronized String nextMessageId(String directory) {
        long numericMessageId = 1;
        String format = "%08d";
        String messageId = String.format(format, numericMessageId);

        // messageid.txt file is (hopefully) in the directory
        File midFile = new File(directory, "messageid.txt");
        String messageIdFilePath = midFile.getPath();
        if (midFile.exists()) {
            try {
                // The messageid in the file is the NEXT one
                messageId = FileUtils.readFileToString(midFile);
                numericMessageId = Long.parseLong(messageId);
            } catch (IOException e) {
                LOG.error("Message::nextMessageId cannot read " + messageIdFilePath, e);
                throw new RuntimeException(e);
            }
        }
        // Increment message id, store for another day
        numericMessageId++;
        try {
            FileUtils.writeStringToFile(midFile, String.format(format, numericMessageId));
        } catch (IOException e) {
            LOG.error("Message::nextMessageId cannot write " + messageIdFilePath, e);
            throw new RuntimeException(e);
        }

        return messageId;
    }

    public String getMessageId() {
        return m_messageId;
    }

    public boolean isUnHeard() {
        return m_unHeard;
    }

    public boolean isUrgent() {
        return m_urgent;
    }

    public void toggleUrgency() {
        getMessageDescriptor();
        if (m_messageDescriptor.getPriority() == Priority.URGENT) {
            m_messageDescriptor.setPriority(Priority.NORMAL);
        } else {
            m_messageDescriptor.setPriority(Priority.URGENT);
        }
        new MessageDescriptorWriter().writeObject(m_messageDescriptor, m_descriptorFile);
    }

    public void markNormal() {
        getMessageDescriptor();
        m_messageDescriptor.setPriority(Priority.NORMAL);
        new MessageDescriptorWriter().writeObject(m_messageDescriptor, m_descriptorFile);
    }

    public void markHeard() {
        if (m_unHeard) {
            if (m_statusFile != null) {
                FileUtils.deleteQuietly(m_statusFile);
            }
            LOG.info(String.format("VmMessage::markHeard %s marked heard", m_descriptorFile.getPath()));
            m_unHeard = false;
        }
    }

    public void markUnheard() {
        if (!m_unHeard) {
            if (m_statusFile != null) {
                String operation = "update status";
                try {
                    operation = "creating status file " + m_statusFile.getPath();
                    FileUtils.touch(m_statusFile);
                    LOG.info(String.format("VmMessage::markHeard %s marked unheard", m_descriptorFile.getPath()));
                    m_unHeard = false;
                } catch (IOException e) {
                    LOG.error("VmMessage::markUnHeard error while " + operation, e);
                }
            }
        }
    }

    public MessageDescriptor getMessageDescriptor() {
        if (m_messageDescriptor == null) {
            // Lazily load the message descriptor
            try {
                MessageDescriptorReader mr = new MessageDescriptorReader();
                m_messageDescriptor = mr.readObject(m_descriptorFile);
            } catch (Throwable t) {
                LOG.error("Somthing amiss reading message descriptor " + m_descriptorFile.getPath(), t);
                m_messageDescriptor = null;
            }
        }
        return m_messageDescriptor;
    }

    public long getTimestamp() {
        getMessageDescriptor();
        if (m_messageDescriptor != null) {
            return m_messageDescriptor.getTimeStampDate().getTime();
        } else {
            return 0;
        }
    }

    public long getDuration() {
        getMessageDescriptor();
        if (m_messageDescriptor != null) {
            return m_messageDescriptor.getDurationSecsLong();
        } else {
            return 0L;
        }
    }

    public File getAudioFile() {
        if (m_combinedAudioFile == null) {
            return m_audioFile;
        } else {
            return m_combinedAudioFile;
        }
    }

    public File getDescriptorFile() {
        return m_descriptorFile;
    }

}