//
// System.Web.UI.WebControls.XmlDataSource
//
// Authors:
//	Ben Maurer (bmaurer@users.sourceforge.net)
//      Chris Toshok (toshok@ximian.com)
//
// (C) 2003 Ben Maurer
// (C) 2005 Novell, Inc (http://www.novell.com)
//

//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//

using System.Collections;
using System.Collections.Specialized;
using System.Drawing;
using System.Text;
using System.Xml;
using System.Xml.Xsl;
using System.ComponentModel;
using System.IO;
using System.Web.Caching;
using System.Security.Permissions;

namespace System.Web.UI.WebControls {

	// CAS
	[AspNetHostingPermission (SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
	[AspNetHostingPermission (SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
	// attributes
	[DesignerAttribute ("System.Web.UI.Design.WebControls.XmlDataSourceDesigner, " + Consts.AssemblySystem_Design, "System.ComponentModel.Design.IDesigner")]
	[DefaultProperty ("DataFile")]
	[DefaultEvent ("Transforming")]
	[ParseChildren (true)]
	[PersistChildren (false)]
	[WebSysDescription ("Connect to an XML file.")]
//	[WebSysDisplayName ("XML file")]
	[ToolboxBitmap ("")]
	public class XmlDataSource : HierarchicalDataSourceControl, IDataSource, IListSource {

		string _data = string.Empty;
		string _transform = string.Empty;
		string _xpath = string.Empty;
		string _dataFile = string.Empty;
		string _transformFile = string.Empty;
		string _cacheKeyDependency = string.Empty;
		bool _enableCaching = true;
		int _cacheDuration = 0;
		bool _documentNeedsUpdate;
		
		DataSourceCacheExpiry _cacheExpirationPolicy = DataSourceCacheExpiry.Absolute;
		static readonly string [] emptyNames = new string [] { "DefaultView" };
		
		event EventHandler IDataSource.DataSourceChanged {
			add { ((IHierarchicalDataSource)this).DataSourceChanged += value; }
			remove { ((IHierarchicalDataSource)this).DataSourceChanged -= value; }
		}
		
		static object EventTransforming = new object ();
		public event EventHandler Transforming {
			add { Events.AddHandler (EventTransforming, value); }
			remove { Events.RemoveHandler (EventTransforming, value); }
		}

		protected virtual void OnTransforming (EventArgs e)
		{
			EventHandler eh = Events [EventTransforming] as EventHandler;
			if (eh != null)
				eh (this, e);
		}
		
		XmlDocument xmlDocument;
		public XmlDocument GetXmlDocument ()
		{
			if (_documentNeedsUpdate)
				UpdateXml ();
			
			if (xmlDocument == null && EnableCaching)
				xmlDocument = GetXmlDocumentFromCache ();

			if (xmlDocument == null) {
				xmlDocument = LoadXmlDocument ();
				UpdateCache ();
			}

			return xmlDocument;
		}

		[MonoTODO ("schema")]
		XmlDocument LoadXmlDocument ()
		{
			XmlDocument document = LoadFileOrData (DataFile, Data);
			if (String.IsNullOrEmpty (TransformFile) && String.IsNullOrEmpty (Transform))
				return document;

			XslTransform xslTransform = new XslTransform ();
			XmlDocument xsl = LoadFileOrData (TransformFile, Transform);
			xslTransform.Load (xsl);

			OnTransforming (EventArgs.Empty);

			XmlDocument transofrResult = new XmlDocument ();
			transofrResult.Load (xslTransform.Transform (document, TransformArgumentList));

			return transofrResult;
		}

		XmlDocument LoadFileOrData (string filename, string data)
		{
			XmlDocument document = new XmlDocument ();
			if (!String.IsNullOrEmpty (filename)) {
				Uri uri;
				if (Uri.TryCreate (filename, UriKind.Absolute, out uri))
					document.Load (filename);
				else
					document.Load (MapPathSecure (filename));
			} else
				if (!String.IsNullOrEmpty (data))
					document.LoadXml (data);
			return document;
		}

		XmlDocument GetXmlDocumentFromCache ()
		{
			if (DataCache != null)
				return (XmlDocument) DataCache [GetDataKey ()];

			return null;
		}

		string GetDataKey ()
		{
			if (String.IsNullOrEmpty (DataFile) && !String.IsNullOrEmpty (Data)) {
				string key = CacheKeyContext;
				if (!String.IsNullOrEmpty (key))
					return key;
			}
			Page page = Page;
			string p = page != null ? page.ToString () : "NullPage";
			
			return TemplateSourceDirectory + "_" + p + "_" + ID;
		}

		Cache DataCache
		{
			get
			{
				if (HttpContext.Current != null)
					return HttpContext.Current.InternalCache;

				return null;
			}
		}

		void UpdateCache ()
		{
			if (!EnableCaching)
				return;

			if (DataCache == null)
				return;

			string dataKey = GetDataKey ();
			if (DataCache [dataKey] != null)
				DataCache.Remove (dataKey);

			DateTime absoluteExpiration = Cache.NoAbsoluteExpiration;
			TimeSpan slidindExpiraion = Cache.NoSlidingExpiration;

			if (CacheDuration > 0) {
				if (CacheExpirationPolicy == DataSourceCacheExpiry.Absolute)
					absoluteExpiration = DateTime.Now.AddSeconds (CacheDuration);
				else
					slidindExpiraion = new TimeSpan (CacheDuration * 10000L);
			}

			CacheDependency dependency = null;
			if (CacheKeyDependency.Length > 0)
				dependency = new CacheDependency (new string [] { }, new string [] { CacheKeyDependency });
			else
				dependency = new CacheDependency (new string [] { }, new string [] { });

			DataCache.Add (dataKey, xmlDocument, dependency,
				absoluteExpiration, slidindExpiraion, CacheItemPriority.Default, null);
		}
		
		// If datafile changed, then DO NOT USE the cached data, but update it.
		void UpdateXml()
		{
			xmlDocument = LoadXmlDocument (); 
			UpdateCache ();
			_documentNeedsUpdate = false;
		}

		public void Save ()
		{
			if (!CanBeSaved)
				throw new InvalidOperationException ();

			if (xmlDocument != null)
				xmlDocument.Save (MapPathSecure (DataFile));
		}
		
		bool CanBeSaved {
			get {
				return Transform == String.Empty && TransformFile == String.Empty && DataFile != String.Empty;
			}
		}
		
		protected override HierarchicalDataSourceView GetHierarchicalView (string viewPath)
		{
			XmlNode doc = this.GetXmlDocument ();
			XmlNodeList ret = null;
			
			if (!String.IsNullOrEmpty (viewPath)) {
				XmlNode n = doc.SelectSingleNode (viewPath);
				if (n != null)
					ret = n.ChildNodes;
			} else if (!String.IsNullOrEmpty (XPath)) {
				ret = doc.SelectNodes (XPath);
			} else {
				ret = doc.ChildNodes;
			}
			
			return new XmlHierarchicalDataSourceView (ret);
		}
		
		IList IListSource.GetList ()
		{
			return ListSourceHelper.GetList (this);
		}
		
		bool IListSource.ContainsListCollection {
			get { return ListSourceHelper.ContainsListCollection (this); }
		}
		
		DataSourceView IDataSource.GetView (string viewName)
		{
			if (String.IsNullOrEmpty (viewName))
				viewName = "DefaultView";
			
			return new XmlDataSourceView (this, viewName);
		}
		
		ICollection IDataSource.GetViewNames ()
		{
			return emptyNames;
		}
		
		[DefaultValue (0)]
		[TypeConverter (typeof(DataSourceCacheDurationConverter))]
		public virtual int CacheDuration {
			get {
				return _cacheDuration;
			}
			set {
				_cacheDuration = value;
			}
		}

		[DefaultValue (DataSourceCacheExpiry.Absolute)]
		public virtual DataSourceCacheExpiry CacheExpirationPolicy {
			get {
				return _cacheExpirationPolicy;
			}
			set {
				_cacheExpirationPolicy = value;
			}
		}

		[DefaultValue ("")]
		public virtual string CacheKeyDependency {
			get {
				return _cacheKeyDependency;
			}
			set {
				_cacheKeyDependency = value;
			}
		}

		[DefaultValue (true)]
		public virtual bool EnableCaching {
			get {
				return _enableCaching;
			}
			set {
				_enableCaching = value;
			}
		}

		[DefaultValue ("")]
		[PersistenceMode (PersistenceMode.InnerProperty)]
		[WebSysDescription ("Inline XML data.")]
		[WebCategory ("Data")]
		[EditorAttribute ("System.ComponentModel.Design.MultilineStringEditor," + Consts.AssemblySystem_Design, "System.Drawing.Design.UITypeEditor, " + Consts.AssemblySystem_Drawing)]
		[TypeConverter (typeof(MultilineStringConverter))]
		public virtual string Data {
			get { return _data; }
			set {
				if (_data != value) {
					_data = value;
					_documentNeedsUpdate = true;
					OnDataSourceChanged(EventArgs.Empty);
				}
			}
		}
		
		[DefaultValueAttribute ("")]
		[EditorAttribute ("System.Web.UI.Design.XmlDataFileEditor, " + Consts.AssemblySystem_Design, "System.Drawing.Design.UITypeEditor, " + Consts.AssemblySystem_Drawing)]
		[MonoLimitation ("Absolute path to the file system is not supported; use a relative URI instead.")]
		public virtual string DataFile {
			get { return _dataFile; }
			set {
				if (_dataFile != value) {
					_dataFile = value;
					_documentNeedsUpdate = true;
					OnDataSourceChanged(EventArgs.Empty);
				}
			}
		}
		
		XsltArgumentList transformArgumentList;
		
		[BrowsableAttribute (false)]
		public virtual XsltArgumentList TransformArgumentList {
			get { return transformArgumentList; }
			set { transformArgumentList = value; }
		}
		
		[PersistenceModeAttribute (PersistenceMode.InnerProperty)]
		[EditorAttribute ("System.ComponentModel.Design.MultilineStringEditor," + Consts.AssemblySystem_Design, "System.Drawing.Design.UITypeEditor, " + Consts.AssemblySystem_Drawing)]
		[DefaultValueAttribute ("")]
		[TypeConverterAttribute (typeof(MultilineStringConverter))]
		public virtual string Transform {
			get { return _transform; }
			set {
				if (_transform != value) {
					_transform = value; 
					_documentNeedsUpdate = true;
					OnDataSourceChanged(EventArgs.Empty);
				}
			}
		}
		
		[EditorAttribute ("System.Web.UI.Design.XslTransformFileEditor, " + Consts.AssemblySystem_Design, "System.Drawing.Design.UITypeEditor, " + Consts.AssemblySystem_Drawing)]
		[DefaultValueAttribute ("")]
		[MonoLimitation ("Absolute path to the file system is not supported; use a relative URI instead.")]
		public virtual string TransformFile {
			get { return _transformFile; }
			set {
				if (_transformFile != value) {
					_transformFile = value;
					_documentNeedsUpdate = true;
					OnDataSourceChanged(EventArgs.Empty);
				}
			}
		}
		
		[DefaultValueAttribute ("")]
		public virtual string XPath {
			get { return _xpath; }
			set {
				if (_xpath != value) {
					_xpath = value;
					OnDataSourceChanged(EventArgs.Empty);
				}
			}
		}
		[DefaultValue ("")]
		public virtual string CacheKeyContext {
			get { return ViewState.GetString ("CacheKeyContext", String.Empty); }
			set { ViewState ["CacheKeyContext"] = value; }
		}
	}
}