Files
2018-06-29 19:47:36 +00:00

956 lines
30 KiB
C#

///////////////////////////////////////////////////////////
// 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
{
/// <summary>
/// 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.
///
///
/// </summary>
[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
/// <summary>
/// Private constructor to prevent direct instantiation
/// </summary>
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
/// <summary>
/// Internal Unique GUID value of Integration record in database
/// </summary>
public Guid ID
{
get
{
return mID;
}
}
/// <summary>
/// Get created date
///
///
/// </summary>
public string Created
{
get
{
return mCreated.ToString();
}
}
/// <summary>
/// Get modified date
///
///
/// </summary>
public string Modified
{
get
{
return mModified.ToString();
}
}
/// <summary>
/// Get user record ID of person who created this record
///
///
/// </summary>
public Guid Creator
{
get
{
return mCreator;
}
}
/// <summary>
/// Get user ID of person who modified this record
///
///
/// </summary>
public Guid Modifier
{
get
{
return mModifier;
}
}
/// <summary>
/// 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.
/// </summary>
public IntegrationMaps Maps
{
get
{
return mMaps;
}
}
/// <summary>
/// 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
/// </summary>
public bool Active
{
get
{
return mActive;
}
set
{
if (bReadOnly)
ThrowSetError();
else
{
if (mActive != value)
{
mActive = value;
MarkDirty();
}
}
}
}
/// <summary>
/// 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
/// </summary>
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();
}
}
}
}
/// <summary>
/// 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
///
/// </summary>
public DateTime LastConnect
{
get
{
return mLastConnect.Date;
}
set
{
if (bReadOnly)
ThrowSetError();
else
{
if (mLastConnect.Date != value)
{
mLastConnect.Date = value;
MarkDirty();
}
}
}
}
/// <summary>
/// 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
/// </summary>
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();
}
}
}
}
/// <summary>
/// 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
/// </summary>
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();
}
}
}
}
/// <summary>
/// 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
/// </summary>
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();
}
}
}
}
/// <summary>
/// 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
///
/// </summary>
public object AIObject
{
get
{
return mAIObject;
}
set
{
if (bReadOnly)
ThrowSetError();
else
{
// if (mAIObject != value)
// {
mAIObject = value;
MarkDirty();
// }
}
}
}
/// <summary>
/// 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)
/// </summary>
private void ThrowSetError()
{
throw new System.Security.SecurityException
(
string.Format
(
LocalizedTextTable.GetLocalizedTextDirect("Error.Security.NotAuthorizedToChange"),
LocalizedTextTable.GetLocalizedTextDirect("O.Integration")
)
);
}
#endregion
#region System.object overrides
/// <summary>
///
/// </summary>
/// <returns></returns>
public override string ToString()
{
return "Integration" + mID.ToString();
}
/// <summary>
///
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public override bool Equals(Object obj)
{
if (obj == null || GetType() != obj.GetType()) return false;
Integration c = (Integration)obj;
return mID == c.mID;
}
/// <summary>
///
/// </summary>
/// <returns></returns>
public override int GetHashCode()
{
return ("Integration" + mID).GetHashCode();
}
#endregion
#region Static methods
/// <summary>
/// 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.
/// </summary>
/// <param name="AppID">Unique application ID</param>
/// <returns></returns>
public static bool IntegrationExists(Guid AppID)
{
return IntegrationExistanceChecker.IntegrationExists(AppID);
}
/// <summary>
/// Create new Integration
/// </summary>
/// <param name="AppID">Uniqe integrating application ID. This must never change once used.</param>
/// <returns>Empty integration object</returns>
public static Integration NewItem(Guid AppID)
{
Integration c;
c = new Integration();
c.AppID = AppID;
return c;
}
/// <summary>
/// Fetch existing Integration
/// </summary>
/// <returns>Integration</returns>
/// <param name="ID">Integration Guid</param>
public static Integration GetItem(Guid ID)
{
return (Integration)DataPortal.Fetch(new Criteria(ID));
}
/// <summary>
/// Delete Integration
/// </summary>
/// <param name="ID">Integration applicationID</param>
public static void DeleteItem(Guid ID)
{
DataPortal.Delete(new Criteria(ID));
}
#endregion
#region DAL DATA ACCESS
#region Fetch
///
/// <param name="Criteria"></param>
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
/// <summary>
/// Called by DataPortal to delete/add/update data into the database
/// </summary>
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
/// <summary>
/// Remove a Integration record .
/// </summary>
/// <param name="Criteria"></param>
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
/// <summary>
///
/// </summary>
public override bool IsValid
{
get
{
return base.IsValid && mMaps.IsValid;
}
}
/// <summary>
///
/// </summary>
public override bool IsDirty
{
get
{
return base.IsDirty || mMaps.IsDirty;
}
}
#endregion
#region criteria
/// <summary>
/// Criteria for identifying existing object
/// </summary>
[Serializable]
private class Criteria
{
public Guid ID;
public Criteria(Guid _ID)
{
ID = _ID;
}
}
#endregion
}//end Integration
#region Deserializer helper
///// <summary>
///// Custom deserialization binder to handle version conflicts from the mistake of using binary formatter in the first place
///// </summary>
//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