/////////////////////////////////////////////////////////// // NotifyEvent.cs // Implementation of Class NotifyEvent // CSLA type: Editable Root // Created on: 13-Oct-2005 // Object design: John // Coded: John 13-Oct-2005 /////////////////////////////////////////////////////////// using System; using System.Data; using CSLA.Data; using GZTW.Data; using CSLA; using System.Threading; using CSLA.Security; using System.Collections; namespace GZTW.AyaNova.BLL { /// /// AyaNova NotifyEvent used to insert new /// notification events in the database. /// /// Event's are Processed appropriately here according /// to their type and purpose. /// /// This class ensures that only events subscribed to /// are inserted and tags them with the correct user ID /// for user specific subscriptions. /// [Serializable] public class NotifyEvent : BusinessBase { #region Attributes private Guid _RootObjectID; private RootObjectTypes _RootObjectType; private int _EventType; private Guid _AppliesToUserID; private SmartDate _EventDate; private Guid _GuidValue; //Sometimes an event is no longer required due to deletion etc //so this flag will remove it private bool _Remove; private bool _RemoveIsBecauseDelivered; private Guid _NotifyEventID; private bool _RemoveIsBecauseUnSubscribed; //case 812 private string _SavedMessage; #endregion #region Constructor /// /// Private constructor to prevent direct instantiation /// used by all shared Add/Update methods but not by any of the remove methods /// private NotifyEvent(RootObjectTypes RootObjectType,Guid RootObjectID, int EventType,Guid AppliesToUserID,SmartDate EventDate,Guid GuidValue, string SavedMessage)//case 812 { _RootObjectType=RootObjectType; _RootObjectID=RootObjectID; _EventType=EventType; _AppliesToUserID=AppliesToUserID; _EventDate=EventDate; _Remove=false; _GuidValue=GuidValue; _RemoveIsBecauseDelivered=false; _RemoveIsBecauseUnSubscribed=false; _SavedMessage = SavedMessage; } /// /// Constructor for remove all / specific event method /// /// Object's events to be removed regardless of status /// 0=all events, non-zero means specific event only private NotifyEvent(Guid RootObjectID,int EventType) { _RootObjectID=RootObjectID; _Remove=true; _RemoveIsBecauseDelivered=false; _EventType=EventType; _RemoveIsBecauseUnSubscribed=false; } /// /// Constructor for RemoveDeliveredEvent method /// /// NotifyEvent ID to be removed private NotifyEvent(Guid NotifyEventID) { _NotifyEventID=NotifyEventID; _Remove=true; _RemoveIsBecauseDelivered=true; _RemoveIsBecauseUnSubscribed=false; } /// /// Constructor for subscription related remove events methods /// /// /// /// /// private NotifyEvent(RootObjectTypes RootObjectType, int EventType,Guid AppliesToUserID, Guid GuidValue) { _RootObjectType=RootObjectType; _EventType=EventType; _AppliesToUserID=AppliesToUserID; _GuidValue=GuidValue; _Remove=true; _RemoveIsBecauseDelivered=false; _RemoveIsBecauseUnSubscribed=true; } #endregion #region Static methods /// /// Add a new event if no matching one already exists or /// replaces a matching existing event. /// /// /// /// /// /// /// /// /// public static void AddOrUpdateEvent(RootObjectTypes RootObjectType,Guid RootObjectID, int EventType,Guid AppliesToUserID,SmartDate EventDate, Guid GuidValue) { DataPortal.Update(new NotifyEvent( RootObjectType, RootObjectID, EventType, AppliesToUserID, EventDate, GuidValue,"")); } /// /// Add a new event if no matching one already exists or /// replaces a matching existing event. /// /// /// /// public static void AddOrUpdateEvent(RootObjectTypes RootObjectType, Guid RootObjectID, int EventType) { DataPortal.Update(new NotifyEvent(RootObjectType, RootObjectID, EventType, Guid.Empty, new SmartDate(), Guid.Empty,"")); } //case 812 for QuickNotification /// /// Add a new event with saved message /// /// /// /// /// /// public static void AddOrUpdateEvent(RootObjectTypes RootObjectType, Guid RootObjectID, int EventType, Guid AppliesToUserID, string sMessage) { //check for license if quick notification if (RootObjectType == RootObjectTypes.User && EventType == (int)UserEvent.QuickNotification) { //case 2094 if(!AyaBizUtils.PluginSubscriptionExists("QuickNotification")) throw new System.NotSupportedException("A license for QuickNotification plugin is required to use this method"); } DataPortal.Update(new NotifyEvent(RootObjectType, RootObjectID, EventType, AppliesToUserID, new SmartDate(), Guid.Empty,sMessage)); } // /// /// Add a new pending event if no matching one already exists or /// replaces an unProcessed matching existing event. /// /// If the pending until date is empty then will simply remove any /// existing matching pending event, otherwise will add/update it /// /// Any pre-existing matching event found is replaced. /// /// /// /// /// /// /// public static void AddOrUpdatePendingEvent(RootObjectTypes RootObjectType,Guid RootObjectID, int EventType,Guid AppliesToUserID,SmartDate EventDate, Guid GuidValue) { DataPortal.Update(new NotifyEvent( RootObjectType, RootObjectID, EventType, AppliesToUserID, EventDate, GuidValue,"")); } #region Object related and initiated removal /// /// Removes all events for a given object ID /// This is used within objects when they are deleted to /// ensure no events remain related to them. /// /// It is also used for updating objects like schedule markers where /// a slight change, i.e. scheduleableusergroup change could /// render a lot of the user specific events meaningless since /// the entire group of users could change with a small change in the /// schedulemarker. In a case like that, this method is called to /// wipe out the entire event list for that object and a new one /// is created /// /// ID of deleted object public static void RemoveAllEventsForObject(Guid RootObjectID) { DataPortal.Update(new NotifyEvent( RootObjectID,0)); } /// /// Removes a specific event for a given object ID /// This is used within objects when they are changed in some way that /// renders the event meaningless, i.e. if a client's contractexpires is set to empty /// /// /// public static void RemoveSpecificEventForObject(Guid RootObjectID,int EventType) { DataPortal.Update(new NotifyEvent( RootObjectID, EventType)); } #endregion #region Subscription related and initiated removal /// /// Removes a specific event for a specific user /// This is used when a user unsubscribes from a specfic event to ensure /// that there are no event's for that event and subscriber left floating /// /// /// /// /// public static void RemoveSpecificEventForUser(RootObjectTypes RootObjectType, int EventType,Guid AppliesToUserID, Guid GuidValue) { DataPortal.Update(new NotifyEvent(RootObjectType,EventType,AppliesToUserID, GuidValue)); } #endregion /// /// Removes an event that has been delivered /// /// Used by notification delivery Processor to remove events /// that have been delivered but not touch ones that are fresh since /// delivery /// /// public static void RemoveDeliveredEvent(Guid NotifyEventID) { DataPortal.Update(new NotifyEvent( NotifyEventID)); } #endregion #region DAL DATA ACCESS /// /// Case 58 /// Used to ensure that a user is within the region /// of the object before inserting notification /// /// /// /// private static bool InUserRegion(Guid userID, TypeAndID tid) { Guid UserRegionID = UserRegionIDFetcher.UserRegion(userID); //Short circuit if user is in default region, no need to check further //they get everything if (UserRegionID == Region.DefaultRegionID) return true; //case 1152 //fix to stop lockup when the "longer way round" code below fires and //objectregionidfetcher attempts to lookup the region of a workorder via the //grandchild object that triggered the notification. Problem being that the //grandchild object has been saved but is in a transaction that hasn't been committed yet //so it seems to cause some sort of deadlock //Fix is to ignore the region for these objects since in most cases they can't be //triggered outside their region anyway from the UI level if (tid.RootObjectType == RootObjectTypes.WorkorderItemScheduledUser) return true; if (tid.RootObjectType == RootObjectTypes.WorkorderItemOutsideService) return true; if (tid.RootObjectType == RootObjectTypes.WorkorderItemPartRequest) return true; //longer way around if(AyaBizUtils.InYourRegion(UserRegionID,ObjectRegionIDFetcher.ObjectRegion(tid))) return true; return false; } /// /// Called by DataPortal to delete/add/update data into the database /// protected override void DataPortal_Update() { #region Delete event //Explicit removal of event(s) if(_Remove) { if(_RemoveIsBecauseDelivered) {//REmove a particular event record that has been delivered DBCommandWrapper cmDeleteOnly=DBUtil.GetCommandFromSQL( "DELETE FROM aNotifyEvent WHERE aID=@ID" ); cmDeleteOnly.AddInParameter("@ID",DbType.Guid,this._NotifyEventID); DBUtil.DB.ExecuteNonQuery(cmDeleteOnly); return; } if(_RemoveIsBecauseUnSubscribed) { DBCommandWrapper cmDeleteUnsubscriber = null; cmDeleteUnsubscriber=DBUtil.GetCommandFromSQL( "DELETE FROM aNotifyEvent WHERE " + "aRootObjectType=@RootObjectType AND " + "aEventType=@EventType AND AAPPLIESTOUSERID=@AppliesToUserID " + (_GuidValue==Guid.Empty?"":"AND (aGuidValue = @GuidValue)") ); cmDeleteUnsubscriber.AddInParameter("@RootObjectType",DbType.Int16,(int)this._RootObjectType); cmDeleteUnsubscriber.AddInParameter("@EventType",DbType.Int16,this._EventType); cmDeleteUnsubscriber.AddInParameter("@AppliesToUserID",DbType.Guid,this._AppliesToUserID); if(_GuidValue!=Guid.Empty) cmDeleteUnsubscriber.AddInParameter("@GuidValue",DbType.Guid,_GuidValue); DBUtil.DB.ExecuteNonQuery(cmDeleteUnsubscriber); return; } //Only reason left is object initiated removal for change or delete: {//this is here to local scope the contents so I don't have to rename the datacommand. //Remove all or a specific event for the object id in question because it was deleted or changed //Also remove any lingering popup notifications in the queue that are now stale DBCommandWrapper cmDeleteOnly=null; if(_EventType==0) { cmDeleteOnly=DBUtil.GetCommandFromSQL( "DELETE FROM aNotifyEvent WHERE " + "aRootObjectID=@RootObjectID " ); //Remove any popup notifications about it as well DBCommandWrapper cmDeletePop=DBUtil.GetCommandFromSQL( "DELETE FROM aNotifyPopUp WHERE " + "aRootObjectID=@RootObjectID " ); cmDeletePop.AddInParameter("@RootObjectID",DbType.Guid,this._RootObjectID); DBUtil.DB.ExecuteNonQuery(cmDeletePop); } else {//specific event for this object only cmDeleteOnly=DBUtil.GetCommandFromSQL( "DELETE FROM aNotifyEvent " + "WHERE (aRootObjectID=@RootObjectID) AND (aEventType = @EventType)" ); cmDeleteOnly.AddInParameter("@EventType",DbType.Int16,this._EventType); } cmDeleteOnly.AddInParameter("@RootObjectID",DbType.Guid,this._RootObjectID); DBUtil.DB.ExecuteNonQuery(cmDeleteOnly); } return; } #endregion delete event #region add/update event //Delete any current matching event record //This is done ahead of checking to see if there are subscribers //because it's possible there are no longer any subscribers but there is an existing //event record from before the last subscriber unsubscribed DBCommandWrapper cmDelete = null; //WARNING: this is going to remove any old workorder status events //when status changes because it doesn't include the GUID value //is this a bad thing or not? I'm leaving it to remove it because //I can't see where a use want's to know what a workorder status *was* //only what it is now. //case 812 bool bIsQuickNotification=(_RootObjectType == RootObjectTypes.User && _EventType == (int)UserEvent.QuickNotification); //BUGBUG: 30-may-2006 Shouldn't this be deleting all events regardless of applies to user id //or at least if it's an event without an applies to user id then it should delete all //that match otherwise? (i.e. workorder close by date passed, every time a workorder is //resaved it keeps adding more notifyevent records if (_AppliesToUserID != Guid.Empty) { cmDelete = DBUtil.GetCommandFromSQL( "DELETE FROM aNotifyEvent WHERE " + "aRootObjectType=@RootObjectType AND aRootObjectID=@RootObjectID AND " + "aEventType=@EventType AND AAPPLIESTOUSERID=@AppliesToUserID" ); cmDelete.AddInParameter("@RootObjectType", DbType.Int16, (int)this._RootObjectType); cmDelete.AddInParameter("@RootObjectID", DbType.Guid, this._RootObjectID); cmDelete.AddInParameter("@EventType", DbType.Int16, this._EventType); cmDelete.AddInParameter("@AppliesToUserID", DbType.Guid, this._AppliesToUserID); } else { //Changed: 31-May-2006 added the following to delete events that are not user specific //problem was that if deliveries were stuck for some reason it would just keep adding //the same events over and over until there were thousands. cmDelete = DBUtil.GetCommandFromSQL( "DELETE FROM aNotifyEvent WHERE " + "aRootObjectType=@RootObjectType AND aRootObjectID=@RootObjectID AND " + "aEventType=@EventType" ); cmDelete.AddInParameter("@RootObjectType", DbType.Int16, (int)this._RootObjectType); cmDelete.AddInParameter("@RootObjectID", DbType.Guid, this._RootObjectID); cmDelete.AddInParameter("@EventType", DbType.Int16, this._EventType); } //case 812, don't delete quick notifications if (!bIsQuickNotification) DBUtil.DB.ExecuteNonQuery(cmDelete); //DOES ANYONE SUBSCRIBE TO THIS EVENT? //If notify event of interest subscriber count = zero then can //bail out early if(!NotifyEventOfInterestFetcher.IsEventInteresting(_RootObjectType,_EventType, _GuidValue)) return; //If AppliesToUserID is non-null then see if this particular event is subscribed to by //that particular user, if not then it doesn't need to be Processed and we can bail early //(this is a design decision to distribute the load amongst the users rather than put it all //at the event Processor) //The reason for this is, for example, let's say there is a event of interest in //memo.created. Every time a memo is created this code will be executed, but //not all memo's are created for users that are subscribed to know about it. //DOES THE INDICATED USER SUBSCRIBE TO THIS EVENT? if(_AppliesToUserID!=Guid.Empty) { //let's see if the user in question is subscribed to the event in question DBCommandWrapper cmSub = DBUtil.DB.GetSqlStringCommandWrapper( "SELECT aID FROM aNotifySubscription " + "WHERE (aRootObjectType = @RootObjectType) AND (aEventType = @EventType) " + "AND (aUserID=@AppliesToUserID) " + (_GuidValue==Guid.Empty?"":"AND (aGuidValue = @GuidValue)") ); if(_GuidValue!=Guid.Empty) cmSub.AddInParameter("@GuidValue",DbType.Guid,this._GuidValue); cmSub.AddInParameter("@RootObjectType",DbType.Int16,(int)this._RootObjectType); cmSub.AddInParameter("@EventType",DbType.Int16,this._EventType); cmSub.AddInParameter("@AppliesToUserID",DbType.Guid,this._AppliesToUserID); if(DBUtil.ToGuid(DBUtil.DB.ExecuteScalar(cmSub))==Guid.Empty) return;//BAIL OUT, No user is not currently subscribed to this event } //Yes, at least one user is currently subscribed to this event //Is it a general event that doesn't apply to a single user only? if(_AppliesToUserID==Guid.Empty) { //then we need to create //a notifyevent record for each user that is subscribed //as they each need a record so that the event can be assured notification delivery //and deletion upon delivery //For example if two users subscribe to a contract.expires event and one gets the delivery //during a notification Process, but the other doesnt' because they don't have an open time //window to deliver at that moment the event needs to be kept for them but deleted for the //person that could receive the delivery. The initial version of this code didn't take that into //account so I want to re-iterate it here in case of future changes that might potentially //affect this behaviour //Iterate through subscriptions looking for people subscribed to this event //create an event record for each one DBCommandWrapper cmSubscribers = DBUtil.DB.GetSqlStringCommandWrapper( //************************************************************ "SELECT aUserID FROM aNotifySubscription WHERE " + "(aRootObjectType = @RootObjectType) AND (aEventType = @EventType) " + (_GuidValue==Guid.Empty?"":"AND (aGuidValue = @GuidValue)") //************************************************************ ); cmSubscribers.AddInParameter("@RootObjectType",DbType.Int16,(int)this._RootObjectType); cmSubscribers.AddInParameter("@EventType",DbType.Int16,this._EventType); if(_GuidValue!=Guid.Empty) cmSubscribers.AddInParameter("@GuidValue",DbType.Guid,this._GuidValue); SafeDataReader dr = new SafeDataReader(DBUtil.DB.ExecuteReader(cmSubscribers)); ArrayList al=new ArrayList(); while(dr.Read()) { al.Add(dr.GetGuid("aUserID")); } foreach(object o in al) { //Case 58 if (InUserRegion((Guid)o, new TypeAndID(this._RootObjectType, this._RootObjectID))) { DBCommandWrapper cm = DBUtil.GetCommandFromSQL( "INSERT INTO aNotifyEvent (aID, aRootObjectType, aRootObjectID, aEventType,aGuidValue, AAPPLIESTOUSERID, aEventDate, ACREATED) " + "VALUES (@ID,@RootObjectType,@RootObjectID,@EventType,@GuidValue,@AppliesToUserID,@EventDate, @CREATED)" ); cm.AddInParameter("@ID", DbType.Guid, Guid.NewGuid()); cm.AddInParameter("@RootObjectID", DbType.Guid, this._RootObjectID); cm.AddInParameter("@GuidValue", DbType.Guid, this._GuidValue); cm.AddInParameter("@RootObjectType", DbType.Int16, (int)this._RootObjectType); cm.AddInParameter("@EventType", DbType.Int16, this._EventType); cm.AddInParameter("@AppliesToUserID", DbType.Guid, (Guid)o); cm.AddInParameter("@EventDate", DbType.DateTime, DBUtil.ToUTC(this._EventDate).DBValue); cm.AddInParameter("@CREATED", DbType.DateTime, DBUtil.ToUTC(DBUtil.CurrentWorkingDateTime)); DBUtil.DB.ExecuteNonQuery(cm); } } } else { //It's *not* an event that applies to more than one user //so we can just stick it in the table as is //because only one user will ever be notified about it //add the record for the event to the table //case 812 if (!bIsQuickNotification) { if (InUserRegion(_AppliesToUserID, new TypeAndID(this._RootObjectType, this._RootObjectID))) { DBCommandWrapper cm = DBUtil.GetCommandFromSQL( "INSERT INTO aNotifyEvent (aID, aRootObjectType, aRootObjectID, aEventType,aGuidValue, AAPPLIESTOUSERID, aEventDate, ACREATED) " + "VALUES (@ID,@RootObjectType,@RootObjectID,@EventType,@GuidValue,@AppliesToUserID,@EventDate, @CREATED)" ); cm.AddInParameter("@ID", DbType.Guid, Guid.NewGuid()); cm.AddInParameter("@RootObjectID", DbType.Guid, this._RootObjectID); cm.AddInParameter("@GuidValue", DbType.Guid, this._GuidValue); cm.AddInParameter("@RootObjectType", DbType.Int16, (int)this._RootObjectType); cm.AddInParameter("@EventType", DbType.Int16, this._EventType); cm.AddInParameter("@AppliesToUserID", DbType.Guid, this._AppliesToUserID); cm.AddInParameter("@EventDate", DbType.DateTime, DBUtil.ToUTC(this._EventDate).DBValue); cm.AddInParameter("@CREATED", DbType.DateTime, DBUtil.ToUTC(DBUtil.CurrentWorkingDateTime)); DBUtil.DB.ExecuteNonQuery(cm); } } else { //case 812 DBCommandWrapper cm = DBUtil.GetCommandFromSQL( "INSERT INTO aNotifyEvent (aID, aRootObjectType, aRootObjectID, aEventType,aGuidValue, AAPPLIESTOUSERID, aEventDate, ASAVEDMESSAGE, ACREATED) " + "VALUES (@ID,@RootObjectType,@RootObjectID,@EventType,@GuidValue,@AppliesToUserID,@EventDate,@SAVEDMESSAGE, @CREATED)" ); cm.AddInParameter("@ID", DbType.Guid, Guid.NewGuid()); cm.AddInParameter("@RootObjectID", DbType.Guid, this._RootObjectID); cm.AddInParameter("@GuidValue", DbType.Guid, this._GuidValue); cm.AddInParameter("@RootObjectType", DbType.Int16, (int)this._RootObjectType); cm.AddInParameter("@EventType", DbType.Int16, this._EventType); cm.AddInParameter("@AppliesToUserID", DbType.Guid, this._AppliesToUserID); cm.AddInParameter("@EventDate", DbType.DateTime, DBUtil.ToUTC(this._EventDate).DBValue); cm.AddInParameter("@SAVEDMESSAGE", DbType.String, _SavedMessage); cm.AddInParameter("@CREATED", DbType.DateTime, DBUtil.ToUTC(DBUtil.CurrentWorkingDateTime)); DBUtil.DB.ExecuteNonQuery(cm); } } #endregion add/update event } #endregion }//end NotifyEvent }//end namespace GZTW.AyaNova.BLL