2009-07-14 Marek Habersack <mhabersack@novell.com>
[mono.git] / mcs / class / System.Configuration / System.Configuration / Configuration.cs
1 //
2 // System.Configuration.Configuration.cs
3 //
4 // Authors:
5 //      Duncan Mak (duncan@ximian.com)
6 //      Lluis Sanchez Gual (lluis@novell.com)
7 //
8 // Permission is hereby granted, free of charge, to any person obtaining
9 // a copy of this software and associated documentation files (the
10 // "Software"), to deal in the Software without restriction, including
11 // without limitation the rights to use, copy, modify, merge, publish,
12 // distribute, sublicense, and/or sell copies of the Software, and to
13 // permit persons to whom the Software is furnished to do so, subject to
14 // the following conditions:
15 // 
16 // The above copyright notice and this permission notice shall be
17 // included in all copies or substantial portions of the Software.
18 // 
19 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
20 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
22 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
23 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
24 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
25 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26 //
27 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
28 //
29 #if NET_2_0
30 using System;
31 using System.Collections;
32 using System.Collections.Specialized;
33 using System.Configuration.Internal;
34 using System.ComponentModel;
35 using System.Reflection;
36 using System.Xml;
37 using System.IO;
38
39 namespace System.Configuration {
40
41         public sealed class Configuration
42         {               
43                 Configuration parent;
44                 Hashtable elementData = new Hashtable ();
45                 string streamName;
46                 ConfigurationSectionGroup rootSectionGroup;
47                 ConfigurationLocationCollection locations;
48                 SectionGroupInfo rootGroup;
49                 IConfigSystem system;
50                 bool hasFile;
51                 string rootNamespace;
52                 
53                 string configPath;
54                 string locationConfigPath;
55                 string locationSubPath;
56
57                 internal static event ConfigurationSaveEventHandler SaveStart;
58                 internal static event ConfigurationSaveEventHandler SaveEnd;
59                 
60                 internal Configuration (Configuration parent, string locationSubPath)
61                 {
62                         this.parent = parent;
63                         this.system = parent.system;
64                         this.rootGroup = parent.rootGroup;
65                         this.locationSubPath = locationSubPath;
66                 }
67                 
68                 internal Configuration (InternalConfigurationSystem system, string locationSubPath)
69                 {
70                         hasFile = true;
71                         this.system = system;
72                         
73                         system.InitForConfiguration (ref locationSubPath, out configPath, out locationConfigPath);
74                         
75                         Configuration parent = null;
76                         
77                         if (locationSubPath != null) {
78                                 parent = new Configuration (system, locationSubPath);
79                                 if (locationConfigPath != null)
80                                         parent = parent.FindLocationConfiguration (locationConfigPath, parent);
81                         }
82                         
83                         Init (system, configPath, parent);
84                 }
85                 
86                 internal Configuration FindLocationConfiguration (string relativePath, Configuration defaultConfiguration)
87                 {
88                         ConfigurationLocation loc = Locations.Find (relativePath);
89                         
90                         Configuration parentConfig = defaultConfiguration;
91                         
92                         if (LocationConfigPath != null) {
93                                 Configuration parentFile = GetParentWithFile ();
94                                 if (parentFile != null) {
95                                         string parentRelativePath = system.Host.GetConfigPathFromLocationSubPath (LocationConfigPath, relativePath);
96                                         parentConfig = parentFile.FindLocationConfiguration (parentRelativePath, defaultConfiguration);
97                                 }
98                         }
99
100                         if (loc == null)
101                                 return parentConfig;
102                         
103                         loc.SetParentConfiguration (parentConfig);
104                         return loc.OpenConfiguration ();
105                 }
106                 
107                 internal void Init (IConfigSystem system, string configPath, Configuration parent)
108                 {
109                         this.system = system;
110                         this.configPath = configPath;
111                         streamName = system.Host.GetStreamName (configPath);
112                         this.parent = parent;
113                         if (parent != null)
114                                 rootGroup = parent.rootGroup;
115                         else {
116                                 rootGroup = new SectionGroupInfo ();
117                                 rootGroup.StreamName = streamName;
118                         }
119                         
120                         if (streamName != null)
121                                 Load ();
122                 }
123                 
124                 internal Configuration Parent {
125                         get { return parent; }
126                         set { parent = value; }
127                 }
128                 
129                 internal Configuration GetParentWithFile ()
130                 {
131                         Configuration parentFile = Parent;
132                         while (parentFile != null && !parentFile.HasFile)
133                                 parentFile = parentFile.Parent;
134                         return parentFile;
135                 }
136                 
137                 internal string FileName {
138                         get { return streamName; }
139                 }
140
141                 internal IInternalConfigHost ConfigHost {
142                         get { return system.Host; }
143                 }
144                 
145                 internal string LocationConfigPath {
146                         get { return locationConfigPath; }
147                 }
148
149                 internal string GetLocationSubPath ()
150                 {
151                         Configuration confg = parent;
152                         string path = null;
153                         while (confg != null) {
154                                 path = confg.locationSubPath;
155                                 if (!String.IsNullOrEmpty (path))
156                                         return path;
157                                 confg = confg.parent;
158                         }
159                         return path;
160                 }
161
162                 internal string ConfigPath {
163                         get { return configPath; }
164                 }
165
166                 public AppSettingsSection AppSettings {
167                         get { return (AppSettingsSection) GetSection ("appSettings"); }
168                 }
169
170                 public ConnectionStringsSection ConnectionStrings {
171                         get { return (ConnectionStringsSection) GetSection ("connectionStrings"); }
172                 }
173
174                 // MSDN: If the value for this FilePath property represents a merged view and 
175                 // no actual file exists for the application, the path to the parent configuration 
176                 // file is returned.
177                 public string FilePath {
178                         get {
179                                 if (streamName == null && parent != null)
180                                         return parent.FilePath;
181                                 return streamName;
182                         }
183                 }
184
185                 public bool HasFile {
186                         get {
187                                 return hasFile;
188                         }
189                 }
190
191                 ContextInformation evaluationContext;
192                 public ContextInformation EvaluationContext {
193                         get {
194                                 if (evaluationContext == null) {
195                                         object ctx = system.Host.CreateConfigurationContext (configPath, GetLocationSubPath() );
196                                         evaluationContext = new ContextInformation (this, ctx);
197                                 }
198
199
200                                 return evaluationContext;
201                         }
202                 }
203                 
204                 public ConfigurationLocationCollection Locations {
205                         get {
206                                 if (locations == null) locations = new ConfigurationLocationCollection ();
207                                 return locations;
208                         }
209                 }
210
211                 public bool NamespaceDeclared {
212                         get { return rootNamespace != null; }
213                         set { rootNamespace = value ? "http://schemas.microsoft.com/.NetConfiguration/v2.0" : null; }
214                 }
215
216                 public ConfigurationSectionGroup RootSectionGroup {
217                         get {
218                                 if (rootSectionGroup == null) {
219                                         rootSectionGroup = new ConfigurationSectionGroup ();
220                                         rootSectionGroup.Initialize (this, rootGroup);
221                                 }
222                                 return rootSectionGroup;
223                         }
224                 }
225
226                 public ConfigurationSectionGroupCollection SectionGroups {
227                         get { return RootSectionGroup.SectionGroups; }
228                 }
229
230                 public ConfigurationSectionCollection Sections {
231                         get { return RootSectionGroup.Sections; }
232                 }
233                 
234                 public ConfigurationSection GetSection (string path)
235                 {
236                         string[] parts = path.Split ('/');
237                         if (parts.Length == 1)
238                                 return Sections [parts[0]];
239
240                         ConfigurationSectionGroup group = SectionGroups [parts[0]];
241                         for (int n=1; group != null && n<parts.Length-1; n++)
242                                 group = group.SectionGroups [parts [n]];
243
244                         if (group != null)
245                                 return group.Sections [parts [parts.Length - 1]];
246                         else
247                                 return null;
248                 }
249                 
250                 public ConfigurationSectionGroup GetSectionGroup (string path)
251                 {
252                         string[] parts = path.Split ('/');
253                         ConfigurationSectionGroup group = SectionGroups [parts[0]];
254                         for (int n=1; group != null && n<parts.Length; n++)
255                                 group = group.SectionGroups [parts [n]];
256                         return group;
257                 }
258                 
259                 internal ConfigurationSection GetSectionInstance (SectionInfo config, bool createDefaultInstance)
260                 {
261                         object data = elementData [config];
262                         ConfigurationSection sec = data as ConfigurationSection;
263                         if (sec != null || !createDefaultInstance) return sec;
264                         
265                         object secObj = config.CreateInstance ();
266                         sec = secObj as ConfigurationSection;
267                         if (sec == null) {
268                                 DefaultSection ds = new DefaultSection ();
269                                 ds.SectionHandler = secObj as IConfigurationSectionHandler;
270                                 sec = ds;
271                         }
272                         sec.Configuration = this;
273
274                         ConfigurationSection parentSection = null;
275                         if (parent != null) {
276                                 parentSection = parent.GetSectionInstance (config, true);
277                                 sec.SectionInformation.SetParentSection (parentSection);
278                         }
279                         sec.SectionInformation.ConfigFilePath = FilePath;
280
281                         sec.ConfigContext = system.Host.CreateDeprecatedConfigContext(configPath);
282                         
283                         string xml = data as string;
284                         sec.RawXml = xml;
285                         sec.Reset (parentSection);
286
287                         if (xml != null && xml == data) {
288                                 XmlTextReader r = new ConfigXmlTextReader (new StringReader (xml), FilePath);
289                                 sec.DeserializeSection (r);
290                                 r.Close ();
291
292                                 if (!String.IsNullOrEmpty (sec.SectionInformation.ConfigSource) && !String.IsNullOrEmpty (FilePath))
293                                         sec.DeserializeConfigSource (Path.GetDirectoryName (FilePath));
294                         }
295                         
296                         elementData [config] = sec;
297                         return sec;
298                 }
299                 
300                 internal ConfigurationSectionGroup GetSectionGroupInstance (SectionGroupInfo group)
301                 {
302                         ConfigurationSectionGroup gr = group.CreateInstance () as ConfigurationSectionGroup;
303                         if (gr != null) gr.Initialize (this, group);
304                         return gr;
305                 }
306                 
307                 internal void SetConfigurationSection (SectionInfo config, ConfigurationSection sec)
308                 {
309                         elementData [config] = sec;
310                 }
311                 
312                 internal void SetSectionXml (SectionInfo config, string data)
313                 {
314                         elementData [config] = data;
315                 }
316                 
317                 internal string GetSectionXml (SectionInfo config)
318                 {
319                         return elementData [config] as string;
320                 }
321                 
322                 internal void CreateSection (SectionGroupInfo group, string name, ConfigurationSection sec)
323                 {
324                         if (group.HasChild (name))
325                                 throw new ConfigurationException ("Cannot add a ConfigurationSection. A section or section group already exists with the name '" + name + "'");
326                                 
327                         if (!HasFile && !sec.SectionInformation.AllowLocation)
328                                 throw new ConfigurationErrorsException ("The configuration section <" + name + "> cannot be defined inside a <location> element."); 
329
330                         if (!system.Host.IsDefinitionAllowed (configPath, sec.SectionInformation.AllowDefinition, sec.SectionInformation.AllowExeDefinition)) {
331                                 object ctx = sec.SectionInformation.AllowExeDefinition != ConfigurationAllowExeDefinition.MachineToApplication ? (object) sec.SectionInformation.AllowExeDefinition : (object) sec.SectionInformation.AllowDefinition;
332                                 throw new ConfigurationErrorsException ("The section <" + name + "> can't be defined in this configuration file (the allowed definition context is '" + ctx + "').");
333                         }
334
335                         if (sec.SectionInformation.Type == null)
336                                 sec.SectionInformation.Type = system.Host.GetConfigTypeName (sec.GetType ());
337
338                         SectionInfo section = new SectionInfo (name, sec.SectionInformation);
339                         section.StreamName = streamName;
340                         section.ConfigHost = system.Host;
341                         group.AddChild (section);
342                         elementData [section] = sec;
343                 }
344                 
345                 internal void CreateSectionGroup (SectionGroupInfo parentGroup, string name, ConfigurationSectionGroup sec)
346                 {
347                         if (parentGroup.HasChild (name)) throw new ConfigurationException ("Cannot add a ConfigurationSectionGroup. A section or section group already exists with the name '" + name + "'");
348                         if (sec.Type == null) sec.Type = system.Host.GetConfigTypeName (sec.GetType ());
349                         sec.SetName (name);
350
351                         SectionGroupInfo section = new SectionGroupInfo (name, sec.Type);
352                         section.StreamName = streamName;
353                         section.ConfigHost = system.Host;
354                         parentGroup.AddChild (section);
355                         elementData [section] = sec;
356
357                         sec.Initialize (this, section);
358                 }
359                 
360                 internal void RemoveConfigInfo (ConfigInfo config)
361                 {
362                         elementData.Remove (config);
363                 }
364                 
365                 public void Save ()
366                 {
367                         Save (ConfigurationSaveMode.Modified, false);
368                 }
369                 
370                 public void Save (ConfigurationSaveMode mode)
371                 {
372                         Save (mode, false);
373                 }
374                 
375                 public void Save (ConfigurationSaveMode mode, bool forceUpdateAll)
376                 {
377                         ConfigurationSaveEventHandler saveStart = SaveStart;
378                         ConfigurationSaveEventHandler saveEnd = SaveEnd;
379                         
380                         object ctx = null;
381                         Exception saveEx = null;
382                         Stream stream = system.Host.OpenStreamForWrite (streamName, null, ref ctx);
383                         try {
384                                 if (saveStart != null)
385                                         saveStart (this, new ConfigurationSaveEventArgs (streamName, true, null, ctx));
386                                 
387                                 Save (stream, mode, forceUpdateAll);
388                                 system.Host.WriteCompleted (streamName, true, ctx);
389                         } catch (Exception ex) {
390                                 saveEx = ex;
391                                 system.Host.WriteCompleted (streamName, false, ctx);
392                                 throw;
393                         } finally {
394                                 stream.Close ();
395                                 if (saveEnd != null)
396                                         saveEnd (this, new ConfigurationSaveEventArgs (streamName, false, saveEx, ctx));
397                         }
398                 }
399                 
400                 public void SaveAs (string filename)
401                 {
402                         SaveAs (filename, ConfigurationSaveMode.Modified, false);
403                 }
404                 
405                 public void SaveAs (string filename, ConfigurationSaveMode mode)
406                 {
407                         SaveAs (filename, mode, false);
408                 }
409
410                 [MonoInternalNote ("Detect if file has changed")]
411                 public void SaveAs (string filename, ConfigurationSaveMode mode, bool forceUpdateAll)
412                 {
413                         string dir = Path.GetDirectoryName (Path.GetFullPath (filename));
414                         if (!Directory.Exists (dir))
415                                 Directory.CreateDirectory (dir);
416                         Save (new FileStream (filename, FileMode.OpenOrCreate, FileAccess.Write), mode, forceUpdateAll);
417                 }
418
419                 void Save (Stream stream, ConfigurationSaveMode mode, bool forceUpdateAll)
420                 {
421                         XmlTextWriter tw = new XmlTextWriter (new StreamWriter (stream));
422                         tw.Formatting = Formatting.Indented;
423                         try {
424                                 tw.WriteStartDocument ();
425                                 if (rootNamespace != null)
426                                         tw.WriteStartElement ("configuration", rootNamespace);
427                                 else
428                                         tw.WriteStartElement ("configuration");
429                                 if (rootGroup.HasConfigContent (this)) {
430                                         rootGroup.WriteConfig (this, tw, mode);
431                                 }
432                                 
433                                 foreach (ConfigurationLocation loc in Locations) {
434                                         if (loc.OpenedConfiguration == null) {
435                                                 tw.WriteRaw ("\n");
436                                                 tw.WriteRaw (loc.XmlContent);
437                                         }
438                                         else {
439                                                 tw.WriteStartElement ("location");
440                                                 tw.WriteAttributeString ("path", loc.Path); 
441                                                 if (!loc.AllowOverride)
442                                                         tw.WriteAttributeString ("allowOverride", "false");
443                                                 loc.OpenedConfiguration.SaveData (tw, mode, forceUpdateAll);
444                                                 tw.WriteEndElement ();
445                                         }
446                                 }
447                                 
448                                 SaveData (tw, mode, forceUpdateAll);
449                                 tw.WriteEndElement ();
450                         }
451                         finally {
452                                 tw.Close ();
453                         }
454                 }
455                 
456                 void SaveData (XmlTextWriter tw, ConfigurationSaveMode mode, bool forceUpdateAll)
457                 {
458                         rootGroup.WriteRootData (tw, this, mode);
459                 }
460                 
461                 bool Load ()
462                 {
463                         if (String.IsNullOrEmpty (streamName))
464                                 return true;
465
466                         if (!File.Exists (streamName))
467                                 return false;
468
469                         Stream stream = stream = system.Host.OpenStreamForRead (streamName);
470                         using (XmlTextReader reader = new ConfigXmlTextReader (stream, streamName)) {
471                                 ReadConfigFile (reader, streamName);
472                         }
473                         return true;
474                 }
475
476                 void ReadConfigFile (XmlReader reader, string fileName)
477                 {
478                         reader.MoveToContent ();
479
480                         if (reader.NodeType != XmlNodeType.Element || reader.Name != "configuration")
481                                 ThrowException ("Configuration file does not have a valid root element", reader);
482
483                         if (reader.HasAttributes) {
484                                 while (reader.MoveToNextAttribute ()) {
485                                         if (reader.LocalName == "xmlns") {
486                                                 rootNamespace = reader.Value;
487                                                 continue;
488                                         }
489                                         ThrowException (String.Format ("Unrecognized attribute '{0}' in root element", reader.LocalName), reader);
490                                 }
491                         }
492
493                         reader.MoveToElement ();
494
495                         if (reader.IsEmptyElement) {
496                                 reader.Skip ();
497                                 return;
498                         }
499                         
500                         reader.ReadStartElement ();
501                         reader.MoveToContent ();
502
503                         if (reader.LocalName == "configSections") {
504                                 if (reader.HasAttributes)
505                                         ThrowException ("Unrecognized attribute in <configSections>.", reader);
506                                 
507                                 rootGroup.ReadConfig (this, fileName, reader);
508                         }
509                         
510                         rootGroup.ReadRootData (reader, this, true);
511                 }
512
513                 internal void ReadData (XmlReader reader, bool allowOverride)
514                 {
515                         rootGroup.ReadData (this, reader, allowOverride);
516                 }
517                 
518
519                 private void ThrowException (string text, XmlReader reader)
520                 {
521                         IXmlLineInfo li = reader as IXmlLineInfo;
522                         throw new ConfigurationException (text, streamName, li != null ? li.LineNumber : 0);
523                 }
524         }
525 }
526
527 #endif