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