다음을 통해 공유


Connecting Java to Exchange over WebDAV, with Apache HttpClient

Earlier I mentioned a Java Exchange Connector I had seen, and in that post I also said that I had some code that connected from Java to Exchange Server via WebDAV. Some people have asked for the code, so here it is.

I use a set of Apache libraries to make this possible: Apache Commons HttpClient for the Java SSL client capability, and Apache Jakarta Slide for the WebDAV piece. As you may have heard, Slide was discontinued as a project a while ago. So maybe you are thinking, Slide? What good is that code if it depends on an abandoned Apache library? . A reasonable point. Honestly though, the Slide stuff is really not that critical for this scenario. Basically, WebDAV is a standard for formatting queries that get sent over HTTP. But in my case, I am just cons-ing up strings that contain the queries. It would be pretty easy to factor out the WebDAV/Slide stuff, I think. I didn't bother to do it, only because I wrote the code a long time ago, well before Slide was discontinued, and I didn't really feel like investing the time to re-factor it now.

The httpclient and slide jars also drag in the commons-logging and codec jars from Apache.

I have the full working proof-of-concept code attached to this post as a zipfile. It is a JSP-based app that runs in Tomcat or Jetty or your favorite servlet container. Requires JDK 1.5 to build it. You can have a look and try it out yourself. Here's the code I use to send out a query to Exchange.

   public org.w3c.dom.Document search(String urlString, String request, int depth, String range) 
    throws Exception
  {
    Document doc= null;

    SearchMethod method = new SearchMethod(urlString, request); 
    try {
      //method.setRequestHeader("Content-type", "text/xml");
      method.setRequestHeader("depth", ""+depth) ;
      method.setRequestHeader("Translate", "f");

      // must set Content-Length explicitly.  Why doesn't the SearchMethod do this?  Who knows. . .
      method.setRequestHeader("Content-Length", String.valueOf(request.length()));

      if ((range!=null) && (range !=""))
          method.setRequestHeader("Range", range);

      int rc = httpclient.executeMethod(method);

      doc = method.getResponseDocument();
    }

    finally {
      // release any connection resources used by the method
      method.releaseConnection();
    }            

    return doc;
  }

 

Ok, that's just some boilerplate WebDAV stuff. Get a request, set the HTTP headers, send out the request. The magic really is in the request itself. This is what a WebDAV request looks like on the wire:

 SEARCH /exchange/dinoch/Inbox HTTP/1.1 
depth: 1 
Translate: f 
Content-Length: 370
Range: rows =0-3
Content-Type: text/xml; charset=utf-8
User-Agent: Jakarta Commons-HttpClient/3.1
Host: mail.microsoft.com
Cookie: $Version=0; sessionid=40e73022-93d3-4739-99ff-bf60fd60fcee; $Path =/
Cookie: $Version=0; cadata=1 gGfmglWDAmcLGmstqx3kSpuecb1BVBmjFwiD0IIem2hm6IsfCLr7DSo63ncgdM7xNi3E5A==; $Path= /

<searchrequest xmlns='DAV:' >
  <sql> 
    SELECT "DAV:id", "DAV:href" , "urn:schemas:httpmail:subject", "urn:schemas: httpmail:from", "urn:schemas:httpmail:datereceived" 
    FROM SCOPE('shallow traversal of "https://mail.microsoft.com/exchange/dinoch/Inbox"') 
    WHERE "DAV:ishidden"=False AND "DAV:isfolder"=False 
  </sql>
</searchrequest>
 SEARCH /exchange/dinoch/Inbox HTTP/1.1 
depth: 1 
Translate: f 
Content-Length: 370
Range: rows =0-3
Content-Type: text/xml; charset=utf-8
User-Agent: Jakarta Commons-HttpClient/3.1
Host: mail.microsoft.com
Cookie: $Version=0; sessionid=40e73022-93d3-4739-99ff-bf60fd60fcee; $Path =/
Cookie: $Version=0; cadata=1 gGfmglWDAmcLGmstqx3kSpuecb1BVBmjFwiD0IIem2hm6IsfCLr7DSo63ncgdM7xNi3E5A==; $Path= /

<searchrequest xmlns='DAV:' >
  <sql> 
    SELECT "DAV:id", "DAV:href" , "urn:schemas:httpmail:subject", "urn:schemas: httpmail:from", "urn:schemas:httpmail:datereceived" 
    FROM SCOPE('shallow traversal of "https://mail.microsoft.com/exchange/dinoch/Inbox"') 
    WHERE "DAV:ishidden"=False AND "DAV:isfolder"=False 
  </sql>
</searchrequest>

 

In that request, I'm searching on my Exchange Inbox. I can also search on any folder: Calendar, Contacts, Notes, Tasks, any mail folder, and so on. The schema are different for different item types, so ya gotta be careful there. Anyway, in the above request, I search on the first 4 rows, and I ask for the fields: subject, id, href, from, and datereceived.

In the proof of concept, I create these queries using a file-based template, one for each type of query. I have a template for the inbox query, another template for the query of the tasks folder, and so on. This is the template for the Tasks query, for example:

 <searchrequest  xmlns='DAV:'>
  <sql>
    SELECT 
       "DAV:href", 
       "DAV:displayname", 
       "DAV:getlastmodified",
       "urn:schemas:httpmail:subject",
       "urn:schemas:httpmail:textdescription",
       "https://schemas.microsoft.com/mapi/id/{00062008-0000-0000-C000-000000000046}/0x00008517" as DueDate2,
       "https://schemas.microsoft.com/mapi/id/{00062003-0000-0000-C000-000000000046}/0x8101" AS Status,
       "https://schemas.microsoft.com/mapi/id/{00062003-0000-0000-C000-000000000046}/0x8102" AS PercentComplete,
       "https://schemas.microsoft.com/mapi/id/{00062003-0000-0000-C000-000000000046}/0x8104" AS StartDate,
       "https://schemas.microsoft.com/mapi/id/{00062003-0000-0000-C000-000000000046}/0x8105" AS DueDate,
       "https://schemas.microsoft.com/mapi/id/{00062003-0000-0000-C000-000000000046}/0x810f" AS DateCompleted,
       "https://schemas.microsoft.com/mapi/id/{00062003-0000-0000-C000-000000000046}/0x811c" AS IsComplete,
       "https://schemas.microsoft.com/mapi/id/{00062003-0000-0000-C000-000000000046}/0x8113" AS State,
       "https://schemas.microsoft.com/mapi/id/{00062003-0000-0000-C000-000000000046}/0x8110" AS ActualEffort,
       "https://schemas.microsoft.com/mapi/id/{00062003-0000-0000-C000-000000000046}/0x8111" AS EstimatedEffort,
       "https://schemas.microsoft.com/mapi/id/{00062003-0000-0000-C000-000000000046}/0x8518" AS Mode,
       "https://schemas.microsoft.com/mapi/id/{00062003-0000-0000-C000-000000000046}/0x811f" AS Owner

    FROM SCOPE('shallow traversal of  "##FOLDERPATH##"')
    WHERE "DAV:ishidden"=False AND "DAV:isfolder"=False  
    ORDER BY IsComplete, DueDate ASC 

 </sql>
</searchrequest>

 

One of the trickiest areas for me was just figuring out the MAPI schema for all the various fields that might be present in a given document type (like task, message, meeting request, contact, note, and so on) that can be stored by Exchange. The schema are not intuitive, nor did I find the documentation to be easily accessible. As you can see, there are some magic incantations above for the task document type.

Just so everyone is clear: The schema used is the same, regardless of the type of client. I am writing a Java app here. But you could use these same queries from a .NET app, a VBScript app, or a PHP app, or Ruby or whatever. The Exchange Server responds to the on-the-wire protocol, and doesn't care about the client you use.

Another key part is logging into the Exchange server. This is the method I use to login.

   public void login (String destUrl, String username, String password) 
    throws Exception
  {
    Username= username;

    String[] urlParts=destUrl.split("/");
    String server= urlParts[2]; 
    String authDllPath= "/exchweb/bin/auth/owaauth.dll";
    String AuthUrl= Protocol + "://" + server + authDllPath;

    //System.setProperty("javax.net.debug", "all");  // too much information
    //System.setProperty("javax.net.debug", "ssl");

    // the class ionic.ssl.SSLSocketFactoryImpl must be available for the classloader,
    // eg, on \lib\ext
    java.security.Security.setProperty("ssl.SocketFactory.provider",
                                       "ionic.ssl.SSLSocketFactoryImpl");

    if (!server.equals(Servername)) 
      throw new Exception ("Cross-site redirection attempt.");

    PostMethod method = new PostMethod(AuthUrl);

    try {

      //System.out.println("DavConnection: Logging in with u/p: " + username + " | " + password); 

      method.setFollowRedirects(false); // false == default

      NameValuePair[] data = {
        new NameValuePair("destination", destUrl),
        new NameValuePair("username", username),
        new NameValuePair("password", password)
      };
      method.setRequestBody(data);

      int rc = httpclient.executeMethod(method);
      int stat= method.getStatusLine().getStatusCode(); 
        
      if (stat== 302) {  // 302 = Moved Temporarily (this is success)

        // The response form Exch2003 says "Moved Temporarily" but 
        // if we are only logging in, we don't need to follow this link. 
        // The apache commons httpclient runtime will log an INFO
        // saying "302 received but followRedirects==false.  This is OK. 

        // String redirectLocation= null; 
        // Header locationHeader = method.getResponseHeader("location");
        // if (locationHeader != null) 
        // redirectLocation = locationHeader.getValue();
        // System.out.println("Redirect to: " + redirectLocation);

      }
      else 
        throw new Exception("Unexpected HTTP status code during login (" + stat +")");
    } 
    finally {
      // release any connection resources used by the method
      method.releaseConnection();
    }            
  }

 

This login method is exposed on a DavConnection object, the class/type that wraps all the interaction with Exchange server. After login, of course, I set the DavConnection into the http session. It gets returned as a cookie to the browser. Thereafter when the browser connects and presents its cookie, we have te active Exchange connection. We only login once.

At this point I want to talk about cookies. My favorite kind are oatmeal cookies, which I love to make. When I make 'em, I tend to eat them, all of them. So I don't make 'em that often. My second-favorite kind of cookies are HTTP Cookies. There are two sets of HTTP cookies in this application scenario. One set of cookies links the browser to the JSP app. Another set of cookies links the JSP app to Exchange Server.

The second set, the cookies that Exchange sends back to the client that is authenticating (in this case our JSP app) must be retained and presented back to the server on subsequent queries. Nicely for us, the Apache HttpClient library does that automagically for us. The DavConnection class embeds an HttpClient instance as a member; this HttpClient instance retains the cookies that keep the conversation with Exchange alive. mmm-kay?

Let's see what else? I guess you will want to have a look at a sample response from an Exchange WebDAV query. This is one response I got.

 <a:multistatus xmlns:b="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/" xmlns:d="urn:schemas:httpmail:" xmlns:c="xml:" xmlns:a="DAV:">
  <a:contentrange>0-9</a:contentrange>
  <a:response>
    <a:href>https://mail.microsoft.com/Exchange/dinoch/Inbox/RE:%20Developing%20and%20debugging%20without%20admin%20rights.EML</a:href>
    <a:propstat>
      <a:status>HTTP/1.1 200 OK</a:status>
      <a:prop>
        <a:id>ARkAAAACUCbqAQAAQL47Tx0AAAAA</a:id>
        <a:href>https://mail.microsoft.com/Exchange/dinoch/Inbox/RE:%20Developing%20and%20debugging%20without%20admin%20rights.EML</a:href>
        <d:subject>RE: Developing and debugging without admin rights</d:subject>
        <d:from>"Alun Jones" &lt;alunj@microsoft.com&gt;</d:from>
        <d:datereceived b:dt="dateTime.tz">2005-04-11T21:26:24.409Z</d:datereceived>
      </a:prop>
    </a:propstat>
  </a:response>
  <a:response>
    <a:href>https://mail.microsoft.com/Exchange/dinoch/Inbox/MarieHu%20team%20meeting:%20FY06%20plans%20%26%20budget.EML</a:href>
    <a:propstat>
      <a:status>HTTP/1.1 200 OK</a:status>
      <a:prop>
        <a:id>ARkAAAACUCbqAQAAQL47TxGAAAAA</a:id>
        <a:href>https://mail.microsoft.com/Exchange/dinoch/Inbox/MarieHu%20team%20meeting:%20FY06%20plans%20%26%20budget.EML</a:href>
        <d:subject>MarieHu team meeting: FY06 plans &amp; budget</d:subject>
        <d:from>"Marie Huwe" &lt;mariehu@microsoft.com&gt;</d:from>
        <d:datereceived b:dt="dateTime.tz">2005-04-11T21:23:44.107Z</d:datereceived>
      </a:prop>
    </a:propstat>
  </a:response>
  <a:response>
    <a:href>https://mail.microsoft.com/Exchange/dinoch/Inbox/Repeater%20control%20%3CItemTemplate%3E%20question.EML</a:href>
    <a:propstat>
      <a:status>HTTP/1.1 200 OK</a:status>
      <a:prop>
        <a:id>ARkAAAACUCbqAQAAQL47TxMAAAAA</a:id>
        <a:href>https://mail.microsoft.com/Exchange/dinoch/Inbox/Repeater%20control%20%3CItemTemplate%3E%20question.EML</a:href>
        <d:subject>Repeater control &lt;ItemTemplate&gt; question</d:subject>
        <d:from>"Mike Burdick" &lt;mikebu@microsoft.com&gt;</d:from>
        <d:datereceived b:dt="dateTime.tz">2005-04-11T21:23:06.000Z</d:datereceived>
      </a:prop>
    </a:propstat>
  </a:response>
  <a:response>
    <a:href>https://mail.microsoft.com/Exchange/dinoch/Inbox/Work%20on%20the%20update%20to%20the%20Business%20Section%20of%20the%20RTB%20Slide%20Deck.EML</a:href>
    <a:propstat>
      <a:status>HTTP/1.1 200 OK</a:status>
      <a:prop>
        <a:id>ARkAAAACUCbqAQAAQL47TxZAAAAA</a:id>
        <a:href>https://mail.microsoft.com/Exchange/dinoch/Inbox/Work%20on%20the%20update%20to%20the%20Business%20Section%20of%20the%20RTB%20Slide%20Deck.EML</a:href>
        <d:subject>Work on the update to the Business Section of the RTB Slide Deck</d:subject>
        <d:from>"Paul Barcoe-Walsh" &lt;paulbwa@exchange.microsoft.com&gt;</d:from>
        <d:datereceived b:dt="dateTime.tz">2005-04-11T20:52:48.000Z</d:datereceived>
      </a:prop>
    </a:propstat>
  </a:response>
  <a:response>
    <a:href>https://mail.microsoft.com/Exchange/dinoch/Inbox/RE:%20demo%20script%20updates%20-%2060min:%20403%20on%20notify%20ws.EML</a:href>
    <a:propstat>
      <a:status>HTTP/1.1 200 OK</a:status>
      <a:prop>
        <a:id>ARkAAAACUCbqAQAAQL47TxSAAAAA</a:id>
        <a:href>https://mail.microsoft.com/Exchange/dinoch/Inbox/RE:%20demo%20script%20updates%20-%2060min:%20403%20on%20notify%20ws.EML</a:href>
        <d:subject>RE: demo script updates - 60min: 403 on notify ws</d:subject>
        <d:from>"Jonathan Moons" &lt;jmoons@microsoft.com&gt;</d:from>
        <d:datereceived b:dt="dateTime.tz">2005-04-11T20:46:05.000Z</d:datereceived>
      </a:prop>
    </a:propstat>
  </a:response>
  <a:response>
    <a:href>https://mail.microsoft.com/Exchange/dinoch/Inbox/ACTION:%20New%20launch%20BOM%20in%20effect.EML</a:href>
    <a:propstat>
      <a:status>HTTP/1.1 200 OK</a:status>
      <a:prop>
        <a:id>ARkAAAACUCbqAQAAQL47TxQAAAAA</a:id>
        <a:href>https://mail.microsoft.com/Exchange/dinoch/Inbox/ACTION:%20New%20launch%20BOM%20in%20effect.EML</a:href>
        <d:subject>ACTION: New launch BOM in effect</d:subject>
        <d:from>"Bill Dunlap" &lt;bdunlap@microsoft.com&gt;</d:from>
        <d:datereceived b:dt="dateTime.tz">2005-04-11T20:43:09.000Z</d:datereceived>
      </a:prop>
    </a:propstat>
  </a:response>
  <a:response>
    <a:href>https://mail.microsoft.com/Exchange/dinoch/Inbox/You%20available%20to%20talk%20today_x003F_.EML</a:href>
    <a:propstat>
      <a:status>HTTP/1.1 200 OK</a:status>
      <a:prop>
        <a:id>ARkAAAACUCbqAQAAQL47TxRAAAAA</a:id>
        <a:href>https://mail.microsoft.com/Exchange/dinoch/Inbox/You%20available%20to%20talk%20today_x003F_.EML</a:href>
        <d:subject>You available to talk today?</d:subject>
        <d:from>"Terry Leeper" &lt;tleeper@microsoft.com&gt;</d:from>
        <d:datereceived b:dt="dateTime.tz">2005-04-11T20:07:57.694Z</d:datereceived>
      </a:prop>
    </a:propstat>
  </a:response>
  <a:response>
    <a:href>https://mail.microsoft.com/Exchange/dinoch/Inbox/MarieHu%20team%20meeting:%20Overview%20of%20the%20Connected%20Systems%20Generico%20application.EML</a:href>
    <a:propstat>
      <a:status>HTTP/1.1 200 OK</a:status>
      <a:prop>
        <a:id>ARkAAAACUCbqAQAAQL47TxYAAAAA</a:id>
        <a:href>https://mail.microsoft.com/Exchange/dinoch/Inbox/MarieHu%20team%20meeting:%20Overview%20of%20the%20Connected%20Systems%20Generico%20application.EML</a:href>
        <d:subject>MarieHu team meeting: Overview of the Connected Systems Generico application</d:subject>
        <d:from>"Marie Huwe" &lt;mariehu@microsoft.com&gt;</d:from>
        <d:datereceived b:dt="dateTime.tz">2005-04-11T20:02:45.693Z</d:datereceived>
      </a:prop>
    </a:propstat>
  </a:response>
  <a:response>
    <a:href>https://mail.microsoft.com/Exchange/dinoch/Inbox/Blowfish%20implementations%20for%20.NET.EML</a:href>
    <a:propstat>
      <a:status>HTTP/1.1 200 OK</a:status>
      <a:prop>
        <a:id>ARkAAAACUCbqAQAAQL47TxKAAAAA</a:id>
        <a:href>https://mail.microsoft.com/Exchange/dinoch/Inbox/Blowfish%20implementations%20for%20.NET.EML</a:href>
        <d:subject>Blowfish implementations for .NET</d:subject>
        <d:from>"Kevin Hammond" &lt;kevinha@microsoft.com&gt;</d:from>
        <d:datereceived b:dt="dateTime.tz">2005-04-11T19:48:00.000Z</d:datereceived>
      </a:prop>
    </a:propstat>
  </a:response>
  <a:response>
    <a:href>https://mail.microsoft.com/Exchange/dinoch/Inbox/Out%20of%20Office%20AutoReply:%20SF%20and%20Interop.EML</a:href>
    <a:propstat>
      <a:status>HTTP/1.1 200 OK</a:status>
      <a:prop>
        <a:id>ARkAAAACUCbqAQAAQL47TxBAAAAA</a:id>
        <a:href>https://mail.microsoft.com/Exchange/dinoch/Inbox/Out%20of%20Office%20AutoReply:%20SF%20and%20Interop.EML</a:href>
        <d:subject>Out of Office AutoReply: SF and Interop</d:subject>
        <d:from>"Mike Wons" &lt;mikewons@microsoft.com&gt;</d:from>
        <d:datereceived b:dt="dateTime.tz">2005-04-11T19:35:23.673Z</d:datereceived>
      </a:prop>
    </a:propstat>
  </a:response>
</a:multistatus>

 

Just standard XML. I use an XSL sheet to transform that into some readable HTML. In the interest of completeness, here it is:

 <xsl:stylesheet 
    xmlns:xsl="https://www.w3.org/1999/XSL/Transform"
    version="1.0"
    xmlns:a="DAV:"
    xmlns:m="urn:schemas:httpmail:" 
    xmlns:c="urn:schemas:contacts:" 
    xmlns:java="java" 
  >

<!-- Contacts.xsl                                                         -->
<!--                                                                      -->
<!-- This sheet is used to Transform XML obtained from an Exchange        -->
<!-- webdav query into html.                                              -->
<!--                                                                      -->
<!-- Tue, 12 Apr 2005  15:11                                              -->


<xsl:param name="PageNumber"/>
<xsl:param name="UserName"/>
<xsl:param name="FolderName"/>
<xsl:param name="PageName"/>
<xsl:param name="CurrentTimeFormatted"/>


<xsl:output 
    method="html" 
    indent="yes" />

  <xsl:template match="a:multistatus">
    <xsl:variable name="FolderDecoded"
              select="java:net.URLDecoder.decode($FolderName, 'UTF-8')" >
    </xsl:variable>
    <xsl:choose>
      <xsl:when test="number($PageNumber) > 1">
      <a>
          <xsl:attribute name="href"><xsl:value-of select="$PageName"/>?action=itemizeFolder&amp;ref=<xsl:value-of select="$FolderName"/>&amp;page=<xsl:value-of select="number($PageNumber)-1" /></xsl:attribute>
            &lt;&lt;prev</a>
      </xsl:when>
      <xsl:otherwise>
      &#160; &#160; &#160; &#160; &#160;
      </xsl:otherwise>
    </xsl:choose>

    &#160;
    <a>
          <xsl:attribute name="href"><xsl:value-of select="$PageName"/>?action=itemizeFolder&amp;ref=<xsl:value-of select="$FolderName"/>&amp;page=<xsl:value-of select="number($PageNumber)+1" /></xsl:attribute>
           next&gt;&gt;
    </a>
    
    <h2>Folder:<xsl:value-of select="$FolderDecoded"/></h2>
    <h3>User: <xsl:value-of select="$UserName"/><br/>
    as of: <xsl:value-of select="$CurrentTimeFormatted"/></h3>
    <table border='1'> 
    <xsl:apply-templates select="*" />
    </table> 
  </xsl:template>

  <xsl:template match="a:contentrange" />

  <xsl:template match="a:response">

    <tr>
     <xsl:choose>

      <!-- ============================================================ -->
      <xsl:when test="$FolderName = 'Inbox' or $FolderName = 'Sent Items' or $FolderName = 'Deleted Items'  or $FolderName = 'Junk E-mail'  " >
      <td>
        <a>
            <xsl:attribute name="href"><xsl:value-of select="$PageName"/>?action=get&amp;item=<xsl:value-of select="a:href" /></xsl:attribute>
              <xsl:value-of select="a:propstat/a:prop/m:subject" />
        </a> 
        </td>
        <td style="font-size:8pt;">
              <xsl:value-of select="translate(a:propstat/a:prop/m:from, '\\', '')" />
        </td>
        <td>
           <xsl:value-of select="substring(a:propstat/a:prop/m:datereceived,0,11)" />&#160;<xsl:value-of select="substring(a:propstat/a:prop/m:datereceived,12,8)" />
        </td>

      </xsl:when>

      <!-- ============================================================ -->
      <xsl:when test="$FolderName = 'Contacts'">
        <td>
        <a>
            <xsl:attribute name="href"><xsl:value-of select="$PageName"/>?action=get&amp;item=<xsl:value-of select="a:href" /></xsl:attribute>
             <b> <xsl:value-of select="a:propstat/a:prop/c:fileas" /></b>
        </a> 
        </td>
        <td style="font-size:8pt;">
              <xsl:value-of select="a:propstat/a:prop/c:telephoneNumber" />
        </td>
      </xsl:when>

      <!-- ============================================================ -->
      <xsl:otherwise>
      <!-- xsl:when test="$FolderName = 'Notes'"  -->
        <xsl:variable name="ItemName"> 
        <xsl:choose>
          <xsl:when test="substring(a:propstat/a:prop/a:displayname,string-length(a:propstat/a:prop/a:displayname)-3) = '.EML'">
            <xsl:value-of select="substring-before(a:propstat/a:prop/a:displayname,'.EML')" />
          </xsl:when>
          <xsl:otherwise>
            <xsl:value-of select="a:propstat/a:prop/a:displayname" />
          </xsl:otherwise>
        </xsl:choose>
        </xsl:variable>

        <td>
        <a>
            <xsl:attribute name="href"><xsl:value-of select="$PageName"/>?action=get&amp;item=<xsl:value-of select="a:href" /></xsl:attribute>
             <b> <xsl:value-of select="$ItemName" /></b>
        </a> 
        </td>
        <td style="font-size:8pt;">
              <xsl:value-of select="a:propstat/a:prop/a:getlastmodified" />
        </td>
      </xsl:otherwise>
     </xsl:choose>
    </tr>
  </xsl:template>
</xsl:stylesheet>

 

Of course, in the more general case, you're not going to just be displaying the result you got from querying exchange. Instead you will be extracting data and mashing it up with something else. In that case you will want to do XPath or DOM walky stuff on the XML doc. And you know how to do that, right?

All the code is available attached here. I hope you all find it useful!

Just a quick disclaimer before I go: I Really DO NOT recomend that people constructing new apps use WebDAV to connect to Exchange Server. Exchange Server 2007 supports a standards-compliant Web services interface, which is much easier to use than the WebDAV interface. If you have a choice, please consider using the web services option.

Cheers!
-Dino

DavExchange.zip

Comments

  • Anonymous
    July 27, 2008
    PingBack from http://ivanblog.gigazu.com/httpsserverexample.html

  • Anonymous
    July 28, 2008
    Dino, Thank you very much for taking the time to pull this together. It is greatly appreciated. Peter,

  • Anonymous
    July 29, 2008
    Yes, it is possible to Connect to Exchange using JAX-WS The fear and trepidation I had when considering

  • Anonymous
    January 07, 2009
    I've noticed a 302 is issued regardless of the credentials.  Is there a different way for determining when the correct credentials have been sent?

  • Anonymous
    January 09, 2009
    The comment has been removed

  • Anonymous
    January 09, 2009
    I think a 401 is returned when you need to authenticate, and then the next response is... I don't know what.  If it is a 302, whether or not the creds are correct, then I would guess you'd have to distinguish based on the target of the 302 redirection, or perhaps something else.  But it seems like the BASE element is a good one. Sorry I was not of more help but this topic has long been swapped out of my hippocampus at this point! Can you post your code that looked for the BASE ?

  • Anonymous
    January 12, 2009
    I used an open source library called htmlparser (http://htmlparser.sourceforge.net/) to do the actual parsing of the html. Sample code: Parser parser = new Parser(body); NodeList list = parser.parse (new TagNameFilter("BASE")); if(list.size() > 0) {    Node node = list.elementAt(0);    Tag tag = (Tag) node;    if (tag instanceof BaseHrefTag) {        BaseHrefTag baseHref = (BaseHrefTag) tag;        URL url = new URL(baseHref.getBaseUrl());    } } The code will pass the body of the response into the Parser ctor.  Authentication succeeds if a valid href containing the user's mailbox is found in the BASE tag.

  • Anonymous
    March 04, 2009
    Dino, thanks for this code, very useful :) In terms of checking for correct login, I found that this code works for me: //The response from Exchange says redirect whether this is a successful login or not. //Thus, we must follow the redirect to see where it takes us to determine whether we've //logged in successfully or not... if (stat == 302) { Header location = method.getResponseHeader("location"); GetMethod getMethod = new GetMethod(location.getValue()); sb.append("Following redirect to: " + location.getValue()); rc = httpclient.executeMethod(getMethod); stat = getMethod.getStatusLine().getStatusCode(); headers = getMethod.getResponseHeaders(); sb.append("nnSecond call"); sb.append("nStatus: " + Integer.toString(stat) + "n"); for (int x=0; x<headers.length; x++) sb.append(headers[x].toString()); String body = getMethod.getResponseBodyAsString(); sb.append("Body: " + body); //with a successful login we seem to get a 404, with an unsuccessful login we get the login page back. Therefore //let's test for the login page if (body.indexOf("You could not be logged on to Outlook Web Access") > -1) //we have not logged in - return an error throw new Exception("Unable to login"); if (debugRequests) SaveToFile(GetLocalLogPath() + "webDAV_Login_" + accountID + ".txt", sb.toString()); } else throw new Exception("Unexpected HTTP status code during login (" + stat +")"); Seperately from this, I have a problem where Exchange is returning the entire contents of the users email folders, even when I request the Inbox and do a shallow traversal. Hence, I am being returned 1000's of historical emails that do not exist in the Inbox, they are in other folders. Do you have any idea why this would be the case? This is happening on more than 1 server, this is my query:    SELECT       "DAV:id",       "DAV:href",       "urn:schemas:httpmail:subject",       "urn:schemas:httpmail:from",       "urn:schemas:httpmail:datereceived"    FROM SCOPE('shallow traversal of  "https://server/exchange/Clinton/Inbox"')    WHERE "DAV:ishidden"=False AND "DAV:isfolder"=False      ORDER BY "urn:schemas:httpmail:datereceived" DESC AHA, Ben :)

  • Anonymous
    March 06, 2009
    Thanks for the hints, Ben.  Sorry I do not have any insight into your other question!

  • Anonymous
    June 01, 2009
    hi ,   thx for this great information, i have done all these tasks like email fetch,contacts,and sent items,but u havnt mention here how to send email using it or how to add contact ?? plz give me a simple example for sending mail    

  • Anonymous
    June 16, 2009
    I don't have a Java example handy.  But the wire protocol is just WebDAV.  If you can translate VB to Java, this article might help: http://blogs.msdn.com/vikas/archive/2009/05/22/howto-webdav-send-mail-with-custom-form-and-set-properties.aspx

  • Anonymous
    April 05, 2011
    Can you please share the difference between webdav implementation Exchange 2003 to 2007. I tried exchange 2003 webdav code for exchange 2007, It doesn't work. I tried Login to 2007 using URL http://server/owa  it works. But extracting mails or meeting details doesn't work. Please share you knowledge if you come across similar issue Thanks

  • Anonymous
    May 02, 2011
    Hey, @Cheeso excellent post! very helpful... I have a question that this a step further.. for my purpose i am trying to achieve single sign-on in a windows environment in the context of a java application.. for this implementation I understand that we need the email address of the user we are trying to access, hence will need some mechanism to determine the email address of the logged in user somehow.. any idea? Has anyone tried migrating this to Apache HTTP Client 4.x? @Subbu drop me an email at krehman@gmail.com I can send you my code that works for both 2003/2007 servers using webDAV.. thanks