microsoft.exchange.webservices.data.notification.StreamingSubscriptionConnection 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.notification;
import microsoft.exchange.webservices.data.core.EwsUtilities;
import microsoft.exchange.webservices.data.core.ExchangeService;
import microsoft.exchange.webservices.data.core.exception.misc.ArgumentNullException;
import microsoft.exchange.webservices.data.core.request.GetStreamingEventsRequest;
import microsoft.exchange.webservices.data.core.request.HangingRequestDisconnectEventArgs;
import microsoft.exchange.webservices.data.core.request.HangingServiceRequestBase;
import microsoft.exchange.webservices.data.core.response.GetStreamingEventsResponse;
import microsoft.exchange.webservices.data.core.enumeration.misc.ExchangeVersion;
import microsoft.exchange.webservices.data.core.enumeration.misc.error.ServiceError;
import microsoft.exchange.webservices.data.core.enumeration.service.ServiceResult;
import microsoft.exchange.webservices.data.core.exception.misc.ArgumentException;
import microsoft.exchange.webservices.data.core.exception.misc.ArgumentOutOfRangeException;
import microsoft.exchange.webservices.data.core.exception.service.local.ServiceLocalException;
import microsoft.exchange.webservices.data.core.exception.service.remote.ServiceResponseException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.io.Closeable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Represents a connection to an ongoing stream of events.
*/
public final class StreamingSubscriptionConnection implements Closeable,
HangingServiceRequestBase.IHandleResponseObject,
HangingServiceRequestBase.IHangingRequestDisconnectHandler {
private static final Log LOG = LogFactory.getLog(StreamingSubscriptionConnection.class);
/**
* Mapping of streaming id to subscriptions currently on the connection.
*/
private Map subscriptions;
/**
* connection lifetime, in minutes
*/
private int connectionTimeout;
/**
* ExchangeService instance used to make the EWS call.
*/
private ExchangeService session;
/**
* Value indicating whether the class is disposed.
*/
private boolean isDisposed;
/**
* Currently used instance of a GetStreamingEventsRequest connected to EWS.
*/
private GetStreamingEventsRequest currentHangingRequest;
public interface INotificationEventDelegate {
/**
* Represents a delegate that is invoked when notification are received
* from the server
*
* @param sender The StreamingSubscriptionConnection instance that received
* the events.
* @param args The event data.
*/
void notificationEventDelegate(Object sender, NotificationEventArgs args);
}
/**
* Notification events Occurs when notification are received from the
* server.
*/
private List onNotificationEvent = new ArrayList();
/**
* Set event to happen when property Notify.
*
* @param notificationEvent notification event
*/
public void addOnNotificationEvent(
INotificationEventDelegate notificationEvent) {
onNotificationEvent.add(notificationEvent);
}
/**
* Remove the event from happening when property Notify.
*
* @param notificationEvent notification event
*/
public void removeNotificationEvent(
INotificationEventDelegate notificationEvent) {
onNotificationEvent.remove(notificationEvent);
}
/**
* Clears notification events list.
*/
public void clearNotificationEvent() {
onNotificationEvent.clear();
}
public interface ISubscriptionErrorDelegate {
/**
* Represents a delegate that is invoked when an error occurs within a
* streaming subscription connection.
*
* @param sender The StreamingSubscriptionConnection instance within which
* the error occurred.
* @param args The event data.
*/
void subscriptionErrorDelegate(Object sender,
SubscriptionErrorEventArgs args);
}
/**
* Subscription events Occur when a subscription encounters an error.
*/
private List onSubscriptionError = new ArrayList();
/**
* Set event to happen when property subscriptionError.
*
* @param subscriptionError subscription event
*/
public void addOnSubscriptionError(
ISubscriptionErrorDelegate subscriptionError) {
onSubscriptionError.add(subscriptionError);
}
/**
* Remove the event from happening when property subscription.
*
* @param subscriptionError subscription event
*/
public void removeSubscriptionError(
ISubscriptionErrorDelegate subscriptionError) {
onSubscriptionError.remove(subscriptionError);
}
/**
* Clears subscription events list.
*/
public void clearSubscriptionError() {
onSubscriptionError.clear();
}
/**
* Disconnect events Occurs when a streaming subscription connection is
* disconnected from the server.
*/
private List onDisconnect = new ArrayList();
/**
* Set event to happen when property disconnect.
*
* @param disconnect disconnect event
*/
public void addOnDisconnect(ISubscriptionErrorDelegate disconnect) {
onDisconnect.add(disconnect);
}
/**
* Remove the event from happening when property disconnect.
*
* @param disconnect disconnect event
*/
public void removeDisconnect(ISubscriptionErrorDelegate disconnect) {
onDisconnect.remove(disconnect);
}
/**
* Clears disconnect events list.
*/
public void clearDisconnect() {
onDisconnect.clear();
}
/**
* Initializes a new instance of the StreamingSubscriptionConnection class.
*
* @param service The ExchangeService instance this connection uses to connect
* to the server.
* @param lifetime The maximum time, in minutes, the connection will remain open.
* Lifetime must be between 1 and 30.
* @throws Exception
*/
public StreamingSubscriptionConnection(ExchangeService service, int lifetime)
throws Exception {
EwsUtilities.validateParam(service, "service");
EwsUtilities.validateClassVersion(service,
ExchangeVersion.Exchange2010_SP1, this.getClass().getName());
if (lifetime < 1 || lifetime > 30) {
throw new ArgumentOutOfRangeException("lifetime");
}
this.session = service;
this.subscriptions = new HashMap();
this.connectionTimeout = lifetime;
}
/**
* Initializes a new instance of the StreamingSubscriptionConnection class.
*
* @param service The ExchangeService instance this connection uses to connect
* to the server.
* @param subscriptions Iterable subcriptions
* @param lifetime The maximum time, in minutes, the connection will remain open.
* Lifetime must be between 1 and 30.
* @throws Exception
*/
public StreamingSubscriptionConnection(ExchangeService service,
Iterable subscriptions, int lifetime)
throws Exception {
this(service, lifetime);
EwsUtilities.validateParamCollection(subscriptions.iterator(), "subscriptions");
for (StreamingSubscription subscription : subscriptions) {
this.subscriptions.put(subscription.getId(), subscription);
}
}
/**
* Adds a subscription to this connection.
*
* @param subscription The subscription to add.
* @throws Exception Thrown when AddSubscription is called while connected.
*/
public void addSubscription(StreamingSubscription subscription)
throws Exception {
this.throwIfDisposed();
EwsUtilities.validateParam(subscription, "subscription");
this.validateConnectionState(false, "Subscriptions can't be added to an open connection.");
synchronized (this) {
if (this.subscriptions.containsKey(subscription.getId())) {
return;
}
this.subscriptions.put(subscription.getId(), subscription);
}
}
/**
* Removes the specified streaming subscription from the connection.
*
* @param subscription The subscription to remove.
* @throws Exception Thrown when RemoveSubscription is called while connected.
*/
public void removeSubscription(StreamingSubscription subscription)
throws Exception {
this.throwIfDisposed();
EwsUtilities.validateParam(subscription, "subscription");
this.validateConnectionState(false, "Subscriptions can't be removed from an open connection.");
synchronized (this) {
this.subscriptions.remove(subscription.getId());
}
}
/**
* Opens this connection so it starts receiving events from the server.This
* results in a long-standing call to EWS.
*
* @throws Exception
* @throws ServiceLocalException Thrown when Open is called while connected.
*/
public void open() throws ServiceLocalException, Exception {
synchronized (this) {
this.throwIfDisposed();
this.validateConnectionState(false, "The connection has already opened.");
if (this.subscriptions.size() == 0) {
throw new ServiceLocalException(
"You must add at least one subscription to this connection before it can be opened.");
}
this.currentHangingRequest = new GetStreamingEventsRequest(
this.session, this, this.subscriptions.keySet(),
this.connectionTimeout);
this.currentHangingRequest.addOnDisconnectEvent(this);
this.currentHangingRequest.internalExecute();
}
}
/**
* Called when the request is disconnected.
*
* @param sender The sender.
* @param args The Microsoft.Exchange.WebServices.Data.
* HangingRequestDisconnectEventArgs instance containing the
* event data.
*/
private void onRequestDisconnect(Object sender,
HangingRequestDisconnectEventArgs args) {
this.internalOnDisconnect(args.getException());
}
/**
* Closes this connection so it stops receiving events from the server.This
* terminates a long-standing call to EWS.
*/
public void close() {
synchronized (this) {
try {
this.throwIfDisposed();
this.validateConnectionState(true, "The connection is already closed.");
// Further down in the stack, this will result in a
// call to our OnRequestDisconnect event handler,
// doing the necessary cleanup.
this.currentHangingRequest.disconnect();
} catch (Exception e) {
LOG.error(e);
}
}
}
/**
* Internal helper method called when the request disconnects.
*
* @param ex The exception that caused the disconnection. May be null.
*/
private void internalOnDisconnect(Exception ex) {
if (!onDisconnect.isEmpty()) {
for (ISubscriptionErrorDelegate disconnect : onDisconnect) {
disconnect.subscriptionErrorDelegate(this,
new SubscriptionErrorEventArgs(null, ex));
}
}
this.currentHangingRequest = null;
}
/**
* Gets a value indicating whether this connection is opened
*
* @throws Exception
*/
public boolean getIsOpen() throws Exception {
this.throwIfDisposed();
if (this.currentHangingRequest == null) {
return false;
} else {
return this.currentHangingRequest.isConnected();
}
}
/**
* Validates the state of the connection.
*
* @param isConnectedExpected Value indicating whether we expect to be currently connected.
* @param errorMessage The error message.
* @throws Exception
*/
private void validateConnectionState(boolean isConnectedExpected,
String errorMessage) throws Exception {
if ((isConnectedExpected && !this.getIsOpen())
|| (!isConnectedExpected && this.getIsOpen())) {
throw new ServiceLocalException(errorMessage);
}
}
/**
* Handles the service response object.
*
* @param response The response.
* @throws ArgumentException
*/
private void handleServiceResponseObject(Object response)
throws ArgumentException {
GetStreamingEventsResponse gseResponse = (GetStreamingEventsResponse) response;
if (gseResponse == null) {
throw new ArgumentNullException("GetStreamingEventsResponse must not be null",
"GetStreamingEventsResponse");
} else {
if (gseResponse.getResult() == ServiceResult.Success
|| gseResponse.getResult() == ServiceResult.Warning) {
if (gseResponse.getResults().getNotifications().size() > 0) {
// We got notification; dole them out.
this.issueNotificationEvents(gseResponse);
} else {
// // This was just a heartbeat, nothing to do here.
}
} else if (gseResponse.getResult() == ServiceResult.Error) {
if (gseResponse.getErrorSubscriptionIds() == null
|| gseResponse.getErrorSubscriptionIds().size() == 0) {
// General error
this.issueGeneralFailure(gseResponse);
} else {
// subscription-specific errors
this.issueSubscriptionFailures(gseResponse);
}
}
}
}
/**
* Issues the subscription failures.
*
* @param gseResponse The GetStreamingEvents response.
*/
private void issueSubscriptionFailures(
GetStreamingEventsResponse gseResponse) {
ServiceResponseException exception = new ServiceResponseException(
gseResponse);
for (String id : gseResponse.getErrorSubscriptionIds()) {
StreamingSubscription subscription = null;
synchronized (this) {
// Client can do any good or bad things in the below event
// handler
if (this.subscriptions != null
&& this.subscriptions.containsKey(id)) {
subscription = this.subscriptions.get(id);
}
}
if (subscription != null) {
SubscriptionErrorEventArgs eventArgs = new SubscriptionErrorEventArgs(
subscription, exception);
if (!onSubscriptionError.isEmpty()) {
for (ISubscriptionErrorDelegate subError : onSubscriptionError) {
subError.subscriptionErrorDelegate(this, eventArgs);
}
}
}
if (gseResponse.getErrorCode() != ServiceError.ErrorMissedNotificationEvents) {
// Client can do any good or bad things in the above event
// handler
synchronized (this) {
if (this.subscriptions != null
&& this.subscriptions.containsKey(id)) {
// We are no longer servicing the subscription.
this.subscriptions.remove(id);
}
}
}
}
}
/**
* Issues the general failure.
*
* @param gseResponse The GetStreamingEvents response.
*/
private void issueGeneralFailure(GetStreamingEventsResponse gseResponse) {
SubscriptionErrorEventArgs eventArgs = new SubscriptionErrorEventArgs(
null, new ServiceResponseException(gseResponse));
if (!onSubscriptionError.isEmpty()) {
for (ISubscriptionErrorDelegate subError : onSubscriptionError) {
subError.subscriptionErrorDelegate(this, eventArgs);
}
}
}
/**
* Issues the notification events.
*
* @param gseResponse The GetStreamingEvents response.
*/
private void issueNotificationEvents(GetStreamingEventsResponse gseResponse) {
for (GetStreamingEventsResults.NotificationGroup events : gseResponse
.getResults().getNotifications()) {
StreamingSubscription subscription = null;
synchronized (this) {
// Client can do any good or bad things in the below event
// handler
if (this.subscriptions != null
&& this.subscriptions
.containsKey(events.subscriptionId)) {
subscription = this.subscriptions
.get(events.subscriptionId);
}
}
if (subscription != null) {
NotificationEventArgs eventArgs = new NotificationEventArgs(
subscription, events.events);
if (!onNotificationEvent.isEmpty()) {
for (INotificationEventDelegate notifyEvent : onNotificationEvent) {
notifyEvent.notificationEventDelegate(this, eventArgs);
}
}
}
}
}
/**
* Frees resources associated with this StreamingSubscriptionConnection.
*/
public void dispose() {
synchronized (this) {
if (!this.isDisposed) {
if (this.currentHangingRequest != null) {
this.currentHangingRequest = null;
}
this.subscriptions = null;
this.session = null;
this.isDisposed = true;
}
}
}
/**
* Throws if disposed.
*
* @throws Exception
*/
private void throwIfDisposed() throws Exception {
if (this.isDisposed) {
throw new Exception(this.getClass().getName());
}
}
@Override
public void handleResponseObject(Object response) throws ArgumentException {
this.handleServiceResponseObject(response);
}
@Override
public void hangingRequestDisconnectHandler(Object sender,
HangingRequestDisconnectEventArgs args) {
this.onRequestDisconnect(sender, args);
}
}