microsoft.exchange.webservices.data.core.request.HangingServiceRequestBase Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ews-java-api Show documentation
Show all versions of ews-java-api Show documentation
Exchange Web Services (EWS) Java API
/*
* 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);
}
}
}