Consuming Exchange Web Services with Java using Glassfish and Metro: Using ExchangeServicePortType with Authentication Credentials

In my first article, Consuming Exchange Web Services with Java using Glassfish and Metro: Creating the Web Service Client, we left off having created a corrected exchange.wsdl, messages.xsd, and types.xsd files. From those files, we generated the web service client and copied the .java source files from the build into our project's source directory. In this article you will clean up a few loose ends from the generation of the Exchange Web Service stubs and create a method to return an ExchangeServicePortType bound with authentication credentials.

Correct Web Service Reference

The first thing you need to do is correct some duplication we've created in the project. With my previous article I had the reader copy the .java source files generated by Exchange's WSDL and drop them into the source folder of the NetBeans project. We also still have NetBeans configured with a “Web Service Reference” named “exchange”. If we were to fast forward into our development a bit, NetBeans defaults to use the “Web Service Reference” class files that are generated instead of the source packages we copied. When you make web service requests for findItem(...), findFolder(...), or any of the other methods in ExchangeServicePortType, these methods require that ExchangeImpersonationType and SerializedSecurityContextType objects be passed to them. In most cases we will not need either of those objects so normally we're supposed to pass them null values. In my experience, and despite scouring the Internet for information I have not been able to get around the xsi:nil attribute error that is thrown.

Exception in thread "main" javax.xml.ws.soap.SOAPFaultException: The request failed schema validation: If the 'nillable' attribute is false in the schema, the 'xsi:nil' attribute must not be present in the instance.
        at com.sun.xml.ws.fault.SOAP11Fault.getProtocolException(SOAP11Fault.java:187)
        at com.sun.xml.ws.fault.SOAPFaultBuilder.createException(SOAPFaultBuilder.java:116)
        at com.sun.xml.ws.client.sei.SyncMethodHandler.invoke(SyncMethodHandler.java:254)
        at com.sun.xml.ws.client.sei.SyncMethodHandler.invoke(SyncMethodHandler.java:224)
        at com.sun.xml.ws.client.sei.SEIStub.invoke(SEIStub.java:117)
        at $Proxy40.findItem(Unknown Source)

I found a bit of discussion on this topic over at the Microsoft TechNet forums but they seem largely frustrated as well. The developers at Metro attribute the inconsistency to the .Net framework incorrectly enforcing Basic Profile v1.1 compliance. Based on my experience with Microsoft, I'm going to trust the Metro folks and blame it on the .Net framework. Nonetheless it's kind of annoying to deal with. In my series I will advise method overloading ExchangeServicePortType to work around the xsi:nil errors. We will not get into this method overloading until later articles, but I wanted to briefly address how I propose getting around the xsi:nil error.

First, because in my last article I advised copying the generated .java files rather than using NetBeans Web Service client context menus, we must remove the “Web Service Reference” from NetBeans so that it uses our source packages instead. To do this:

  1. Using the “Projects” navigation along the left-hand side open your project. → Expand the “Web Service References” category.
  2. You should see a client named “exchange” listed. Right-click on it and delete it.
  3. Now right-click on your project and select “Clean and Build”.

All of the references to this web service client have now been removed from NetBeans and it will use our source packages to communicate with the Exchange server now.

Deploy your exchange.wsdl

The second bit of house cleaning needed is to deploy the corrected exchange.wsdl, messages.xsd, and types.xsd files to Glassfish. So even though there isn't much developed yet, deploy the project out to Glassfish:

  1. Right-click on your project and select “Run”
  2. After deploying visit: http://localhost:8080/[Your Project Name]/exchange.wsdl

You should see the WSDL file. In Firefox sometimes I've noticed it'll display it as a blank page. If you view the source all the WSDL XML should be there. Now that it's deployed, leave the server up. The SOAP requests depend on that file being available.

Write a class to bind authentication credentials to ExchangeServicePortType

Finally, we're ready to start writing some Java to interact with Exchange. This assumes that you have an account on the Exchange environment you're developing against and need to authenticate. I was originally pointed down the path of using java.net.Authenticator which worked, sort of, but it authenticated the entire application with my credentials, which is obviously not desirable for a web application environment. You'll see that I've followed up with some appropriate notes at the forum topic :)

To get started, let's write an ExchangeAuthenticator class. A method in that class will return a ExchangeServicePortType with authentication credentials. I think the clearest way to explain this is through some well-commented code.

import com.microsoft.schemas.exchange.services._2006.messages.ExchangeServicePortType;
import com.microsoft.schemas.exchange.services._2006.messages.ExchangeWebService;
import java.net.MalformedURLException;
import java.net.URL;
import javax.xml.namespace.QName;
import javax.xml.ws.BindingProvider;

/**
 * This class manages web services authentication requests to the Exchange
 * server. 
 * 
 * @author Reid Miller
 */
public class ExchangeAuthenticator {	
    /**
     * Obtains an authenticated ExchangeServicePortType with given credentials.
     * 
     * See https://jax-ws.dev.java.net/faq/index.html#auth
     * 
     * @param username
     * @param password
     * @param domain 
     * @param wsdlURL
     * @return ExchangeServicePortType
     * @throws MalformedURLException 
     */
    public ExchangeServicePortType getExchangeServicePort(String username, String password, String domain, URL wsdlURL) throws MalformedURLException {
    	// Concatinate our domain and username for the UID needed in authentication.
    	String uid = domain + "\\" + username;

        // Create an ExchangeWebService object that uses the supplied WSDL file, wsdlURL.
        ExchangeWebService exchangeWebService = new ExchangeWebService(wsdlURL, new QName("http://schemas.microsoft.com/exchange/services/2006/messages", "ExchangeWebService"));
        ExchangeServicePortType port = exchangeWebService.getExchangeWebPort();
        // Supply your username and password when the ExchangeServicePortType is used for binding in the SOAP request.
        ((BindingProvider)port).getRequestContext().put(BindingProvider.USERNAME_PROPERTY, uid);
        ((BindingProvider)port).getRequestContext().put(BindingProvider.PASSWORD_PROPERTY, password);
        
        return port;
    }
}

Next, create a throw-away class with a main method for testing ExchangeAuthenticator.

import com.microsoft.schemas.exchange.services._2006.messages.ExchangeServicePortType;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Throw-away class for doing quick tests of features and classes during 
 * development.
 * 
 * @author Reid Miller
 */
public class ExchangeDevelopmentTest {
    /**
     * Main method so we can quickly test things.
     * @param args
     */
    public static void main (String[] args) {
        ExchangeAuthenticator exchangeAuthenticator = new ExchangeAuthenticator();

        // Print statement so we can easily see where our statements start in the Java console.
        System.out.println("Let's get started!");

        try {
            // Create a URL object which points at the .wsdl we deployed in the previous step.
            URL wsdlURL = new URL("http://localhost:8080/[Your Project Name]/exchange.wsdl");
            // Call to the class we just created to return an ExchangeServicePortType with authentication credentials.
            ExchangeServicePortType port = exchangeAuthenticator.getExchangeServicePort("username", "password", "domain", wsdlURL);

            // Prints out the default toString() for the ExchangeServicePortType.
            System.out.println(port.toString());
        } catch (MalformedURLException ex) {
            // Catch any errors that may occur.
            Logger.getLogger(ExchangeDevelopmentTest.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
}

Run your work

Let's run our work!

  1. Using the “Projects” column on the left-hand side, find the package you created ExchangeDevelopmentTest in. Right-click on ExchangeDevelopmentTest.java and select “Run”.

In the Java console below you should see something similar to the following message if all went well:

init:
deps-module-jar:
deps-ear-jar:
deps-jar:
Compiling 1 source file to /home/oreomasta/ExchangeWebServices/build/web/WEB-INF/classes
compile-single:
run-main:
Let's get started!
JAX-WS RI 2.1.2_01-hudson-189-: Stub for https://mail.example.com/EWS/exchange.asmx?wsdl
BUILD SUCCESSFUL (total time: 2 seconds)

Conclusion

It wasn't much but you're now writing some Java to interact with Exchange Web Services. In my next articles I will be going over the many methods in ExchangeServicePortType for doing work on Exchange. Many of the articles will simply be translations of Microsoft's C# and .Net examples out at MSDN.

Revisions

I've updated this article a few times as I receive user comments and look into questions further:

  • Jan 16, 2009: Updated the "Correct Web Service Reference" section to elaborate a bit on the xsi:nil error, and I included a link to the Metro documentation for the developers' explanation.

16 Comments

Exchange Web Service, please help me

Hello Reid,
I’m a student writing in the moment my Bachelor thesis. Within this thesis I’m trying to use the Web services offered by Microsoft Exchange 2007. I have special interest to use the Availability Service. I tried to follow your example but till now I didn’t had any success with that. It is getting really frustrating now, I just spend nearly the whole week with different solutions I found in the Internet.
Could you please send me a full working example.
Or is there any possibility to disable SSL for the Web service connection?
Thanks a lot!

Felix

Doubt about a Exception

First of all, congratulations for the article!

I am following it and I am getting the following answer:

Let's get started!
JAX-WS RI 2.1.3.1-hudson-417-SNAPSHOT: Stub for https://myserver.com/EWS/Services.wsdl
Exception in thread "main" com.sun.xml.ws.client.ClientTransportException: The server sent HTTP status code 405: Method Not Allowed

Do you what it can be?
Thanks in advance!

did you change the wsdl file on exchange?

Thanks to your instructions I'm almost there.
I had things working when I referenced the original WSDL file - up until the point where I rebuild and it erased all my modifications in ExchangeServicePortType - you warned against that and I wouldn't listen.
In order to prevent this from happening I used the local copy of the WSDL and XSD and re-added the web service reference.

That's all good except now in

ExchangeWebService exchangeWebService = new ExchangeWebService(wsdlURL, new QName("http://schemas.microsoft.com/exchange/services/2006/messages", "ExchangeWebService"));

JAXWS craps out - since my webservice reference is an offline reference, it somehow want so validate the schema before proceeding.. so it loads the WSDL and notes that there's no service element and aborts right there.

How did you manage to get past that? If I initialize as

ExchangeWebService exchangeWebService = new ExchangeWebService();

that works but then it uses the address that I put in the WSDL file.. and we're supposed to use autodiscovery to get the appropriate endpoint so this will only work until I work in a multi CAS server environment where I'm supposed to use another server for a particular mailbox.

Authenticated ONLY with my credentials

Hi Reid,

Thanks for info about CXF. I am able to read my own email when I run client from my desktop, but even if I provide other user credentials and running on my desktop I am still reading my own emails. Infact even if I do not provide my credentails , I am able to read my emails. It looks like httpclient somehow getting my credentials from my desktop.

Appreciate your help on this.

thanks
Kumar

Manuel_B's picture

How to use NTLM and ignore bad SSL certificates

Hi,
great article helped me a lot. Here are two hints just in case your Exchange server requires NTLM or has an invalid certificate:

TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {

public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}

public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {
//No need to implement.
}

public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) {
//No need to implement.
}
}
};

// Let us create the factory where we can set some parameters for the connection
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, trustAllCerts, new java.security.SecureRandom());

SSLSocketFactory socketFactory =sc.getSocketFactory();

((BindingProvider)serviceBinding).getRequestContext().put("com.sun.xml.internal.ws.transport.https.client.SSLSocketFactory", socketFactory);

Holder findItemResult = new Holder();
Holder serverVersion = new Holder();

// Make NTLM work
Authenticator.setDefault(new Authenticator() {
public PasswordAuthentication getPasswordAuthentication () {
return new PasswordAuthentication ("DOMAIN\\your.username", "password".toCharArray());
}
});

Clarification on who delivers the SOAP request

Hi Reid,

Thanks for this great tutorial. We are in the process of building a Java application to interact with Microsoft Exchange. This series of articles of yours will be very helpful to us since we are new to web service.

I have a clarification. You have stated that the SOAP request depends on the "exchange.wsdl" being available. Hence, the server should always be up.

Example: http://localhost:8080/[Your Project Name]/exchange.wsdl

My question is does the SOAP request is submitted by the running server (i.e. http://localhost:8080/[Your Project Name] ) to Microsoft Exchange? Or the SOAP request is directly submitted by the Java application (i.e. ExchangeDevelopmentTest)?

Though it is clear that you mentioned "Create an ExchangeWebService object that uses the supplied WSDL file, wsdlURL."

I just want to clarify who delivers the SOAP request and who accepts the SOAP response?

Again, thank you very much.