2006-01-02 Chris Toshok <toshok@ximian.com>
[mono.git] / mcs / class / System / System.Configuration / ConfigurationSettings.cs
1 //
2 // System.Configuration.ConfigurationSettings.cs
3 //
4 // Author:
5 //   Christopher Podurgiel (cpodurgiel@msn.com)
6 //   Gonzalo Paniagua Javier (gonzalo@ximian.com)
7 //   Eric Lindvall (eric@5stops.com)
8 //
9 // (c) Christopher Podurgiel
10 // (c) 2002 Ximian, Inc. (http://www.ximian.com)
11 // (c) 2003 Novell, Inc. (http://www.novell.com)
12 //
13
14 //
15 // Permission is hereby granted, free of charge, to any person obtaining
16 // a copy of this software and associated documentation files (the
17 // "Software"), to deal in the Software without restriction, including
18 // without limitation the rights to use, copy, modify, merge, publish,
19 // distribute, sublicense, and/or sell copies of the Software, and to
20 // permit persons to whom the Software is furnished to do so, subject to
21 // the following conditions:
22 // 
23 // The above copyright notice and this permission notice shall be
24 // included in all copies or substantial portions of the Software.
25 // 
26 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
27 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
28 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
29 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
30 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
31 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
32 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33 //
34
35 #if CONFIGURATION_DEP
36 extern alias PrebuiltSystem;
37 using NameValueCollection = PrebuiltSystem.System.Collections.Specialized.NameValueCollection;
38 #endif
39
40 using System;
41 using System.Collections;
42 using System.Collections.Specialized;
43 using System.IO;
44 using System.Runtime.CompilerServices;
45 #if (XML_DEP)
46 using System.Xml;
47 using System.Xml.XPath;
48 #endif
49
50 namespace System.Configuration
51 {
52         public sealed class ConfigurationSettings
53         {
54                 static IConfigurationSystem config = DefaultConfig.GetInstance ();
55                 static object lockobj = new object ();
56                 private ConfigurationSettings ()
57                 {
58                 }
59
60                 public static object GetConfig (string sectionName)
61                 {
62                         return config.GetConfig (sectionName);
63                 }
64
65 #if NET_2_0
66                 [Obsolete ("This property is obsolete.  Please use System.Configuration.ConfigurationManager.AppSettings")]
67 #endif
68                 public static NameValueCollection AppSettings
69                 {
70                         get {
71 #if NET_2_0 && CONFIGURATION_2_0
72 #if CONFIGURATION_DEP
73                                 return ConfigurationManager.AppSettings;
74 #else
75                                 return null;
76 #endif
77 #else
78                                 object appSettings = GetConfig ("appSettings");
79                                 if (appSettings == null)
80                                         appSettings = new NameValueCollection ();
81
82                                 return (NameValueCollection) appSettings;
83 #endif
84                         }
85                 }
86
87                 // Invoked from System.Web
88                 static IConfigurationSystem ChangeConfigurationSystem (IConfigurationSystem newSystem)
89                 {
90                         if (newSystem == null)
91                                 throw new ArgumentNullException ("newSystem");
92
93                         lock (lockobj) {
94                                 IConfigurationSystem old = config;
95                                 config = newSystem;
96                                 return old;
97                         }
98                 }
99         }
100
101         //
102         // class DefaultConfig: read configuration from machine.config file and application
103         // config file if available.
104         //
105         class DefaultConfig : IConfigurationSystem
106         {
107                 static readonly DefaultConfig instance = new DefaultConfig ();
108                 ConfigurationData config;
109                 
110                 private DefaultConfig ()
111                 {
112                 }
113
114                 public static DefaultConfig GetInstance ()
115                 {
116                         return instance;
117                 }
118
119 #if NET_2_0
120                 [Obsolete ("This method is obsolete.  Please use System.Configuration.ConfigurationManager.GetConfig")]
121 #endif
122                 public object GetConfig (string sectionName)
123                 {
124 #if NET_2_0 && CONFIGURATION_2_0
125 #if CONFIGURATION_DEP
126                         Init ();
127                         return ConfigurationManager.GetSection (sectionName);
128 #else
129                         return null;
130 #endif
131
132 #else
133                         Init ();
134                         return config.GetConfig (sectionName);
135 #endif
136                 }
137
138                 public void Init ()
139                 {
140                         lock (this) {
141                                 if (config != null)
142                                         return;
143
144                                 ConfigurationData data = new ConfigurationData ();
145                                 if (!data.Load (GetMachineConfigPath ()))
146                                         throw new ConfigurationException ("Cannot find " + GetMachineConfigPath ());
147
148                                 string appfile = GetAppConfigPath ();
149                                 if (appfile == null) {
150                                         config = data;
151                                         return;
152                                 }
153
154                                 ConfigurationData appData = new ConfigurationData (data);
155                                 if (appData.Load (appfile))
156                                         config = appData;
157                                 else
158                                         config = data;
159                         }
160                 }
161
162                 [MethodImplAttribute(MethodImplOptions.InternalCall)]
163                 extern private static string get_machine_config_path ();
164
165                 internal static string GetMachineConfigPath ()
166                 {
167                         return get_machine_config_path ();
168                 }
169
170                 private static string GetAppConfigPath ()
171                 {
172                         AppDomainSetup currentInfo = AppDomain.CurrentDomain.SetupInformation;
173
174                         string configFile = currentInfo.ConfigurationFile;
175                         if (configFile == null || configFile.Length == 0)
176                                 return null;
177
178                         return configFile;
179
180                 }
181         }
182
183         enum AllowDefinition
184         {
185                 Everywhere,
186                 MachineOnly,
187                 MachineToApplication
188         }
189         
190         class SectionData
191         {
192                 public readonly string SectionName;
193                 public readonly string TypeName;
194                 public readonly bool AllowLocation;
195                 public readonly AllowDefinition AllowDefinition;
196                 public string FileName;
197
198                 public SectionData (string sectionName, string typeName,
199                                     bool allowLocation, AllowDefinition allowDefinition)
200                 {
201                         SectionName = sectionName;
202                         TypeName = typeName;
203                         AllowLocation = allowLocation;
204                         AllowDefinition = allowDefinition;
205                 }
206         }
207         
208
209         class ConfigurationData
210         {
211                 ConfigurationData parent;
212                 Hashtable factories;
213                 Hashtable pending;
214                 string fileName;
215                 static object removedMark = new object ();
216                 static object groupMark = new object ();
217                 static object emptyMark = new object ();
218                 Hashtable cache;
219
220                 Hashtable FileCache {
221                         get {
222                                 if (cache != null)
223                                         return cache;
224
225                                 cache = new Hashtable ();
226                                 return cache;
227                         }
228                 }
229
230                 public ConfigurationData () : this (null)
231                 {
232                 }
233
234                 public ConfigurationData (ConfigurationData parent)
235                 {
236                         this.parent = (parent == this) ? null : parent;
237                         factories = new Hashtable ();
238                 }
239
240                 public bool Load (string fileName)
241                 {
242                         this.fileName = fileName;
243                         if (fileName == null || !File.Exists (fileName))
244                                 return false;
245 #if (XML_DEP)
246                         XmlTextReader reader = null;
247
248                         try {
249                                 FileStream fs = new FileStream (fileName, FileMode.Open, FileAccess.Read);
250                                 reader = new XmlTextReader (fs);
251                                 InitRead (reader);
252                                 ReadConfigFile (reader);
253                         } catch (ConfigurationException) {
254                                 throw;
255                         } catch (Exception e) {
256                                 throw new ConfigurationException ("Error reading " + fileName, e);
257                         } finally {
258                                 if (reader != null)
259                                         reader.Close();
260                         }
261 #endif
262                         return true;
263                 }
264
265                 object GetHandler (string sectionName)
266                 {
267                         lock (factories) {
268                                 object o = factories [sectionName];
269                                 if (o == null || o == removedMark) {
270                                         if (parent != null)
271                                                 return parent.GetHandler (sectionName);
272
273                                         return null;
274                                 }
275
276                                 if (o is IConfigurationSectionHandler)
277                                         return (IConfigurationSectionHandler) o;
278
279                                 o = CreateNewHandler (sectionName, (SectionData) o);
280                                 factories [sectionName] = o;
281                                 return o;
282                         }
283                 }
284
285                 object CreateNewHandler (string sectionName, SectionData section)
286                 {
287                         Type t = Type.GetType (section.TypeName);
288                         if (t == null)
289                                 throw new ConfigurationException ("Cannot get Type for " + section.TypeName);
290
291 #if false
292                         Type iconfig = typeof (IConfigurationSectionHandler);
293                         if (!iconfig.IsAssignableFrom (t))
294                                 throw new ConfigurationException (sectionName + " does not implement " + iconfig);
295 #endif
296                         
297                         object o = Activator.CreateInstance (t, true);
298                         if (o == null)
299                                 throw new ConfigurationException ("Cannot get instance for " + t);
300
301                         return o;
302                 }
303 #if (XML_DEP)
304                 XmlDocument GetInnerDoc (XmlDocument doc, int i, string [] sectionPath)
305                 {
306                         if (++i >= sectionPath.Length)
307                                 return doc;
308
309                         if (doc.DocumentElement == null)
310                                 return null;
311
312                         XmlNode node = doc.DocumentElement.FirstChild;
313                         while (node != null) {
314                                 if (node.Name == sectionPath [i]) {
315                                         ConfigXmlDocument result = new ConfigXmlDocument ();
316                                         result.Load (new StringReader (node.OuterXml));
317                                         return GetInnerDoc (result, i, sectionPath);
318                                 }
319                                 node = node.NextSibling;
320                         }
321
322                         return null;
323                 }
324
325                 XmlDocument GetDocumentForSection (string sectionName)
326                 {
327                         ConfigXmlDocument doc = new ConfigXmlDocument ();
328                         if (pending == null)
329                                 return doc;
330
331                         string [] sectionPath = sectionName.Split ('/');
332                         string outerxml = pending [sectionPath [0]] as string;
333                         if (outerxml == null)
334                                 return doc;
335
336                         StringReader reader = new StringReader (outerxml);
337                         XmlTextReader rd = new XmlTextReader (reader);
338                         rd.MoveToContent ();
339                         doc.LoadSingleElement (fileName, rd);
340
341                         return GetInnerDoc (doc, 0, sectionPath);
342                 }
343                 
344                 object GetConfigInternal (string sectionName)
345                 {
346                         object handler = GetHandler (sectionName);
347                         IConfigurationSectionHandler iconf = handler as IConfigurationSectionHandler;
348                         if (iconf == null)
349                                 return handler;
350
351                         object parentConfig = null;
352                         if (parent != null)
353                                 parentConfig = parent.GetConfig (sectionName);
354
355                         XmlDocument doc = GetDocumentForSection (sectionName);
356                         if (doc == null || doc.DocumentElement == null)
357                                 return parentConfig;
358                         
359                         return iconf.Create (parentConfig, fileName, doc.DocumentElement);
360                 }
361 #else
362                 object GetConfigInternal (string sectionName)
363                 {
364                     return null;
365                 }
366 #endif
367                 public object GetConfig (string sectionName)
368                 {
369                         object config;
370                         lock (this) {
371                                 config = this.FileCache [sectionName];
372                         }
373
374                         if (config == emptyMark)
375                                 return null;
376
377                         if (config != null)
378                                 return config;
379
380                         lock (this) {
381                                 config = GetConfigInternal (sectionName);
382                                 this.FileCache [sectionName] = (config == null) ? emptyMark : config;
383                         }
384
385                         return config;
386                 }
387
388                 private object LookForFactory (string key)
389                 {
390                         object o = factories [key];
391                         if (o != null)
392                                 return o;
393
394                         if (parent != null)
395                                 return parent.LookForFactory (key);
396
397                         return null;
398                 }
399 #if (XML_DEP)
400                 private void InitRead (XmlTextReader reader)
401                 {
402                         reader.MoveToContent ();
403                         if (reader.NodeType != XmlNodeType.Element || reader.Name != "configuration")
404                                 ThrowException ("Configuration file does not have a valid root element", reader);
405
406                         if (reader.HasAttributes)
407                                 ThrowException ("Unrecognized attribute in root element", reader);
408
409                         MoveToNextElement (reader);
410                 }
411
412                 private void MoveToNextElement (XmlTextReader reader)
413                 {
414                         while (reader.Read ()) {
415                                 XmlNodeType ntype = reader.NodeType;
416                                 if (ntype == XmlNodeType.Element)
417                                         return;
418
419                                 if (ntype != XmlNodeType.Whitespace &&
420                                     ntype != XmlNodeType.Comment &&
421                                     ntype != XmlNodeType.SignificantWhitespace &&
422                                     ntype != XmlNodeType.EndElement)
423                                         ThrowException ("Unrecognized element", reader);
424                         }
425                 }
426
427                 private void ReadSection (XmlTextReader reader, string sectionName)
428                 {
429                         string attName;
430                         string nameValue = null;
431                         string typeValue = null;
432                         string allowLoc = null, allowDef = null;
433                         bool allowLocation = true;
434                         AllowDefinition allowDefinition = AllowDefinition.Everywhere;
435
436                         while (reader.MoveToNextAttribute ()) {
437                                 attName = reader.Name;
438                                 if (attName == null)
439                                         continue;
440
441                                 if (attName == "allowLocation") {
442                                         if (allowLoc != null)
443                                                 ThrowException ("Duplicated allowLocation attribute.", reader);
444
445                                         allowLoc = reader.Value;
446                                         allowLocation = (allowLoc == "true");
447                                         if (!allowLocation && allowLoc != "false")
448                                                 ThrowException ("Invalid attribute value", reader);
449
450                                         continue;
451                                 }
452
453                                 if (attName == "allowDefinition") {
454                                         if (allowDef != null)
455                                                 ThrowException ("Duplicated allowDefinition attribute.", reader);
456
457                                         allowDef = reader.Value;
458                                         try {
459                                                 allowDefinition = (AllowDefinition) Enum.Parse (
460                                                                    typeof (AllowDefinition), allowDef);
461                                         } catch {
462                                                 ThrowException ("Invalid attribute value", reader);
463                                         }
464
465                                         continue;
466                                 }
467
468                                 if (attName == "type")  {
469                                         if (typeValue != null)
470                                                 ThrowException ("Duplicated type attribute.", reader);
471                                         typeValue = reader.Value;
472                                         continue;
473                                 }
474                                 
475                                 if (attName == "name")  {
476                                         if (nameValue != null)
477                                                 ThrowException ("Duplicated name attribute.", reader);
478                                         nameValue = reader.Value;
479                                         if (nameValue == "location")
480                                                 ThrowException ("location is a reserved section name", reader);
481                                         continue;
482                                 }
483
484                                 ThrowException ("Unrecognized attribute.", reader);
485                         }
486
487                         if (nameValue == null || typeValue == null)
488                                 ThrowException ("Required attribute missing", reader);
489
490                         if (sectionName != null)
491                                 nameValue = sectionName + '/' + nameValue;
492
493                         reader.MoveToElement();
494                         object o = LookForFactory (nameValue);
495                         if (o != null && o != removedMark)
496                                 ThrowException ("Already have a factory for " + nameValue, reader);
497                         SectionData section = new SectionData (nameValue, typeValue, allowLocation, allowDefinition);
498                         section.FileName = fileName;
499                         factories [nameValue] = section;
500
501                         MoveToNextElement (reader);
502                 }
503
504                 private void ReadRemoveSection (XmlTextReader reader, string sectionName)
505                 {
506                         if (!reader.MoveToNextAttribute () || reader.Name != "name")
507                                 ThrowException ("Unrecognized attribute.", reader);
508
509                         string removeValue = reader.Value;
510                         if (removeValue == null || removeValue.Length == 0)
511                                 ThrowException ("Empty name to remove", reader);
512
513                         reader.MoveToElement ();
514
515                         if (sectionName != null)
516                                 removeValue = sectionName + '/' + removeValue;
517
518                         object o = LookForFactory (removeValue);
519                         if (o != null && o == removedMark)
520                                 ThrowException ("No factory for " + removeValue, reader);
521
522                         factories [removeValue] = removedMark;
523                         MoveToNextElement (reader);
524                 }
525
526                 private void ReadSectionGroup (XmlTextReader reader, string configSection)
527                 {
528                         if (!reader.MoveToNextAttribute ())
529                                 ThrowException ("sectionGroup must have a 'name' attribute.", reader);
530
531                         string value = null;
532                         do {
533                                 if (reader.Name == "name") {
534                                         if (value != null)
535                                                 ThrowException ("Duplicate 'name' attribute.", reader);
536                                         value = reader.Value;
537                                 }
538                                 else 
539 #if NET_2_0 && CONFIGURATION_2_0
540                                 if (reader.Name != "type")
541 #endif
542                                         ThrowException ("Unrecognized attribute.", reader);
543                         } while (reader.MoveToNextAttribute ());
544
545                         if (value == null)
546                                 ThrowException ("No 'name' attribute.", reader);
547
548                         if (value == "location")
549                                 ThrowException ("location is a reserved section name", reader);
550                         
551                         if (configSection != null)
552                                 value = configSection + '/' + value;
553
554                         object o = LookForFactory (value);
555                         if (o != null && o != removedMark && o != groupMark)
556                                 ThrowException ("Already have a factory for " + value, reader);
557
558                         factories [value] = groupMark;
559                         MoveToNextElement (reader);
560                         ReadSections (reader, value);
561                 }
562
563                 private void ReadSections (XmlTextReader reader, string configSection)
564                 {
565                         int depth = reader.Depth;
566                         while (reader.Depth == depth) {
567                                 string name = reader.Name;
568                                 if (name == "section") {
569                                         ReadSection (reader, configSection);
570                                         continue;
571                                 } 
572                                 
573                                 if (name == "remove") {
574                                         ReadRemoveSection (reader, configSection);
575                                         continue;
576                                 }
577
578                                 if (name == "clear") {
579                                         if (reader.HasAttributes)
580                                                 ThrowException ("Unrecognized attribute.", reader);
581
582                                         factories.Clear ();
583                                         MoveToNextElement (reader);
584                                         continue;
585                                 }
586
587                                 if (name == "sectionGroup") {
588                                         ReadSectionGroup (reader, configSection);
589                                         continue;
590                                 }
591                                 
592
593                                 ThrowException ("Unrecognized element: " + reader.Name, reader);
594                         }
595                 }
596
597                 void StorePending (string name, XmlTextReader reader)
598                 {
599                         if (pending == null)
600                                 pending = new Hashtable ();
601
602                         pending [name] = reader.ReadOuterXml ();
603                 }
604
605                 private void ReadConfigFile (XmlTextReader reader)
606                 {
607                         int depth = reader.Depth;
608                         while (!reader.EOF && reader.Depth == depth) {
609                                 string name = reader.Name;
610                                 if (name == "configSections") {
611                                         if (reader.HasAttributes)
612                                                 ThrowException ("Unrecognized attribute in <configSections>.", reader);
613
614                                         MoveToNextElement (reader);
615                                         if (reader.Depth > depth)
616                                                 ReadSections (reader, null);
617                                 } else if (name != null && name != "") {
618                                         StorePending (name, reader);
619                                         MoveToNextElement (reader);
620                                 } else {
621                                         MoveToNextElement (reader);
622                                 }
623                         }
624                 }
625                                 
626                 private void ThrowException (string text, XmlTextReader reader)
627                 {
628                         throw new ConfigurationException (text, fileName, reader.LineNumber);
629                 }
630 #endif
631         }
632 }
633
634