using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Data;
using System.Data.Odbc;
using System.Data.OleDb;
using System.Data.SqlClient;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml.Linq;
using Neo.Afx.ComponentModel;
using SystemData = System.Data;
using System.IO;
using Neo.Afx.Common;
namespace Neo.Afx.Services.Integration
{
// ReSharper disable LoopCanBeConvertedToQuery
//
// DO NOT use LINQ statements as the compiler generated code for these may contain
// static fields which are not allowed by SQL 2005 (unless they are read-only)
//
///
/// A data store for 's.
///
public abstract partial class IntegrationStore
{
readonly static IEnumerable EmptyResult = new XElement[0];
const char ParameterPrefix = '@';
const string ParameterMatchPattern = @"@{1,}\w*"; // matches @ or @anyword or @@anyword or @@@anyword, etc.
const string EntityTag = "e";
const string EntityCollectionTag = "d";
const string CachePrefix = "IntegrationStore_";
const string CacheKeyIsCacheEnabled = CachePrefix + "IsCacheEnabled";
const string CacheKeyDataSources = CachePrefix + "DataSources";
const int CacheTimeout = 1440;
#region Initialization
///
/// Initializes a new instance of the class.
///
/// The connection string.
protected IntegrationStore(string connectionString)
{
ConnectionString = connectionString;
}
#endregion
#region ConnectionString
///
/// Gets the connection string.
///
public string ConnectionString
{
get;
private set;
}
#endregion
#region DataSources
///
/// Gets the dictionary of .
///
Dictionary DataSources
{
get;
set;
}
#endregion
#region IsCacheEnabled
///
/// Gets or sets a value indicating whether caching is enabled.
///
public bool IsCacheEnabled
{
get;
private set;
}
#endregion
#region CacheContext
///
/// Gets or sets the .
///
public ICacheContext CacheContext
{
get;
set;
}
#endregion
#region GetData
///
/// Gets the data from the named data source.
///
/// The must be invoked before calling this method.
///
///
/// The name of the data source to be used.
/// The query string to be used.
/// The request query - overrides the database value if specified.
/// The data from the named data source.
public XElement GetData(string name, string queryString, string requestQuery = null)
{
EnsureCache();
var ds = GetDataSource(name);
if(ds == null)
{
return ToEntitiesXml(EmptyResult);
}
if(!string.IsNullOrEmpty(requestQuery))
{
ds.RequestQuery = requestQuery;
}
var cacheKey = CachePrefix + name;
var isCacheEnabled = CacheContext != null && IsCacheEnabled && ds.IsCacheEnabled;
if(isCacheEnabled)
{
var cachedEntities = CacheContext.Get(cacheKey);
if(cachedEntities != null)
{
return cachedEntities;
}
}
var resultList = EmptyResult;
var queryStringArgs = queryString.ToNameValueCollection();
switch(ds.ConnectionTypeEnum)
{
case ConnectionTypeEnum.SqlServer:
case ConnectionTypeEnum.OleDb:
case ConnectionTypeEnum.Odbc:
case ConnectionTypeEnum.Oracle:
resultList = ExecuteDbRequest(ds, queryStringArgs);
break;
case ConnectionTypeEnum.Web:
resultList = ExecuteWebRequest(ds, queryStringArgs);
break;
}
XElement result;
if(ds.EntitySchemaDoc == null)
{
result = resultList.FirstOrDefault();
}
else
{
var top = queryStringArgs["top"];
int count;
Int32.TryParse(top, out count);
if(count > 0)
{
resultList = resultList.Take(count);
}
result = ToEntitiesXml(resultList);
}
if(isCacheEnabled)
{
CacheContext.Set(cacheKey, result, ds.CacheMinutes);
}
return result;
}
#endregion
/////////////////////////////////////////////////////////////////////////////
// Abstract methods
/////////////////////////////////////////////////////////////////////////////
#region Open
///
/// Establishes a connection to the data store.
///
/// Must be invoked before the first call to any read method.
///
///
public abstract void Open();
#endregion
#region Read
///
/// Gets a dictionary of all from the data store.
///
/// must be invoked before the first call to this.
///
///
/// A dictionary of all keyed by name.
protected abstract Dictionary Read();
///
/// Reads the from the data store.
///
/// must be invoked before the first call to this.
///
///
/// The name of the data source to be read.
/// A if the read was successful; null otherwise.
protected abstract DataSource Read(string dataSourceName);
#endregion
#region ReadCacheEnabled
///
/// Reads from the data store whether caching is enabled globally.
///
/// must be invoked before the first call to this.
///
///
/// true if caching is enabled; false otherwise.
protected abstract bool ReadCacheEnabled();
#endregion
#region Close
///
/// Closes connection to the data store
///
/// This should dispose of managed resources e.g. connections that were created when was invoked.
/// This method is automatically called when this object is disposed.
///
///
public abstract void Close();
#endregion
/////////////////////////////////////////////////////////////////////////////
// Helper methods for caching
/////////////////////////////////////////////////////////////////////////////
#region ClearCache
///
/// Clears the cache.
///
public void ClearCache()
{
if(CacheContext != null)
{
CacheContext.Clear();
}
}
///
/// Removes the given data source from the cache.
///
/// The name of the data source to be removed.
public void ClearCache(string dataSourceName)
{
if(CacheContext != null)
{
CacheContext.Remove(CachePrefix + dataSourceName);
}
}
#endregion
#region EnsureCache
void EnsureCache()
{
IsCacheEnabled = false;
DataSources = null;
//////if(CacheContext != null)
//////{
////// var result = CacheContext.Get(CacheKeyIsCacheEnabled);
////// if(result == null)
////// {
////// IsCacheEnabled = ReadCacheEnabled();
////// CacheContext.Set(CacheKeyIsCacheEnabled, IsCacheEnabled, CacheTimeout);
////// }
////// else
////// {
////// IsCacheEnabled = (bool)result;
////// }
////// if(IsCacheEnabled)
////// {
////// result = CacheContext.Get(CacheKeyDataSources);
////// if(result == null)
////// {
////// DataSources = Read();
////// CacheContext.Set(CacheKeyDataSources, DataSources, CacheTimeout);
////// }
////// else
////// {
////// DataSources = (Dictionary)result;
////// }
////// }
//////}
}
#endregion
/////////////////////////////////////////////////////////////////////////////
// Helper methods for creating a data source
/////////////////////////////////////////////////////////////////////////////
#region GetDataSource
DataSource GetDataSource(string dataSourceName)
{
return IsCacheEnabled ? DataSources[dataSourceName] : Read(dataSourceName);
}
#endregion
#region CreateDataSource
///
/// Creates the using the given function to retrieve field values.
///
/// The function used to retrieve field values.
/// A or null if is null.
protected static DataSource CreateDataSource(Func getValue)
{
if(getValue != null)
{
var dataSource = new DataSource
{
DataSourceID = Int32.Parse(getValue(DataSourceField.DataSourceID)),
DataSourceName = getValue(DataSourceField.DataSourceName),
ConnectionTypeEnum = (ConnectionTypeEnum)Enum.Parse(typeof(ConnectionTypeEnum), getValue(DataSourceField.ConnectionType), true),
CommandTypeEnum = (CommandTypeEnum)Enum.Parse(typeof(CommandTypeEnum), getValue(DataSourceField.CommandType), true),
ConnectionString = getValue(DataSourceField.ConnectionString),
RequestQuery = getValue(DataSourceField.RequestQuery),
ResponseXsl = getValue(DataSourceField.ResponseXsl),
EntitySchemaDoc = getValue(DataSourceField.EntitySchema).ToXElement(),
IsCacheEnabled = getValue(DataSourceField.IsCacheEnabled).ToBoolean(),
CacheMinutes = Int32.Parse(getValue(DataSourceField.CacheMinutes)),
AuthTypeEnum = (AuthTypeEnum)Enum.Parse(typeof(AuthTypeEnum), getValue(DataSourceField.AuthType), true),
AuthDomain = getValue(DataSourceField.AuthDomain),
AuthUser = getValue(DataSourceField.AuthUser),
AuthPassword = getValue(DataSourceField.AuthPassword)
};
AppendRequestHeaders(dataSource, getValue(DataSourceField.RequestHeaders));
SetProperties(dataSource);
return dataSource;
}
return null;
}
#endregion
#region AppendRequestHeaders
static void AppendRequestHeaders(DataSource dataSource, string requestHeaders)
{
if(!string.IsNullOrEmpty(requestHeaders))
{
var headers = XElement.Parse(requestHeaders);
foreach(var header in headers.Elements())
{
// ReSharper disable PossibleNullReferenceException
dataSource.RequestHeadersCollection.Add(header.Attribute("name").Value, header.Attribute("value").Value);
// ReSharper restore PossibleNullReferenceException
}
}
}
#endregion
#region SetProperties
static void SetProperties(DataSource dataSource)
{
if(dataSource.EntitySchemaDoc == null)
{
return;
}
dataSource.EntityName = dataSource.EntitySchemaDoc.ToString("name", string.Empty);
dataSource.KeySeparator = dataSource.EntitySchemaDoc.ToString("keySeparator", ",");
dataSource.TitleSeparator = dataSource.EntitySchemaDoc.ToString("titleSeparator", " ");
foreach(var element in dataSource.EntitySchemaDoc.Elements())
{
var name = element.ToString("name", string.Empty);
if(string.IsNullOrEmpty(name))
{
continue;
}
var isKey = element.ToBoolean("isKey", false);
var isTitle = element.ToBoolean("isTitle", false);
var isAttribute = element.ToBoolean("isAttribute", false);
var property = new DataSourceProperty
{
Name = name,
IsAttribute = isAttribute
};
if(isKey)
{
dataSource.KeyProperties.Add(property);
}
if(isTitle)
{
dataSource.TitleProperties.Add(property);
}
if(!(isKey || isTitle))
{
dataSource.Properties.Add(property);
}
}
}
#endregion
/////////////////////////////////////////////////////////////////////////////
// Helper methods for fetching data from a data source
/////////////////////////////////////////////////////////////////////////////
#region ExecuteWebRequest
static IEnumerable ExecuteWebRequest(DataSource ds, NameValueCollection queryString)
{
var responseText = string.Empty;
var webClient = new IntegrationWebClient();
webClient.Headers.Add(ds.RequestHeadersCollection);
if(ds.AuthTypeEnum == AuthTypeEnum.NetworkCredential)
{
webClient.Credentials = new NetworkCredential(ds.AuthUser, ds.AuthPassword, ds.AuthDomain);
}
var request = GetWebParameters(ds, queryString);
switch(ds.CommandTypeEnum)
{
case CommandTypeEnum.Get:
var requestUrl = string.Format("{0}?{1}", ds.ConnectionString, request);
responseText = webClient.DownloadString(requestUrl);
break;
case CommandTypeEnum.Post:
responseText = webClient.UploadString(ds.ConnectionString, request);
break;
}
return ParseResponse(ds, responseText);
}
#endregion
#region ExecuteDbRequest
static IEnumerable ExecuteDbRequest(DataSource ds, NameValueCollection queryString)
{
using(var conn = CreateDbConnection(ds.ConnectionTypeEnum, ds.ConnectionString))
{
if(conn == null)
{
return null;
}
using(var cmd = conn.CreateCommand())
{
AddDbParameters(ds, queryString, cmd.Parameters);
cmd.CommandType = (ds.CommandTypeEnum == CommandTypeEnum.StoredProcedure
|| ds.CommandTypeEnum == CommandTypeEnum.StoredProcedureExec)
? SystemData.CommandType.StoredProcedure
: SystemData.CommandType.Text;
cmd.CommandText = (ds.CommandTypeEnum == CommandTypeEnum.StoredProcedure
|| ds.CommandTypeEnum == CommandTypeEnum.StoredProcedureExec)
? ds.RequestQuery.Substring(0, ds.RequestQuery.IndexOf("@")).Replace("@", "")
: ds.RequestQuery;
cmd.Connection.Open();
var table = new DataTable();
if(ds.CommandTypeEnum != CommandTypeEnum.StoredProcedureExec)
{
using(var reader = cmd.ExecuteReader(CommandBehavior.SchemaOnly & CommandBehavior.KeyInfo))
{
table.Load(reader, LoadOption.OverwriteChanges);
}
}
else
{
cmd.ExecuteNonQuery();
}
return ParseResponse(ds, table);
}
}
}
#endregion
#region CreateDbConnection
static IDbConnection CreateDbConnection(ConnectionTypeEnum connectionType, string connectionString)
{
switch(connectionType)
{
case ConnectionTypeEnum.SqlServer:
return new SqlConnection(connectionString);
case ConnectionTypeEnum.OleDb:
return new OleDbConnection(connectionString);
case ConnectionTypeEnum.Odbc:
return new OdbcConnection(connectionString);
case ConnectionTypeEnum.Oracle:
break;
}
return null;
}
#endregion
#region AddDbParameters
static void AddDbParameters(DataSource ds, NameValueCollection queryString, IList parameters)
{
var paramMatches = new List();
var matches = Regex.Matches(ds.RequestQuery, ParameterMatchPattern);
foreach(Match match in matches)
{
var paramName = match.Value;
if(!paramMatches.Contains(paramName))
{
paramMatches.Add(paramName);
var value = GetDbParameterValue(queryString, paramName);
switch(ds.ConnectionTypeEnum)
{
case ConnectionTypeEnum.SqlServer:
parameters.Add(new SqlParameter(paramName, value));
break;
case ConnectionTypeEnum.OleDb:
parameters.Add(new OleDbParameter(paramName, value));
break;
case ConnectionTypeEnum.Odbc:
parameters.Add(new OdbcParameter(paramName, value));
break;
case ConnectionTypeEnum.Oracle:
break;
}
}
}
}
#endregion
#region GetDbParameterValue
static string GetDbParameterValue(NameValueCollection queryString, string paramName)
{
var key = paramName.Remove(0, 1);
if(key.Length > 0 && key[0] == ParameterPrefix)
{
// Do not replace parameters with a @@ prefix just remove the extra @
return key;
}
return queryString[key] ?? string.Empty;
}
#endregion
#region GetWebParameters
static string GetWebParameters(DataSource ds, NameValueCollection queryString)
{
return Regex.Replace(ds.RequestQuery, ParameterMatchPattern, m => GetDbParameterValue(queryString, m.Value));
}
#endregion
#region ParseResponse
static IEnumerable ParseResponse(DataSource ds, string responseText)
{
if(string.IsNullOrEmpty(responseText))
{
return null;
}
var response = string.IsNullOrEmpty(ds.ResponseXsl)
? XDocument.Parse(responseText)
: responseText.Transform(ds.ResponseXsl);
var result = new List();
if(ds.EntitySchemaDoc == null)
{
// Don't convert, just return the XML
result.Add(response.Root);
}
else if(response.Root != null && !response.Root.IsEmpty)
{
var entities = response.Root.HasElements
? response.Children(ds.EntityName)
: XDocument.Parse(response.Root.Value).Children(ds.EntityName);
foreach(var entity in entities)
{
var e = entity;
var xml = ToEntityXml(ds, p => p.IsAttribute
? e.ToString(p.Name, string.Empty)
: e.Child(p.Name).ToString(string.Empty));
result.Add(xml);
}
}
return result;
}
static IEnumerable ParseResponse(DataSource ds, DataTable dt)
{
var result = new List();
foreach(DataRow dr in dt.Rows)
{
var row = dr;
result.Add(ToEntityXml(ds, p => row[p.Name].ToStringSafe()));
}
return result;
}
#endregion
#region ToEntitiesXml
static XElement ToEntitiesXml(IEnumerable entities)
{
var result = new XElement(EntityCollectionTag);
if(entities != null)
{
foreach(var entity in entities)
{
result.Add(entity);
}
}
return result;
}
#endregion
#region ToEntityXml
static XElement ToEntityXml(DataSource dataSource, Func getValue)
{
var result = new XElement(EntityTag,
new XElement("key", JoinNames(dataSource.KeyProperties, dataSource.KeySeparator, getValue)),
new XElement("title", JoinNames(dataSource.TitleProperties, dataSource.TitleSeparator, getValue))
);
if(dataSource.Properties.Count > 0)
{
var properties = new XElement("properties");
foreach(var property in dataSource.Properties)
{
properties.Add(new XElement(property.Name.ToLower(), getValue(property)));
}
result.Add(properties);
}
return result;
}
#endregion
#region JoinNames
static string JoinNames(IEnumerable properties, string separator, Func getValue)
{
var result = new StringBuilder();
foreach(var property in properties)
{
var value = getValue(property);
if(!string.IsNullOrEmpty(value))
{
if(result.Length > 0)
{
result.Append(separator);
}
result.Append(value);
}
}
return result.ToString();
}
#endregion
}
// ReSharper restore LoopCanBeConvertedToQuery
}