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

601 lines
21 KiB
C#

///////////////////////////////////////////////////////////
// SearchResultList.cs
// Implementation of Class SearchResultList
// CSLA type: Read only collection
// Created on: 19-April-2005
// Object design: John
// Coded: 19-April-2005
///////////////////////////////////////////////////////////
using System;
using System.Data;
using GZTW.Data;
using CSLA.Data;
using CSLA;
using System.Text;
using System.Collections.Generic;
//using log4net;
namespace GZTW.AyaNova.BLL
{
/// <summary>
/// Read only collection of <see cref="SearchResultList.SearchResultListInfo"/> objects
/// </summary>
[Serializable]
public class SearchResultList : ReadOnlyCollectionBase
{
// Create a logger for use in this class
//private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
//case 981
/// <summary>
/// List of object id's if performing GetPickListForObjectType search
/// </summary>
public List<Guid> ListPickListID = new List<Guid>();
#region Data structure
/// <summary>
///
/// </summary>
[Serializable]
public struct SearchResultListInfo
{
internal Guid mRootObjectID;
internal RootObjectTypes mRootObjectType;
internal Guid mAncestorRootObjectID;
internal RootObjectTypes mAncestorRootObjectType;
internal string mExtract;
internal string mDescription;
internal float mRank;
internal GridNameValueCellItem mSource;
//Public properties
/// <summary>
/// Object id most closely associated with search result (may not be a directly openable object, that's what ancestor style is for)
/// </summary>
[Display(DisplayType.Hidden)]
public Guid RootObjectID {get{return mRootObjectID;}}
/// <summary>
/// Object type most closely associated with search result
/// </summary>
[Display(DisplayType.Hidden)]
public RootObjectTypes RootObjectType {get{return mRootObjectType;}}
/// <summary>
/// Object to open to view this search results original record
/// If this object is not a child or grandchild, then this is the same as RootObjectID
/// </summary>
[Display(DisplayType.Hidden)]
public Guid AncestorRootObjectID {get{return mAncestorRootObjectID;}}
/// <summary>
/// Object type to open to view this search results original record
/// </summary>
[Display(DisplayType.Hidden)]
public RootObjectTypes AncestorRootObjectType {get{return mAncestorRootObjectType;}}
/// <summary>
/// A brief excerpt automatically generated that tries to show the most relevant block of text
/// in the search result for the given search string
/// </summary>
[Display(DisplayType.Text)]
public string LT_SearchResult_Label_Extract {get{return mExtract;}}
/// <summary>
/// Description of search result
/// </summary>
[Display(DisplayType.Text)]
public string LT_SearchResult_Label_Description {get{return mDescription;}}
/// <summary>
/// Ranking of search result in order of how closely it matches the searched for string of text
/// See <see cref="ExtractAndRank"/> class for an explanation of this value
/// </summary>
[Display(DisplayType.DecimalNumber)]
public float LT_SearchResult_Label_Rank{get{return mRank;}}
internal SmartDate mCreated;
/// <summary>
/// Date and time object found that matches was created
/// </summary>
[Display(DisplayType.DateTime)]
public object LT_Common_Label_Created {get{return mCreated.DBValue;}}
internal SmartDate mModified;
/// <summary>
/// Date and time object found that matches was last modified
/// </summary>
[Display(DisplayType.DateTime)]
public object LT_Common_Label_Modified {get{return mModified.DBValue;}}
//Case 194
//internal Guid mCreator;
//[Display(DisplayType.ListUsers)]
//public Guid LT_Common_Label_Creator {get{return mCreator;}}
//internal Guid mModifier;
//[Display(DisplayType.ListUsers)]
//public Guid LT_Common_Label_Modifier {get{return mModifier;}}
internal string mCreator;
/// <summary>
/// Creator of matching found object record
/// </summary>
[Display(DisplayType.Text)]
public string LT_Common_Label_Creator { get { return mCreator; } }
internal string mModifier;
/// <summary>
/// Modifier of matching found object record
/// </summary>
[Display(DisplayType.Text)]
public string LT_Common_Label_Modifier { get { return mModifier; } }
/// <summary>
/// Descriptive text and id of openable item to view source of search result
/// (used to create search result list buttons in AyaNova UI)
/// </summary>
[Display(DisplayType.Button, RootObjectType = RootObjectTypes.SearchResult)]
public GridNameValueCellItem LT_SearchResult_Label_Source
{
get
{
return mSource;
}
}
/// <summary>
///
/// </summary>
/// <param name="obj"></param>
public bool Equals(SearchResultListInfo obj)
{
return this.mRootObjectID.Equals(obj.mRootObjectID);
}
}//end SearchResultListInfo
#endregion
#region Constructor
/// <summary>
///
/// </summary>
protected SearchResultList()
{
////case 1039 //log.Debug("SearchResultList()");
// AllowSort=false;
// AllowFind=true;
// AllowEdit=false;
// AllowNew=false;
// AllowRemove=false;
}
#endregion
#region Business properties and methods
/// <summary>
/// Get item by index
/// </summary>
/// <param name="Item"></param>
public SearchResultListInfo this[int Item]
{
get
{
return (SearchResultListInfo) List[Item];
}
}
/// <summary>
/// Returns SearchResultListInfo item that matches passed in itemid value
/// </summary>
/// <param name="ItemID"></param>
public SearchResultListInfo this[Guid ItemID]
{
get
{
foreach (SearchResultListInfo child in List)
{
if(child.mRootObjectID==ItemID) return child;
}
throw new ArgumentException("SearchResultList: ID not found:\r\n"+ItemID.ToString());
}
}
#endregion
#region contains
/// <summary>
/// Check if item in collection
/// </summary>
/// <param name="obj"></param>
public bool Contains(SearchResultListInfo obj)
{
foreach (SearchResultListInfo child in List)
{
if(child.Equals(obj)) return true;
}
return false;
}
#endregion
#region Reporting and shared UI editor helpers
/// <summary>
/// Returns the report key which is a property of
/// reports used to link all reports that can be used
/// with a particular data source.
/// </summary>
public static string ReportKey
{
get
{
return "SearchResultList";
}
}
/// <summary>
/// Returns the Detailed report key
/// which is used to determine which reports and objects
/// will be used for detailed reports
///
/// If empty string then indicates there is no detailed report object or reports
/// </summary>
public static string DetailedReportKey
{
get
{
return "";
}
}
/// <summary>
/// Base object that this list is reporting on
/// used by shared UI editor to instantiate new objects
/// when user selects new in UI elements that display this list
///
/// (I.E. when user clicks on new in a read only list grid, this is the object type created)
/// </summary>
public static RootObjectTypes BaseObjectType
{
get
{
return RootObjectTypes.SearchResult;
}
}
/// <summary>
/// Locale key so that generic list editor
/// UI code knows what title to give the list in a
/// grid
/// </summary>
public string LocaleKey
{
get
{
return "";
}
}
/// <summary>
/// The Type of the struct used to store list records
/// Used to fetch the custom display attributes of the fields
/// contained within the record to modify the grid display accordingly
/// <see cref="DisplayAttribute"/>
/// </summary>
public static Type ListRecordType
{
get
{
return typeof(SearchResultListInfo);
}
}
/// <summary>
/// Field that contains the ID of the objects
/// that are the basis of this list.
///
/// Used for compiling an ID list for reporting from user
/// selections in a grid.
/// </summary>
public static string IDField
{
get
{
return "RootObjectID";
}
}
/// <summary>
/// Same as IDField but for detailed reports
/// </summary>
public static string IDFieldDetailed
{
get
{
return IDField;
}
}
#endregion
#region Static methods
/// <summary>
/// Internal method used by list factory
/// </summary>
internal static SearchResultList Get(string Filter, int MaxRecords, List<Guid> IDList)
{
if (Filter == null) return new SearchResultList();
if (Filter == "") return new SearchResultList();
//case 2073 was stripping out apostrophes when it should because that vary's from how the
//word breaker originally broked the text they are likely searching for (i.e. "Mary's place")
//return (SearchResultList)DataPortal.Fetch(new Criteria(Filter.Replace("'", "").Replace("\"", ""),IDList, MaxRecords,RootObjectTypes.Nothing));
return (SearchResultList)DataPortal.Fetch(new Criteria(Filter.Replace("\"", ""), IDList, MaxRecords, RootObjectTypes.Nothing));
}
/// <summary>
/// Process search and return list of results
/// </summary>
/// <returns>collection of <see cref="SearchResultList.SearchResultListInfo"/> objects</returns>
public static SearchResultList GetList(string Search)
{
////case 1039 //log.Debug("GetList("+Search+")");
//if empty search return empty list
if(Search==null) return new SearchResultList();
if(Search=="") return new SearchResultList();
//scrub search string for sql injection attacks
//and pass to fetch through criteria
return (SearchResultList) DataPortal.Fetch(new Criteria(Search.Replace("\"",""),null,-1,RootObjectTypes.Nothing));
}
/// <summary>
/// Return an empty list
/// used for initializing grid
/// </summary>
/// <returns></returns>
public static SearchResultList GetEmptyList()
{
return new SearchResultList();
}
//case 981
/// <summary>
/// Special search format:
/// Instead of a normal search that returns the
/// object that *contains* the search text, instead,
/// this search will attempt to connect the object that does contain
/// the search term to the object type desired. If it can't
/// then no result is returned even if the object found contains the search term.
///
/// This can be used, for example, to return a list of clients who can be
/// connected in any way to related objects that contain the search term.
///
/// So for example if a user is looking for a client they can enter text they know appears
/// somewhere on a workorder for that client, the search will find the work order
/// and walk up the workorder heiarchy to retrieve the client ID
///
/// This type of search is used for the "finder" feature in AyaNova
///
/// This search format, does not return a regular search result list
/// insead it populates the ListPickListID list.
///
/// (only unique id results are returned / no duplicates and no empty id's)
/// </summary>
/// <param name="Search">Search terms</param>
/// <param name="objectType">Only return results that can be linked to this object type (object types will be added as required)</param>
/// <returns>A list with RootObjectID *only* populated, all other fields are empty</returns>
public static SearchResultList GetPickListForObjectType(string Search, RootObjectTypes objectType)
{
if (objectType != RootObjectTypes.Client)
throw new System.NotSupportedException(objectType.ToString() + " is not currently supported. Object types supported are: Client");
////case 1039 //log.Debug("GetList("+Search+")");
//if empty search return empty list
if (Search == null) return new SearchResultList();
if (Search == "") return new SearchResultList();
if (objectType == RootObjectTypes.Nothing) return new SearchResultList();
//scrub search string for sql injection attacks
//and pass to fetch through criteria
//case 2073
return (SearchResultList)DataPortal.Fetch(new Criteria(Search.Replace("\"", ""), null, -1, objectType));
}
#endregion
#region DAL DATA ACCESS
///
/// <param name="Criteria"></param>
protected override void DataPortal_Fetch(object Criteria)
{
////case 1039 //log.Debug("DataPortal_Fetch");
Criteria crit = (Criteria)Criteria;
if(crit.Search=="") return;
//sql ize it
crit.Search=crit.Search.Replace("*","%");
StringBuilder sbSQL= new StringBuilder();
string[] saTerms=AyaBizUtils.BreakSearchPhrase(crit.Search).Split(',');
if(saTerms==null || saTerms.GetLength(0)==0) return;
//convert search terms to query
string q="SELECT ~MAXRECS~ aSearchKey.aSourceObjectID, aSearchKey.aSourceObjectType " +
"FROM aSearchDictionary INNER " +
"JOIN aSearchKey ON aSearchDictionary.aID " +
"= aSearchKey.aWordID WHERE ";
if (crit.MaxRecords > 0)
q = q.Replace("~MAXRECS~", "TOP " + crit.MaxRecords.ToString());
else
q = q.Replace("~MAXRECS~", "");
sbSQL.Append(q);
//case 3581
string insertNationalTypeQueryCharacter = string.Empty;
if (DBUtil.DB.DBType == DataBaseType.MSSQL)
{
insertNationalTypeQueryCharacter = "N";
}
foreach (string sTerm in saTerms)
{
if (sTerm.IndexOf("%") != -1)
sbSQL.Append(" (aSearchDictionary.aWord Like " + insertNationalTypeQueryCharacter + "'" + sTerm + "') OR");//case 3581
else
sbSQL.Append(" (aSearchDictionary.aWord = " + insertNationalTypeQueryCharacter + "'" + sTerm + "') OR");//case 3581
}
//trim off the final "OR"
sbSQL.Length=sbSQL.Length-2;
if (crit.IDList != null)
{
//Case 556
System.Text.StringBuilder sbIN = new System.Text.StringBuilder();
sbIN.Append(" AND (aSearchKey.aSourceObjectID in (");
foreach (Guid gItem in crit.IDList)
{
sbIN.Append("'");
sbIN.Append("{");
sbIN.Append(gItem.ToString().ToUpperInvariant());
sbIN.Append("}");
sbIN.Append("',");
}
sbIN.Length = sbIN.Length - 1;
sbIN.Append(")) ");
sbSQL.Append(sbIN.ToString());
}
sbSQL.Append(" GROUP BY aSearchKey.aSourceObjectID, " +
"aSearchKey.aSourceObjectType HAVING (COUNT(*) " +
"= " + saTerms.GetLength(0).ToString() +")");
//------------------
//Case 194
UserPickList upl = null;
if (crit.objectType == RootObjectTypes.Nothing)//case 981
upl = UserPickList.GetList(false);
SafeDataReader dr = null;
ListPickListID.Clear();
try
{
dr=DBUtil.GetReaderFromSQLString(sbSQL.ToString());
while (dr.Read())
{
//standard search as before?
if (crit.objectType == RootObjectTypes.Nothing)//case 981
{
//standard search
SearchResult sr = AyaBizUtils.GetSearchResultFor((RootObjectTypes)dr.GetInt16("aSourceObjectType"), dr.GetGuid("aSourceObjectID"), saTerms);
//sr.rank will equal zero if there is no good match or
//if the user has no rights to retrieve that object etc
if (sr.Rank > 0)
{
SearchResultListInfo info = new SearchResultListInfo();
info.mRootObjectID = dr.GetGuid("aSourceObjectID");
info.mRootObjectType = (RootObjectTypes)dr.GetInt16("aSourceObjectType");
info.mAncestorRootObjectID = sr.AncestorRootObjectID;
info.mAncestorRootObjectType = sr.AncestorRootObjectType;
info.mDescription = sr.Description;
info.mExtract = sr.Extract;
info.mRank = sr.Rank;
info.mModified = sr.Modified;
info.mModifier = upl[sr.Modifier];
info.mCreated = sr.Created;
info.mCreator = upl[sr.Creator];
info.mSource = new GridNameValueCellItem(
info.mAncestorRootObjectID,
EnumDescConverter.GetEnumDescription(info.mRootObjectType),
info.mAncestorRootObjectType);
InnerList.Add(info);
}
}
else
{
//Picklist search
// SearchResultListInfo info = new SearchResultListInfo();
// info.mRootObjectID = dr.GetGuid("aSourceObjectID");
// info.mRootObjectType = (RootObjectTypes)dr.GetInt16("aSourceObjectType");
// info.mAncestorRootObjectID = RelatedObjectIDFetcher.GetRelatedObjectID(new TypeAndID(info.mRootObjectType, info.mRootObjectID), crit.objectType);
List<Guid> items = RelatedObjectIDFetcher.GetRelatedObjectID(new TypeAndID((RootObjectTypes)dr.GetInt16("aSourceObjectType"), dr.GetGuid("aSourceObjectID")), crit.objectType);
foreach (Guid id in items)
{
if (!(id==Guid.Empty) && !ListPickListID.Contains(id))
ListPickListID.Add(id);
}
//if (info.AncestorRootObjectID == Guid.Empty || ListPickListID.Contains(info.AncestorRootObjectID))
// continue;
//ListPickListID.Add(info.AncestorRootObjectID);
//InnerList.Add(info);
}
}
}
finally
{
if(dr!=null) dr.Close();
}
}
#endregion
#region criteria
/// <summary>
/// Criteria for identifying existing object
/// </summary>
[Serializable]
private class Criteria
{
public List<Guid> IDList;
public string Search;
public int MaxRecords;
public RootObjectTypes objectType;
public Criteria(string _Search, List<Guid> _IDList, int _MaxRecords, RootObjectTypes _objectType)
{
Search = _Search;
MaxRecords = _MaxRecords;
IDList = _IDList;
objectType = _objectType;
}
}
#endregion
}//end SearchResultList
}//end namespace GZTW.AyaNova.BLL