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