New test.
[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                 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                 FileSystemWatcher config_watcher;
78 #endif
79                 Stack available = new Stack ();
80                 Stack available_for_end = new Stack ();
81                 
82                 // Watch this thing out when getting an instance
83                 IHttpHandler custom_application;
84
85                 bool IsEventHandler (MethodInfo m)
86                 {
87                         int pos = m.Name.IndexOf ('_');
88                         if (pos == -1 || (m.Name.Length - 1) <= pos)
89                                 return false;
90
91                         if (m.ReturnType != typeof (void))
92                                 return false;
93
94                         ParameterInfo [] pi = m.GetParameters ();
95                         int length = pi.Length;
96                         if (length == 0)
97                                 return true;
98
99                         if (length != 2)
100                                 return false;
101
102                         if (pi [0].ParameterType != typeof (object) ||
103                             pi [1].ParameterType != typeof (EventArgs))
104                                 return false;
105                         
106                         return true;
107                 }
108
109                 void AddEvent (MethodInfo method, Hashtable appTypeEventHandlers)
110                 {
111                         string name = method.Name.Replace ("_On", "_");
112                         if (appTypeEventHandlers [name] == null) {
113                                 appTypeEventHandlers [name] = method;
114                                 return;
115                         }
116
117                         MethodInfo old_method = appTypeEventHandlers [name] as MethodInfo;
118                         ArrayList list;
119                         if (old_method != null){
120                                 list = new ArrayList (4);
121                                 list.Add (old_method);
122                                 appTypeEventHandlers [name] = list;
123                         } else 
124                                 list = appTypeEventHandlers [name] as ArrayList;
125
126                         list.Add (method);
127                 }
128                 
129                 Hashtable GetApplicationTypeEvents (Type type)
130                 {
131                         lock (this) {
132                                 if (app_event_handlers != null)
133                                         return app_event_handlers;
134
135                                 app_event_handlers = new Hashtable ();
136                                 BindingFlags flags = BindingFlags.Public    | BindingFlags.NonPublic | 
137                                                      BindingFlags.Instance  | BindingFlags.Static;
138
139                                 MethodInfo [] methods = type.GetMethods (flags);
140                                 foreach (MethodInfo m in methods) {
141                                         if (m.DeclaringType != typeof (HttpApplication) && IsEventHandler (m))
142                                                 AddEvent (m, app_event_handlers);
143                                 }
144                         }
145
146                         return app_event_handlers;
147                 }
148
149                 Hashtable GetApplicationTypeEvents (HttpApplication app)
150                 {
151                         lock (this) {
152                                 if (app_event_handlers != null)
153                                         return app_event_handlers;
154
155                                 return GetApplicationTypeEvents (app.GetType ());
156                         }
157                 }
158
159                 bool FireEvent (string method_name, object target, object [] args)
160                 {
161                         Hashtable possibleEvents = GetApplicationTypeEvents ((HttpApplication) target);
162                         MethodInfo method = possibleEvents [method_name] as MethodInfo;
163                         if (method == null)
164                                 return false;
165
166                         if (method.GetParameters ().Length == 0)
167                                 args = null;
168
169                         method.Invoke (target, args);
170
171                         return true;
172                 }
173
174                 HttpApplication FireOnAppStart (HttpContext context)
175                 {
176                         HttpApplication app = (HttpApplication) Activator.CreateInstance (app_type, true);
177                         context.ApplicationInstance = app;
178                         app.SetContext (context);
179                         object [] args = new object [] {app, EventArgs.Empty};
180                         FireEvent ("Application_Start", app, args);
181                         return app;
182                 }
183
184                 void FireOnAppEnd ()
185                 {
186                         if (app_type == null)
187                                 return; // we didn't even get an application
188
189                         HttpApplication app = (HttpApplication) Activator.CreateInstance (app_type, true);
190                         FireEvent ("Application_End", app, new object [] {new object (), EventArgs.Empty});
191                         app.Dispose ();
192                 }
193
194                 //
195                 // This is invoked by HttpRuntime.Dispose, when we unload an AppDomain
196                 // To reproduce this in action, touch "global.asax" while XSP is running.
197                 //
198                 public static void Dispose ()
199                 {
200                         theFactory.FireOnAppEnd ();
201                 }
202
203 #if !TARGET_JVM
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                         watcher.Changed += hnd;
212                         watcher.Created += hnd;
213                         watcher.Deleted += hnd;
214                         watcher.Renamed += reh;
215
216                         watcher.EnableRaisingEvents = true;
217
218                         return watcher;
219                 }
220
221                 void OnAppFileRenamed (object sender, RenamedEventArgs args)
222                 {
223                         OnAppFileChanged (sender, args);
224                 }
225
226                 void OnAppFileChanged (object sender, FileSystemEventArgs args)
227                 {
228                         if (config_watcher != null)
229                                 config_watcher.EnableRaisingEvents = false;
230                         if (bin_watcher != null)
231                                 bin_watcher.EnableRaisingEvents = false;
232                         if (app_file_watcher != null)
233                                 app_file_watcher.EnableRaisingEvents = false;
234                         HttpRuntime.UnloadAppDomain ();
235                 }
236 #endif
237
238                 internal static void AttachEvents (HttpApplication app)
239                 {
240                         HttpApplicationFactory factory = theFactory;
241                         Hashtable possibleEvents = factory.GetApplicationTypeEvents (app);
242                         foreach (string key in possibleEvents.Keys) {
243                                 int pos = key.IndexOf ('_');
244                                 string moduleName = key.Substring (0, pos);
245                                 object target;
246                                 if (moduleName == "Application") {
247                                         target = app;
248                                 } else {
249                                         target = app.Modules [moduleName];
250                                         if (target == null)
251                                                 continue;
252                                 }
253
254                                 string eventName = key.Substring (pos + 1);
255                                 EventInfo evt = target.GetType ().GetEvent (eventName);
256                                 if (evt == null)
257                                         continue;
258
259                                 string usualName = moduleName + "_" + eventName;
260                                 object methodData = possibleEvents [usualName];
261                                 if (methodData != null && eventName == "End" && moduleName == "Session") {
262                                         lock (factory) {
263                                                 if (factory.session_end == null)
264                                                         factory.session_end = (MethodInfo) methodData;
265                                         }
266                                         continue;
267                                 }
268
269                                 if (methodData == null)
270                                         continue;
271
272                                 if (methodData is MethodInfo) {
273                                         factory.AddHandler (evt, target, app, (MethodInfo) methodData);
274                                         continue;
275                                 }
276
277                                 ArrayList list = (ArrayList) methodData;
278                                 foreach (MethodInfo method in list)
279                                         factory.AddHandler (evt, target, app, method);
280                         }
281                 }
282
283                 void AddHandler (EventInfo evt, object target, HttpApplication app, MethodInfo method)
284                 {
285                         int length = method.GetParameters ().Length;
286
287                         if (length == 0) {
288                                 NoParamsInvoker npi = new NoParamsInvoker (app, method.Name);
289                                 evt.AddEventHandler (target, npi.FakeDelegate);
290                         } else {
291                                 evt.AddEventHandler (target, Delegate.CreateDelegate (
292                                                         evt.EventHandlerType, app, method.Name));
293                         }
294                 }
295
296                 internal static void InvokeSessionEnd (object state)
297                 {
298                         HttpApplicationFactory factory = theFactory;
299                         MethodInfo method = null;
300                         HttpApplication app = null;
301                         lock (factory.available_for_end) {
302                                 method = factory.session_end;
303                                 if (method == null)
304                                         return;
305
306                                 app = GetApplicationForSessionEnd ();
307                         }
308
309                         app.SetSession ((HttpSessionState) state);
310                         try {
311                                 method.Invoke (app, new object [] {app, EventArgs.Empty});
312                         } catch (Exception) {
313                                 // Ignore
314                         }
315                         RecycleForSessionEnd (app);
316                 }
317
318                 static HttpStaticObjectsCollection MakeStaticCollection (ArrayList list)
319                 {
320                         if (list == null || list.Count == 0)
321                                 return null;
322
323                         HttpStaticObjectsCollection coll = new HttpStaticObjectsCollection ();
324                         foreach (ObjectTagBuilder tag in list) {
325                                 coll.Add (tag);
326                         }
327
328                         return coll;
329                 }
330                 
331                 internal static HttpApplicationState ApplicationState {
332 #if TARGET_J2EE
333                         get {
334                                 HttpApplicationFactory factory = theFactory;
335                                 if (factory.app_state == null)
336                                         factory.app_state = new HttpApplicationState (null, null);
337                                 return factory.app_state;
338                         }
339 #else
340                         get {
341                                 if (theFactory.app_state == null) {
342                                         HttpStaticObjectsCollection app = MakeStaticCollection (GlobalAsaxCompiler.ApplicationObjects);
343                                         HttpStaticObjectsCollection ses = MakeStaticCollection (GlobalAsaxCompiler.SessionObjects);
344
345                                         theFactory.app_state = new HttpApplicationState (app, ses);
346                                 }
347                                 return theFactory.app_state;
348                         }
349 #endif
350                 }
351
352                 public static void SetCustomApplication (IHttpHandler customApplication)
353                 {
354                         theFactory.custom_application = customApplication;
355                 }
356
357                 internal static Type AppType {
358                         get {
359                                 return theFactory.app_type;
360                         }
361                 }
362
363                 void InitType (HttpContext context)
364                 {
365                         lock (this) {
366                                 if (!needs_init)
367                                         return;
368                                 
369                                 string physical_app_path = context.Request.PhysicalApplicationPath;
370                                 string app_file;
371                                 
372                                 app_file = Path.Combine (physical_app_path, "Global.asax");
373                                 if (!File.Exists (app_file))
374                                         app_file = Path.Combine (physical_app_path, "global.asax");
375
376                         
377 #if NET_2_0
378                                 WebConfigurationManager.Init ();
379 #else
380                                 WebConfigurationSettings.Init (context);
381 #endif
382                                 
383                                 if (File.Exists (app_file)) {
384 #if TARGET_J2EE
385                                         app_type = System.Web.J2EE.PageMapper.GetObjectType(app_file);
386 #else
387                                         app_type = ApplicationFileParser.GetCompiledApplicationType (app_file, context);
388                                         if (app_type == null) {
389                                                 string msg = String.Format ("Error compiling application file ({0}).", app_file);
390                                                 throw new ApplicationException (msg);
391                                         }
392 #endif
393                                 } else {
394                                         app_type = typeof (System.Web.HttpApplication);
395                                         app_state = new HttpApplicationState ();
396                                 }
397
398 #if !TARGET_JVM
399                                 FileSystemEventHandler fseh = new FileSystemEventHandler (OnAppFileChanged);
400                                 RenamedEventHandler reh = new RenamedEventHandler (OnAppFileRenamed);
401                                 app_file_watcher = CreateWatcher (app_file, fseh, reh);
402
403                                 string config_file = Path.Combine (physical_app_path, "Web.config");
404                                 if (!File.Exists (config_file))
405                                         config_file = Path.Combine (physical_app_path, "web.config");
406
407                                 config_watcher = CreateWatcher (config_file, fseh, reh);
408 #endif
409                                 needs_init = false;
410
411                                 //
412                                 // Now init the settings
413                                 //
414
415                         }
416                 }
417                 
418                 //
419                 // Multiple-threads might hit this one on startup, and we have
420                 // to delay-initialize until we have the HttpContext
421                 //
422                 internal static HttpApplication GetApplication (HttpContext context)
423                 {
424                         HttpApplicationFactory factory = theFactory;
425                         HttpApplication app = null;
426                         if (factory.app_start_needed){
427                                 if (context == null)
428                                         return null;
429
430                                 factory.InitType (context);
431                                 lock (factory) {
432                                         if (factory.app_start_needed) {
433 #if !TARGET_JVM
434                                                 string bin = HttpRuntime.BinDirectory;
435                                                 if (Directory.Exists (bin))
436                                                         bin = Path.Combine (bin, "*.dll");
437
438                                                 FileSystemEventHandler fseh = new FileSystemEventHandler (factory.OnAppFileChanged);
439                                                 RenamedEventHandler reh = new RenamedEventHandler (factory.OnAppFileRenamed);
440                                                 // We watch bin or bin/*.dll if the directory exists
441                                                 factory.bin_watcher = CreateWatcher (bin, fseh, reh);
442 #endif
443                                                 app = factory.FireOnAppStart (context);
444                                                 factory.app_start_needed = false;
445                                                 return app;
446                                         }
447                                 }
448                         }
449
450                         lock (factory.available) {
451                                 if (factory.available.Count > 0) {
452                                         app = (HttpApplication) factory.available.Pop ();
453                                         app.RequestCompleted = false;
454                                         return app;
455                                 }
456                         }
457                         
458                         return (HttpApplication) Activator.CreateInstance (factory.app_type, true);
459                 }
460
461                 // The lock is in InvokeSessionEnd
462                 static HttpApplication GetApplicationForSessionEnd ()
463                 {
464                         HttpApplicationFactory factory = theFactory;
465                         if (factory.available_for_end.Count > 0)
466                                 return (HttpApplication) factory.available_for_end.Pop ();
467
468                         HttpApplication app = (HttpApplication) Activator.CreateInstance (factory.app_type, true);
469                         app.InitOnce (false);
470
471                         return app;
472                 }
473
474                 internal static void RecycleForSessionEnd (HttpApplication app)
475                 {
476                         HttpApplicationFactory factory = theFactory;
477                         lock (factory.available_for_end) {
478                                 if (factory.available_for_end.Count < 32)
479                                         factory.available_for_end.Push (app);
480                                 else
481                                         app.Dispose ();
482                         }
483                 }
484
485                 internal static void Recycle (HttpApplication app)
486                 {
487                         HttpApplicationFactory factory = theFactory;
488                         lock (factory.available) {
489                                 if (factory.available.Count < 32)
490                                         factory.available.Push (app);
491                                 else
492                                         app.Dispose ();
493                         }
494                 }
495
496                 internal static bool ContextAvailable {
497                         get { return theFactory != null && !theFactory.app_start_needed; }
498                 }
499         }
500 }
501