Folder Class

package javaxt.exchange;

//******************************************************************************
//**  Folder Class
//******************************************************************************
/**
 *   Used to represent a folder (e.g. "Inbox", "Contacts", etc.)
 *
 ******************************************************************************/

public class Folder {

    private String id;
    private String parentID;
    private String name;
    private String changeKey;
    private Integer totalCount;
    private Integer unreadCount;
    private Integer folderCount;
    private Connection conn;


  //**************************************************************************
  //** Constructor
  //**************************************************************************
  /** This constructor is provided for application developers who wish to
   *  extend this class.
   */
    protected Folder(){}


  //**************************************************************************
  //** Constructor
  //**************************************************************************
  /** Creates a new instance of this class using a folder
   */
    protected Folder(Folder folder){
        this.id = folder.id;
        this.parentID = folder.parentID;
        this.name = folder.name;
        this.changeKey = folder.changeKey;
        this.totalCount = folder.totalCount;
        this.unreadCount = folder.unreadCount;
        this.folderCount = folder.folderCount;
        this.conn = folder.conn;
    }


  //**************************************************************************
  //** Constructor
  //**************************************************************************
  /** Creates a new instance of this class using a folder name/id.
   *  @param folderName Name of the exchange folder (e.g. inbox, contacts, etc).
   */
    public Folder(String folderName, Connection conn) throws ExchangeException {

        this.conn = conn;

        String folderID = getDistinguishedFolderId(folderName);
        if (folderID==null){
            folderID = "<t:FolderId Id=\"" + folderName + "\"/>";
        }
        else{
            folderID = "<t:DistinguishedFolderId Id=\"" + folderID + "\"/>";
        }

        String msg =
        "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
        + "<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\">"
        + "<soap:Body>"
        + "<GetFolder xmlns=\"http://schemas.microsoft.com/exchange/services/2006/messages\" xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\">"
        + "<FolderShape>"
            + "<t:BaseShape>Default</t:BaseShape>"
            + "<t:AdditionalProperties>"

            //The following returns the LastModifiedTime for the folder. Unfortunately,
            //this does not reflect changes made to items in this folder.
            //+ "<t:ExtendedFieldURI PropertyTag=\"0x3008\" PropertyType=\"SystemTime\" />"

            + "<t:FieldURI FieldURI=\"folder:ParentFolderId\" />"
            + "</t:AdditionalProperties>"

        + "</FolderShape>"
        + "<FolderIds>" + folderID + "</FolderIds></GetFolder>"
        + "</soap:Body>"
        + "</soap:Envelope>";
        parseXML(conn.execute(msg));
    }


  //**************************************************************************
  //** Constructor
  //**************************************************************************
  /** Private constructor used by the getFolders() method.
   */
    private Folder(org.w3c.dom.Node folder, Connection conn) throws ExchangeException {
        this.conn = conn;
        parseFolderNode(folder);
    }


  //**************************************************************************
  //** getFolders
  //**************************************************************************
  /** Returns an array of folders found in this folder. Returns a zero length
   *  array of no folders are found.
   */
    public Folder[] getFolders() throws ExchangeException {
        return getFolders("Shallow");
    }


  //**************************************************************************
  //** getFolders
  //**************************************************************************
  /** Returns an array of folders found in this folder. Returns a zero length
   *  array of no folders are found.
   *  @param Traversal Possible values include "Deep", "Shallow", "SoftDeleted".
   *  Defaults to "Shallow" if a null or invalid string is used.
   */
    public Folder[] getFolders(String Traversal) throws ExchangeException {
        java.util.ArrayList<Folder> folders = new java.util.ArrayList<Folder>();

        if (Traversal==null) Traversal = "";
        if (Traversal.equalsIgnoreCase("Deep")) Traversal = "Deep";
        else if (Traversal.equalsIgnoreCase("SoftDeleted")) Traversal = "SoftDeleted";
        else Traversal = "Shallow";


        String msg =
        "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
        + "<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\">"
        + "<soap:Body>"
        + "<FindFolder Traversal=\"" + Traversal + "\" xmlns=\"http://schemas.microsoft.com/exchange/services/2006/messages\" xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\">"
        + "<FolderShape>"
            + "<t:BaseShape>Default</t:BaseShape>"
            + "<t:AdditionalProperties>"
            + "<t:FieldURI FieldURI=\"folder:ParentFolderId\" />"
            + "</t:AdditionalProperties>"
        + "</FolderShape>"
        + "<ParentFolderIds><t:FolderId Id=\"" + id + "\"/></ParentFolderIds>"
        + "</FindFolder>"
        + "</soap:Body>"
        + "</soap:Envelope>";

        org.w3c.dom.Document response = conn.execute(msg);
        for (org.w3c.dom.Node node : javaxt.xml.DOM.getElementsByTagName("Folder", response)){
            folders.add(new Folder(node, conn));
        }
        return folders.toArray(new Folder[folders.size()]);
    }


  //**************************************************************************
  //** createFolder
  //**************************************************************************
  /** Used to create a folder within this folder.
   */
    public Folder createFolder(String name) throws ExchangeException {
        name = name.trim();
        String msg =
        "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
        + "<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\">"
        + "<soap:Body>"
        + "<CreateFolder xmlns=\"http://schemas.microsoft.com/exchange/services/2006/messages\" xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\">"
        + "<ParentFolderId><t:FolderId Id=\"" + id + "\"/></ParentFolderId>"
        + "<Folders><t:Folder><t:DisplayName>" + name + "</t:DisplayName></t:Folder></Folders>"
        + "</CreateFolder>"
        + "</soap:Body>"
        + "</soap:Envelope>";

        org.w3c.dom.Document response = conn.execute(msg);
        org.w3c.dom.Node[] nodes = javaxt.xml.DOM.getElementsByTagName("Folder", response);
        if (nodes.length>0){
            Folder folder = new Folder(nodes[0], conn);
            folder.name = name;
            folder.totalCount = 0;
            folder.unreadCount = 0;
            folder.folderCount = 0;
            folder.parentID = id;
            return folder;
        }
        else throw new ExchangeException("Failed to create folder.");
    }


  //**************************************************************************
  //** rename
  //**************************************************************************
  /** Used to rename this folder.
   */
    public void rename(String name) throws ExchangeException {
        changeKey = getChangeKey(conn);
        name = name.trim();
        String msg =
        "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
        + "<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\">"
        + "<soap:Body>"
        + "<UpdateFolder xmlns=\"http://schemas.microsoft.com/exchange/services/2006/messages\" xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\">"
        + "<FolderChanges>"
        + "<t:FolderChange>"
                + "<t:FolderId Id=\"" + id + "\" ChangeKey=\"" + changeKey + "\"/>"
                + "<t:Updates>"
                    + "<t:SetFolderField>"
                    + "<t:FieldURI FieldURI=\"folder:DisplayName\" />"
                    + "<t:Folder><t:DisplayName>" + name + "</t:DisplayName></t:Folder>"
                    + "</t:SetFolderField>"
                + "</t:Updates>"
        + "</t:FolderChange>"
        + "</FolderChanges>"
        + "</UpdateFolder>"
        + "</soap:Body>"
        + "</soap:Envelope>";
        conn.execute(msg);
        this.name = name;
    }


  //**************************************************************************
  //** move
  //**************************************************************************
  /** Used to move this folder to another folder.
   */
    public void move(Folder destination) throws ExchangeException {
        String msg =
        "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
        + "<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\">"
        + "<soap:Body>"
        + "<MoveFolder xmlns=\"http://schemas.microsoft.com/exchange/services/2006/messages\" xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\">"
        + "<ToFolderId><t:FolderId Id=\"" + destination.id + "\"/></ToFolderId>"
        + "<FolderIds><t:FolderId Id=\"" + id + "\"/></FolderIds>"
        + "</MoveFolder>"
        + "</soap:Body>"
        + "</soap:Envelope>";
        conn.execute(msg);
    }


  //**************************************************************************
  //** delete
  //**************************************************************************
  /** Used to delete this folder.
   */
    public void delete() throws ExchangeException {
        String msg =
        "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
        + "<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\">"
        + "<soap:Body>"
        + "<DeleteFolder DeleteType=\"HardDelete\" xmlns=\"http://schemas.microsoft.com/exchange/services/2006/messages\" xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\">"
        + "<FolderIds><t:FolderId Id=\"" + id + "\"/></FolderIds>"
        + "</DeleteFolder>"
        + "</soap:Body>"
        + "</soap:Envelope>";
        conn.execute(msg);
    }


  //**************************************************************************
  //** getChangeKey
  //**************************************************************************
  /** Used to retrieve the latest ChangeKey for this folder. This method is
   *  required to update an item.
   */
    protected String getChangeKey(Connection conn) throws ExchangeException {
        changeKey = new Folder(id, conn).changeKey;
        return changeKey;
    }

    private void parseXML(org.w3c.dom.Document xml) throws ExchangeException {
        org.w3c.dom.Node[] nodes = javaxt.xml.DOM.getElementsByTagName("FolderId", xml);
        if (nodes.length>0) parseFolderNode(nodes[0].getParentNode());
        else throw new ExchangeException("Failed to parse FolderId.");
    }


    private void parseFolderNode(org.w3c.dom.Node folder) throws ExchangeException {
        org.w3c.dom.NodeList properties = folder.getChildNodes();
        for (int i=0; i<properties.getLength(); i++){
            org.w3c.dom.Node property = properties.item(i);
            if (property.getNodeType()==1){
                String key = property.getNodeName();
                String value = property.getTextContent().trim();
                if (key.contains(":")) key = key.substring(key.indexOf(":")+1);
                if (key.equalsIgnoreCase("FolderId")){
                    id = javaxt.xml.DOM.getAttributeValue(property, "Id");
                    changeKey = javaxt.xml.DOM.getAttributeValue(property, "ChangeKey");
                }
                else if(key.equalsIgnoreCase("ParentFolderId")){
                    parentID = javaxt.xml.DOM.getAttributeValue(property, "Id");
                    //javaxt.xml.DOM.getAttributeValue(property, "ChangeKey");
                }
                else if(key.equalsIgnoreCase("DisplayName")) name = value;
                else if(key.equalsIgnoreCase("TotalCount")) totalCount = cint(value);
                else if(key.equalsIgnoreCase("ChildFolderCount")) folderCount = cint(value);
                else if(key.equalsIgnoreCase("UnreadCount")) unreadCount = cint(value);
            }
        }

        if (id==null || id.length()==0) throw new ExchangeException("Failed to parse Folder.");
    }


    private Integer cint(String str){
        try{
            return Integer.parseInt(str);
        }
        catch(Exception e){
            return null;
        }
    }


  //**************************************************************************
  //** getID
  //**************************************************************************
  /** Returns the unique Exchange ID for this folder. */

    public String getID(){
        return id;
    }


  //**************************************************************************
  //** getParentID
  //**************************************************************************
  /** Returns the unique ID for the parent folder. */

    public String getParentID(){
        return parentID;
    }

    /*
    public String getChangeKey(){
        return changeKey;
    }
    */

  //**************************************************************************
  //** getName
  //**************************************************************************
  /** Returns the name of the folder. */

    public String getName(){
        return name;
    }


  //**************************************************************************
  //** getTotalCount
  //**************************************************************************
  /** Returns the total number of items found in this folder. */

    public Integer getTotalCount(){
        return totalCount;
    }


  //**************************************************************************
  //** getUnreadCount
  //**************************************************************************
  /** Returns the total number of unread items found in this folder. */

    public Integer getUnreadCount(){
        return unreadCount;
    }


  //**************************************************************************
  //** getChildFolderCount
  //**************************************************************************
  /** Returns the total number of folders found in this this folder. */

    public Integer getChildFolderCount(){
        return folderCount;
    }


  //**************************************************************************
  //** toString
  //**************************************************************************
  /** Returns the name of the folder. */

    public String toString(){
        return name;
    }


  //**************************************************************************
  //** getItems
  //**************************************************************************
  /** Returns an XML document with shallow representations of items found in
   *  this folder.
   *  @param offset Item offset. 0 implies no offset.
   *  @param limit Maximum number of items to return.
   */
    protected org.w3c.dom.Document getItems(int offset, int limit, java.util.HashSet<FieldURI> additionalProperties, String where, FieldOrder[] sortOrder) throws ExchangeException {
        if (offset<1) offset = 0;
        if (limit<1) limit = 1;
        return getItems("<m:IndexedPageItemView MaxEntriesReturned=\"" + limit + "\" Offset=\"" + offset + "\" BasePoint=\"Beginning\"/>",
            additionalProperties, where, sortOrder);
    }


  //**************************************************************************
  //** getItems
  //**************************************************************************
  /** Returns an XML document with shallow representations of items found in
   *  this folder.
   *
   *  @param view XML node representing a page view (e.g. IndexedPageItemView,
   *  FractionalPageItemView, CalendarView, ContactsView).
   *
   *  @param additionalProperties By default, this method returns a shallow
   *  representation of each item found in this folder. You can retrieve
   *  additional attributes by providing a list of properties
   *  (e.g. "calendar:TimeZone", "item:Sensitivity", etc).
   *
   *  @param sortOrder SQL-style order by clause used to sort the results
   *  (e.g. "item:DateTimeReceived DESC").
   */
    protected org.w3c.dom.Document getItems(String view, java.util.HashSet<FieldURI> additionalProperties, String where, FieldOrder[] sortOrder) throws ExchangeException {

        
      //Parse order by statement
        String sort = "";
        if (sortOrder!=null){
            for (FieldOrder field : sortOrder){
                sort += field.toXML();
            }
            if (sort.length()>0){
                sort = "<m:SortOrder>" + sort + "</m:SortOrder>";
            }
        }


      //Parse where clasue and create restriction
        if (where==null) where = "";
        else where = where.trim();
        String restriction = where;


      //Update the view xml node. Make sure the node name is prefixed with a "m:" namespace
        if (view==null) view = "";
        else{
            view = view.trim();

            String nodeName = view.substring(1, view.indexOf(">"));
            if (nodeName.endsWith("/")) nodeName = nodeName.substring(0, nodeName.length()-1);
            if (nodeName.contains(" ")) nodeName = nodeName.substring(0, nodeName.indexOf(" "));
            nodeName = nodeName.trim();
            if (nodeName.contains(":")){
                String ns = nodeName.substring(0, nodeName.indexOf(":"));
                if (!ns.equals("m")){
                    String newNodeName = nodeName.substring(ns.length()+1);
                    view = view.replace("<" + nodeName, "<m:" + newNodeName);
                    view = view.replace("</" + nodeName, "</m:" + newNodeName);
                }
            }
            else{
                view = view.replace("<" + nodeName, "<m:" + nodeName);
                view = view.replace("</" + nodeName, "</m:" + nodeName);
            }
        }

      //Pa
        StringBuffer props = new StringBuffer();
        if (additionalProperties!=null){
            java.util.Iterator<FieldURI> it = additionalProperties.iterator();
            while (it.hasNext()){
                FieldURI prop = it.next();
                if (prop!=null){
                    props.append(prop.toXML("t")); //("<t:FieldURI FieldURI=\"" + prop + "\"/>");
                }
            }
        }

        String msg =
        "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
        + "<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\" xmlns:m=\"http://schemas.microsoft.com/exchange/services/2006/messages\">"
        + "<soap:Body>"
        + "<m:FindItem Traversal=\"Shallow\">"
        /*
        + "<m:ItemShape><t:BaseShape>Default</t:BaseShape>" //<--"Default" vs "AllProperties"
        + "<t:AdditionalProperties><t:FieldURI FieldURI=\"item:ItemClass\"/></t:AdditionalProperties>"
        + "</m:ItemShape>"
        */
        + "<m:ItemShape>"
                + "<t:BaseShape>Default</t:BaseShape>" //<--"Default" vs "AllProperties"
                + "<t:AdditionalProperties>"
                + "<t:FieldURI FieldURI=\"item:ItemClass\"/>"
                //+ "<t:FieldURI FieldURI=\"item:LastModifiedTime\"/>" //value="item:LastModifiedTime" //<--This doesn't work...
                + "<t:ExtendedFieldURI PropertyTag=\"0x3008\" PropertyType=\"SystemTime\" />" //<--This returns the LastModifiedTime!
                + props.toString() 
                + "</t:AdditionalProperties>"
        + "</m:ItemShape>"

        + view
        + restriction
        + sort

        + "<m:ParentFolderIds>"
        + "<t:FolderId Id=\"" + id + "\"/>"
        + "</m:ParentFolderIds>"
        + "</m:FindItem>"
        + "</soap:Body>"
        + "</soap:Envelope>";
    
        return conn.execute(msg);
    }


  //**************************************************************************
  //** getIndex
  //**************************************************************************
  /** Returns a hashmap of all the items found in this folder. The hashmap key
   *  is the item id and the corresponding value is the last modification date.
   */
    public java.util.HashMap<String, javaxt.utils.Date> getIndex() throws ExchangeException {

        String msg =
        "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
        + "<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\" xmlns:m=\"http://schemas.microsoft.com/exchange/services/2006/messages\">"
        + "<soap:Body>"
        + "<m:FindItem Traversal=\"Shallow\">"
        + "<m:ItemShape>"
                + "<t:BaseShape>IdOnly</t:BaseShape>" //<--"Default" vs "AllProperties"
                + "<t:AdditionalProperties>"
                + "<t:ExtendedFieldURI PropertyTag=\"0x3008\" PropertyType=\"SystemTime\" />" //<--This returns the LastModifiedTime!
                + "</t:AdditionalProperties>"
        + "</m:ItemShape>"

        + "<m:SortOrder>"
        + "<t:FieldOrder Order=\"Descending\">"
            + "<t:ExtendedFieldURI PropertyTag=\"0x3008\" PropertyType=\"SystemTime\" />"
        + "</t:FieldOrder>"
        + "</m:SortOrder>"
            
        //+ "<m:IndexedPageItemView MaxEntriesReturned=\"1\" Offset=\"0\" BasePoint=\"Beginning\"/>"
        + "<m:ParentFolderIds>"
        + "<t:FolderId Id=\"" + id + "\"/>"
        + "</m:ParentFolderIds>"
        + "</m:FindItem>"
        + "</soap:Body>"
        + "</soap:Envelope>";

        org.w3c.dom.Document xml = conn.execute(msg);
        //new javaxt.io.File("/temp/exchange-sort.xml").write(xml);

        java.util.HashMap<String, javaxt.utils.Date> index = new java.util.HashMap<String, javaxt.utils.Date>();

        org.w3c.dom.Node[] items = javaxt.xml.DOM.getElementsByTagName("Items", xml);
        if (items.length>0){
            org.w3c.dom.NodeList nodes = items[0].getChildNodes();
            for (int i=0; i<nodes.getLength(); i++){
                org.w3c.dom.Node node = nodes.item(i);
                if (node.getNodeType()==1){
                    FolderItem item = new FolderItem(node);
                    index.put(item.getID(), item.getLastModifiedTime());
                }
            }

        }        

        return index;
    }


    protected void findItem(){
    /*
      <m:FindItem Traversal="Shallow"
          xmlns:m=".../messages"
          xmlns:t=".../types">
          <m:ItemShape>
            <t:BaseShape>IdOnly</t:BaseShape>
            <t:AdditionalProperties>
              <t:FieldURI FieldURI="item:Subject" />
              <t:FieldURI FieldURI="calendar:CalendarItemType" />
            </t:AdditionalProperties>
          </m:ItemShape>
          <m:Restriction>
            <t:And>
              <t:IsGreaterThan>
                <t:FieldURI FieldURI="calendar:Start" />
                <t:FieldURIOrConstant>
                  <t:Constant Value="2006-10-16T00:00:00-08:00" />
                </t:FieldURIOrConstant>
              </t:IsGreaterThan>
              <t:IsLessThan>
                <t:FieldURI FieldURI="calendar:End" />
                <t:FieldURIOrConstant>
                  <t:Constant Value="2006-10-20T23:59:59-08:00" />
                </t:FieldURIOrConstant>
              </t:IsLessThan>
            </t:And>
          </m:Restriction>
          <m:ParentFolderIds>
            <t:DistinguishedFolderId Id="calendar"/>
          </m:ParentFolderIds>
        </m:FindItem>
     */
    }


  //**************************************************************************
  //** getDistinguishedFolderIds
  //**************************************************************************
  /** Returns a list of Distinguished Folder IDs.
   */
    public static String[] getDistinguishedFolderIds(){
        return DistinguishedFolderIds;
    }

    
  //**************************************************************************
  //** getDistinguishedFolderId
  //**************************************************************************
  /** Returns the DistinguishedFolderID for a given folder.
   */
    public static String getDistinguishedFolderId(String folderName){
        for (String folderID : DistinguishedFolderIds){
            if (folderID.equalsIgnoreCase(folderName)) return folderID;
        }
        return null;
    }

    
  //**************************************************************************
  //** DistinguishedFolderIds
  //**************************************************************************
  /** Static list of Distinguished Folder IDs. Source:
   *  http://msdn.microsoft.com/en-us/library/exchangewebservices.distinguishedfolderidnametype%28v=exchg.140%29.aspx
   */
    private static String[] DistinguishedFolderIds = new String[]{
        "archivedeleteditems",
        "archivemsgfolderroot",
        "archiverecoverableitemsdeletions",
        "archiverecoverableitemspurges",
        "archiverecoverableitemsroot",
        "archiverecoverableitemsversions",
        "archiveroot",
        "calendar",  //Represents the Calendar folder.
        "contacts",  //Represents the Contacts folder.
        "deleteditems",  //Represents the Deleted Items folder.
        "drafts",  //Represents the Drafts folder.
        "inbox",  //Represents the Inbox folder.
        "journal",  //Represents the Journal folder.
        "junkemail",  //Represents the Junk E-mail folder.
        "msgfolderroot",  //Represents the message folder root.
        "notes",  //Represents the Notes folder.
        "outbox",  //Represents the Outbox folder.
        "publicfoldersroot",
        "recoverableitemsdeletions",
        "recoverableitemspurges",
        "recoverableitemsroot",
        "recoverableitemsversions",
        "root",  //Represents the root of the mailbox.
        "searchfolders",  //Represents the Search Folders folder. This is also an alias for the Finder folder.
        "sentitems",  //Represents the Sent Items folder.
        "tasks",  //Represents the Tasks folder.
        "voicemail"  //Represents the Voice Mail folder.
    };
}