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