2006-01-10 Gonzalo Paniagua Javier <gonzalo@ximian.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 #if !TARGET_J2EE
38 using System.Web.Compilation;
39 #else
40 using vmw.common;
41 #endif
42
43 namespace System.Web {
44         class HttpApplicationFactory {
45                 // Initialized in InitType
46 #if TARGET_J2EE
47                 static HttpApplicationFactory theFactory {
48                         get
49                         {
50                                 HttpApplicationFactory factory = (HttpApplicationFactory)AppDomain.CurrentDomain.GetData("HttpApplicationFactory");
51                                 if (factory == null) {
52                                         lock(typeof(HttpApplicationFactory)) {
53                                                 factory = (HttpApplicationFactory)AppDomain.CurrentDomain.GetData("HttpApplicationFactory");
54                                                 if (factory == null) {
55                                                         factory = new HttpApplicationFactory();
56                                                         System.Threading.Thread.Sleep(1);
57                                                         AppDomain.CurrentDomain.SetData("HttpApplicationFactory", factory);
58                                                 }
59                                         }
60                                 }
61                                 return factory;
62                         }
63                 }
64 #else
65                 static HttpApplicationFactory theFactory = new HttpApplicationFactory();
66 #endif
67
68                 static MethodInfo session_end;
69                 bool needs_init = true;
70                 bool app_start_needed = true;
71                 Type app_type;
72                 HttpApplicationState app_state;
73                 Hashtable app_event_handlers;
74 #if !TARGET_JVM
75                 FileSystemWatcher app_file_watcher;
76                 FileSystemWatcher bin_watcher;
77 #endif
78                 Stack available = new Stack ();
79                 Stack available_for_end = new Stack ();
80                 
81                 // Watch this thing out when getting an instance
82                 IHttpHandler custom_application;
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 #if !TARGET_JVM
203                 static FileSystemWatcher CreateWatcher (string file, FileSystemEventHandler hnd)
204                 {
205                         FileSystemWatcher watcher = new FileSystemWatcher ();
206
207                         watcher.Path = Path.GetFullPath (Path.GetDirectoryName (file));
208                         watcher.Filter = Path.GetFileName (file);
209
210                         watcher.Changed += hnd;
211                         watcher.Created += hnd;
212                         watcher.Deleted += hnd;
213
214                         watcher.EnableRaisingEvents = true;
215
216                         return watcher;
217                 }
218
219                 void OnAppFileChanged (object sender, FileSystemEventArgs args)
220                 {
221                         if (bin_watcher != null)
222                                 bin_watcher.EnableRaisingEvents = false;
223                         if (app_file_watcher != null)
224                                 app_file_watcher.EnableRaisingEvents = false;
225                         HttpRuntime.UnloadAppDomain ();
226                 }
227 #endif
228
229                 internal static void AttachEvents (HttpApplication app)
230                 {
231                         HttpApplicationFactory factory = theFactory;
232                         Hashtable possibleEvents = factory.GetApplicationTypeEvents (app);
233                         foreach (string key in possibleEvents.Keys) {
234                                 int pos = key.IndexOf ('_');
235                                 string moduleName = key.Substring (0, pos);
236                                 object target;
237                                 if (moduleName == "Application") {
238                                         target = app;
239                                 } else {
240                                         target = app.Modules [moduleName];
241                                         if (target == null)
242                                                 continue;
243                                 }
244
245                                 string eventName = key.Substring (pos + 1);
246                                 EventInfo evt = target.GetType ().GetEvent (eventName);
247                                 if (evt == null)
248                                         continue;
249
250                                 string usualName = moduleName + "_" + eventName;
251                                 object methodData = possibleEvents [usualName];
252                                 if (methodData != null && eventName == "End" && moduleName == "Session") {
253                                         lock (factory) {
254                                                 if (session_end == null)
255                                                         session_end = (MethodInfo) methodData;
256                                         }
257                                         continue;
258                                 }
259
260                                 if (methodData == null)
261                                         continue;
262
263                                 if (methodData is MethodInfo) {
264                                         factory.AddHandler (evt, target, app, (MethodInfo) methodData);
265                                         continue;
266                                 }
267
268                                 ArrayList list = (ArrayList) methodData;
269                                 foreach (MethodInfo method in list)
270                                         factory.AddHandler (evt, target, app, method);
271                         }
272                 }
273
274                 void AddHandler (EventInfo evt, object target, HttpApplication app, MethodInfo method)
275                 {
276                         int length = method.GetParameters ().Length;
277
278                         if (length == 0) {
279                                 NoParamsInvoker npi = new NoParamsInvoker (app, method.Name);
280                                 evt.AddEventHandler (target, npi.FakeDelegate);
281                         } else {
282                                 evt.AddEventHandler (target, Delegate.CreateDelegate (
283                                                         typeof (EventHandler), app, method.Name));
284                         }
285                 }
286
287                 internal static void InvokeSessionEnd (object state)
288                 {
289                         HttpApplicationFactory factory = theFactory;
290                         MethodInfo method = null;
291                         HttpApplication app = null;
292                         lock (factory.available_for_end) {
293                                 method = session_end;
294                                 if (method == null)
295                                         return;
296
297                                 app = GetApplicationForSessionEnd ();
298                         }
299
300                         app.SetSession ((HttpSessionState) state);
301                         try {
302                                 method.Invoke (app, new object [] {app, EventArgs.Empty});
303                         } catch (Exception e) {
304                                 // Ignore
305                         }
306                         RecycleForSessionEnd (app);
307                 }
308
309                 static HttpStaticObjectsCollection MakeStaticCollection (ArrayList list)
310                 {
311                         if (list == null || list.Count == 0)
312                                 return null;
313
314                         HttpStaticObjectsCollection coll = new HttpStaticObjectsCollection ();
315                         foreach (ObjectTagBuilder tag in list) {
316                                 coll.Add (tag);
317                         }
318
319                         return coll;
320                 }
321                 
322                 internal static HttpApplicationState ApplicationState {
323 #if TARGET_J2EE
324                         get {
325                                 HttpApplicationFactory factory = theFactory;
326                                 if (factory.app_state == null)
327                                         factory.app_state = new HttpApplicationState (null, null);
328                                 return factory.app_state;
329                         }
330 #else
331                         get {
332                                 if (theFactory.app_state == null) {
333                                         HttpStaticObjectsCollection app = MakeStaticCollection (GlobalAsaxCompiler.ApplicationObjects);
334                                         HttpStaticObjectsCollection ses = MakeStaticCollection (GlobalAsaxCompiler.SessionObjects);
335
336                                         theFactory.app_state = new HttpApplicationState (app, ses);
337                                 }
338                                 return theFactory.app_state;
339                         }
340 #endif
341                 }
342
343                 public static void SetCustomApplication (IHttpHandler customApplication)
344                 {
345                         theFactory.custom_application = customApplication;
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                                 string physical_app_path = context.Request.PhysicalApplicationPath;
361                                 string app_file;
362                                 
363                                 app_file = Path.Combine (physical_app_path, "Global.asax");
364                                 if (!File.Exists (app_file))
365                                         app_file = Path.Combine (physical_app_path, "global.asax");
366                         
367 #if !CONFIGURATION_2_0
368                                 WebConfigurationSettings.Init (context);
369 #endif
370                                 
371                                 if (File.Exists (app_file)) {
372 #if TARGET_J2EE
373                                         app_type = System.Web.J2EE.PageMapper.GetObjectType(app_file);
374 #else
375                                         app_type = ApplicationFileParser.GetCompiledApplicationType (app_file, context);
376                                         if (app_type == null) {
377                                                 string msg = String.Format ("Error compiling application file ({0}).", app_file);
378                                                 throw new ApplicationException (msg);
379                                         }
380                                         
381                                         app_file_watcher = CreateWatcher (app_file, new FileSystemEventHandler (OnAppFileChanged));
382 #endif
383                                 } else {
384                                         app_type = typeof (System.Web.HttpApplication);
385                                         app_state = new HttpApplicationState ();
386                                 }
387                                 needs_init = false;
388
389                                 //
390                                 // Now init the settings
391                                 //
392
393                         }
394                 }
395                 
396                 //
397                 // Multiple-threads might hit this one on startup, and we have
398                 // to delay-initialize until we have the HttpContext
399                 //
400                 internal static HttpApplication GetApplication (HttpContext context)
401                 {
402                         HttpApplicationFactory factory = theFactory;
403                         HttpApplication app = null;
404                         if (factory.needs_init){
405                                 if (context == null)
406                                         return null;
407
408                                 factory.InitType (context);
409                                 lock (factory) {
410                                         if (factory.app_start_needed) {
411 #if !TARGET_JVM
412                                                 string bin = HttpRuntime.BinDirectory;
413                                                 if (Directory.Exists (bin))
414                                                         bin = Path.Combine (bin, "*.dll");
415
416                                                 // We watch bin or bin/*.dll if the directory exists
417                                                 factory.bin_watcher = CreateWatcher (bin, new FileSystemEventHandler (factory.OnAppFileChanged));
418 #endif
419                                                 app = factory.FireOnAppStart (context);
420                                                 factory.app_start_needed = false;
421                                                 return app;
422                                         }
423                                 }
424                         }
425
426                         lock (factory.available) {
427                                 if (factory.available.Count > 0) {
428                                         app = (HttpApplication) factory.available.Pop ();
429                                         app.RequestCompleted = false;
430                                         return app;
431                                 }
432                         }
433                         
434                         return (HttpApplication) Activator.CreateInstance (factory.app_type, true);
435                 }
436
437                 // The lock is in InvokeSessionEnd
438                 static HttpApplication GetApplicationForSessionEnd ()
439                 {
440                         HttpApplicationFactory factory = theFactory;
441                         if (factory.available_for_end.Count > 0)
442                                 return (HttpApplication) factory.available_for_end.Pop ();
443
444                         HttpApplication app = (HttpApplication) Activator.CreateInstance (factory.app_type, true);
445                         app.InitOnce (false);
446
447                         return app;
448                 }
449
450                 internal static void RecycleForSessionEnd (HttpApplication app)
451                 {
452                         HttpApplicationFactory factory = theFactory;
453                         lock (factory.available_for_end) {
454                                 if (factory.available_for_end.Count < 32)
455                                         factory.available_for_end.Push (app);
456                                 else
457                                         app.Dispose ();
458                         }
459                 }
460
461                 internal static void Recycle (HttpApplication app)
462                 {
463                         HttpApplicationFactory factory = theFactory;
464                         lock (factory.available) {
465                                 if (factory.available.Count < 32)
466                                         factory.available.Push (app);
467                                 else
468                                         app.Dispose ();
469                         }
470                 }
471
472                 internal static bool ContextAvailable {
473                         get { return theFactory != null && theFactory.app_start_needed && theFactory.needs_init; }
474                 }
475         }
476 }
477