locking fixes
[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 using System;
36 using System.Collections;
37 using System.Collections.Specialized;
38 using System.IO;
39 using System.Runtime.CompilerServices;
40 #if (XML_DEP)
41 using System.Xml;
42 using System.Xml.XPath;
43 #endif
44
45 namespace System.Configuration
46 {
47         public sealed class ConfigurationSettings
48         {
49                 static IConfigurationSystem config = DefaultConfig.GetInstance ();
50                 static object lockobj = new object ();
51                 private ConfigurationSettings ()
52                 {
53                 }
54
55                 public static object GetConfig (string sectionName)
56                 {
57                         return config.GetConfig (sectionName);
58                 }
59
60                 public static NameValueCollection AppSettings
61                 {
62                         get {
63                                 object appSettings = GetConfig ("appSettings");
64                                 if (appSettings == null)
65                                         appSettings = new NameValueCollection ();
66
67                                 return (NameValueCollection) appSettings;
68                         }
69                 }
70
71 #if NET_2_0
72 #if XML_DEP
73                 public static ConnectionStringSettingsCollection ConnectionStrings
74                 {
75                         get {
76                                 ConnectionStringsSection connSection = (ConnectionStringsSection) GetConfig ("connectionStrings");
77                                 ConnectionStringSettingsCollection connectionStrings = null;
78                                 
79                                 if (connSection != null)
80                                         connectionStrings = connSection.ConnectionStrings;
81                                 else 
82                                         connectionStrings = new ConnectionStringSettingsCollection ();
83                                 
84                                 return connectionStrings;
85                         }
86                 }
87 #endif // XML_DEP
88 #endif // NET_2_0
89
90                 // Invoked from System.Web
91                 static IConfigurationSystem ChangeConfigurationSystem (IConfigurationSystem newSystem)
92                 {
93                         if (newSystem == null)
94                                 throw new ArgumentNullException ("newSystem");
95
96                         lock (lockobj) {
97                                 IConfigurationSystem old = config;
98                                 config = newSystem;
99                                 return old;
100                         }
101                 }
102         }
103
104         //
105         // class DefaultConfig: read configuration from machine.config file and application
106         // config file if available.
107         //
108         class DefaultConfig : IConfigurationSystem
109         {
110                 static readonly DefaultConfig instance = new DefaultConfig ();
111                 ConfigurationData config;
112                 
113                 private DefaultConfig ()
114                 {
115                 }
116
117                 public static DefaultConfig GetInstance ()
118                 {
119                         return instance;
120                 }
121
122                 public object GetConfig (string sectionName)
123                 {
124                         Init ();
125                         return config.GetConfig (sectionName);
126                 }
127
128                 public void Init ()
129                 {
130                         lock (this) {
131                                 if (config != null)
132                                         return;
133
134                                 ConfigurationData data = new ConfigurationData ();
135                                 if (!data.Load (GetMachineConfigPath ()))
136                                         throw new ConfigurationException ("Cannot find " + GetMachineConfigPath ());
137
138                                 string appfile = GetAppConfigPath ();
139                                 if (appfile == null) {
140                                         config = data;
141                                         return;
142                                 }
143
144                                 ConfigurationData appData = new ConfigurationData (data);
145                                 if (appData.Load (appfile))
146                                         config = appData;
147                                 else
148                                         config = data;
149                         }
150                 }
151
152                 [MethodImplAttribute(MethodImplOptions.InternalCall)]
153                 extern private static string get_machine_config_path ();
154
155                 internal static string GetMachineConfigPath ()
156                 {
157                         return get_machine_config_path ();
158                 }
159
160                 private static string GetAppConfigPath ()
161                 {
162                         AppDomainSetup currentInfo = AppDomain.CurrentDomain.SetupInformation;
163
164                         string configFile = currentInfo.ConfigurationFile;
165                         if (configFile == null || configFile.Length == 0)
166                                 return null;
167
168                         return configFile;
169
170                 }
171         }
172
173         enum AllowDefinition
174         {
175                 Everywhere,
176                 MachineOnly,
177                 MachineToApplication
178         }
179         
180         class SectionData
181         {
182                 public readonly string SectionName;
183                 public readonly string TypeName;
184                 public readonly bool AllowLocation;
185                 public readonly AllowDefinition AllowDefinition;
186                 public string FileName;
187
188                 public SectionData (string sectionName, string typeName,
189                                     bool allowLocation, AllowDefinition allowDefinition)
190                 {
191                         SectionName = sectionName;
192                         TypeName = typeName;
193                         AllowLocation = allowLocation;
194                         AllowDefinition = allowDefinition;
195                 }
196         }
197         
198
199         class ConfigurationData
200         {
201                 ConfigurationData parent;
202                 Hashtable factories;
203                 Hashtable pending;
204                 string fileName;
205                 static object removedMark = new object ();
206                 static object groupMark = new object ();
207                 static object emptyMark = new object ();
208                 Hashtable cache;
209
210                 Hashtable FileCache {
211                         get {
212                                 if (cache != null)
213                                         return cache;
214
215                                 cache = new Hashtable ();
216                                 return cache;
217                         }
218                 }
219
220                 public ConfigurationData () : this (null)
221                 {
222                 }
223
224                 public ConfigurationData (ConfigurationData parent)
225                 {
226                         this.parent = (parent == this) ? null : parent;
227                         factories = new Hashtable ();
228                 }
229
230                 public bool Load (string fileName)
231                 {
232                         this.fileName = fileName;
233                         if (fileName == null || !File.Exists (fileName))
234                                 return false;
235 #if (XML_DEP)
236                         XmlTextReader reader = null;
237
238                         try {
239                                 FileStream fs = new FileStream (fileName, FileMode.Open, FileAccess.Read);
240                                 reader = new XmlTextReader (fs);
241                                 InitRead (reader);
242                                 ReadConfigFile (reader);
243                         } catch (ConfigurationException) {
244                                 throw;
245                         } catch (Exception e) {
246                                 throw new ConfigurationException ("Error reading " + fileName, e);
247                         } finally {
248                                 if (reader != null)
249                                         reader.Close();
250                         }
251 #endif
252                         return true;
253                 }
254
255                 object GetHandler (string sectionName)
256                 {
257                         lock (factories) {
258                                 object o = factories [sectionName];
259                                 if (o == null || o == removedMark) {
260                                         if (parent != null)
261                                                 return parent.GetHandler (sectionName);
262
263                                         return null;
264                                 }
265
266                                 if (o is IConfigurationSectionHandler)
267                                         return (IConfigurationSectionHandler) o;
268
269                                 o = CreateNewHandler (sectionName, (SectionData) o);
270                                 factories [sectionName] = o;
271                                 return o;
272                         }
273                 }
274
275                 object CreateNewHandler (string sectionName, SectionData section)
276                 {
277                         Type t = Type.GetType (section.TypeName);
278                         if (t == null)
279                                 throw new ConfigurationException ("Cannot get Type for " + section.TypeName);
280
281                         Type iconfig = typeof (IConfigurationSectionHandler);
282                         if (!iconfig.IsAssignableFrom (t))
283                                 throw new ConfigurationException (sectionName + " does not implement " + iconfig);
284                         
285                         object o = Activator.CreateInstance (t, true);
286                         if (o == null)
287                                 throw new ConfigurationException ("Cannot get instance for " + t);
288
289                         return o;
290                 }
291 #if (XML_DEP)
292                 XmlDocument GetInnerDoc (XmlDocument doc, int i, string [] sectionPath)
293                 {
294                         if (++i >= sectionPath.Length)
295                                 return doc;
296
297                         if (doc.DocumentElement == null)
298                                 return null;
299
300                         XmlNode node = doc.DocumentElement.FirstChild;
301                         while (node != null) {
302                                 if (node.Name == sectionPath [i]) {
303                                         ConfigXmlDocument result = new ConfigXmlDocument ();
304                                         result.Load (new StringReader (node.OuterXml));
305                                         return GetInnerDoc (result, i, sectionPath);
306                                 }
307                                 node = node.NextSibling;
308                         }
309
310                         return null;
311                 }
312
313                 XmlDocument GetDocumentForSection (string sectionName)
314                 {
315                         ConfigXmlDocument doc = new ConfigXmlDocument ();
316                         if (pending == null)
317                                 return doc;
318
319                         string [] sectionPath = sectionName.Split ('/');
320                         string outerxml = pending [sectionPath [0]] as string;
321                         if (outerxml == null)
322                                 return doc;
323
324                         StringReader reader = new StringReader (outerxml);
325                         XmlTextReader rd = new XmlTextReader (reader);
326                         rd.MoveToContent ();
327                         doc.LoadSingleElement (fileName, rd);
328
329                         return GetInnerDoc (doc, 0, sectionPath);
330                 }
331                 
332                 object GetConfigInternal (string sectionName)
333                 {
334                         object handler = GetHandler (sectionName);
335                         IConfigurationSectionHandler iconf = handler as IConfigurationSectionHandler;
336                         if (iconf == null)
337                                 return handler;
338
339                         object parentConfig = null;
340                         if (parent != null)
341                                 parentConfig = parent.GetConfig (sectionName);
342
343                         XmlDocument doc = GetDocumentForSection (sectionName);
344                         if (doc == null || doc.DocumentElement == null)
345                                 return parentConfig;
346                         
347                         return iconf.Create (parentConfig, fileName, doc.DocumentElement);
348                 }
349 #else
350                 object GetConfigInternal (string sectionName)
351                 {
352                     return null;
353                 }
354 #endif
355                 public object GetConfig (string sectionName)
356                 {
357                         object config;
358                         lock (this) {
359                                 config = this.FileCache [sectionName];
360                         }
361
362                         if (config == emptyMark)
363                                 return null;
364
365                         if (config != null)
366                                 return config;
367
368                         lock (this) {
369                                 config = GetConfigInternal (sectionName);
370                                 this.FileCache [sectionName] = (config == null) ? emptyMark : config;
371                         }
372
373                         return config;
374                 }
375
376                 private object LookForFactory (string key)
377                 {
378                         object o = factories [key];
379                         if (o != null)
380                                 return o;
381
382                         if (parent != null)
383                                 return parent.LookForFactory (key);
384
385                         return null;
386                 }
387 #if (XML_DEP)
388                 private void InitRead (XmlTextReader reader)
389                 {
390                         reader.MoveToContent ();
391                         if (reader.NodeType != XmlNodeType.Element || reader.Name != "configuration")
392                                 ThrowException ("Configuration file does not have a valid root element", reader);
393
394                         if (reader.HasAttributes)
395                                 ThrowException ("Unrecognized attribute in root element", reader);
396
397                         MoveToNextElement (reader);
398                 }
399
400                 private void MoveToNextElement (XmlTextReader reader)
401                 {
402                         while (reader.Read ()) {
403                                 XmlNodeType ntype = reader.NodeType;
404                                 if (ntype == XmlNodeType.Element)
405                                         return;
406
407                                 if (ntype != XmlNodeType.Whitespace &&
408                                     ntype != XmlNodeType.Comment &&
409                                     ntype != XmlNodeType.SignificantWhitespace &&
410                                     ntype != XmlNodeType.EndElement)
411                                         ThrowException ("Unrecognized element", reader);
412                         }
413                 }
414
415                 private void ReadSection (XmlTextReader reader, string sectionName)
416                 {
417                         string attName;
418                         string nameValue = null;
419                         string typeValue = null;
420                         string allowLoc = null, allowDef = null;
421                         bool allowLocation = true;
422                         AllowDefinition allowDefinition = AllowDefinition.Everywhere;
423
424                         while (reader.MoveToNextAttribute ()) {
425                                 attName = reader.Name;
426                                 if (attName == null)
427                                         continue;
428
429                                 if (attName == "allowLocation") {
430                                         if (allowLoc != null)
431                                                 ThrowException ("Duplicated allowLocation attribute.", reader);
432
433                                         allowLoc = reader.Value;
434                                         allowLocation = (allowLoc == "true");
435                                         if (!allowLocation && allowLoc != "false")
436                                                 ThrowException ("Invalid attribute value", reader);
437
438                                         continue;
439                                 }
440
441                                 if (attName == "allowDefinition") {
442                                         if (allowDef != null)
443                                                 ThrowException ("Duplicated allowDefinition attribute.", reader);
444
445                                         allowDef = reader.Value;
446                                         try {
447                                                 allowDefinition = (AllowDefinition) Enum.Parse (
448                                                                    typeof (AllowDefinition), allowDef);
449                                         } catch {
450                                                 ThrowException ("Invalid attribute value", reader);
451                                         }
452
453                                         continue;
454                                 }
455
456                                 if (attName == "type")  {
457                                         if (typeValue != null)
458                                                 ThrowException ("Duplicated type attribute.", reader);
459                                         typeValue = reader.Value;
460                                         continue;
461                                 }
462                                 
463                                 if (attName == "name")  {
464                                         if (nameValue != null)
465                                                 ThrowException ("Duplicated name attribute.", reader);
466                                         nameValue = reader.Value;
467                                         if (nameValue == "location")
468                                                 ThrowException ("location is a reserved section name", reader);
469                                         continue;
470                                 }
471
472                                 ThrowException ("Unrecognized attribute.", reader);
473                         }
474
475                         if (nameValue == null || typeValue == null)
476                                 ThrowException ("Required attribute missing", reader);
477
478                         if (sectionName != null)
479                                 nameValue = sectionName + '/' + nameValue;
480
481                         reader.MoveToElement();
482                         object o = LookForFactory (nameValue);
483                         if (o != null && o != removedMark)
484                                 ThrowException ("Already have a factory for " + nameValue, reader);
485                         SectionData section = new SectionData (nameValue, typeValue, allowLocation, allowDefinition);
486                         section.FileName = fileName;
487                         factories [nameValue] = section;
488
489                         MoveToNextElement (reader);
490                 }
491
492                 private void ReadRemoveSection (XmlTextReader reader, string sectionName)
493                 {
494                         if (!reader.MoveToNextAttribute () || reader.Name != "name")
495                                 ThrowException ("Unrecognized attribute.", reader);
496
497                         string removeValue = reader.Value;
498                         if (removeValue == null || removeValue.Length == 0)
499                                 ThrowException ("Empty name to remove", reader);
500
501                         reader.MoveToElement ();
502
503                         if (sectionName != null)
504                                 removeValue = sectionName + '/' + removeValue;
505
506                         object o = LookForFactory (removeValue);
507                         if (o != null && o == removedMark)
508                                 ThrowException ("No factory for " + removeValue, reader);
509
510                         factories [removeValue] = removedMark;
511                         MoveToNextElement (reader);
512                 }
513
514                 private void ReadSectionGroup (XmlTextReader reader, string configSection)
515                 {
516                         if (!reader.MoveToNextAttribute ())
517                                 ThrowException ("sectionGroup must have a 'name' attribute.", reader);
518
519                         if (reader.Name != "name")
520                                 ThrowException ("Unrecognized attribute.", reader);
521
522                         if (reader.MoveToNextAttribute ())
523                                 ThrowException ("Unrecognized attribute.", reader);
524
525                         string value = reader.Value;
526                         if (value == "location")
527                                 ThrowException ("location is a reserved section name", reader);
528                         
529                         if (configSection != null)
530                                 value = configSection + '/' + value;
531
532                         object o = LookForFactory (value);
533                         if (o != null && o != removedMark && o != groupMark)
534                                 ThrowException ("Already have a factory for " + value, reader);
535
536                         factories [value] = groupMark;
537                         MoveToNextElement (reader);
538                         ReadSections (reader, value);
539                 }
540
541                 private void ReadSections (XmlTextReader reader, string configSection)
542                 {
543                         int depth = reader.Depth;
544                         while (reader.Depth == depth) {
545                                 string name = reader.Name;
546                                 if (name == "section") {
547                                         ReadSection (reader, configSection);
548                                         continue;
549                                 } 
550                                 
551                                 if (name == "remove") {
552                                         ReadRemoveSection (reader, configSection);
553                                         continue;
554                                 }
555
556                                 if (name == "clear") {
557                                         if (reader.HasAttributes)
558                                                 ThrowException ("Unrecognized attribute.", reader);
559
560                                         factories.Clear ();
561                                         MoveToNextElement (reader);
562                                         continue;
563                                 }
564
565                                 if (name == "sectionGroup") {
566                                         ReadSectionGroup (reader, configSection);
567                                         continue;
568                                 }
569                                 
570
571                                 ThrowException ("Unrecognized element: " + reader.Name, reader);
572                         }
573                 }
574
575                 void StorePending (string name, XmlTextReader reader)
576                 {
577                         if (pending == null)
578                                 pending = new Hashtable ();
579
580                         pending [name] = reader.ReadOuterXml ();
581                 }
582
583                 private void ReadConfigFile (XmlTextReader reader)
584                 {
585                         int depth = reader.Depth;
586                         while (!reader.EOF && reader.Depth == depth) {
587                                 string name = reader.Name;
588                                 if (name == "configSections") {
589                                         if (reader.HasAttributes)
590                                                 ThrowException ("Unrecognized attribute in <configSections>.", reader);
591
592                                         MoveToNextElement (reader);
593                                         if (reader.Depth > depth)
594                                                 ReadSections (reader, null);
595                                 } else if (name != null && name != "") {
596                                         StorePending (name, reader);
597                                         MoveToNextElement (reader);
598                                 } else {
599                                         MoveToNextElement (reader);
600                                 }
601                         }
602                 }
603                                 
604                 private void ThrowException (string text, XmlTextReader reader)
605                 {
606                         throw new ConfigurationException (text, fileName, reader.LineNumber);
607                 }
608 #endif
609         }
610 }
611
612