2009-02-28 Gonzalo Paniagua Javier <gonzalo@novell.com>
[mono.git] / mcs / class / System.Web / System.Web / XmlSiteMapProvider.cs
1 //
2 // System.Web.XmlSiteMapProvider
3 //
4 // Authors:
5 //      Ben Maurer (bmaurer@users.sourceforge.net)
6 //      Lluis Sanchez Gual (lluis@novell.com)
7 //
8 // (C) 2003 Ben Maurer
9 // (C) 2005 Novell, Inc (http://www.novell.com)
10 //
11
12 //
13 // Permission is hereby granted, free of charge, to any person obtaining
14 // a copy of this software and associated documentation files (the
15 // "Software"), to deal in the Software without restriction, including
16 // without limitation the rights to use, copy, modify, merge, publish,
17 // distribute, sublicense, and/or sell copies of the Software, and to
18 // permit persons to whom the Software is furnished to do so, subject to
19 // the following conditions:
20 // 
21 // The above copyright notice and this permission notice shall be
22 // included in all copies or substantial portions of the Software.
23 // 
24 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
28 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
29 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
30 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 //
32
33 #if NET_2_0
34 using System.Collections;
35 using System.Collections.Generic;
36 using System.Collections.Specialized;
37 using System.Configuration;
38 using System.Configuration.Provider;
39 using System.Globalization;
40 using System.Text;
41 using System.Xml;
42 using System.Web.Util;
43 using System.IO;
44
45 namespace System.Web
46 {
47         public class XmlSiteMapProvider : StaticSiteMapProvider, IDisposable
48         {
49                 static readonly char [] seperators = { ';', ',' };
50                 static readonly StringComparison stringComparison = HttpRuntime.RunningOnWindows ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;
51                 
52                 bool building;
53                 string file;
54                 string fileVirtualPath;
55                 SiteMapNode root = null;
56                 List <FileSystemWatcher> watchers;
57                 Dictionary <string, bool> _childProvidersPresent;
58                 List <SiteMapProvider> _childProviders;
59                 
60                 Dictionary <string, bool> ChildProvidersPresent {
61                         get {
62                                 if (_childProvidersPresent == null)
63                                         _childProvidersPresent = new Dictionary <string, bool> ();
64
65                                 return _childProvidersPresent;
66                         }
67                 }
68
69                 List <SiteMapProvider> ChildProviders {
70                         get {
71                                 if (_childProviders == null)
72                                         _childProviders = new List <SiteMapProvider> ();
73
74                                 return _childProviders;
75                         }
76                 }
77                 
78                 protected internal override void AddNode (SiteMapNode node, SiteMapNode parentNode)
79                 {
80                         base.AddNode (node, parentNode);
81                 }
82
83                 protected virtual void AddProvider (string providerName, SiteMapNode parentNode)
84                 {
85                         if (parentNode == null)
86                                 throw new ArgumentNullException ("parentNode");
87
88                         if (parentNode.Provider != this)
89                                 throw new ArgumentException ("The Provider property of the parentNode does not reference the current provider.", "parentNode");
90
91                         SiteMapProvider smp = SiteMap.Providers [providerName];
92                         if (smp == null)
93                                 throw new ProviderException ("Provider with name [" + providerName + "] was not found.");
94
95                         AddNode (smp.GetRootNodeCore ());
96                         RegisterChildProvider (providerName, smp);
97                 }
98
99                 void RegisterChildProvider (string name, SiteMapProvider smp)
100                 {
101                         Dictionary <string, bool> childProvidersPresent = ChildProvidersPresent;
102                         
103                         if (childProvidersPresent.ContainsKey (name))
104                                 return;
105
106                         childProvidersPresent.Add (name, true);
107                         ChildProviders.Add (smp);
108                 }
109                 
110                 XmlNode FindStartingNode (string file, string virtualPath, out bool enableLocalization)
111                 {
112                         if (String.Compare (Path.GetExtension (file), ".sitemap", stringComparison) != 0)
113                                 throw new InvalidOperationException (
114                                         String.Format ("The file {0} has an invalid extension, only .sitemap files are allowed in XmlSiteMapProvider.",
115                                                        String.IsNullOrEmpty (virtualPath) ? Path.GetFileName (file) : virtualPath));
116                         if (!File.Exists (file))
117                                 throw new InvalidOperationException (
118                                         String.Format ("The file '{0}' required by XmlSiteMapProvider does not exist.",
119                                                        String.IsNullOrEmpty (virtualPath) ? Path.GetFileName (file) : virtualPath));
120                         
121                         XmlDocument d = new XmlDocument ();
122                         d.Load (file);
123
124                         XmlNode enloc = d.DocumentElement.Attributes ["enableLocalization"];
125                         if (enloc != null && !String.IsNullOrEmpty (enloc.Value))
126                                 enableLocalization = (bool) Convert.ChangeType (enloc.Value, typeof (bool));
127                         else
128                                 enableLocalization = false;
129                                         
130                         XmlNode nod = d.DocumentElement ["siteMapNode"];
131                         if (nod == null)
132                                 throw new HttpException ("Invalid site map file: " + Path.GetFileName (file));
133
134                         return nod;
135                 }
136                 
137                 public override SiteMapNode BuildSiteMap ()
138                 {
139                         if (root != null)
140                                 return root;
141                         // Whenever you call AddNode, it tries to find dups, and will call this method
142                         // Is this a bug in MS??
143                         if (building)
144                                 return null;
145                         
146                         lock (this_lock) {
147                                 try {
148                                         building = true;
149                                         if (root != null)
150                                                 return root;
151
152                                         bool enableLocalization;
153                                         XmlNode node = FindStartingNode (file, fileVirtualPath, out enableLocalization);
154                                         EnableLocalization = enableLocalization;
155                                         SiteMapNode builtRoot = BuildSiteMapRecursive (node, EnableLocalization);
156
157                                         if (builtRoot != root) {
158                                                 root = builtRoot;
159                                                 AddNode (root);
160                                         }
161                                 } finally {
162                                         building = false;
163                                 }
164                                 
165                                 return root;
166                         }
167                 }
168                 
169                 string GetNonEmptyOptionalAttribute (XmlNode n, string name)
170                 {
171                         return System.Web.Configuration.HandlersUtil.ExtractAttributeValue (name, n, true);
172                 }
173                 
174                 string GetOptionalAttribute (XmlNode n, string name)
175                 {
176                         return System.Web.Configuration.HandlersUtil.ExtractAttributeValue (name, n, true, true);
177                 }
178
179                 void PutInCollection (string name, string value, ref NameValueCollection coll)
180                 {
181                         PutInCollection (name, null, value, ref coll);
182                 }
183                 
184                 void PutInCollection (string name, string classKey, string value, ref NameValueCollection coll)
185                 {
186                         if (coll == null)
187                                 coll = new NameValueCollection ();
188                         if (!String.IsNullOrEmpty (classKey))
189                                 coll.Add (name, classKey);
190                         coll.Add (name, value);
191                 }
192
193                 bool GetAttributeLocalization (string value, out string resClass, out string resKey, out string resDefault)
194                 {
195                         resClass = null;
196                         resKey = null;
197                         resDefault = null;
198
199                         if (String.IsNullOrEmpty (value))
200                                 return false;
201                         string val = value.TrimStart (new char[] {' ', '\t'});
202                         if (val.Length < 11 ||
203                                 String.Compare (val, 0, "$resources:", 0, 11, StringComparison.InvariantCultureIgnoreCase) != 0)
204                                 return false;
205
206                         val = val.Substring (11);
207                         if (val.Length == 0)
208                                 return false;
209                         string[] parts = val.Split (',');
210                         if (parts.Length < 2)
211                                 return false;
212                         resClass = parts [0].Trim ();
213                         resKey = parts [1].Trim ();
214                         if (parts.Length == 3)
215                                 resDefault = parts [2];
216                         else if (parts.Length > 3)
217                                 resDefault = String.Join (",", parts, 2, parts.Length - 2);
218
219                         return true;
220                 }
221                 
222                 void CollectLocalizationInfo (XmlNode xmlNode, ref string title, ref string description,
223                                               ref NameValueCollection attributes,
224                                               ref NameValueCollection explicitResourceKeys)
225                 {
226                         string resClass;
227                         string resKey;
228                         string resDefault;
229
230                         if (GetAttributeLocalization (title, out resClass, out resKey, out resDefault)) {
231                                 PutInCollection ("title", resClass, resKey, ref explicitResourceKeys);
232                                 title = resDefault;
233                         }
234                         
235                         if (GetAttributeLocalization (description, out resClass, out resKey, out resDefault)) {
236                                 PutInCollection ("description", resClass, resKey, ref explicitResourceKeys);
237                                 description = resDefault;
238                         }
239
240                         string value;
241                         foreach (XmlNode att in xmlNode.Attributes) {
242                                 if (GetAttributeLocalization (att.Value, out resClass, out resKey, out resDefault)) {
243                                         PutInCollection (att.Name, resClass, resKey, ref explicitResourceKeys);
244                                         value = resDefault;
245                                 } else
246                                         value = att.Value;
247                                 PutInCollection (att.Name, value, ref attributes);
248                         }
249                 }
250                 
251                 SiteMapNode BuildSiteMapRecursive (XmlNode xmlNode, bool localize)
252                 {
253                         if (xmlNode.Name != "siteMapNode")
254                                 throw new ConfigurationException ("incorrect element name", xmlNode);
255                         
256                         string provider = GetNonEmptyOptionalAttribute (xmlNode, "provider");
257                         string siteMapFile = GetNonEmptyOptionalAttribute (xmlNode, "siteMapFile");
258                         
259                         if (provider != null) {
260                                 SiteMapProvider smp = SiteMap.Providers [provider];
261                                 if (smp == null)
262                                         throw new ProviderException ("Provider with name [" + provider + "] was not found.");
263
264                                 smp.ParentProvider = this;
265                                 SiteMapNode root = smp.GetRootNodeCore();
266                                 RegisterChildProvider (provider, smp);
267                                 
268                                 return root;
269                         } else if (siteMapFile != null) {
270                                 if (file.Length == 0)
271                                         throw new InvalidOperationException ("The 'siteMapFile' attribute cannot be an empty string.");
272                                 string realPath = HttpContext.Current.Request.MapPath (siteMapFile);
273                                 bool enableLocalization;
274                                 XmlNode node = FindStartingNode (realPath, siteMapFile, out enableLocalization);
275
276                                 CreateWatcher (realPath);
277                                 return BuildSiteMapRecursive (node, enableLocalization);
278                         } else {
279                                 string url = GetOptionalAttribute (xmlNode, "url");
280                                 string title = GetOptionalAttribute (xmlNode, "title");
281                                 string description = GetOptionalAttribute (xmlNode, "description");
282                                 string keywords = GetOptionalAttribute (xmlNode, "keywords");
283                                 string roles = GetOptionalAttribute (xmlNode, "roles");
284                                 string implicitResourceKey = GetOptionalAttribute (xmlNode, "resourceKey");
285                                 
286                                 ArrayList keywordsList = new ArrayList ();
287                                 if (keywords != null && keywords.Length > 0) {
288                                         foreach (string s in keywords.Split (seperators)) {
289                                                 string ss = s.Trim ();
290                                                 if (ss.Length > 0)
291                                                         keywordsList.Add (ss);
292                                         }
293                                 }
294                                 
295                                 ArrayList rolesList = new ArrayList ();
296                                 if (roles != null && roles.Length > 0) {
297                                         foreach (string s in roles.Split (seperators)) {
298                                                 string ss = s.Trim ();
299                                                 if (ss.Length > 0)
300                                                         rolesList.Add (ss);
301                                         }
302                                 }
303
304                                 if (!string.IsNullOrEmpty (url)) {
305                                         if (UrlUtils.IsRelativeUrl (url))
306                                                 url = UrlUtils.Combine (HttpRuntime.AppDomainAppVirtualPath, url);
307                                 }
308
309                                 NameValueCollection attributes = null;
310                                 NameValueCollection explicitResourceKeys = null;
311                                 if (localize)
312                                         CollectLocalizationInfo (xmlNode, ref title, ref description, ref attributes,
313                                                                  ref explicitResourceKeys);
314                                 else
315                                         foreach (XmlNode att in xmlNode.Attributes)
316                                                 PutInCollection (att.Name, att.Value, ref attributes);
317
318                                 string key = Guid.NewGuid ().ToString ();
319                                 SiteMapNode node = new SiteMapNode (this, key, url, title, description,
320                                                                     ArrayList.ReadOnly (rolesList),
321                                                                     attributes,
322                                                                     explicitResourceKeys,
323                                                                     implicitResourceKey);
324                                         
325                                 foreach (XmlNode child in xmlNode.ChildNodes) {
326                                         if (child.NodeType != XmlNodeType.Element)
327                                                 continue;
328                                         AddNode (BuildSiteMapRecursive (child, EnableLocalization), node);
329                                 }
330                                 
331                                 return node;
332                         }
333                 }
334
335                 protected override void Clear ()
336                 {
337                         base.Clear ();
338                         root = null;
339                         ChildProviders.Clear ();
340                         ChildProvidersPresent.Clear ();
341                 }
342
343                 protected virtual void Dispose (bool disposing)
344                 {
345                         if (disposing) {
346                                 foreach (FileSystemWatcher watcher in watchers)
347                                         watcher.Dispose ();
348                                 watchers = null;
349                         }
350                 }
351
352                 public void Dispose ()
353                 {
354                         Dispose (true);
355                 }
356                 
357                 public override SiteMapNode FindSiteMapNode (string rawUrl)
358                 {
359                         SiteMapNode node = base.FindSiteMapNode (rawUrl);
360                         if (node != null)
361                                 return node;
362
363                         foreach (SiteMapProvider smp in ChildProviders) {
364                                 node = smp.FindSiteMapNode (rawUrl);
365                                 if (node != null)
366                                         return node;
367                         }
368
369                         return null;
370                 }
371
372                 public override SiteMapNode FindSiteMapNodeFromKey (string key)
373                 {
374                         SiteMapNode node = base.FindSiteMapNodeFromKey (key);
375                         if (node != null)
376                                 return node;
377
378                         foreach (SiteMapProvider smp in ChildProviders) {
379                                 node = smp.FindSiteMapNodeFromKey (key);
380                                 if (node != null)
381                                         return node;
382                         }
383
384                         return null;
385                 }
386
387                 public override void Initialize (string name, NameValueCollection attributes)
388                 {
389                         base.Initialize (name, attributes);
390                         fileVirtualPath = attributes ["siteMapFile"];
391                         if (String.IsNullOrEmpty (fileVirtualPath))
392                                 throw new ArgumentException ("The siteMapFile attribute must be specified on the XmlSiteMapProvider.");
393
394                         HttpContext ctx = HttpContext.Current;
395                         HttpRequest req = ctx != null ? ctx.Request : null;
396                         
397                         if (req != null)
398                                 file = req.MapPath (fileVirtualPath, HttpRuntime.AppDomainAppVirtualPath, false);
399                         else
400                                 throw new InvalidOperationException ("Request is missing - cannot map paths.");
401
402                         if (File.Exists (file)) {
403                                 ResourceKey = Path.GetFileName (file);
404                                 CreateWatcher (file);
405                         }
406                 }
407
408                 void CreateWatcher (string file)
409                 {
410                         var watcher = new FileSystemWatcher ();
411                         watcher.NotifyFilter |= NotifyFilters.Size;
412                         watcher.Path = Path.GetFullPath (Path.GetDirectoryName (file));
413                         watcher.Filter = Path.GetFileName (file);
414                         watcher.Changed += new FileSystemEventHandler (OnFileChanged);
415                         watcher.EnableRaisingEvents = true;
416
417                         if (watchers == null)
418                                 watchers = new List <FileSystemWatcher> ();
419                         
420                         watchers.Add (watcher);
421                 }
422                 
423                 protected override void RemoveNode (SiteMapNode node)
424                 {
425                         base.RemoveNode (node);
426                 }
427
428                 [MonoTODO ("Not implemented")]
429                 protected virtual void RemoveProvider (string providerName)
430                 {
431                         throw new NotImplementedException ();
432                 }
433
434                 void OnFileChanged (object sender, FileSystemEventArgs args)
435                 {
436                         Clear ();
437                 }
438
439                 public override SiteMapNode RootNode {
440                         get {
441                                 BuildSiteMap ();
442                                 return root;
443                         }
444                 }
445                 
446                 protected internal override SiteMapNode GetRootNodeCore ()
447                 {
448                         return BuildSiteMap ();
449                 }
450         }
451
452 }
453 #endif
454