2008-11-21 Marek Habersack <mhabersack@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                 SiteMapNode root = null;
55                 FileSystemWatcher watcher;
56                 Dictionary <string, bool> _childProvidersPresent;
57                 List <SiteMapProvider> _childProviders;
58                 
59                 Dictionary <string, bool> ChildProvidersPresent {
60                         get {
61                                 if (_childProvidersPresent == null)
62                                         _childProvidersPresent = new Dictionary <string, bool> ();
63
64                                 return _childProvidersPresent;
65                         }
66                 }
67
68                 List <SiteMapProvider> ChildProviders {
69                         get {
70                                 if (_childProviders == null)
71                                         _childProviders = new List <SiteMapProvider> ();
72
73                                 return _childProviders;
74                         }
75                 }
76                 
77                 protected internal override void AddNode (SiteMapNode node, SiteMapNode parentNode)
78                 {
79                         base.AddNode (node, parentNode);
80                 }
81
82                 protected virtual void AddProvider (string providerName, SiteMapNode parentNode)
83                 {
84                         if (parentNode == null)
85                                 throw new ArgumentNullException ("parentNode");
86
87                         if (parentNode.Provider != this)
88                                 throw new ArgumentException ("The Provider property of the parentNode does not reference the current provider.", "parentNode");
89
90                         SiteMapProvider smp = SiteMap.Providers [providerName];
91                         if (smp == null)
92                                 throw new ProviderException ("Provider with name [" + providerName + "] was not found.");
93
94                         AddNode (smp.GetRootNodeCore ());
95                         RegisterChildProvider (providerName, smp);
96                 }
97
98                 void RegisterChildProvider (string name, SiteMapProvider smp)
99                 {
100                         Dictionary <string, bool> childProvidersPresent = ChildProvidersPresent;
101                         
102                         if (childProvidersPresent.ContainsKey (name))
103                                 return;
104
105                         childProvidersPresent.Add (name, true);
106                         ChildProviders.Add (smp);
107                 }
108                 
109                 XmlNode FindStartingNode (string file, out bool enableLocalization)
110                 {
111                         if (String.Compare (Path.GetExtension (file), ".sitemap", stringComparison) != 0)
112                                 throw new InvalidOperationException (
113                                         String.Format ("The file {0} has an invalid extension, only .sitemap files are allowed in XmlSiteMapProvider.",
114                                                        Path.GetFileName (file)));
115                         
116                         XmlDocument d = new XmlDocument ();
117                         d.Load (file);
118
119                         XmlNode enloc = d.DocumentElement.Attributes ["enableLocalization"];
120                         if (enloc != null && !String.IsNullOrEmpty (enloc.Value))
121                                 enableLocalization = (bool) Convert.ChangeType (enloc.Value, typeof (bool));
122                         else
123                                 enableLocalization = false;
124                                         
125                         XmlNode nod = d.DocumentElement ["siteMapNode"];
126                         if (nod == null)
127                                 throw new HttpException ("Invalid site map file: " + Path.GetFileName (file));
128
129                         return nod;
130                 }
131                 
132                 public override SiteMapNode BuildSiteMap ()
133                 {
134                         if (root != null)
135                                 return root;
136                         // Whenever you call AddNode, it tries to find dups, and will call this method
137                         // Is this a bug in MS??
138                         if (building)
139                                 return null;
140                         
141                         lock (this_lock) {
142                                 try {
143                                         building = true;
144                                         if (root != null)
145                                                 return root;
146
147                                         bool enableLocalization;
148                                         XmlNode node = FindStartingNode (file, out enableLocalization);
149                                         EnableLocalization = enableLocalization;
150                                         SiteMapNode builtRoot = BuildSiteMapRecursive (node, EnableLocalization);
151
152                                         if (builtRoot != root) {
153                                                 root = builtRoot;
154                                                 AddNode (root);
155                                         }
156                                 } finally {
157                                         building = false;
158                                 }
159                                 
160                                 return root;
161                         }
162                 }
163                 
164                 string GetNonEmptyOptionalAttribute (XmlNode n, string name)
165                 {
166                         return System.Web.Configuration.HandlersUtil.ExtractAttributeValue (name, n, true);
167                 }
168                 
169                 string GetOptionalAttribute (XmlNode n, string name)
170                 {
171                         return System.Web.Configuration.HandlersUtil.ExtractAttributeValue (name, n, true, true);
172                 }
173
174                 void PutInCollection (string name, string value, ref NameValueCollection coll)
175                 {
176                         PutInCollection (name, null, value, ref coll);
177                 }
178                 
179                 void PutInCollection (string name, string classKey, string value, ref NameValueCollection coll)
180                 {
181                         if (coll == null)
182                                 coll = new NameValueCollection ();
183                         if (!String.IsNullOrEmpty (classKey))
184                                 coll.Add (name, classKey);
185                         coll.Add (name, value);
186                 }
187
188                 bool GetAttributeLocalization (string value, out string resClass, out string resKey, out string resDefault)
189                 {
190                         resClass = null;
191                         resKey = null;
192                         resDefault = null;
193
194                         if (String.IsNullOrEmpty (value))
195                                 return false;
196                         string val = value.TrimStart (new char[] {' ', '\t'});
197                         if (val.Length < 11 ||
198                                 String.Compare (val, 0, "$resources:", 0, 11, StringComparison.InvariantCultureIgnoreCase) != 0)
199                                 return false;
200
201                         val = val.Substring (11);
202                         if (val.Length == 0)
203                                 return false;
204                         string[] parts = val.Split (',');
205                         if (parts.Length < 2)
206                                 return false;
207                         resClass = parts [0].Trim ();
208                         resKey = parts [1].Trim ();
209                         if (parts.Length == 3)
210                                 resDefault = parts [2];
211                         else if (parts.Length > 3)
212                                 resDefault = String.Join (",", parts, 2, parts.Length - 2);
213
214                         return true;
215                 }
216                 
217                 void CollectLocalizationInfo (XmlNode xmlNode, ref string title, ref string description,
218                                               ref NameValueCollection attributes,
219                                               ref NameValueCollection explicitResourceKeys)
220                 {
221                         string resClass;
222                         string resKey;
223                         string resDefault;
224
225                         if (GetAttributeLocalization (title, out resClass, out resKey, out resDefault)) {
226                                 PutInCollection ("title", resClass, resKey, ref explicitResourceKeys);
227                                 title = resDefault;
228                         }
229                         
230                         if (GetAttributeLocalization (description, out resClass, out resKey, out resDefault)) {
231                                 PutInCollection ("description", resClass, resKey, ref explicitResourceKeys);
232                                 description = resDefault;
233                         }
234
235                         string value;
236                         foreach (XmlNode att in xmlNode.Attributes) {
237                                 if (GetAttributeLocalization (att.Value, out resClass, out resKey, out resDefault)) {
238                                         PutInCollection (att.Name, resClass, resKey, ref explicitResourceKeys);
239                                         value = resDefault;
240                                 } else
241                                         value = att.Value;
242                                 PutInCollection (att.Name, value, ref attributes);
243                         }
244                 }
245                 
246                 SiteMapNode BuildSiteMapRecursive (XmlNode xmlNode, bool localize)
247                 {
248                         if (xmlNode.Name != "siteMapNode")
249                                 throw new ConfigurationException ("incorrect element name", xmlNode);
250                         
251                         string provider = GetNonEmptyOptionalAttribute (xmlNode, "provider");
252                         string siteMapFile = GetNonEmptyOptionalAttribute (xmlNode, "siteMapFile");
253                         
254                         if (provider != null) {
255                                 SiteMapProvider smp = SiteMap.Providers [provider];
256                                 if (smp == null)
257                                         throw new ProviderException ("Provider with name [" + provider + "] was not found.");
258
259                                 smp.ParentProvider = this;
260                                 SiteMapNode root = smp.GetRootNodeCore();
261                                 RegisterChildProvider (provider, smp);
262                                 
263                                 return root;
264                         } else if (siteMapFile != null) {
265                                 bool enableLocalization;
266                                 XmlNode node = FindStartingNode (HttpContext.Current.Request.MapPath (siteMapFile),
267                                                                  out enableLocalization);
268                                 return BuildSiteMapRecursive (node, enableLocalization);
269                         } else {
270                                 string url = GetOptionalAttribute (xmlNode, "url");
271                                 string title = GetOptionalAttribute (xmlNode, "title");
272                                 string description = GetOptionalAttribute (xmlNode, "description");
273                                 string keywords = GetOptionalAttribute (xmlNode, "keywords");
274                                 string roles = GetOptionalAttribute (xmlNode, "roles");
275                                 string implicitResourceKey = GetOptionalAttribute (xmlNode, "resourceKey");
276                                 
277                                 ArrayList keywordsList = new ArrayList ();
278                                 if (keywords != null && keywords.Length > 0) {
279                                         foreach (string s in keywords.Split (seperators)) {
280                                                 string ss = s.Trim ();
281                                                 if (ss.Length > 0)
282                                                         keywordsList.Add (ss);
283                                         }
284                                 }
285                                 
286                                 ArrayList rolesList = new ArrayList ();
287                                 if (roles != null && roles.Length > 0) {
288                                         foreach (string s in roles.Split (seperators)) {
289                                                 string ss = s.Trim ();
290                                                 if (ss.Length > 0)
291                                                         rolesList.Add (ss);
292                                         }
293                                 }
294
295                                 if (!string.IsNullOrEmpty (url)) {
296                                         if (UrlUtils.IsRelativeUrl (url))
297                                                 url = UrlUtils.Combine (HttpRuntime.AppDomainAppVirtualPath, url);
298                                 }
299
300                                 NameValueCollection attributes = null;
301                                 NameValueCollection explicitResourceKeys = null;
302                                 if (localize)
303                                         CollectLocalizationInfo (xmlNode, ref title, ref description, ref attributes,
304                                                                  ref explicitResourceKeys);
305                                 else
306                                         foreach (XmlNode att in xmlNode.Attributes)
307                                                 PutInCollection (att.Name, att.Value, ref attributes);
308
309                                 string key = Guid.NewGuid ().ToString ();
310                                 SiteMapNode node = new SiteMapNode (this, key, url, title, description,
311                                                                     ArrayList.ReadOnly (rolesList),
312                                                                     attributes,
313                                                                     explicitResourceKeys,
314                                                                     implicitResourceKey);
315                                         
316                                 foreach (XmlNode child in xmlNode.ChildNodes) {
317                                         if (child.NodeType != XmlNodeType.Element)
318                                                 continue;
319                                         AddNode (BuildSiteMapRecursive (child, EnableLocalization), node);
320                                 }
321                                 
322                                 return node;
323                         }
324                 }
325
326                 protected override void Clear ()
327                 {
328                         base.Clear ();
329                         root = null;
330                         ChildProviders.Clear ();
331                         ChildProvidersPresent.Clear ();
332                 }
333
334                 protected virtual void Dispose (bool disposing)
335                 {
336                         if (disposing)
337                                 watcher.Dispose ();
338                 }
339
340                 public void Dispose ()
341                 {
342                         Dispose (true);
343                 }
344                 
345                 public override SiteMapNode FindSiteMapNode (string rawUrl)
346                 {
347                         SiteMapNode node = base.FindSiteMapNode (rawUrl);
348                         if (node != null)
349                                 return node;
350
351                         foreach (SiteMapProvider smp in ChildProviders) {
352                                 node = smp.FindSiteMapNode (rawUrl);
353                                 if (node != null)
354                                         return node;
355                         }
356
357                         return null;
358                 }
359
360                 public override SiteMapNode FindSiteMapNodeFromKey (string key)
361                 {
362                         SiteMapNode node = base.FindSiteMapNodeFromKey (key);
363                         if (node != null)
364                                 return node;
365
366                         foreach (SiteMapProvider smp in ChildProviders) {
367                                 node = smp.FindSiteMapNodeFromKey (key);
368                                 if (node != null)
369                                         return node;
370                         }
371
372                         return null;
373                 }
374
375                 public override void Initialize (string name, NameValueCollection attributes)
376                 {
377                         base.Initialize (name, attributes);
378                         file = attributes ["siteMapFile"];
379                         if (String.IsNullOrEmpty (file))
380                                 throw new ArgumentException ("The siteMapFile attribute must be specified on the XmlSiteMapProvider.");
381
382                         HttpContext ctx = HttpContext.Current;
383                         HttpRequest req = ctx != null ? ctx.Request : null;
384                         
385                         if (req != null)
386                                 file = req.MapPath (file, HttpRuntime.AppDomainAppVirtualPath, false);
387                         else
388                                 throw new InvalidOperationException ("Request is missing - cannot map paths.");
389
390                         if (File.Exists (file)) {
391                                 ResourceKey = Path.GetFileName (file);
392                                 
393                                 watcher = new FileSystemWatcher ();
394                                 watcher.NotifyFilter |= NotifyFilters.Size;
395                                 watcher.Path = Path.GetFullPath (Path.GetDirectoryName (file));
396                                 watcher.Filter = Path.GetFileName (file);
397                                 watcher.Changed += new FileSystemEventHandler (OnFileChanged);
398                                 watcher.EnableRaisingEvents = true;
399                         }
400                 }
401
402                 protected override void RemoveNode (SiteMapNode node)
403                 {
404                         base.RemoveNode (node);
405                 }
406
407                 [MonoTODO ("Not implemented")]
408                 protected virtual void RemoveProvider (string providerName)
409                 {
410                         throw new NotImplementedException ();
411                 }
412
413                 void OnFileChanged (object sender, FileSystemEventArgs args)
414                 {
415                         Clear ();
416                 }
417
418                 public override SiteMapNode RootNode {
419                         get {
420                                 BuildSiteMap ();
421                                 return root;
422                         }
423                 }
424                 
425                 protected internal override SiteMapNode GetRootNodeCore ()
426                 {
427                         return BuildSiteMap ();
428                 }
429         }
430
431 }
432 #endif
433