gt: 		if( children.getLength() > 0 ) {
context: /*
   *  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.logging.log4j.LogManager;
  import org.apache.logging.log4j.Logger;
  import org.exist.dom.QName;
  import org.exist.dom.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 = LogManager.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();
  		
  