/////////////////////////////////////////////////////////// // Project.cs // Implementation of Class IntegrationSimple // CSLA type: Editable Root // Created on: 12-Feb-2016 // Object design: John // Coded: John 12-Feb-2016 /////////////////////////////////////////////////////////// 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 { /// /// SIMPLE Integration object /// /// This is an easier and recommended alternative to the Integration object with no children and simple string persistance /// /// Use in all cases where a single string can hold your integration data such as configuration options /// for your integration project /// /// NOTE: IntegrationSimple and Integration are stored in the same database table but are not compatible /// errors will be thrown if you attempt to retrieve Integration data with an IntegrationSimple object and vice versa /// /// THIS OBJECT IS PREFERRED OVER USING THE Integration OBJECT /// (Integration uses a Binary formatter and may pose problems deserializing in some circumstances /// IntegrationSimple simply stores the string as a unicode object) /// /// This object is used for assisting with integrating 3rd party and /// AyaNova utility programs with AyaNova where only a small amount of textual data is required. /// /// 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 /// /// /// 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 IntegrationSimple : 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 string mAIString = string.Empty; //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 IntegrationSimple() { //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(); } } } } /// /// String for use by integration application to persist settings /// /// You can store any data you wish in this string, 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. /// /// 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 string AIString { get { return mAIString; } set { if (bReadOnly) ThrowSetError(); else { // if (mAIObject != value) // { mAIString = 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 "IntegrationSimple" + mID.ToString(); } /// /// /// /// /// public override bool Equals(Object obj) { if (obj == null || GetType() != obj.GetType()) return false; IntegrationSimple c = (IntegrationSimple)obj; return mID == c.mID; } /// /// /// /// public override int GetHashCode() { return ("IntegrationSimple" + mID).GetHashCode(); } #endregion #region Static methods /// /// Test for existance of integration/IntegrationSimple 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 IntegrationSimple object public static IntegrationSimple NewItem(Guid AppID) { IntegrationSimple c; c = new IntegrationSimple(); c.AppID = AppID; return c; } /// /// Fetch existing IntegrationSimple /// /// IntegrationSimple /// IntegrationSimple Guid public static IntegrationSimple GetItem(Guid ID) { return (IntegrationSimple)DataPortal.Fetch(new Criteria(ID)); } /// /// Delete IntegrationSimple /// /// IntegrationSimple 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) { byte[] obData = new Byte[mAIObjectSize]; //retrieve the bytes dr.GetBytes("aIObject", 0, obData, 0, mAIObjectSize); this.mAIString = System.Text.Encoding.Unicode.GetString(obData); } else this.mAIString = string.Empty; if (dr != null) dr.Close(); } finally { if (dr != null) dr.Close(); } MarkOld(); IntegrationLog.Log(this.mID, "(simple) 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, "(simple)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); //byte[] array = Encoding.ASCII.GetBytes(sLongDes); // supports other encoding //parameters[10] = new OleDbParameter("@LONG_DESCRIPTION", array); //parameters[10].OleDbType = OleDbType.LongVarBinary; //MemoryStream ms = new MemoryStream(System.Text.Encoding.Unicode.GetBytes(sContent)) //cm.AddInParameter("@OBJECT", DbType.Object, mContent); //cm.AddInParameter("@OBJECTSIZE", DbType.Int32, mContent.GetLength(0)); //AIObject serialize: if (string.IsNullOrWhiteSpace(mAIString)) mAIString = "NO DATA"; //save string as unicode byte array byte[] aiData = System.Text.Encoding.Unicode.GetBytes(mAIString); cm.AddInParameter("@IObjectSize", DbType.Int32, aiData.GetLength(0)); cm.AddInParameter("@IObject", DbType.Object, aiData); 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); MarkOld();//db is now synched with object // Commit the transaction transaction.Commit(); } catch (Exception ex) { IntegrationLog.Log(this.mAppID, "(simple) 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, "(simple) 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 }//end namespace GZTW.AyaNova.BLL