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