/////////////////////////////////////////////////////////// // Project.cs // Implementation of Class Integration // CSLA type: Editable Root // Created on: 02-Feb-2006 // Object design: John // Coded: John 02-Feb-2006 /////////////////////////////////////////////////////////// using System; using System.Data; using CSLA.Data; using GZTW.Data; using CSLA; using System.Runtime.Serialization.Formatters.Binary; using System.IO; namespace GZTW.AyaNova.BLL { /// /// Integration object /// /// This object is used for assisting with integrating 3rd party and /// AyaNova utility programs with AyaNova /// /// It's primary function is to provide an API standard method /// for storing and retrieving external program data in the AyaNova /// database for the purposes of integration with AyaNova /// /// It also contains a child collection for mapping external data /// to AyaNova objects /// /// The APPID GUID value must be set to uniquely identify the external /// application. The APPVERSION field is used by the implementer for /// version control of the data they are storing in AyaNova. /// /// /// [Serializable] public class Integration : BusinessBase { #region Attributes private Guid mID; private SmartDate mCreated; private SmartDate mModified; private bool mActive; private Guid mCreator; private Guid mModifier; private string mName = null; private SmartDate mLastConnect; private Guid mAppID = Guid.NewGuid(); private string mAppVersion = null; private string mSyncCheckPoint = ""; private object mAIObject = null; //Child collections private IntegrationMaps mMaps; //Not a read only type object, just kept for //compatibility reasons private bool bReadOnly = false; #endregion #region Constructor /// /// Private constructor to prevent direct instantiation /// private Integration() { //New ID mID = Guid.NewGuid(); Active = true; //Set record history to defaults mCreated = new SmartDate(DBUtil.CurrentWorkingDateTime); mModified = new SmartDate(); mCreator = Guid.Empty; mModifier = Guid.Empty; mMaps = IntegrationMaps.NewItems(); mLastConnect = new SmartDate(); AppID = Guid.Empty; //pre-break name rule Name = ""; AppVersion = ""; } #endregion #region Business properties /// /// Internal Unique GUID value of Integration record in database /// public Guid ID { get { return mID; } } /// /// Get created date /// /// /// public string Created { get { return mCreated.ToString(); } } /// /// Get modified date /// /// /// public string Modified { get { return mModified.ToString(); } } /// /// Get user record ID of person who created this record /// /// /// public Guid Creator { get { return mCreator; } } /// /// Get user ID of person who modified this record /// /// /// public Guid Modifier { get { return mModifier; } } /// /// Collection of Integration data mappings /// for this integration /// /// This collection is used to map id values between an external /// integration application and AyaNova. /// /// For example the optional QuickBooks interface uses this /// collection to map the AyaNova object type and ID of objects with /// their counterpart in QuickBooks. /// public IntegrationMaps Maps { get { return mMaps; } } /// /// Get /set active status of Integration /// /// This is set by an AyaNova user to turn on or off integration /// AyaNova does not and can not enforce this setting, it is up to the external /// program developer to honour this setting if applicable. /// /// It is intended to provide a method for the AyaNova user to temporarily /// disable integration for a particular application /// public bool Active { get { return mActive; } set { if (bReadOnly) ThrowSetError(); else { if (mActive != value) { mActive = value; MarkDirty(); } } } } /// /// The name should be a descriptive name of the product /// integrated with AyaNova. It is displayed to the user /// through the user interface when viewing the integration log /// and managing integration products. /// /// For example the AyaNova add on QBI for integrating with QuickBooks /// is identified as "AyaNova QBI" so users understand which application /// they are dealing with. /// /// This is a required field 1-255 Unicode characters /// public string Name { get { return mName; } set { if (bReadOnly) ThrowSetError(); else { if (mName != value) { mName = value; BrokenRules.Assert("NameRequired", "Error.Object.RequiredFieldEmpty,Integration.Label.Name", "Name", value.Length == 0); BrokenRules.Assert("NameLength", "Error.Object.FieldLengthExceeded255,Integration.Label.Name", "Name", value.Length > 255); MarkDirty(); } } } } /// /// DateTime field indicating the last time integration was performed /// Set by the external application. /// /// Implementers may use this date as required however it is strongly recommended /// that it be set to the last date and time that integration was performed as it will /// be displayed to the end user and may assist in troubleshooting. /// /// If a null or non existant date is desired set this value to /// System.DateTime.MinValue as that is the standard used in AyaNova for null dates /// /// public DateTime LastConnect { get { return mLastConnect.Date; } set { if (bReadOnly) ThrowSetError(); else { if (mLastConnect.Date != value) { mLastConnect.Date = value; MarkDirty(); } } } } /// /// Unique GUID application ID value /// This required field identifies the integration application /// uniquely. It should be a standard value used to identify the application /// for all users. In other words always stick to the same AppID value don't generate /// a unique one for each site using your integration application. /// /// Use the separate AppVersion field for version control /// public Guid AppID { get { return mAppID; } set { if (bReadOnly) ThrowSetError(); else { if (mAppID != value) { mAppID = value; BrokenRules.Assert("AppIDRequired", "Error.Object.RequiredFieldEmpty,Integration.AppID", "AppID", value == Guid.Empty); MarkDirty(); } } } } /// /// The required AppVersion field is used for version control /// by the external integrating application. /// /// It is displayed to the user /// through the user interface. /// /// Typically this will be a version value such as 2.4.4 /// however you can put anything you want in this field so /// "2008" is acceptible. /// /// This is a required field 1-25 unicode characters /// public string AppVersion { get { return mAppVersion; } set { if (bReadOnly) ThrowSetError(); else { if (mAppVersion != value) { mAppVersion = value; BrokenRules.Assert("AppVersionRequired", "Error.Object.RequiredFieldEmpty,Integration.Label.AppVersion", "AppVersion", value.Length == 0); BrokenRules.Assert("AppVersionLength", "Error.Object.FieldLengthExceeded,Integration.Label.AppVersion,25", "AppVersion", value.Length > 25); MarkDirty(); } } } } /// /// The SyncCheckPoint field is provided for the convenience of the /// external program to maintain a checkpoint for syncronization /// error recovery. /// /// /// For example the AyaNova add on QBI for integrating with QuickBooks /// uses this field for error recovery if there is an error processing /// a request. It contains the last ID of the last message sent to QuickBooks /// for processing so that recovery can be made in case of failure. /// /// You can store any data you wish in this field, it is not displayed in the /// user interface of AyaNova /// /// /// This is an optional field 0-255 ASCII characters /// public string SyncCheckPoint { get { return mSyncCheckPoint; } set { if (bReadOnly) ThrowSetError(); else { if (mSyncCheckPoint != value) { mSyncCheckPoint = value; BrokenRules.Assert("SyncCheckPointLength", "Error.Object.FieldLengthExceeded255,Integration.Label.SyncCheckPoint", "SyncCheckPoint", value.Length > 255); MarkDirty(); } } } } /// /// Object for use by integration application to persist user settings /// /// You can store any data you wish in this object, it is /// serialized to and from the database in a binary blob / image field. /// /// Typically this would be used to persist an integrating applications /// settings. I.E. create a serializable class or structure to contain /// settings and store / retrieve via this field. /// /// Don't go overboard with this setting it *is* stored in the database and /// should not be used to store huge binary objects that do not change, this will /// only slow down your application and bloat the users database un-necessarily. /// /// 32k or less is a good rule of thumb /// /// public object AIObject { get { return mAIObject; } set { if (bReadOnly) ThrowSetError(); else { // if (mAIObject != value) // { mAIObject = value; MarkDirty(); // } } } } /// /// Throw an error when a read only user /// tries to set a property /// (this should normally never be called unless someone is using the developer api since the UI /// should prevent it from happening initially) /// private void ThrowSetError() { throw new System.Security.SecurityException ( string.Format ( LocalizedTextTable.GetLocalizedTextDirect("Error.Security.NotAuthorizedToChange"), LocalizedTextTable.GetLocalizedTextDirect("O.Integration") ) ); } #endregion #region System.object overrides /// /// /// /// public override string ToString() { return "Integration" + mID.ToString(); } /// /// /// /// /// public override bool Equals(Object obj) { if (obj == null || GetType() != obj.GetType()) return false; Integration c = (Integration)obj; return mID == c.mID; } /// /// /// /// public override int GetHashCode() { return ("Integration" + mID).GetHashCode(); } #endregion #region Static methods /// /// Test for existance of integration for /// specified app id. /// /// Use this to ensure that your integration data /// exists in the database before attempting to fetch it /// or create it. /// /// Unique application ID /// public static bool IntegrationExists(Guid AppID) { return IntegrationExistanceChecker.IntegrationExists(AppID); } /// /// Create new Integration /// /// Uniqe integrating application ID. This must never change once used. /// Empty integration object public static Integration NewItem(Guid AppID) { Integration c; c = new Integration(); c.AppID = AppID; return c; } /// /// Fetch existing Integration /// /// Integration /// Integration Guid public static Integration GetItem(Guid ID) { return (Integration)DataPortal.Fetch(new Criteria(ID)); } /// /// Delete Integration /// /// Integration applicationID public static void DeleteItem(Guid ID) { DataPortal.Delete(new Criteria(ID)); } #endregion #region DAL DATA ACCESS #region Fetch /// /// protected override void DataPortal_Fetch(object Criteria) { Criteria crit = (Criteria)Criteria; SafeDataReader dr = null; try { dr = DBUtil.GetReaderFromSQLString("SELECT * FROM aIntegration WHERE AAPPID=@ID;", crit.ID); if (!dr.Read()) DBUtil.ThrowFetchError("Integration ID: " + crit.ID.ToString()); //Standard fields mID = dr.GetGuid("aID"); mCreated = DBUtil.ToLocal(dr.GetSmartDate("aCreated")); mModified = DBUtil.ToLocal(dr.GetSmartDate("aModified")); mCreator = dr.GetGuid("aCreator"); mModifier = dr.GetGuid("aModifier"); //Integration fields mActive = dr.GetBoolean("AACTIVE"); AppID = dr.GetGuid("AAPPID"); Name = dr.GetString("aName"); AppVersion = dr.GetString("AAPPVERSION"); mSyncCheckPoint = dr.GetString("aSyncCheckPoint"); mLastConnect = DBUtil.ToLocal(dr.GetSmartDate("aLastConnect")); //Get the AIObject //Get the layout size int mAIObjectSize = dr.GetInt32("aIObjectSize"); //Is there anything to load? if (mAIObjectSize > 0) { BinaryFormatter bformatter = new BinaryFormatter(); bformatter.AssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple; //Keeping this for future if necessary //was considering it for RI but instead made IntegrationSimple //bformatter.Binder = new CurrentAssemblyDeserializationBinder(); byte[] obData = new Byte[mAIObjectSize]; //retrieve the bytes dr.GetBytes("aIObject", 0, obData, 0, mAIObjectSize); MemoryStream mstream = new MemoryStream(obData); //intentionally not wrapped in a try block as this should bubble to the caller app this.mAIObject = (object)bformatter.Deserialize(mstream); } else this.mAIObject = null; if (dr != null) dr.Close(); //Load child collection objects //Maps dr = DBUtil.GetReaderFromSQLString( "SELECT * " + "FROM aIntegrationMap WHERE aIntegrationMap.aIntegrationID=@ID;" , mID); mMaps = IntegrationMaps.GetItems(dr); if (dr != null) dr.Close(); } finally { if (dr != null) dr.Close(); } MarkOld(); IntegrationLog.Log(this.mID, "Fetch: " + this.Name + " " + this.mAppID.ToString()); } #endregion fetch #region Update /// /// Called by DataPortal to delete/add/update data into the database /// protected override void DataPortal_Update() { // If not a new record, check if record was modified //by another user since original retrieval: if (!IsNew) DBUtil.CheckSafeToUpdate(this.mModified.Date, this.mID, "aIntegration"); #region Delete if (IsDeleted) { if (!IsNew) { IntegrationLog.Log(this.mID, "Delete: " + this.Name + " " + this.mAppID.ToString()); //Delete object and child objects DBCommandWrapper cmDelete = DBUtil.GetCommandFromSQL("DELETE FROM aIntegration WHERE AAPPID = @ID;"); cmDelete.AddInParameter("@ID", DbType.Guid, this.mAppID); DBCommandWrapper cmDeleteMaps = DBUtil.GetCommandFromSQL("DELETE FROM aIntegrationMap WHERE aIntegrationID = @ID;"); cmDeleteMaps.AddInParameter("@ID", DbType.Guid, this.mID);//case 1287 was using appid here not integration id using (IDbConnection connection = DBUtil.DB.GetConnection()) { connection.Open(); IDbTransaction transaction = connection.BeginTransaction(); try { DBUtil.DB.ExecuteNonQuery(cmDeleteMaps, transaction); DBUtil.DB.ExecuteNonQuery(cmDelete, transaction); // Commit the transaction transaction.Commit(); } catch { // Rollback transaction transaction.Rollback(); throw; } finally { connection.Close(); } } //----------------------------- } MarkNew(); return; } #endregion #region Add / Update /* * AID D_GUID NOT NULL, ACREATED TIMESTAMP, AMODIFIED TIMESTAMP, ACREATOR D_GUID, AMODIFIER D_GUID, AACTIVE D_BOOL NOT NULL CHECK (VALUE IN(0,1)), ANAME VARCHAR(255) CHARACTER SET UNICODE_FSS NOT NULL COLLATE UNICODE_FSS, ALASTCONNECT TIMESTAMP, AIOBJECT BLOB SEGMENT SIZE 1, AIOBJECTSIZE INTEGER, AAPPID D_GUID NOT NULL, AAPPVERSION VARCHAR(25) CHARACTER SET UNICODE_FSS NOT NULL COLLATE UNICODE_FSS, ASYNCCHECKPOINT VARCHAR(255) CHARACTER SET ASCII COLLATE ASCII); */ //get modification time temporarily, if update succeeds then //set to this time System.DateTime dtModified = DBUtil.CurrentWorkingDateTime; DBCommandWrapper cm = null; if (IsNew)//Add or update? cm = DBUtil.GetCommandFromSQL( "INSERT INTO aIntegration (aID, AACTIVE, aCreated,aModified,aCreator,aModifier, " + "aName,aLastConnect,aIObject,aIObjectSize,AAPPID,AAPPVERSION,aSyncCheckPoint) " + "VALUES (@ID,@Active,@Created,@Modified,@CurrentUserID,@CurrentUserID, " + "@Name,@LastConnect,@IObject,@IObjectSize,@AppID,@AppVersion,@SyncCheckPoint)" ); else cm = DBUtil.GetCommandFromSQL( "UPDATE aIntegration SET aID=@ID, " + "AACTIVE=@Active,aModified=@Modified,aModifier=@CurrentUserID,aName=@Name, " + "aLastConnect=@LastConnect,aIObject=@IObject,aIObjectSize=@IObjectSize, " + "AAPPID=@AppID,AAPPVERSION=@AppVersion,aSyncCheckPoint=@SyncCheckPoint " + "WHERE AAPPID=@AppID" ); //Integration fields cm.AddInParameter("@ID", DbType.Guid, mID); cm.AddInParameter("@Active", DbType.Boolean, mActive); cm.AddInParameter("@Name", DbType.String, mName); cm.AddInParameter("@LastConnect", DbType.DateTime, DBUtil.ToUTC(mLastConnect).DBValue); cm.AddInParameter("@AppID", DbType.Guid, this.mAppID); //AIObject serialize: if (mAIObject != null) { MemoryStream ms = new MemoryStream(); BinaryFormatter b = new BinaryFormatter(); b.AssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple; b.Serialize(ms, mAIObject); cm.AddInParameter("@IObjectSize", DbType.Int32, ms.Length); cm.AddInParameter("@IObject", DbType.Object, ms.GetBuffer()); } else { cm.AddInParameter("@IObjectSize", DbType.Int32, 0); cm.AddInParameter("@IObject", DbType.Object, new byte[1]); } cm.AddInParameter("@AppVersion", DbType.String, this.mAppVersion); cm.AddInParameter("@SyncCheckPoint", DbType.String, this.mSyncCheckPoint); //Standard fields cm.AddInParameter("@CurrentUserID", DbType.Guid, CurrentUserID); cm.AddInParameter("@Created", DbType.DateTime, DBUtil.ToUTC(mCreated).DBValue); cm.AddInParameter("@Modified", DbType.DateTime, DBUtil.ToUTC(dtModified)); using (IDbConnection connection = DBUtil.DB.GetConnection()) { connection.Open(); IDbTransaction transaction = connection.BeginTransaction(); try { DBUtil.DB.ExecuteNonQuery(cm, transaction); //Update child objects mMaps.Update(this, transaction); MarkOld();//db is now synched with object // Commit the transaction transaction.Commit(); } catch (Exception ex) { IntegrationLog.Log(this.mAppID, "Update exception: " + this.Name + " " + this.mAppID.ToString() + "\r\n" + ex.Message); // Rollback transaction transaction.Rollback(); throw; } finally { connection.Close(); } //Successful update so //change modification time to match this.mModified.Date = dtModified; } IntegrationLog.Log(this.mAppID, "Saved: " + this.Name + " " + this.mAppID.ToString()); #endregion } #endregion update #region Delete /// /// Remove a Integration record . /// /// protected override void DataPortal_Delete(object Criteria) { Criteria crit = (Criteria)Criteria; //case 1981 changed from original block IntegrationExistanceChecker exists = IntegrationExistanceChecker.IntegrationExistsEx(crit.ID); if (!exists.Exists) return; Guid integrationInternalId = exists.InternalID; //Delete object and child objects DBCommandWrapper cmDelete = DBUtil.GetCommandFromSQL("DELETE FROM aIntegration WHERE AAPPID = @ID;"); cmDelete.AddInParameter("@ID", DbType.Guid, crit.ID); DBCommandWrapper cmDeleteMaps = DBUtil.GetCommandFromSQL("DELETE FROM aIntegrationMap WHERE aIntegrationID = @ID;"); cmDeleteMaps.AddInParameter("@ID", DbType.Guid, integrationInternalId);//case 1287 using (IDbConnection connection = DBUtil.DB.GetConnection()) { connection.Open(); IDbTransaction transaction = connection.BeginTransaction(); try { DBUtil.DB.ExecuteNonQuery(cmDeleteMaps, transaction); DBUtil.DB.ExecuteNonQuery(cmDelete, transaction); // Commit the transaction transaction.Commit(); } catch { // Rollback transaction transaction.Rollback(); throw; } finally { connection.Close(); } } } #endregion delete #endregion #region Override IsValid / IsDirty /// /// /// public override bool IsValid { get { return base.IsValid && mMaps.IsValid; } } /// /// /// public override bool IsDirty { get { return base.IsDirty || mMaps.IsDirty; } } #endregion #region criteria /// /// Criteria for identifying existing object /// [Serializable] private class Criteria { public Guid ID; public Criteria(Guid _ID) { ID = _ID; } } #endregion }//end Integration #region Deserializer helper ///// ///// Custom deserialization binder to handle version conflicts from the mistake of using binary formatter in the first place ///// //public sealed class CurrentAssemblyDeserializationBinder : System.Runtime.Serialization.SerializationBinder //{ // public override Type BindToType(string assemblyName, string typeName) // { // return Type.GetType(String.Format("{0}, {1}", typeName, System.Reflection.Assembly.GetExecutingAssembly().FullName)); // } //} #endregion }//end namespace GZTW.AyaNova.BLL