2007-08-09 Marek Habersack <mhabersack@novell.com>
[mono.git] / mcs / class / System.Web / System.Web / HttpApplicationFactory.cs
1 //
2 // System.Web.HttpApplicationFactory
3 //
4 // Author:
5 //      Gonzalo Paniagua Javier (gonzalo@ximian.com)
6 //
7 // (c) 2002,2003 Ximian, Inc. (http://www.ximian.com)
8 // (c) Copyright 2004 Novell, Inc. (http://www.novell.com)
9 //
10 // Permission is hereby granted, free of charge, to any person obtaining
11 // a copy of this software and associated documentation files (the
12 // "Software"), to deal in the Software without restriction, including
13 // without limitation the rights to use, copy, modify, merge, publish,
14 // distribute, sublicense, and/or sell copies of the Software, and to
15 // permit persons to whom the Software is furnished to do so, subject to
16 // the following conditions:
17 // 
18 // The above copyright notice and this permission notice shall be
19 // included in all copies or substantial portions of the Software.
20 // 
21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28 //
29 using System;
30 using System.Collections;
31 using System.IO;
32 using System.Reflection;
33 using System.Web.UI;
34 using System.Web.SessionState;
35 using System.Web.Configuration;
36
37 using System.Web.Compilation;
38 #if TARGET_J2EE
39 using vmw.common;
40 #endif
41
42 #if NET_2_0 && !TARGET_J2EE
43 using System.CodeDom.Compiler;
44 #endif
45
46 namespace System.Web {
47         class HttpApplicationFactory {
48                 object this_lock = new object ();
49                 
50                 // Initialized in InitType
51 #if TARGET_J2EE
52                 static HttpApplicationFactory theFactory {
53                         get
54                         {
55                                 HttpApplicationFactory factory = (HttpApplicationFactory)AppDomain.CurrentDomain.GetData("HttpApplicationFactory");
56                                 if (factory == null) {
57                                         lock(typeof(HttpApplicationFactory)) {
58                                                 factory = (HttpApplicationFactory)AppDomain.CurrentDomain.GetData("HttpApplicationFactory");
59                                                 if (factory == null) {
60                                                         factory = new HttpApplicationFactory();
61                                                         System.Threading.Thread.Sleep(1);
62                                                         AppDomain.CurrentDomain.SetData("HttpApplicationFactory", factory);
63                                                 }
64                                         }
65                                 }
66                                 return factory;
67                         }
68                 }
69 #else
70                 static HttpApplicationFactory theFactory = new HttpApplicationFactory();
71 #endif
72         
73
74                 MethodInfo session_end;
75                 bool needs_init = true;
76                 bool app_start_needed = true;
77                 Type app_type;
78                 HttpApplicationState app_state;
79                 Hashtable app_event_handlers;
80                 static ArrayList watchers = new ArrayList();
81                 static object watchers_lock = new object();
82                 static bool app_shutdown = false;
83                 Stack available = new Stack ();
84                 Stack available_for_end = new Stack ();
85                 
86                 bool IsEventHandler (MethodInfo m)
87                 {
88                         int pos = m.Name.IndexOf ('_');
89                         if (pos == -1 || (m.Name.Length - 1) <= pos)
90                                 return false;
91
92                         if (m.ReturnType != typeof (void))
93                                 return false;
94
95                         ParameterInfo [] pi = m.GetParameters ();
96                         int length = pi.Length;
97                         if (length == 0)
98                                 return true;
99
100                         if (length != 2)
101                                 return false;
102
103                         if (pi [0].ParameterType != typeof (object) ||
104                             pi [1].ParameterType != typeof (EventArgs))
105                                 return false;
106                         
107                         return true;
108                 }
109
110                 void AddEvent (MethodInfo method, Hashtable appTypeEventHandlers)
111                 {
112                         string name = method.Name.Replace ("_On", "_");
113                         if (appTypeEventHandlers [name] == null) {
114                                 appTypeEventHandlers [name] = method;
115                                 return;
116                         }
117
118                         MethodInfo old_method = appTypeEventHandlers [name] as MethodInfo;
119                         ArrayList list;
120                         if (old_method != null){
121                                 list = new ArrayList (4);
122                                 list.Add (old_method);
123                                 appTypeEventHandlers [name] = list;
124                         } else 
125                                 list = appTypeEventHandlers [name] as ArrayList;
126
127                         list.Add (method);
128                 }
129                 
130                 Hashtable GetApplicationTypeEvents (Type type)
131                 {
132                         lock (this_lock) {
133                                 if (app_event_handlers != null)
134                                         return app_event_handlers;
135
136                                 app_event_handlers = new Hashtable ();
137                                 BindingFlags flags = BindingFlags.Public    | BindingFlags.NonPublic | 
138                                         BindingFlags.Instance  | BindingFlags.Static;
139
140                                 MethodInfo [] methods = type.GetMethods (flags);
141                                 foreach (MethodInfo m in methods) {
142                                         if (m.DeclaringType != typeof (HttpApplication) && IsEventHandler (m))
143                                                 AddEvent (m, app_event_handlers);
144                                 }
145                         }
146
147                         return app_event_handlers;
148                 }
149
150                 Hashtable GetApplicationTypeEvents (HttpApplication app)
151                 {
152                         lock (this_lock) {
153                                 if (app_event_handlers != null)
154                                         return app_event_handlers;
155
156                                 return GetApplicationTypeEvents (app.GetType ());
157                         }
158                 }
159
160                 bool FireEvent (string method_name, object target, object [] args)
161                 {
162                         Hashtable possibleEvents = GetApplicationTypeEvents ((HttpApplication) target);
163                         MethodInfo method = possibleEvents [method_name] as MethodInfo;
164                         if (method == null)
165                                 return false;
166
167                         if (method.GetParameters ().Length == 0)
168                                 args = null;
169
170                         method.Invoke (target, args);
171
172                         return true;
173                 }
174
175                 HttpApplication FireOnAppStart (HttpContext context)
176                 {
177                         HttpApplication app = (HttpApplication) Activator.CreateInstance (app_type, true);
178                         context.ApplicationInstance = app;
179                         app.SetContext (context);
180                         object [] args = new object [] {app, EventArgs.Empty};
181                         FireEvent ("Application_Start", app, args);
182                         return app;
183                 }
184
185                 void FireOnAppEnd ()
186                 {
187                         if (app_type == null)
188                                 return; // we didn't even get an application
189
190                         HttpApplication app = (HttpApplication) Activator.CreateInstance (app_type, true);
191                         FireEvent ("Application_End", app, new object [] {new object (), EventArgs.Empty});
192                         app.Dispose ();
193                 }
194
195                 //
196                 // This is invoked by HttpRuntime.Dispose, when we unload an AppDomain
197                 // To reproduce this in action, touch "global.asax" while XSP is running.
198                 //
199                 public static void Dispose ()
200                 {
201                         theFactory.FireOnAppEnd ();
202                 }
203
204                 static FileSystemWatcher CreateWatcher (string file, FileSystemEventHandler hnd, RenamedEventHandler reh)
205                 {
206                         FileSystemWatcher watcher = new FileSystemWatcher ();
207
208                         watcher.Path = Path.GetFullPath (Path.GetDirectoryName (file));
209                         watcher.Filter = Path.GetFileName (file);
210                         
211                         // This will enable the Modify flag for Linux/inotify
212                         watcher.NotifyFilter |= NotifyFilters.Size;
213                         
214                         watcher.Changed += hnd;
215                         watcher.Created += hnd;
216                         watcher.Deleted += hnd;
217                         watcher.Renamed += reh;
218
219                         watcher.EnableRaisingEvents = true;
220
221                         return watcher;
222                 }
223
224                 internal static void AttachEvents (HttpApplication app)
225                 {
226                         HttpApplicationFactory factory = theFactory;
227                         Hashtable possibleEvents = factory.GetApplicationTypeEvents (app);
228                         foreach (string key in possibleEvents.Keys) {
229                                 int pos = key.IndexOf ('_');
230                                 string moduleName = key.Substring (0, pos);
231                                 object target;
232                                 if (moduleName == "Application") {
233                                         target = app;
234                                 } else {
235                                         target = app.Modules [moduleName];
236                                         if (target == null)
237                                                 continue;
238                                 }
239
240                                 string eventName = key.Substring (pos + 1);
241                                 EventInfo evt = target.GetType ().GetEvent (eventName);
242                                 if (evt == null)
243                                         continue;
244
245                                 string usualName = moduleName + "_" + eventName;
246                                 object methodData = possibleEvents [usualName];
247                                 if (methodData != null && eventName == "End" && moduleName == "Session") {
248                                         lock (factory) {
249                                                 if (factory.session_end == null)
250                                                         factory.session_end = (MethodInfo) methodData;
251                                         }
252                                         continue;
253                                 }
254
255                                 if (methodData == null)
256                                         continue;
257
258                                 if (methodData is MethodInfo) {
259                                         factory.AddHandler (evt, target, app, (MethodInfo) methodData);
260                                         continue;
261                                 }
262
263                                 ArrayList list = (ArrayList) methodData;
264                                 foreach (MethodInfo method in list)
265                                         factory.AddHandler (evt, target, app, method);
266                         }
267                 }
268
269                 void AddHandler (EventInfo evt, object target, HttpApplication app, MethodInfo method)
270                 {
271                         int length = method.GetParameters ().Length;
272
273                         if (length == 0) {
274                                 NoParamsInvoker npi = new NoParamsInvoker (app, method);
275                                 evt.AddEventHandler (target, npi.FakeDelegate);
276                         } else {
277                                 evt.AddEventHandler (target, Delegate.CreateDelegate (
278                                                              evt.EventHandlerType, app,
279 #if NET_2_0
280                                                              method
281 #else
282                                                              method.Name
283 #endif
284                                                      ));
285                         }
286                         
287                 }
288
289                 internal static void InvokeSessionEnd (object state)
290                 {
291                         InvokeSessionEnd (state, null, EventArgs.Empty);
292                 }
293                 
294                 internal static void InvokeSessionEnd (object state, object source, EventArgs e)
295                 {
296                         HttpApplicationFactory factory = theFactory;
297                         MethodInfo method = null;
298                         HttpApplication app = null;
299                         lock (factory.available_for_end) {
300                                 method = factory.session_end;
301                                 if (method == null)
302                                         return;
303
304                                 app = GetApplicationForSessionEnd ();
305                         }
306
307                         app.SetSession ((HttpSessionState) state);
308                         try {
309                                 method.Invoke (app, new object [] {(source == null ? app : source), e});
310                         } catch (Exception) {
311                                 // Ignore
312                         }
313                         RecycleForSessionEnd (app);
314                 }
315
316                 static HttpStaticObjectsCollection MakeStaticCollection (ArrayList list)
317                 {
318                         if (list == null || list.Count == 0)
319                                 return null;
320
321                         HttpStaticObjectsCollection coll = new HttpStaticObjectsCollection ();
322                         foreach (ObjectTagBuilder tag in list) {
323                                 coll.Add (tag);
324                         }
325
326                         return coll;
327                 }
328                 
329                 internal static HttpApplicationState ApplicationState {
330 #if TARGET_J2EE
331                         get {
332                                 HttpApplicationFactory factory = theFactory;
333                                 if (factory.app_state == null)
334                                         factory.app_state = new HttpApplicationState (null, null);
335                                 return factory.app_state;
336                         }
337 #else
338                         get {
339                                 if (theFactory.app_state == null) {
340                                         HttpStaticObjectsCollection app = MakeStaticCollection (GlobalAsaxCompiler.ApplicationObjects);
341                                         HttpStaticObjectsCollection ses = MakeStaticCollection (GlobalAsaxCompiler.SessionObjects);
342
343                                         theFactory.app_state = new HttpApplicationState (app, ses);
344                                 }
345                                 return theFactory.app_state;
346                         }
347 #endif
348                 }
349
350                 internal static Type AppType {
351                         get {
352                                 return theFactory.app_type;
353                         }
354                 }
355                 
356                 void InitType (HttpContext context)
357                 {
358                         lock (this_lock) {
359                                 if (!needs_init)
360                                         return;
361
362 #if NET_2_0
363                                 try {
364 #endif
365                                         string physical_app_path = context.Request.PhysicalApplicationPath;
366                                         string app_file = null;
367                                         
368                                         app_file = Path.Combine (physical_app_path, "Global.asax");
369                                         if (!File.Exists (app_file)) {
370                                                 app_file = Path.Combine (physical_app_path, "global.asax");
371                                                 if (!File.Exists (app_file))
372                                                         app_file = null;
373                                         }
374                         
375 #if !NET_2_0
376                                         WebConfigurationSettings.Init (context);
377 #endif
378                 
379 #if NET_2_0 && !TARGET_J2EE
380                                         AppResourcesCompiler ac = new AppResourcesCompiler (context);
381                                         ac.Compile ();
382                                 
383                                         // Todo: Process App_WebResources here
384                                 
385                                         // Todo: Generate profile properties assembly from Web.config here
386                                 
387                                         // Todo: Compile code from App_Code here
388                                         AppCodeCompiler acc = new AppCodeCompiler ();
389                                         acc.Compile ();
390 #endif
391
392                                         if (app_file != null) {
393 #if TARGET_J2EE
394                                                 app_file = System.Web.Util.UrlUtils.ResolveVirtualPathFromAppAbsolute("~/" + Path.GetFileName(app_file));
395                                                 app_type = System.Web.J2EE.PageMapper.GetObjectType(context, app_file);
396 #else
397                                                 app_type = ApplicationFileParser.GetCompiledApplicationType (app_file, context);
398 #endif
399                                                 if (app_type == null) {
400                                                         string msg = String.Format ("Error compiling application file ({0}).", app_file);
401                                                         throw new ApplicationException (msg);
402                                                 }
403                                         } else {
404                                                 app_type = typeof (System.Web.HttpApplication);
405                                                 app_state = new HttpApplicationState ();
406                                         }
407
408                                         WatchLocationForRestart("Global.asax");
409                                         WatchLocationForRestart("global.asax");
410                                         WatchLocationForRestart("Web.config");
411                                         WatchLocationForRestart("web.config");
412                                         WatchLocationForRestart("Web.Config");
413                                         needs_init = false;
414 #if NET_2_0
415                                 } catch (Exception) {
416                                         if (BuildManager.CodeAssemblies != null)
417                                                 BuildManager.CodeAssemblies.Clear ();
418                                         if (BuildManager.TopLevelAssemblies != null)
419                                                 BuildManager.TopLevelAssemblies.Clear ();
420                                         if (WebConfigurationManager.ExtraAssemblies != null)
421                                                 WebConfigurationManager.ExtraAssemblies.Clear ();
422                                         throw;
423                                 }
424 #endif
425                                 
426                                 //
427                                 // Now init the settings
428                                 //
429
430                         }
431                 }
432                 
433                 //
434                 // Multiple-threads might hit this one on startup, and we have
435                 // to delay-initialize until we have the HttpContext
436                 //
437                 internal static HttpApplication GetApplication (HttpContext context)
438                 {
439                         HttpApplicationFactory factory = theFactory;
440                         HttpApplication app = null;
441                         if (factory.app_start_needed){
442                                 if (context == null)
443                                         return null;
444
445                                 factory.InitType (context);
446                                 lock (factory) {
447                                         if (factory.app_start_needed) {
448                                                 WatchLocationForRestart (AppDomain.CurrentDomain.SetupInformation.PrivateBinPath,
449                                                                          "*.dll");
450 #if NET_2_0
451                                                 WatchLocationForRestart ("App_Code", "*", true);
452                                                 WatchLocationForRestart ("App_Browsers", "*");
453                                                 WatchLocationForRestart ("App_GlobalResources", "*");
454 #endif
455                                                 app = factory.FireOnAppStart (context);
456                                                 factory.app_start_needed = false;
457                                                 return app;
458                                         }
459                                 }
460                         }
461
462                         lock (factory.available) {
463                                 if (factory.available.Count > 0) {
464                                         app = (HttpApplication) factory.available.Pop ();
465                                         app.RequestCompleted = false;
466                                         return app;
467                                 }
468                         }
469                         
470                         return (HttpApplication) Activator.CreateInstance (factory.app_type, true);
471                 }
472
473                 // The lock is in InvokeSessionEnd
474                 static HttpApplication GetApplicationForSessionEnd ()
475                 {
476                         HttpApplicationFactory factory = theFactory;
477                         if (factory.available_for_end.Count > 0)
478                                 return (HttpApplication) factory.available_for_end.Pop ();
479
480                         HttpApplication app = (HttpApplication) Activator.CreateInstance (factory.app_type, true);
481                         app.InitOnce (false);
482
483                         return app;
484                 }
485
486                 internal static void RecycleForSessionEnd (HttpApplication app)
487                 {
488                         HttpApplicationFactory factory = theFactory;
489                         lock (factory.available_for_end) {
490                                 if (factory.available_for_end.Count < 32)
491                                         factory.available_for_end.Push (app);
492                                 else
493                                         app.Dispose ();
494                         }
495                 }
496
497                 internal static void Recycle (HttpApplication app)
498                 {
499                         HttpApplicationFactory factory = theFactory;
500                         lock (factory.available) {
501                                 if (factory.available.Count < 32)
502                                         factory.available.Push (app);
503                                 else
504                                         app.Dispose ();
505                         }
506                 }
507
508                 internal static bool ContextAvailable {
509                         get { return theFactory != null && !theFactory.app_start_needed; }
510                 }
511
512
513                 internal static bool WatchLocationForRestart (string filter)
514                 {
515                         return WatchLocationForRestart ("", filter, false);
516                 }
517
518                 internal static bool WatchLocationForRestart (string virtualPath, string filter)
519                 {
520                         return WatchLocationForRestart (virtualPath, filter, false);
521                 }
522                 
523                 internal static bool WatchLocationForRestart(string virtualPath, string filter, bool watchSubdirs)
524                 {
525                         // map the path to the physical one
526                         string physicalPath = HttpRuntime.AppDomainAppPath;
527                         physicalPath = Path.Combine(physicalPath, virtualPath);
528                         bool isDir = Directory.Exists(physicalPath);
529                         bool isFile = isDir ? false : File.Exists(physicalPath);
530                         
531                         if (isDir || isFile) {
532                                 // create the watcher
533                                 FileSystemEventHandler fseh = new FileSystemEventHandler(OnFileChanged);
534                                 RenamedEventHandler reh = new RenamedEventHandler(OnFileRenamed);
535                                 FileSystemWatcher watcher = CreateWatcher(Path.Combine(physicalPath, filter), fseh, reh);
536                                 if (isDir)
537                                         watcher.IncludeSubdirectories = watchSubdirs;
538                                 
539                                 lock (watchers_lock) {
540                                         watchers.Add(watcher);
541                                 }
542                                 return true;
543                         } else {
544                                 return false;
545                         }
546                 }
547                 
548                 static void OnFileRenamed(object sender, RenamedEventArgs args)
549                 {
550                         OnFileChanged(sender, args);
551                 }
552
553                 static void OnFileChanged(object sender, FileSystemEventArgs args)
554                 {
555                         lock (watchers_lock) {
556                                 if(app_shutdown)
557                                         return;
558                                 app_shutdown = true;
559
560                                 // Disable event raising to avoid concurrent restarts
561                                 foreach (FileSystemWatcher watcher in watchers) {
562                                         watcher.EnableRaisingEvents = false;
563                                 }
564                                 // Restart application
565                                 HttpRuntime.UnloadAppDomain();
566                         }
567                 }
568         }
569 }
570