Package com.ripariandata.timberwolf.mail.exchange

Source Code of com.ripariandata.timberwolf.mail.exchange.ExchangeService

/**
* Copyright 2012 Riparian Data
* http://www.ripariandata.com
* contact@ripariandata.com
*
* Licensed 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.ripariandata.timberwolf.mail.exchange;

import com.microsoft.schemas.exchange.services.x2006.messages.FindFolderResponseType;
import com.microsoft.schemas.exchange.services.x2006.messages.FindFolderType;
import com.microsoft.schemas.exchange.services.x2006.messages.GetItemResponseType;
import com.microsoft.schemas.exchange.services.x2006.messages.GetItemType;
import com.microsoft.schemas.exchange.services.x2006.messages.SyncFolderItemsResponseType;
import com.microsoft.schemas.exchange.services.x2006.messages.SyncFolderItemsType;

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.nio.charset.Charset;

import org.apache.xmlbeans.XmlException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xmlsoap.schemas.soap.envelope.BodyType;
import org.xmlsoap.schemas.soap.envelope.EnvelopeDocument;
import org.xmlsoap.schemas.soap.envelope.EnvelopeType;

import static com.ripariandata.timberwolf.Utilities.inputStreamToString;

/**
* ExchangeService handles packing xmlbeans objects into a SOAP envelope,
* sending them off to the Exchange server and then returning the xmlbeans
* objects that come back.
*
* Note that all the service calls are performed synchronously.
*/
public class ExchangeService
{
    private static final Logger LOG = LoggerFactory.getLogger(ExchangeService.class);

    private static final String DECLARATION = "<?xml version=\"1.0\" encoding=\"utf-8\"?>";
    private static final String SOAP_ENCODING = "UTF-8";

    private String endpoint;
    private HttpUrlConnectionFactory connectionFactory;

    public ExchangeService(final String url, final HttpUrlConnectionFactory factory)
    {
        endpoint = url;
        connectionFactory = factory;
    }

    /**
     * Creates a new ExchangeService that talks to the given Exchange server.
     *
     * @param url A string representing the URL of the service endpoint for the Exchange server.
     */
    public ExchangeService(final String url)
    {
        this(url, new SaslHttpUrlConnectionFactory());
    }

    /**
     * Sends a SOAP envelope request and returns the response.
     *
     *
     * @param envelope An EnvelopeDocument with the SOAP envelope to send to Exchange.
     * @return An SOAP body from the Exchange's response.
     * @throws HttpErrorException If the HTTP response from Exchange has a non-200 status code.
     * @throws ServiceCallException If there was a non-HTTP error sending the response,
     *                              such as an improper encoding or IO error.
     */
    private BodyType sendRequest(final EnvelopeDocument envelope)
        throws HttpErrorException, ServiceCallException
    {
        String request = DECLARATION + envelope.xmlText();
        LOG.trace("Sending SOAP request to {}.  SOAP envelope:", endpoint);
        LOG.trace(envelope.toString());

        HttpURLConnection conn = createConnection(request);
        int code = getResponseCode(conn);

        String charset = getCharset(conn);

        InputStream responseData = getInputStream(conn);

        int amtAvailable = getAmountAvailable(responseData);

        if (code == HttpURLConnection.HTTP_OK)
        {
            checkNonEmptyResponse(request, amtAvailable);

            EnvelopeDocument response = parseResponse(responseData, charset);
            LOG.trace("SOAP response received from {}.  SOAP envelope:", endpoint);
            LOG.trace(response.toString());
            return getSoapBody(response);
        }
        else
        {
            return logAndThrowHttpErrorCode(request, code, responseData, amtAvailable, charset);
        }
    }

    /**
     * If for whatever reason we fail to get the charset, this logs that fact,
     * and returns UTF-8.
     * @param connection the connection with a response
     * @return the character encoding for that response
     */
    private static String getCharset(final HttpURLConnection connection)
    {
        final String defaultCharset = "UTF-8";
        String contentType = connection.getHeaderField("Content-Type");
        if (contentType == null)
        {
            LOG.debug("Error getting charset for response, no Content-Type specified, falling back to \"{}\"",
                      contentType, defaultCharset);
            return defaultCharset;
        }
        for (String part : contentType.replace(" ", "").split(";"))
        {
            if (part.startsWith("charset="))
            {
                String charset = part.split("=", 2)[1].trim();
                if (Charset.isSupported(charset))
                {
                    return charset;
                }
                else
                {
                    LOG.debug("Error getting charset from Content-Type: \"{}\", falling back to \"{}\"", contentType,
                              defaultCharset);
                    LOG.debug("Charset: \"{}\" is not supported", charset);
                    return defaultCharset;
                }
            }
        }
        LOG.debug("Could not find charset in Content-Type: \"{}\", falling back to \"{}\"", contentType,
                  defaultCharset);
        return defaultCharset;
    }

    private BodyType logAndThrowHttpErrorCode(final String request, final int code, final InputStream responseData,
                                              final int amtAvailable, final String charset)
            throws ServiceCallException, HttpErrorException
    {
        LOG.error("Server responded with HTTP error code {}.", code);
        if (!LOG.isTraceEnabled())
        {
            LOG.debug("Request that generated the error:");
            LOG.debug(request);
        }

        if (amtAvailable > 0)
        {
            LOG.debug("Error response body:");
            try
            {
                LOG.debug(inputStreamToString(responseData, charset));
            }
            catch (IOException ioe)
            {
                throw ServiceCallException.log(LOG, new ServiceCallException(ServiceCallException.Reason.OTHER,
                                "Error reading from the response stream.", ioe));
            }
        }

        throw new HttpErrorException(code);
    }

    /**
     * Extracts the soap body from the response.
     * @param response the response from exchange
     * @return the soap body from the response
     * @throws ServiceCallException if there was an error extracting the body.
     * The error will be logged.
     */
    private BodyType getSoapBody(final EnvelopeDocument response) throws ServiceCallException
    {
        BodyType body = response.getEnvelope().getBody();
        if (body != null)
        {
            return body;
        }
        else
        {
            LOG.error("SOAP envelope did not contain a valid body.");
            if (!LOG.isTraceEnabled())
            {
                LOG.debug("SOAP envelope:");
                LOG.debug(response.xmlText());
            }
            throw new ServiceCallException(ServiceCallException.Reason.OTHER,
                                           "SOAP response did not contain a body.");
        }
    }

    private void checkNonEmptyResponse(final String request, final int amtAvailable) throws ServiceCallException
    {
        if (amtAvailable == 0)
        {
            LOG.error("HTTP response was successful, but has no data.");
            if (!LOG.isTraceEnabled())
            {
                LOG.debug("Request that generated the empty response:");
                LOG.debug(request);
            }
            throw new ServiceCallException(ServiceCallException.Reason.OTHER, "Response has empty body.");
        }
    }

    private EnvelopeDocument parseResponse(final InputStream responseData, final String charset)
            throws ServiceCallException
    {
        EnvelopeDocument response;
        try
        {
            response = EnvelopeDocument.Factory.parse(responseData);
        }
        catch (IOException e)
        {
            throw ServiceCallException.log(LOG, new ServiceCallException(ServiceCallException.Reason.OTHER,
                    "There was an error reading from the response stream.", e));
        }
        catch (XmlException e)
        {
            LOG.error("There was an error parsing the SOAP response from Exchange.");
            LOG.debug("Response body:");
            try
            {
                LOG.debug(inputStreamToString(responseData, charset));
                throw new ServiceCallException(ServiceCallException.Reason.OTHER, "Error parsing SOAP response.", e);
            }
            catch (IOException ioe)
            {
                throw ServiceCallException.log(LOG, new ServiceCallException(ServiceCallException.Reason.OTHER,
                        "There was an error reading from the response stream.", ioe));
            }
        }
        return response;
    }

    private int getAmountAvailable(final InputStream responseData) throws ServiceCallException
    {
        int amtAvailable;
        try
        {
            amtAvailable = responseData.available();
        }
        catch (IOException e)
        {
            throw ServiceCallException.log(LOG, new ServiceCallException(ServiceCallException.Reason.OTHER,
                    "There was an error reading from the response stream."));
        }
        return amtAvailable;
    }

    private InputStream getInputStream(final HttpURLConnection conn) throws ServiceCallException
    {
        InputStream responseData;
        try
        {
           responseData = conn.getInputStream();
        }
        catch (IOException e)
        {
            throw new ServiceCallException(ServiceCallException.Reason.OTHER,
                    "There was an error getting the input stream for the response.", e);
        }
        return responseData;
    }

    private int getResponseCode(final HttpURLConnection conn) throws ServiceCallException
    {
        int code;
        try
        {
            code = conn.getResponseCode();
        }
        catch (IOException e)
        {
            throw ServiceCallException.log(LOG, new ServiceCallException(ServiceCallException.Reason.OTHER,
                    "There was an error getting the HTTP status code for the response.", e));
        }
        return code;
    }

    private HttpURLConnection createConnection(final String request) throws ServiceCallException
    {
        HttpURLConnection conn;
        try
        {
            conn = connectionFactory.newInstance(endpoint, request.getBytes(SOAP_ENCODING));
        }
        catch (UnsupportedEncodingException e)
        {
            throw ServiceCallException.log(LOG, new ServiceCallException(ServiceCallException.Reason.OTHER,
                    "Request body could not be encoded into " + SOAP_ENCODING, e));
        }
        return conn;
    }

    EnvelopeDocument createEmptyRequest(final String targetUser)
    {
        EnvelopeDocument request = EnvelopeDocument.Factory.newInstance();
        EnvelopeType envelope = request.addNewEnvelope();
        envelope.addNewHeader().addNewExchangeImpersonation().addNewConnectingSID().setPrincipalName(targetUser);
        return request;
    }

    /**
     * Returns the result of a sync folder items request.
     * @param syncFolderItems A SyncFolderItemsType object that specifies the folder to sync.
     * @param targetUser The principal name of the user to find items for.
     * @return A SyncFolderItemsResponseType object with the requested items.
     * @throws ServiceCallException If the HTTP response from Exchange has a non-200 status code.
     * @throws HttpErrorException If there was a non-HTTP error sending the response,
     *                            such as an improper encoding or IO error.
     */
    public SyncFolderItemsResponseType syncFolderItems(final SyncFolderItemsType syncFolderItems,
                                                       final String targetUser)
            throws ServiceCallException, HttpErrorException
    {
        EnvelopeDocument request = createEmptyRequest(targetUser);
        EnvelopeType envelope = request.getEnvelope();
        envelope.addNewBody().setSyncFolderItems(syncFolderItems);

        return sendRequest(request).getSyncFolderItemsResponse();
    }

    /**
     * Returns the results of a get item request.
     *
     * @param getItem A GetItemType object that specifies the set of items to
     *                gather from the Exchange server.
     * @param targetUser The principal name of the user to get items for.
     * @return A GetItemResponseType object with the requested items.
     * @throws HttpErrorException If the HTTP response from Exchange has a non-200 status code.
     * @throws ServiceCallException If there was a non-HTTP error sending the response,
     *                              such as an improper encoding or IO error.
     */
    public GetItemResponseType getItem(final GetItemType getItem, final String targetUser)
        throws ServiceCallException, HttpErrorException
    {
        EnvelopeDocument request = createEmptyRequest(targetUser);
        EnvelopeType envelope = request.getEnvelope();
        envelope.addNewBody().setGetItem(getItem);

        return sendRequest(request).getGetItemResponse();
    }

    /**
     * Returns the response of a FindFolder request.
     * @param findFolder The FindFolder request,
     * @param targetUser The principal name of the user to find folders for.
     * @return The response.
     * @throws ServiceCallException A non-HTTP error has occurred during the request.
     * @throws HttpErrorException A HTTP error has occurred during the request.
     */
    public FindFolderResponseType findFolder(final FindFolderType findFolder, final String targetUser)
        throws ServiceCallException, HttpErrorException
    {
        EnvelopeDocument request = createEmptyRequest(targetUser);
        EnvelopeType envelope = request.getEnvelope();
        envelope.addNewBody().setFindFolder(findFolder);

        return sendRequest(request).getFindFolderResponse();
    }
}
TOP

Related Classes of com.ripariandata.timberwolf.mail.exchange.ExchangeService

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.