org.exist.xquery.modules.mail.MessageListFunctions.java Source code

Java tutorial

Introduction

Here is the source code for org.exist.xquery.modules.mail.MessageListFunctions.java

Source

/*
 *  eXist Mail Module Extension MessageListFunctions
 *  Copyright (C) 2006-09 Adam Retter <adam.retter@devon.gov.uk>
 *  www.adamretter.co.uk
 *  
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public License
 *  as published by the Free Software Foundation; either version 2
 *  of the License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with this program; if not, write to the Free Software Foundation
 *  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *  
 *  $Id$
 */

package org.exist.xquery.modules.mail;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import javax.mail.Address;
import javax.mail.FetchProfile;
import javax.mail.Flags;
import javax.mail.Folder;
import javax.mail.Header;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.search.AndTerm;
import javax.mail.search.BodyTerm;
import javax.mail.search.ComparisonTerm;
import javax.mail.search.FlagTerm;
import javax.mail.search.FromStringTerm;
import javax.mail.search.HeaderTerm;
import javax.mail.search.NotTerm;
import javax.mail.search.OrTerm;
import javax.mail.search.ReceivedDateTerm;
import javax.mail.search.RecipientStringTerm;
import javax.mail.search.SearchTerm;
import javax.mail.search.SentDateTerm;
import javax.mail.search.SubjectTerm;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import org.exist.dom.QName;
import org.exist.memtree.MemTreeBuilder;
import org.exist.xquery.BasicFunction;
import org.exist.xquery.Cardinality;
import org.exist.xquery.FunctionSignature;
import org.exist.xquery.XPathException;
import org.exist.xquery.XQueryContext;
import org.exist.xquery.value.FunctionParameterSequenceType;
import org.exist.xquery.value.FunctionReturnSequenceType;
import org.exist.xquery.value.IntegerValue;
import org.exist.xquery.value.NodeValue;
import org.exist.xquery.value.Sequence;
import org.exist.xquery.value.SequenceType;
import org.exist.xquery.value.Type;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * eXist Mail Module Extension GetMessageList
 * 
 * Get a mail store
 * 
 * @author Andrzej Taramina <andrzej@chaeron.com>
 * @serial 2009-03-12
 * @version 1.3
 *
 * @see org.exist.xquery.BasicFunction#BasicFunction(org.exist.xquery.XQueryContext, org.exist.xquery.FunctionSignature)
 */
public class MessageListFunctions extends BasicFunction {
    protected static final Logger logger = Logger.getLogger(MessageListFunctions.class);

    public final static FunctionSignature signatures[] = {
            new FunctionSignature(new QName("get-message-list", MailModule.NAMESPACE_URI, MailModule.PREFIX),
                    "Returns a message list of all messages in a folder.",
                    new SequenceType[] { new FunctionParameterSequenceType("mail-folder-handle", Type.INTEGER,
                            Cardinality.EXACTLY_ONE,
                            "The mail folder handle retrieved from mail:get-mail-folder()") },
                    new FunctionReturnSequenceType(Type.LONG, Cardinality.ZERO_OR_ONE,
                            "an xs:long representing the message list handle.")),

            new FunctionSignature(new QName("search-message-list", MailModule.NAMESPACE_URI, MailModule.PREFIX),
                    "Searches messages in a folder. "
                            + "Search terms are of the form <searchTerm type=\"xxx\">...</searchTerm>.  Valid types include: not, and, or, from, subject, body, recipient, header, flag, sent, received. "
                            + "<searchTerm type=\"not\"> requires a single nested child search term. <searchTerm type=\"and\"> and <searchTerm type=\"or\"> must have one or more nested child search terms. "
                            + "<searchTerm type=\"from\" pattern=\"pat\">, <searchTerm type=\"subject\" pattern=\"pat\"> and <searchTerm type=\"body\" pattern=\"pat\">  require a pattern attribute and will search for a substring that matches the pattern. "
                            + "<searchTerm type=\"recipient\" pattern=\"pat\" recipientType=\"to|cc|bcc\"> requires pattern and recipientType attributes. "
                            + "<searchTerm type=\"header\" pattern=\"pat\" name=\"Content-Type\"> requires pattern and name attributes. "
                            + "<searchTerm type=\"flag\" flag=\"answered|deleted|draft|recent|seen\" value=\"true|false\"> requires flag and value attributes. "
                            + "<searchTerm type=\"sent\" comparison=\"eq|gt|ge|lt|le|ne\" format=\"format\" date=\"date\"> and <searchTerm type=\"received\" comparison=\"eq|gt|ge|lt|le|ne\" format=\"format\" date=\"date\"> require comparison, format and date attributes. "
                            + "The format string should conform to Java SimpleDateFormat specifications and the date string must conform to the specified format string.",
                    new SequenceType[] {
                            new FunctionParameterSequenceType("mail-folder-handle", Type.INTEGER,
                                    Cardinality.EXACTLY_ONE,
                                    "The mail folder handle retrieved from mail:get-mail-folder()"),
                            new FunctionParameterSequenceType("search-parameters", Type.ELEMENT,
                                    Cardinality.EXACTLY_ONE, "The xml fragment defining the search terms") },
                    new FunctionReturnSequenceType(Type.LONG, Cardinality.ZERO_OR_ONE,
                            "an xs:long representing the message list handle.")),

            new FunctionSignature(new QName("get-message-list-as-xml", MailModule.NAMESPACE_URI, MailModule.PREFIX),
                    "Returns a message list of all messages in a folder as XML.  If there are no messages in the list, an empty sequence will be returned",
                    new SequenceType[] { new FunctionParameterSequenceType("message-list-handle", Type.INTEGER,
                            Cardinality.EXACTLY_ONE,
                            "The message list handle retrieved from mail:get-message-list() or mail:search-message-list()"),
                            new FunctionParameterSequenceType("include-headers", Type.BOOLEAN,
                                    Cardinality.EXACTLY_ONE,
                                    "A boolean specifying whether to include message headers") },
                    new FunctionReturnSequenceType(Type.ELEMENT, Cardinality.ZERO_OR_ONE,
                            "the list of all messages in a folder as XML")),

            new FunctionSignature(new QName("close-message-list", MailModule.NAMESPACE_URI, MailModule.PREFIX),
                    "Closes a message list.",
                    new SequenceType[] { new FunctionParameterSequenceType("message-list-handle", Type.INTEGER,
                            Cardinality.EXACTLY_ONE,
                            "The message list handle retrieved from mail:get-message-list() or mail:search-message-list()") },
                    new SequenceType(Type.ITEM, Cardinality.EMPTY)) };

    private static final String DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSZ";

    private static final String PREFETCH_HEADERS[] = { "Return-Path", "Delivered-To", "Received", "Date", "From",
            "To", "Message-ID", "Subject", "MIME-Version", "Content-Type", "Content-Transfer-Encoding", "X-Mailer",
            "X-Priority" };

    /**
     * MessageListFunctions Constructor
     * 
     * @param context   The Context of the calling XQuery
     */
    public MessageListFunctions(XQueryContext context, FunctionSignature signature) {
        super(context, signature);
    }

    /**
     * evaluate the call to the xquery get-message-list function,
     * it is really the main entry point of this class
     * 
     * @param args      arguments from the get-message-list() function call
     * @param contextSequence   the Context Sequence to operate on (not used here internally!)
     * @return      A sequence representing the result of the get-message-list() function call
     * 
     * @see org.exist.xquery.BasicFunction#eval(org.exist.xquery.value.Sequence[], org.exist.xquery.value.Sequence)
     */
    public Sequence eval(Sequence[] args, Sequence contextSequence) throws XPathException {
        if (isCalledAs("get-message-list")) {
            Sequence messageList = getMessageList(args, contextSequence);
            return messageList;
        } else if (isCalledAs("search-message-list")) {
            Sequence searchMessageList = searchMessageList(args, contextSequence);
            return searchMessageList;
        } else if (isCalledAs("get-message-list-as-xml")) {
            Sequence messageListAsXML = getMessageListAsXML(args, contextSequence);
            return messageListAsXML;
        } else if (isCalledAs("close-message-list")) {
            Sequence closeMessageList = closeMessageList(args, contextSequence);
            return closeMessageList;
        }

        throw (new XPathException(this, "Invalid function name"));
    }

    //***************************************************************************
    //*
    //*    Function Implementation Methods
    //*
    //***************************************************************************/

    private Sequence getMessageList(Sequence[] args, Sequence contextSequence) throws XPathException {
        Message[] msgList;

        // was a folder handle specified?
        if (args[0].isEmpty()) {
            throw (new XPathException(this, "Folder handle not specified"));
        }

        // get the Folder
        long folderHandle = ((IntegerValue) args[0].itemAt(0)).getLong();
        Folder folder = MailModule.retrieveFolder(context, folderHandle);
        if (folder == null) {
            throw (new XPathException(this, "Invalid Folder handle specified"));
        }

        try {
            msgList = folder.getMessages();
            prefetchMessages(folder, msgList);
        } catch (MessagingException me) {
            throw (new XPathException(this, "Failed to get mail list", me));
        }

        // save the message list and return the handle of the message list

        return (new IntegerValue(MailModule.storeMessageList(context, msgList, folderHandle)));
    }

    private Sequence searchMessageList(Sequence[] args, Sequence contextSequence) throws XPathException {
        Message[] msgList;

        // was a folder handle specified?
        if (args[0].isEmpty() || args[1].isEmpty()) {
            throw (new XPathException(this, "Folder handle or Search Terms not specified"));
        }

        // get the Folder
        long folderHandle = ((IntegerValue) args[0].itemAt(0)).getLong();
        Folder folder = MailModule.retrieveFolder(context, folderHandle);
        if (folder == null) {
            throw (new XPathException(this, "Invalid Folder handle specified"));
        }

        Node searchTermsXML = ((NodeValue) args[1].itemAt(0)).getNode();

        try {
            msgList = folder.search(parseSearchTerms(searchTermsXML));

            prefetchMessages(folder, msgList);
        } catch (MessagingException me) {
            throw (new XPathException(this, "Failed to get mail list", me));
        }

        // save the message list and return the handle of the message list

        return (new IntegerValue(MailModule.storeMessageList(context, msgList, folderHandle)));
    }

    private void prefetchMessages(Folder folder, Message[] msgList) throws MessagingException {
        // Prefetch all the key information and headers

        FetchProfile fp = new FetchProfile();
        fp.add(FetchProfile.Item.ENVELOPE);

        for (String PREFETCH_HEADER : PREFETCH_HEADERS) {
            fp.add(PREFETCH_HEADER);
        }
        folder.fetch(msgList, fp);
    }

    private Sequence getMessageListAsXML(Sequence[] args, Sequence contextSequence) throws XPathException {
        Message[] msgList;
        Sequence ret = Sequence.EMPTY_SEQUENCE;

        // was a msgList handle specified?
        if (args[0].isEmpty()) {
            throw (new XPathException(this, "Message List handle not specified"));
        }

        // get the MessageList
        long msgListHandle = ((IntegerValue) args[0].itemAt(0)).getLong();
        msgList = MailModule.retrieveMessageList(context, msgListHandle);
        if (msgList == null) {
            throw (new XPathException(this, "Invalid Message List handle specified"));
        }

        if (msgList.length > 0) {

            boolean includeHeaders = args[1].effectiveBooleanValue();

            MemTreeBuilder builder = context.getDocumentBuilder();

            builder.startDocument();
            builder.startElement(new QName("messages", MailModule.NAMESPACE_URI, MailModule.PREFIX), null);
            builder.addAttribute(new QName("count", null, null), String.valueOf(msgList.length));

            try {
                for (int i = 0; i < msgList.length; i++) {
                    Message message = msgList[i];

                    builder.startElement(new QName("message", MailModule.NAMESPACE_URI, MailModule.PREFIX), null);

                    builder.addAttribute(new QName("number", null, null),
                            String.valueOf(message.getMessageNumber()));

                    // Sent Date
                    if (message.getSentDate() != null) {
                        builder.startElement(new QName("sent", MailModule.NAMESPACE_URI, MailModule.PREFIX), null);
                        builder.characters(formatDate(message.getSentDate()));
                        builder.endElement();
                    }

                    // Received Date
                    if (message.getReceivedDate() != null) {
                        builder.startElement(new QName("received", MailModule.NAMESPACE_URI, MailModule.PREFIX),
                                null);
                        builder.characters(formatDate(message.getReceivedDate()));
                        builder.endElement();
                    }

                    // From
                    if (message.getFrom() != null) {
                        builder.startElement(new QName("from", MailModule.NAMESPACE_URI, MailModule.PREFIX), null);
                        builder.characters(message.getFrom()[0].toString());
                        builder.endElement();
                    }

                    // Recipients
                    builder.startElement(new QName("recipients", MailModule.NAMESPACE_URI, MailModule.PREFIX),
                            null);
                    // To Recipients
                    Address[] toAddresses = message.getRecipients(Message.RecipientType.TO);
                    if (toAddresses != null) {
                        for (Address to : toAddresses) {
                            builder.startElement(
                                    new QName("recipient", MailModule.NAMESPACE_URI, MailModule.PREFIX), null);
                            builder.addAttribute(new QName("type", null, null), "to");
                            builder.characters(to.toString());
                            builder.endElement();
                        }
                    }

                    // cc Recipients
                    Address[] ccAddresses = message.getRecipients(Message.RecipientType.CC);
                    if (ccAddresses != null) {
                        for (Address ccAddress : ccAddresses) {
                            builder.startElement(
                                    new QName("recipient", MailModule.NAMESPACE_URI, MailModule.PREFIX), null);
                            builder.addAttribute(new QName("type", null, null), "cc");
                            builder.characters(ccAddress.toString());
                            builder.endElement();
                        }
                    }

                    // bcc Recipients
                    Address[] bccAddresses = message.getRecipients(Message.RecipientType.BCC);
                    if (bccAddresses != null) {
                        for (Address bccAddress : bccAddresses) {
                            builder.startElement(
                                    new QName("recipient", MailModule.NAMESPACE_URI, MailModule.PREFIX), null);
                            builder.addAttribute(new QName("type", null, null), "bcc");
                            builder.characters(bccAddress.toString());
                            builder.endElement();
                        }
                    }
                    builder.endElement();

                    // Flags

                    Flags flags = message.getFlags();
                    Flags.Flag[] systemFlags = flags.getSystemFlags();
                    String[] userFlags = flags.getUserFlags();

                    if (systemFlags.length > 0 || userFlags.length > 0) {
                        builder.startElement(new QName("flags", MailModule.NAMESPACE_URI, MailModule.PREFIX), null);

                        for (Flags.Flag systemFlag : systemFlags) {
                            if (systemFlag == Flags.Flag.ANSWERED) {
                                builder.startElement(new QName("flag", MailModule.NAMESPACE_URI, MailModule.PREFIX),
                                        null);
                                builder.addAttribute(new QName("type", null, null), "answered");
                                builder.endElement();
                            } else if (systemFlag == Flags.Flag.DELETED) {
                                builder.startElement(new QName("flag", MailModule.NAMESPACE_URI, MailModule.PREFIX),
                                        null);
                                builder.addAttribute(new QName("type", null, null), "deleted");
                                builder.endElement();
                            } else if (systemFlag == Flags.Flag.DRAFT) {
                                builder.startElement(new QName("flag", MailModule.NAMESPACE_URI, MailModule.PREFIX),
                                        null);
                                builder.addAttribute(new QName("type", null, null), "draft");
                                builder.endElement();
                            } else if (systemFlag == Flags.Flag.FLAGGED) {
                                builder.startElement(new QName("flag", MailModule.NAMESPACE_URI, MailModule.PREFIX),
                                        null);
                                builder.addAttribute(new QName("type", null, null), "flagged");
                                builder.endElement();
                            } else if (systemFlag == Flags.Flag.RECENT) {
                                builder.startElement(new QName("flag", MailModule.NAMESPACE_URI, MailModule.PREFIX),
                                        null);
                                builder.addAttribute(new QName("type", null, null), "recent");
                                builder.endElement();
                            } else if (systemFlag == Flags.Flag.SEEN) {
                                builder.startElement(new QName("flag", MailModule.NAMESPACE_URI, MailModule.PREFIX),
                                        null);
                                builder.addAttribute(new QName("type", null, null), "seen");
                                builder.endElement();
                            }
                        }

                        for (String userFlag : userFlags) {
                            builder.startElement(new QName("flag", MailModule.NAMESPACE_URI, MailModule.PREFIX),
                                    null);
                            builder.addAttribute(new QName("type", null, null), "user");
                            builder.addAttribute(new QName("value", null, null), userFlag);
                            builder.endElement();
                        }

                        builder.endElement();
                    }

                    // Headers

                    if (includeHeaders) {
                        Enumeration headers = message.getAllHeaders();

                        if (headers.hasMoreElements()) {
                            builder.startElement(new QName("headers", MailModule.NAMESPACE_URI, MailModule.PREFIX),
                                    null);

                            while (headers.hasMoreElements()) {
                                Header header = (Header) headers.nextElement();

                                builder.startElement(
                                        new QName("header", MailModule.NAMESPACE_URI, MailModule.PREFIX), null);
                                builder.addAttribute(new QName("name", null, null), header.getName());
                                builder.addAttribute(new QName("value", null, null), header.getValue());
                                builder.endElement();
                            }

                            builder.endElement();
                        }
                    }

                    // Subject
                    builder.startElement(new QName("subject", MailModule.NAMESPACE_URI, MailModule.PREFIX), null);
                    builder.characters(message.getSubject());
                    builder.endElement();

                    builder.endElement();
                }
            } catch (MessagingException me) {
                throw (new XPathException(this, "Failed to retrieve messages from list", me));
            }

            builder.endElement();

            ret = (NodeValue) builder.getDocument().getDocumentElement();
        }

        return (ret);
    }

    private String formatDate(Date date) {
        String formatted = "";

        SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT);

        String temp = sdf.format(date);

        formatted = temp.substring(0, temp.length() - 2) + ":" + temp.substring(temp.length() - 2);

        return (formatted);
    }

    private Sequence closeMessageList(Sequence[] args, Sequence contextSequence) throws XPathException {
        // was a msgList handle specified?
        if (args[0].isEmpty()) {
            throw (new XPathException(this, "Message List handle not specified"));
        }

        // get the msgList
        long msgListHandle = ((IntegerValue) args[0].itemAt(0)).getLong();

        MailModule.removeMessageList(context, msgListHandle);

        return (Sequence.EMPTY_SEQUENCE);
    }

    //***************************************************************************
    //*
    //*    Search Term Methods
    //*
    //***************************************************************************/

    private SearchTerm parseSearchTerms(Node terms) throws XPathException {
        SearchTerm st = null;

        if (terms.getNodeType() == Node.ELEMENT_NODE && terms.getLocalName().equalsIgnoreCase("searchTerm")) {
            String type = ((Element) terms).getAttribute("type");

            if (type != null) {
                if (type.equalsIgnoreCase("not")) {
                    st = new NotTerm(parseChildSearchTerm(terms));
                } else if (type.equalsIgnoreCase("and")) {
                    st = new AndTerm(parseChildSearchTerms(terms));
                } else if (type.equalsIgnoreCase("or")) {
                    st = new OrTerm(parseChildSearchTerms(terms));
                } else if (type.equalsIgnoreCase("from")) {
                    st = parseFromTerm(terms);
                } else if (type.equalsIgnoreCase("subject")) {
                    st = parseSubjectTerm(terms);
                } else if (type.equalsIgnoreCase("body")) {
                    st = parseBodyTerm(terms);
                } else if (type.equalsIgnoreCase("to") || type.equalsIgnoreCase("recipient")) {
                    st = parseRecipientTerm(terms);
                } else if (type.equalsIgnoreCase("header")) {
                    st = parseHeaderTerm(terms);
                } else if (type.equalsIgnoreCase("flag")) {
                    st = parseFlagTerm(terms);
                } else if (type.equalsIgnoreCase("sent")) {
                    st = parseSentDateTerm(terms);
                } else if (type.equalsIgnoreCase("received")) {
                    st = parseReceivedDateTerm(terms);
                } else {
                    throw (new XPathException(this, "Invalid Search Term type specified: " + type));
                }
            } else {
                throw (new XPathException(this, "Invalid Search Term type specified: null"));
            }
        }

        if (st == null) {
            throw (new XPathException(this, "Invalid Search Terms specified"));
        }

        return (st);
    }

    private SearchTerm parseChildSearchTerm(Node terms) throws XPathException {
        // Parent only allows a single child search term

        SearchTerm st = null;

        NodeList children = terms.getChildNodes();

        if (children.getLength() == 1) {
            Node child = children.item(0);

            st = parseSearchTerms(child);
        } else {
            throw (new XPathException(this, "Only one child term is allowed for term with type: "
                    + ((Element) terms).getAttribute("type")));
        }

        return (st);
    }

    private SearchTerm[] parseChildSearchTerms(Node terms) throws XPathException {
        // Parent allows multiple child search terms

        ArrayList<SearchTerm> st = new ArrayList<SearchTerm>();

        NodeList children = terms.getChildNodes();

        if (children.getLength() > 0) {
            for (int i = 0; i < children.getLength(); i++) {
                Node child = children.item(i);

                st.add(parseSearchTerms(child));
            }
        } else {
            throw (new XPathException(this, "At least one child term is required for term with type: "
                    + ((Element) terms).getAttribute("type")));
        }

        return ((SearchTerm[]) st.toArray(new SearchTerm[] {}));
    }

    private SearchTerm parseFromTerm(Node terms) throws XPathException {
        SearchTerm st = null;

        String pattern = ((Element) terms).getAttribute("pattern");

        if (pattern != null && pattern.length() > 0) {
            st = new FromStringTerm(pattern);
        } else {
            throw (new XPathException(this, "Pattern attribute must be specified for term with type: "
                    + ((Element) terms).getAttribute("type")));
        }

        return (st);
    }

    private SearchTerm parseSubjectTerm(Node terms) throws XPathException {
        SearchTerm st = null;

        String pattern = ((Element) terms).getAttribute("pattern");

        if (pattern != null && pattern.length() > 0) {
            st = new SubjectTerm(pattern);
        } else {
            throw (new XPathException(this, "Pattern attribute must be specified for term with type: "
                    + ((Element) terms).getAttribute("type")));
        }

        return (st);
    }

    private SearchTerm parseBodyTerm(Node terms) throws XPathException {
        SearchTerm st = null;

        String pattern = ((Element) terms).getAttribute("pattern");

        if (pattern != null && pattern.length() > 0) {
            st = new BodyTerm(pattern);
        } else {
            throw (new XPathException(this, "Pattern attribute must be specified for term with type: "
                    + ((Element) terms).getAttribute("type")));
        }

        return (st);
    }

    private SearchTerm parseRecipientTerm(Node terms) throws XPathException {
        SearchTerm st = null;

        String pattern = ((Element) terms).getAttribute("pattern");
        String type = ((Element) terms).getAttribute("recipientType");

        if (StringUtils.isEmpty(type)) {
            throw (new XPathException(this,
                    "recipientType not specified for term with type: " + ((Element) terms).getAttribute("type")));
        }

        if (pattern != null && pattern.length() > 0) {
            Message.RecipientType rtype = null;

            if (type.equalsIgnoreCase("to")) {
                rtype = Message.RecipientType.TO;
            } else if (type.equalsIgnoreCase("cc")) {
                rtype = Message.RecipientType.CC;
            } else if (type.equalsIgnoreCase("bcc")) {
                rtype = Message.RecipientType.BCC;
            } else {
                throw (new XPathException(this, "Invalid recipientType: " + type + ", for term with type: "
                        + ((Element) terms).getAttribute("type")));
            }

            st = new RecipientStringTerm(rtype, pattern);
        } else {
            throw (new XPathException(this, "Pattern attribute must be specified for term with type: "
                    + ((Element) terms).getAttribute("type")));
        }

        return (st);
    }

    private SearchTerm parseHeaderTerm(Node terms) throws XPathException {
        SearchTerm st = null;

        String pattern = ((Element) terms).getAttribute("pattern");
        String name = ((Element) terms).getAttribute("name");

        if (StringUtils.isEmpty(name)) {
            throw (new XPathException(this,
                    "name not specified for term with type: " + ((Element) terms).getAttribute("type")));
        }

        if (pattern != null && pattern.length() > 0) {
            st = new HeaderTerm(name, pattern);
        } else {
            throw (new XPathException(this, "pattern attribute must be specified for term with type: "
                    + ((Element) terms).getAttribute("type")));
        }

        return (st);
    }

    private SearchTerm parseFlagTerm(Node terms) throws XPathException {
        SearchTerm st = null;

        String flag = ((Element) terms).getAttribute("flag");
        String value = ((Element) terms).getAttribute("value");

        if (StringUtils.isEmpty(value)) {
            throw (new XPathException(this,
                    "value not specified for term with type: " + ((Element) terms).getAttribute("type")));
        }

        if (flag != null && flag.length() > 0) {
            Flags flags = null;

            if (flag.equalsIgnoreCase("answered")) {
                flags = new Flags(Flags.Flag.ANSWERED);
            } else if (flag.equalsIgnoreCase("deleted")) {
                flags = new Flags(Flags.Flag.DELETED);
            } else if (flag.equalsIgnoreCase("draft")) {
                flags = new Flags(Flags.Flag.DRAFT);
            } else if (flag.equalsIgnoreCase("recent")) {
                flags = new Flags(Flags.Flag.RECENT);
            } else if (flag.equalsIgnoreCase("seen")) {
                flags = new Flags(Flags.Flag.SEEN);
            } else {
                throw (new XPathException(this, "Invalid flag: " + flag + ", for term with type: "
                        + ((Element) terms).getAttribute("type")));
            }

            st = new FlagTerm(flags, value.equalsIgnoreCase("true"));
        } else {
            throw (new XPathException(this, "flag attribute must be specified for term with type: "
                    + ((Element) terms).getAttribute("type")));
        }

        return (st);
    }

    private SearchTerm parseSentDateTerm(Node terms) throws XPathException {
        SearchTerm st = null;

        String value = ((Element) terms).getAttribute("date");
        String format = ((Element) terms).getAttribute("format");

        if (StringUtils.isEmpty(value)) {
            throw (new XPathException(this,
                    "value not specified for term with type: " + ((Element) terms).getAttribute("type")));
        }

        if (StringUtils.isEmpty(format)) {
            throw (new XPathException(this,
                    "format not specified for term with type: " + ((Element) terms).getAttribute("type")));
        }

        int cp = parseComparisonAttribute(terms);

        try {
            SimpleDateFormat sdf = new SimpleDateFormat(format);

            Date date = sdf.parse(value);

            st = new SentDateTerm(cp, date);
        } catch (ParseException pe) {
            throw (new XPathException(this, "Cannot parse date value: " + value + ", using format: " + format
                    + ", for term with type: " + ((Element) terms).getAttribute("type")));
        }

        return (st);
    }

    private SearchTerm parseReceivedDateTerm(Node terms) throws XPathException {
        SearchTerm st = null;

        String value = ((Element) terms).getAttribute("date");
        String format = ((Element) terms).getAttribute("format");

        if (StringUtils.isEmpty(value)) {
            throw (new XPathException(this,
                    "value not specified for term with type: " + ((Element) terms).getAttribute("type")));
        }

        if (StringUtils.isEmpty(format)) {
            throw (new XPathException(this,
                    "format not specified for term with type: " + ((Element) terms).getAttribute("type")));
        }

        int cp = parseComparisonAttribute(terms);

        try {
            SimpleDateFormat sdf = new SimpleDateFormat(format);

            Date date = sdf.parse(value);

            st = new ReceivedDateTerm(cp, date);
        } catch (ParseException pe) {
            throw (new XPathException(this, "Cannot parse date value: " + value + ", using format: " + format
                    + ", for term with type: " + ((Element) terms).getAttribute("type")));
        }

        return (st);
    }

    private int parseComparisonAttribute(Node terms) throws XPathException {
        int cp = ComparisonTerm.EQ;

        String comp = ((Element) terms).getAttribute("comparison");

        if (comp != null && comp.length() > 0) {
            if (comp.equalsIgnoreCase("eq")) {
                cp = ComparisonTerm.EQ;
            } else if (comp.equalsIgnoreCase("ge")) {
                cp = ComparisonTerm.GE;
            } else if (comp.equalsIgnoreCase("gt")) {
                cp = ComparisonTerm.GT;
            } else if (comp.equalsIgnoreCase("le")) {
                cp = ComparisonTerm.LE;
            } else if (comp.equalsIgnoreCase("lt")) {
                cp = ComparisonTerm.LT;
            } else if (comp.equalsIgnoreCase("ne")) {
                cp = ComparisonTerm.NE;
            } else {
                throw (new XPathException(this, "Invalid comparison: " + comp + ", for term with type: "
                        + ((Element) terms).getAttribute("type")));
            }
        } else {
            throw (new XPathException(this, "comparison attribute must be specified for term with type: "
                    + ((Element) terms).getAttribute("type")));
        }

        return (cp);
    }

}