001 /*
002 * The MIT License
003 * Copyright (c) 2012 Microsoft Corporation
004 *
005 * Permission is hereby granted, free of charge, to any person obtaining a copy
006 * of this software and associated documentation files (the "Software"), to deal
007 * in the Software without restriction, including without limitation the rights
008 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
009 * copies of the Software, and to permit persons to whom the Software is
010 * furnished to do so, subject to the following conditions:
011 *
012 * The above copyright notice and this permission notice shall be included in
013 * all copies or substantial portions of the Software.
014 *
015 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
016 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
017 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
018 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
019 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
020 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
021 * THE SOFTWARE.
022 */
023
024 package microsoft.exchange.webservices.data.core.request;
025
026 import microsoft.exchange.webservices.data.core.EwsServiceMultiResponseXmlReader;
027 import microsoft.exchange.webservices.data.core.EwsServiceXmlReader;
028 import microsoft.exchange.webservices.data.core.ExchangeService;
029 import microsoft.exchange.webservices.data.core.enumeration.misc.HangingRequestDisconnectReason;
030 import microsoft.exchange.webservices.data.core.enumeration.misc.TraceFlags;
031 import microsoft.exchange.webservices.data.core.exception.http.EWSHttpException;
032 import microsoft.exchange.webservices.data.core.exception.misc.ArgumentException;
033 import microsoft.exchange.webservices.data.core.exception.service.local.ServiceVersionException;
034 import microsoft.exchange.webservices.data.core.exception.service.local.ServiceXmlDeserializationException;
035 import microsoft.exchange.webservices.data.core.exception.service.remote.ServiceRequestException;
036 import microsoft.exchange.webservices.data.core.exception.xml.XmlException;
037 import microsoft.exchange.webservices.data.misc.HangingTraceStream;
038 import microsoft.exchange.webservices.data.security.XmlNodeType;
039 import org.apache.commons.logging.Log;
040 import org.apache.commons.logging.LogFactory;
041
042 import javax.xml.stream.XMLStreamException;
043
044 import java.io.ByteArrayOutputStream;
045 import java.io.IOException;
046 import java.io.InputStream;
047 import java.io.ObjectStreamException;
048 import java.net.SocketTimeoutException;
049 import java.net.UnknownServiceException;
050 import java.util.ArrayList;
051 import java.util.List;
052 import java.util.concurrent.ArrayBlockingQueue;
053 import java.util.concurrent.ThreadPoolExecutor;
054 import java.util.concurrent.TimeUnit;
055
056
057 /**
058 * Represents an abstract, hanging service request.
059 */
060 public abstract class HangingServiceRequestBase<T> extends ServiceRequestBase<T> {
061
062 private static final Log LOG = LogFactory.getLog(HangingServiceRequestBase.class);
063
064
065 public interface IHandleResponseObject {
066
067 /**
068 * Callback delegate to handle asynchronous response.
069 *
070 * @param response Response received from the server
071 * @throws ArgumentException
072 */
073 void handleResponseObject(Object response) throws ArgumentException;
074 }
075
076
077 public static final int BUFFER_SIZE = 4096;
078
079 /**
080 * Test switch to log all bytes that come across the wire.
081 * Helpful when parsing fails before certain bytes hit the trace logs.
082 */
083 private static volatile boolean logAllWireBytes = false;
084
085 /**
086 * Callback delegate to handle response objects
087 */
088 private IHandleResponseObject responseHandler;
089
090 /**
091 * Response from the server.
092 */
093 private HttpWebRequest response;
094
095 /**
096 * Expected minimum frequency in response, in milliseconds.
097 */
098 protected int heartbeatFrequencyMilliseconds;
099
100
101 public interface IHangingRequestDisconnectHandler {
102
103 /**
104 * Delegate method to handle a hanging request disconnection.
105 *
106 * @param sender the object invoking the delegate
107 * @param args event data
108 */
109 void hangingRequestDisconnectHandler(Object sender,
110 HangingRequestDisconnectEventArgs args);
111
112 }
113
114
115 public static boolean isLogAllWireBytes() {
116 return logAllWireBytes;
117 }
118
119 public static void setLogAllWireBytes(final boolean logAllWireBytes) {
120 HangingServiceRequestBase.logAllWireBytes = logAllWireBytes;
121 }
122
123 /**
124 * Disconnect events Occur when the hanging request is disconnected.
125 */
126 private List<IHangingRequestDisconnectHandler> onDisconnectList =
127 new ArrayList<IHangingRequestDisconnectHandler>();
128
129 /**
130 * Set event to happen when property disconnect.
131 *
132 * @param disconnect disconnect event
133 */
134 public void addOnDisconnectEvent(IHangingRequestDisconnectHandler disconnect) {
135 onDisconnectList.add(disconnect);
136 }
137
138 /**
139 * Remove the event from happening when property disconnect.
140 *
141 * @param disconnect disconnect event
142 */
143 protected void removeDisconnectEvent(
144 IHangingRequestDisconnectHandler disconnect) {
145 onDisconnectList.remove(disconnect);
146 }
147
148 /**
149 * Clears disconnect events list.
150 */
151 protected void clearDisconnectEvents() {
152 onDisconnectList.clear();
153 }
154
155 /**
156 * Initializes a new instance of the HangingServiceRequestBase class.
157 *
158 * @param service The service.
159 * @param handler Callback delegate to handle response objects
160 * @param heartbeatFrequency Frequency at which we expect heartbeats, in milliseconds.
161 */
162 protected HangingServiceRequestBase(ExchangeService service,
163 IHandleResponseObject handler, int heartbeatFrequency)
164 throws ServiceVersionException {
165 super(service);
166 this.responseHandler = handler;
167 this.heartbeatFrequencyMilliseconds = heartbeatFrequency;
168 }
169
170 /**
171 * Exectures the request.
172 */
173 public void internalExecute() throws Exception {
174 synchronized (this) {
175 this.response = this.validateAndEmitRequest();
176 this.internalOnConnect();
177 }
178 }
179
180 /**
181 * Parses the response.
182 *
183 */
184 private void parseResponses() {
185 HangingTraceStream tracingStream = null;
186 ByteArrayOutputStream responseCopy = null;
187
188
189 try {
190 boolean traceEWSResponse = this.getService().isTraceEnabledFor(TraceFlags.EwsResponse);
191 InputStream responseStream = this.response.getInputStream();
192 tracingStream = new HangingTraceStream(responseStream,
193 this.getService());
194 //EWSServiceMultiResponseXmlReader. Create causes a read.
195
196 if (traceEWSResponse) {
197 responseCopy = new ByteArrayOutputStream();
198 tracingStream.setResponseCopy(responseCopy);
199 }
200
201 while (this.isConnected()) {
202 T responseObject;
203 if (traceEWSResponse) {
204 EwsServiceMultiResponseXmlReader ewsXmlReader =
205 EwsServiceMultiResponseXmlReader.create(tracingStream, getService());
206 responseObject = this.readResponse(ewsXmlReader);
207 this.responseHandler.handleResponseObject(responseObject);
208
209 // reset the stream collector.
210 responseCopy.close();
211 responseCopy = new ByteArrayOutputStream();
212 tracingStream.setResponseCopy(responseCopy);
213
214 } else {
215 EwsServiceMultiResponseXmlReader ewsXmlReader =
216 EwsServiceMultiResponseXmlReader.create(tracingStream, getService());
217 responseObject = this.readResponse(ewsXmlReader);
218 this.responseHandler.handleResponseObject(responseObject);
219 }
220 }
221 } catch (SocketTimeoutException ex) {
222 // The connection timed out.
223 this.disconnect(HangingRequestDisconnectReason.Timeout, ex);
224 } catch (UnknownServiceException ex) {
225 // Stream is closed, so disconnect.
226 this.disconnect(HangingRequestDisconnectReason.Exception, ex);
227 } catch (ObjectStreamException ex) {
228 // Stream is closed, so disconnect.
229 this.disconnect(HangingRequestDisconnectReason.Exception, ex);
230 } catch (IOException ex) {
231 // Stream is closed, so disconnect.
232 this.disconnect(HangingRequestDisconnectReason.Exception, ex);
233 } catch (UnsupportedOperationException ex) {
234 LOG.error(ex);
235 // This is thrown if we close the stream during a
236 //read operation due to a user method call.
237 // Trying to delay closing until the read finishes
238 //simply results in a long-running connection.
239 this.disconnect(HangingRequestDisconnectReason.UserInitiated, null);
240 } catch (Exception ex) {
241 // Stream is closed, so disconnect.
242 this.disconnect(HangingRequestDisconnectReason.Exception, ex);
243 } finally {
244 if (responseCopy != null) {
245 try {
246 responseCopy.close();
247 responseCopy = null;
248 } catch (Exception ex) {
249 LOG.error(ex);
250 }
251 }
252 }
253 }
254
255 private boolean isConnected;
256
257 /**
258 * Gets a value indicating whether this instance is connected.
259 *
260 * @return true, if this instance is connected; otherwise, false
261 */
262 public boolean isConnected() {
263 return this.isConnected;
264 }
265
266 private void setIsConnected(boolean value) {
267 this.isConnected = value;
268 }
269
270 /**
271 * Disconnects the request.
272 */
273 public void disconnect() {
274 synchronized (this) {
275 try {
276 this.response.close();
277 } catch (IOException e) {
278 // Ignore exception on disconnection
279 }
280 this.disconnect(HangingRequestDisconnectReason.UserInitiated, null);
281 }
282 }
283
284 /**
285 * Disconnects the request with the specified reason and exception.
286 *
287 * @param reason The reason.
288 * @param exception The exception.
289 */
290 public void disconnect(HangingRequestDisconnectReason reason, Exception exception) {
291 if (this.isConnected()) {
292 try {
293 this.response.close();
294 } catch (IOException e) {
295 // Ignore exception on disconnection
296 }
297 this.internalOnDisconnect(reason, exception);
298 }
299 }
300
301 /**
302 * Perform any bookkeeping needed when we connect
303 * @throws XMLStreamException the XML stream exception
304 */
305 private void internalOnConnect() throws XMLStreamException,
306 IOException, EWSHttpException {
307 if (!this.isConnected()) {
308 this.isConnected = true;
309
310 if (this.getService().isTraceEnabledFor(TraceFlags.EwsResponseHttpHeaders)) {
311 // Trace Http headers
312 this.getService().processHttpResponseHeaders(
313 TraceFlags.EwsResponseHttpHeaders,
314 this.response);
315 }
316 int poolSize = 1;
317
318 int maxPoolSize = 1;
319
320 long keepAliveTime = 10;
321
322 final ArrayBlockingQueue<Runnable> queue =
323 new ArrayBlockingQueue<Runnable>(
324 1);
325 ThreadPoolExecutor threadPool = new ThreadPoolExecutor(poolSize,
326 maxPoolSize,
327 keepAliveTime, TimeUnit.SECONDS, queue);
328 threadPool.execute(new Runnable() {
329 public void run() {
330 parseResponses();
331 }
332 });
333 threadPool.shutdown();
334 }
335 }
336
337 /**
338 * Perform any bookkeeping needed when we disconnect (cleanly or forcefully)
339 *
340 * @param reason The reason.
341 * @param exception The exception.
342 */
343 private void internalOnDisconnect(HangingRequestDisconnectReason reason,
344 Exception exception) {
345 if (this.isConnected()) {
346 this.isConnected = false;
347 for (IHangingRequestDisconnectHandler disconnect : onDisconnectList) {
348 disconnect.hangingRequestDisconnectHandler(this,
349 new HangingRequestDisconnectEventArgs(reason, exception));
350 }
351 }
352 }
353
354 /**
355 * Reads any preamble data not part of the core response.
356 *
357 * @param ewsXmlReader The EwsServiceXmlReader.
358 * @throws Exception
359 */
360 @Override
361 protected void readPreamble(EwsServiceXmlReader ewsXmlReader)
362 throws Exception {
363 // Do nothing.
364 try {
365 ewsXmlReader.read(new XmlNodeType(XmlNodeType.START_DOCUMENT));
366 } catch (XmlException ex) {
367 throw new ServiceRequestException("The response received from the service didn't contain valid XML.", ex);
368 } catch (ServiceXmlDeserializationException ex) {
369 throw new ServiceRequestException("The response received from the service didn't contain valid XML.", ex);
370 }
371 }
372 }