/**
* See the NOTICE file distributed with this work
* for additional information regarding copyright ownership.
* Board of Regents of the University of Wisconsin System
* licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a
* copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.microsoft.exchange.autodiscover;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.JAXBElement;
import javax.xml.namespace.QName;
import javax.xml.transform.TransformerException;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.oxm.Marshaller;
import org.springframework.ws.WebServiceMessage;
import org.springframework.ws.client.core.WebServiceMessageCallback;
import org.springframework.ws.client.core.WebServiceOperations;
import org.springframework.ws.soap.SoapMessage;
import org.springframework.ws.soap.client.core.SoapActionCallback;
import org.springframework.xml.transform.StringResult;
import com.microsoft.exchange.exception.AutodiscoverException;
import com.microsoft.exchange.exception.ExchangeWebServicesRuntimeException;
import com.microsoft.exchange.exception.SoapAutodiscoverException;
/**
* An autodiscover implementation that queries all potential SOAP
* autodiscover endpoints for a given email address
*
* @see <a
* href="http://msdn.microsoft.com/EN-US/library/office/ee332364(v=exchg.140).aspx">Implementing
* an Autodiscover Client in Microsoft Exchange</a>
*
* @author ctcudd
*
*/
public class SoapAutodiscoverServiceImpl extends AbstractExchangeAutodiscoverService{
protected final static String AUTODISCOVER_SCHEMA = "http://schemas.microsoft.com/exchange/autodiscover/outlook/requestschema/2006";
protected final static String AUTODISCOVER_RESPONSE_SCHEMA = "http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a";
protected final static QName REQUEST_SERVER_VERSION_QNAME = new QName(
"http://schemas.microsoft.com/exchange/2010/Autodiscover", "RequestedServerVersion", "a");
protected final static QName SOAP_ACTION_HEADER_QNAME = new QName("http://www.w3.org/2005/08/addressing", "Action", "wsa");
protected final static String SOAP_ACTION_BASE = "http://schemas.microsoft.com/exchange/2010/Autodiscover/Autodiscover/";
protected final static String GET_USER_SETTINGS_ACTION = SOAP_ACTION_BASE + "GetUserSettings";
private final static String INTERNAL_EWS_SERVER = "InternalEwsUrl";
private final static String EXTERNAL_EWS_SERVER = "ExternalEwsUrl";
private final static String ENDPOINT_SUFFIX = "svc";
@Override
public String getServiceSuffix(){
return ENDPOINT_SUFFIX;
}
private final ObjectFactory objectFactory = new ObjectFactory();
@Autowired
@Qualifier("autodiscoverWebServiceTemplate")
private WebServiceOperations webServiceOperations;
public WebServiceOperations getWebServiceOperations() {
return webServiceOperations;
}
public void setWebServiceOperations(WebServiceOperations webServiceOperations) {
this.webServiceOperations = webServiceOperations;
}
private Marshaller marshaller;
public Marshaller getMarshaller() {
return marshaller;
}
public void setMarshaller(Marshaller marshaller) {
this.marshaller = marshaller;
}
private String parseGetUserSettingsResponse( GetUserSettingsResponseMessage response) throws SoapAutodiscoverException {
GetUserSettingsResponse soapResponse = response.getResponse().getValue();
UserSettings userSettings = null;
boolean warning = false;
boolean error = false;
StringBuilder msg = new StringBuilder();
if (!ErrorCode.NO_ERROR.equals(soapResponse.getErrorCode())) {
error = true;
msg.append("Error: ").append(soapResponse.getErrorCode().value())
.append(": ").append(soapResponse.getErrorMessage().getValue()).append("\n");
} else {
JAXBElement<ArrayOfUserResponse> JAXBresponseArray = soapResponse.getUserResponses();
ArrayOfUserResponse responseArray = JAXBresponseArray != null ? JAXBresponseArray.getValue() : null;
List<UserResponse> responses = responseArray != null ? responseArray.getUserResponses() : new ArrayList<UserResponse>();
if (responses.size() == 0) {
error = true;
msg.append("Error: Autodiscovery returned no Exchange mail server for mailbox");
} else if (responses.size() > 1) {
warning = true;
msg.append("Warning: Autodiscovery returned multiple responses for Exchange server mailbox query");
} else {
UserResponse userResponse = responses.get(0);
if (!ErrorCode.NO_ERROR.equals(userResponse.getErrorCode())) {
error = true;
msg.append("Received error message obtaining user mailbox's server. Error "
+ userResponse.getErrorCode().value() + ": " + userResponse.getErrorMessage().getValue());
}
userSettings = userResponse.getUserSettings().getValue();
}
}
if (warning || error) {
throw new SoapAutodiscoverException("Unable to perform soap operation; try again later. Message text: "
+ msg.toString());
}
//return userSettings;
//UserSettings userSettings = sendMessageAndExtractSingleResponse(getUserSettingsSoapMessage, GET_USER_SETTINGS_ACTION);
//Give preference to Internal URL over External URL
String internalUri = null;
String externalUri = null;
for (UserSetting userSetting : userSettings.getUserSettings()) {
String potentialAccountServiceUrl = ((StringSetting) userSetting).getValue().getValue();
if (EXTERNAL_EWS_SERVER.equals(userSetting.getName())) {
externalUri = potentialAccountServiceUrl;
}
if (INTERNAL_EWS_SERVER.equals(userSetting.getName())) {
internalUri = potentialAccountServiceUrl;
}
}
if (internalUri == null && externalUri == null) {
throw new ExchangeWebServicesRuntimeException("Unable to find EWS Server URI in properies "
+ EXTERNAL_EWS_SERVER + " or " + INTERNAL_EWS_SERVER + " from User's Autodiscover record");
}
return internalUri != null ? internalUri : externalUri;
}
private GetUserSettingsRequestMessage createGetUserSettingsSoapMessage(String emailAddress) {
GetUserSettingsRequest msg = objectFactory.createGetUserSettingsRequest();
User user = objectFactory.createUser();
user.setMailbox(emailAddress);
Users users = objectFactory.createUsers();
users.getUsers().add(user);
msg.setUsers(users);
msg.setRequestedVersion(ExchangeVersion.EXCHANGE_2010);
RequestedSettings settings = objectFactory.createRequestedSettings();
settings.getSettings().add(EXTERNAL_EWS_SERVER);
settings.getSettings().add(INTERNAL_EWS_SERVER);
msg.setRequestedSettings(settings);
// Construct the SOAP request object to use
GetUserSettingsRequestMessage request = objectFactory.createGetUserSettingsRequestMessage();
request.setRequest(objectFactory.createGetUserSettingsRequestMessageRequest(msg));
return request;
}
/**
*
* @param soapRequest
* @param soapAction
* @param uri
* @return
*/
private GetUserSettingsResponseMessage getUserSettings(String uri, GetUserSettingsRequestMessage soapRequest, final String soapAction) {
GetUserSettingsResponseMessage response = null;
final WebServiceMessageCallback actionCallback = new SoapActionCallback(
soapAction);
final WebServiceMessageCallback customCallback = new WebServiceMessageCallback() {
@Override
public void doWithMessage(WebServiceMessage message) throws IOException, TransformerException {
actionCallback.doWithMessage(message);
SoapMessage soap = (SoapMessage) message;
soap.getEnvelope().getHeader().addHeaderElement(REQUEST_SERVER_VERSION_QNAME)
.setText(ExchangeVersion.EXCHANGE_2010.value());
soap.getEnvelope().getHeader().addHeaderElement(SOAP_ACTION_HEADER_QNAME).setText(soapAction);
}
};
if (log.isDebugEnabled()) {
StringResult message = new StringResult();
try {
marshaller.marshal(soapRequest, message);
log.debug("Attempting to send SOAP request to "+uri+"\nSoap Action: "+soapAction+"\nSoap message body"
+ " (not exact, log org.apache.http.wire to see actual message):\n"+ message);
} catch (IOException ex) {
log.debug("IOException attempting to display soap response", ex);
}
}
// use the request to retrieve data from the Exchange server
try{
response = (GetUserSettingsResponseMessage) webServiceOperations.marshalSendAndReceive(uri, soapRequest, customCallback);
}catch(Exception e){
log.warn("getUserSettings for uri="+uri+" failed: "+e.getMessage());
}
if (log.isDebugEnabled()) {
StringResult messageResponse = new StringResult();
try {
marshaller.marshal(response, messageResponse);
log.debug("Soap response body (not exact, log org.apache.http.wire to see actual message):\n"+messageResponse );
} catch (IOException exception) {
log.debug("IOException attempting to display soap response", exception);
}
}
return response;
}
@Override
public String getAutodiscoverEndpoint(String email) throws AutodiscoverException {
String ewsUrl = null;
GetUserSettingsRequestMessage request = createGetUserSettingsSoapMessage(email);
for(String potential : getPotentialAutodiscoverEndpoints(email)){
log.info("attempting soap autodiscover for email="+email+" uri="+potential);
GetUserSettingsResponseMessage response = null;
try {
response = getUserSettings(potential, request, GET_USER_SETTINGS_ACTION);
if(null != response){
ewsUrl = parseGetUserSettingsResponse(response);
if(StringUtils.isNotBlank(ewsUrl))
return ewsUrl;
}
} catch (Exception e) {
log.warn("caught exception while attempting SOAP autodiscover : "+e.getMessage());
}
}
throw new SoapAutodiscoverException("SOAP autodiscover failed. cannot find ewsurl for email="+email);
}
}