All Downloads are FREE. Search and download functionalities are using the official Maven repository.

microsoft.exchange.webservices.data.core.request.HangingServiceRequestBase Maven / Gradle / Ivy

/*
 * The MIT License
 * Copyright (c) 2012 Microsoft Corporation
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package microsoft.exchange.webservices.data.core.request;

import microsoft.exchange.webservices.data.core.EwsServiceMultiResponseXmlReader;
import microsoft.exchange.webservices.data.core.EwsServiceXmlReader;
import microsoft.exchange.webservices.data.core.ExchangeService;
import microsoft.exchange.webservices.data.core.enumeration.misc.HangingRequestDisconnectReason;
import microsoft.exchange.webservices.data.core.enumeration.misc.TraceFlags;
import microsoft.exchange.webservices.data.core.exception.http.EWSHttpException;
import microsoft.exchange.webservices.data.core.exception.misc.ArgumentException;
import microsoft.exchange.webservices.data.core.exception.service.local.ServiceVersionException;
import microsoft.exchange.webservices.data.core.exception.service.local.ServiceXmlDeserializationException;
import microsoft.exchange.webservices.data.core.exception.service.remote.ServiceRequestException;
import microsoft.exchange.webservices.data.core.exception.xml.XmlException;
import microsoft.exchange.webservices.data.misc.HangingTraceStream;
import microsoft.exchange.webservices.data.security.XmlNodeType;
import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import javax.xml.stream.XMLStreamException;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectStreamException;
import java.net.SocketTimeoutException;
import java.net.UnknownServiceException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;


/**
 * Represents an abstract, hanging service request.
 */
public abstract class HangingServiceRequestBase extends ServiceRequestBase {

  private static final Log LOG = LogFactory.getLog(HangingServiceRequestBase.class);


  public interface IHandleResponseObject {

    /**
     * Callback delegate to handle asynchronous response.
     *
     * @param response Response received from the server
     * @throws ArgumentException
     */
    void handleResponseObject(Object response) throws ArgumentException;
  }


  public static final int BUFFER_SIZE = 4096;

  /**
   * Test switch to log all bytes that come across the wire.
   * Helpful when parsing fails before certain bytes hit the trace logs.
   */
  private static volatile boolean logAllWireBytes = false;

  /**
   * Callback delegate to handle response objects
   */
  private IHandleResponseObject responseHandler;

  /**
   * Response from the server.
   */
  private HttpWebRequest response;

  /**
   * Expected minimum frequency in response, in milliseconds.
   */
  protected int heartbeatFrequencyMilliseconds;


  public interface IHangingRequestDisconnectHandler {

    /**
     * Delegate method to handle a hanging request disconnection.
     *
     * @param sender the object invoking the delegate
     * @param args event data
     */
    void hangingRequestDisconnectHandler(Object sender,
        HangingRequestDisconnectEventArgs args);

  }


  public static boolean isLogAllWireBytes() {
    return logAllWireBytes;
  }

  public static void setLogAllWireBytes(final boolean logAllWireBytes) {
    HangingServiceRequestBase.logAllWireBytes = logAllWireBytes;
  }

  /**
   * Disconnect events Occur when the hanging request is disconnected.
   */
  private List onDisconnectList =
      new ArrayList();

  /**
   * Set event to happen when property disconnect.
   *
   * @param disconnect disconnect event
   */
  public void addOnDisconnectEvent(IHangingRequestDisconnectHandler disconnect) {
    onDisconnectList.add(disconnect);
  }

  /**
   * Remove the event from happening when property disconnect.
   *
   * @param disconnect disconnect event
   */
  protected void removeDisconnectEvent(
      IHangingRequestDisconnectHandler disconnect) {
    onDisconnectList.remove(disconnect);
  }

  /**
   * Clears disconnect events list.
   */
  protected void clearDisconnectEvents() {
    onDisconnectList.clear();
  }

  /**
   * Initializes a new instance of the HangingServiceRequestBase class.
   *
   * @param service            The service.
   * @param handler            Callback delegate to handle response objects
   * @param heartbeatFrequency Frequency at which we expect heartbeats, in milliseconds.
   */
  protected HangingServiceRequestBase(ExchangeService service,
      IHandleResponseObject handler, int heartbeatFrequency)
      throws ServiceVersionException {
    super(service);
    this.responseHandler = handler;
    this.heartbeatFrequencyMilliseconds = heartbeatFrequency;
  }

  /**
   * Exectures the request.
   */
  public void internalExecute() throws Exception {
    synchronized (this) {
      this.response = this.validateAndEmitRequest();
      this.internalOnConnect();
    }
  }

  /**
   * Parses the response.
   *
   */
  private void parseResponses() {
    HangingTraceStream tracingStream = null;
    ByteArrayOutputStream responseCopy = null;


    try {
      boolean traceEWSResponse = this.getService().isTraceEnabledFor(TraceFlags.EwsResponse);
      InputStream responseStream = this.response.getInputStream();
      tracingStream = new HangingTraceStream(responseStream,
          this.getService());
      //EWSServiceMultiResponseXmlReader. Create causes a read.

      if (traceEWSResponse) {
        responseCopy = new ByteArrayOutputStream();
        tracingStream.setResponseCopy(responseCopy);
      }

      while (this.isConnected()) {
        T responseObject;
        if (traceEWSResponse) {
          EwsServiceMultiResponseXmlReader ewsXmlReader =
              EwsServiceMultiResponseXmlReader.create(tracingStream, getService());
          responseObject = this.readResponse(ewsXmlReader);
          this.responseHandler.handleResponseObject(responseObject);

          // reset the stream collector.
          responseCopy.close();
          responseCopy = new ByteArrayOutputStream();
          tracingStream.setResponseCopy(responseCopy);

        } else {
          EwsServiceMultiResponseXmlReader ewsXmlReader =
              EwsServiceMultiResponseXmlReader.create(tracingStream, getService());
          responseObject = this.readResponse(ewsXmlReader);
          this.responseHandler.handleResponseObject(responseObject);
        }
      }
    } catch (SocketTimeoutException ex) {
      // The connection timed out.
      this.disconnect(HangingRequestDisconnectReason.Timeout, ex);
    } catch (UnknownServiceException ex) {
      // Stream is closed, so disconnect.
      this.disconnect(HangingRequestDisconnectReason.Exception, ex);
    } catch (ObjectStreamException ex) {
      // Stream is closed, so disconnect.
      this.disconnect(HangingRequestDisconnectReason.Exception, ex);
    } catch (IOException ex) {
      // Stream is closed, so disconnect.
      this.disconnect(HangingRequestDisconnectReason.Exception, ex);
    } catch (UnsupportedOperationException ex) {
      LOG.error(ex);
      // This is thrown if we close the stream during a
      //read operation due to a user method call.
      // Trying to delay closing until the read finishes
      //simply results in a long-running connection.
      this.disconnect(HangingRequestDisconnectReason.UserInitiated, null);
    } catch (Exception ex) {
      // Stream is closed, so disconnect.
      this.disconnect(HangingRequestDisconnectReason.Exception, ex);
    } finally {
      IOUtils.closeQuietly(responseCopy);
    }
  }

  private boolean isConnected;

  /**
   * Gets a value indicating whether this instance is connected.
   *
   * @return true, if this instance is connected; otherwise, false
   */
  public boolean isConnected() {
    return this.isConnected;
  }

  private void setIsConnected(boolean value) {
    this.isConnected = value;
  }

  /**
   * Disconnects the request.
   */
  public void disconnect() {
    synchronized (this) {
      IOUtils.closeQuietly(this.response);
      this.disconnect(HangingRequestDisconnectReason.UserInitiated, null);
    }
  }

  /**
   * Disconnects the request with the specified reason and exception.
   *
   * @param reason    The reason.
   * @param exception The exception.
   */
  public void disconnect(HangingRequestDisconnectReason reason, Exception exception) {
    if (this.isConnected()) {
      IOUtils.closeQuietly(this.response);
      this.internalOnDisconnect(reason, exception);
    }
  }

  /**
   * Perform any bookkeeping needed when we connect
   * @throws XMLStreamException the XML stream exception
   */
  private void internalOnConnect() throws XMLStreamException,
      IOException, EWSHttpException {
    if (!this.isConnected()) {
      this.isConnected = true;

      if (this.getService().isTraceEnabledFor(TraceFlags.EwsResponseHttpHeaders)) {
        // Trace Http headers
        this.getService().processHttpResponseHeaders(
            TraceFlags.EwsResponseHttpHeaders,
            this.response);
      }
      int poolSize = 1;

      int maxPoolSize = 1;

      long keepAliveTime = 10;

      final ArrayBlockingQueue queue =
          new ArrayBlockingQueue(
              1);
      ThreadPoolExecutor threadPool = new ThreadPoolExecutor(poolSize,
          maxPoolSize,
          keepAliveTime, TimeUnit.SECONDS, queue);
      threadPool.execute(new Runnable() {
        public void run() {
          parseResponses();
        }
      });
      threadPool.shutdown();
    }
  }

  /**
   * Perform any bookkeeping needed when we disconnect (cleanly or forcefully)
   *
   * @param reason    The reason.
   * @param exception The exception.
   */
  private void internalOnDisconnect(HangingRequestDisconnectReason reason,
      Exception exception) {
    if (this.isConnected()) {
      this.isConnected = false;
      for (IHangingRequestDisconnectHandler disconnect : onDisconnectList) {
        disconnect.hangingRequestDisconnectHandler(this,
            new HangingRequestDisconnectEventArgs(reason, exception));
      }
    }
  }

  /**
   * Reads any preamble data not part of the core response.
   *
   * @param ewsXmlReader The EwsServiceXmlReader.
   * @throws Exception
   */
  @Override
  protected void readPreamble(EwsServiceXmlReader ewsXmlReader)
      throws Exception {
    // Do nothing.
    try {
      ewsXmlReader.read(new XmlNodeType(XmlNodeType.START_DOCUMENT));
    } catch (XmlException ex) {
      throw new ServiceRequestException("The response received from the service didn't contain valid XML.", ex);
    } catch (ServiceXmlDeserializationException ex) {
      throw new ServiceRequestException("The response received from the service didn't contain valid XML.", ex);
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy