2005-01-31 Zoltan Varga <vargaz@freemail.hu>
[mono.git] / mcs / class / System.Web / System.Web / HttpApplicationFactory.cs
1 //
2 // System.Web.HttpApplicationFactory
3 //
4 // Author:
5 //      Patrik Torstensson (ptorsten@hotmail.com)
6 //      Gonzalo Paniagua Javier (gonzalo@ximian.com)
7 //
8 // (c) 2002,2003 Ximian, Inc. (http://www.ximian.com)
9 // (c) Copyright 2004 Novell, Inc. (http://www.novell.com)
10 //
11
12 //
13 // Permission is hereby granted, free of charge, to any person obtaining
14 // a copy of this software and associated documentation files (the
15 // "Software"), to deal in the Software without restriction, including
16 // without limitation the rights to use, copy, modify, merge, publish,
17 // distribute, sublicense, and/or sell copies of the Software, and to
18 // permit persons to whom the Software is furnished to do so, subject to
19 // the following conditions:
20 // 
21 // The above copyright notice and this permission notice shall be
22 // included in all copies or substantial portions of the Software.
23 // 
24 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
28 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
29 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
30 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 //
32 using System;
33 using System.Collections;
34 using System.IO;
35 using System.Reflection;
36 using System.Web.UI;
37 using System.Web.Compilation;
38 using System.Web.SessionState;
39
40 namespace System.Web {
41         class HttpApplicationFactory {
42                 private string _appFilename;
43                 private Type _appType;
44
45                 private bool _appInitialized;
46                 private bool _appFiredEnd;
47
48                 private Stack _appFreePublicList;
49                 private int _appFreePublicInstances;
50
51                 FileSystemWatcher appFileWatcher;
52                 FileSystemWatcher binWatcher;
53                 
54                 static private int _appMaxFreePublicInstances = 32;
55
56                 private HttpApplicationState _state;\r
57 \r
58                 static IHttpHandler custApplication;\r
59 \r
60                 static private HttpApplicationFactory s_Factory = new HttpApplicationFactory();\r
61 \r
62                 public HttpApplicationFactory() {\r
63                         _appInitialized = false;\r
64                         _appFiredEnd = false;\r
65 \r
66                         _appFreePublicList = new Stack();\r
67                         _appFreePublicInstances = 0;\r
68                 }\r
69 \r
70                 static private string GetAppFilename (HttpContext context)\r
71                 {\r
72                         string physicalAppPath = context.Request.PhysicalApplicationPath;\r
73                         string appFilePath = Path.Combine (physicalAppPath, "Global.asax");\r
74                         if (File.Exists (appFilePath))\r
75                                 return appFilePath;\r
76 \r
77                         return Path.Combine (physicalAppPath, "global.asax");\r
78                 }\r
79
80                 void CompileApp (HttpContext context)
81                 {
82                         if (File.Exists (_appFilename)) {
83                                 _appType = ApplicationFileParser.GetCompiledApplicationType (_appFilename, context);
84                                 if (_appType == null) {
85                                         string msg = String.Format ("Error compiling application file ({0}).", _appFilename);
86                                         throw new ApplicationException (msg);
87                                 }
88
89                                 appFileWatcher = CreateWatcher (_appFilename, new FileSystemEventHandler (OnAppFileChanged));
90                         } else {
91                                 _appType = typeof (System.Web.HttpApplication);
92                                 _state = new HttpApplicationState ();
93                         }
94                 }
95
96                 static bool IsEventHandler (MethodInfo m)\r
97                 {\r
98                         if (m.ReturnType != typeof (void))\r
99                                 return false;\r
100 \r
101                         ParameterInfo [] pi = m.GetParameters ();\r
102                         int length = pi.Length;\r
103                         if (length == 0)\r
104                                 return true;\r
105 \r
106                         if (length != 2)\r
107                                 return false;\r
108 \r
109                         if (pi [0].ParameterType != typeof (object) ||\r
110                             pi [1].ParameterType != typeof (EventArgs))\r
111                                 return false;\r
112                         \r
113                         return true;\r
114                 }\r
115 \r
116                 static void AddEvent (MethodInfo method, Hashtable appTypeEventHandlers)\r
117                 {\r
118                         string name = method.Name.Replace ("_On", "_");\r
119                         if (appTypeEventHandlers [name] == null) {\r
120                                 appTypeEventHandlers [name] = method;\r
121                                 return;\r
122                         }\r
123                         \r
124                         ArrayList list;\r
125                         if (appTypeEventHandlers [name] is MethodInfo)\r
126                                 list = new ArrayList ();\r
127                         else\r
128                                 list = appTypeEventHandlers [name] as ArrayList;\r
129 \r
130                         list.Add (method);\r
131                 }\r
132                 \r
133                 static Hashtable GetApplicationTypeEvents (HttpApplication app)\r
134                 {\r
135                         Type appType = app.GetType ();\r
136                         Hashtable appTypeEventHandlers = new Hashtable ();\r
137                         BindingFlags flags = BindingFlags.Public    |\r
138                                              BindingFlags.NonPublic | \r
139                                              BindingFlags.Instance |\r
140                                              BindingFlags.Static;\r
141 \r
142                         MethodInfo [] methods = appType.GetMethods (flags);\r
143                         foreach (MethodInfo m in methods) {\r
144                                 if (IsEventHandler (m))\r
145                                         AddEvent (m, appTypeEventHandlers);\r
146                         }\r
147 \r
148                         return appTypeEventHandlers;\r
149                 }\r
150
151                 static bool FireEvent (string method_name, object target, object [] args)
152                 {
153                         Hashtable possibleEvents = GetApplicationTypeEvents ((HttpApplication) target);
154                         MethodInfo method = possibleEvents [method_name] as MethodInfo;
155                         if (method == null)
156                                 return false;
157
158                         if (method.GetParameters ().Length == 0)
159                                 method.Invoke (target, null);
160                         else
161                                 method.Invoke (target, args);
162
163                         return true;
164                 }
165
166                 internal static void FireOnAppStart (HttpApplication app)\r
167                 {
168                         object [] args = new object [] {app, EventArgs.Empty};\r
169                         FireEvent ("Application_Start", app, args);\r
170                 }
171
172                 void FireOnAppEnd ()
173                 {
174                         if (_appType == null)
175                                 return; // we didn't even get an application
176
177                         HttpApplication app = (HttpApplication) HttpRuntime.CreateInternalObject (_appType);
178                         AttachEvents (app);
179                         FireEvent ("Application_End", app, new object [] {this, EventArgs.Empty});
180                         app.Dispose ();
181                 }
182
183                 FileSystemWatcher CreateWatcher (string file, FileSystemEventHandler hnd)
184                 {
185                         FileSystemWatcher watcher = new FileSystemWatcher ();
186
187                         watcher.Path = Path.GetFullPath (Path.GetDirectoryName (file));
188                         watcher.Filter = Path.GetFileName (file);
189
190                         watcher.Changed += hnd;
191                         watcher.Created += hnd;
192                         watcher.Deleted += hnd;
193
194                         watcher.EnableRaisingEvents = true;
195
196                         return watcher;
197                 }
198
199                 void OnAppFileChanged (object sender, FileSystemEventArgs args)
200                 {
201                         binWatcher.EnableRaisingEvents = false;
202                         appFileWatcher.EnableRaisingEvents = false;
203                         HttpRuntime.UnloadAppDomain ();
204                 }
205
206                 private void InitializeFactory (HttpContext context)\r
207                 {\r
208                         _appFilename = GetAppFilename (context);\r
209 \r
210                         CompileApp (context);\r
211 \r
212                         // Create a application object\r
213                         HttpApplication app = (HttpApplication) HttpRuntime.CreateInternalObject (_appType);\r
214 \r
215                         // Startup\r
216                         app.Startup(context, HttpApplicationFactory.ApplicationState);\r
217
218                         // Shutdown the application if bin directory changes.
219                         string binFiles = HttpRuntime.BinDirectory;
220                         if (Directory.Exists (binFiles))
221                                 binFiles = Path.Combine (binFiles, "*.*");
222
223                         binWatcher = CreateWatcher (binFiles, new FileSystemEventHandler (OnAppFileChanged));
224
225                         // Fire OnAppStart\r
226                         HttpApplicationFactory.FireOnAppStart (app);\r
227 \r
228                         // Recycle our application instance\r
229                         RecyclePublicInstance(app);\r
230                 }\r
231 \r
232                 private void Dispose() {\r
233                         ArrayList torelease = new ArrayList();\r
234                         lock (_appFreePublicList) {\r
235                                 while (_appFreePublicList.Count > 0) {\r
236                                         torelease.Add(_appFreePublicList.Pop());\r
237                                         _appFreePublicInstances--;\r
238                                 }\r
239                         }\r
240 \r
241                         if (torelease.Count > 0) {\r
242                                 foreach (Object obj in torelease) {\r
243                                         ((HttpApplication) obj).Cleanup();\r
244                                 }\r
245                         }\r
246 \r
247                         if (!_appFiredEnd) {\r
248                                 lock (this) {\r
249                                         if (!_appFiredEnd) {\r
250                                                 FireOnAppEnd();\r
251                                                 _appFiredEnd = true;\r
252                                         }\r
253                                 }\r
254                         }\r
255                 }\r
256 \r
257                 internal static IHttpHandler GetInstance(HttpContext context)\r
258                 {\r
259                         if (custApplication != null)\r
260                                 return custApplication;\r
261 \r
262                         if (!s_Factory._appInitialized) {\r
263                                 lock (s_Factory) {\r
264                                         if (!s_Factory._appInitialized) {\r
265                                                 s_Factory.InitializeFactory(context);\r
266                                                 s_Factory._appInitialized = true;\r
267                                         }\r
268                                 }\r
269                         }\r
270 \r
271                         return s_Factory.GetPublicInstance(context);\r
272                 }\r
273 \r
274                 internal static void RecycleInstance(HttpApplication app) {\r
275                         if (!s_Factory._appInitialized)\r
276                                 throw new InvalidOperationException("Factory not intialized");\r
277 \r
278                         s_Factory.RecyclePublicInstance(app);\r
279                 }\r
280 \r
281                 internal static void AttachEvents (HttpApplication app)\r
282                 {\r
283                         Hashtable possibleEvents = GetApplicationTypeEvents (app);\r
284                         foreach (string key in possibleEvents.Keys) {\r
285                                 int pos = key.IndexOf ('_');\r
286                                 if (pos == -1 || key.Length <= pos + 1)\r
287                                         continue;\r
288 \r
289                                 string moduleName = key.Substring (0, pos);\r
290                                 object target;\r
291                                 if (moduleName == "Application") {\r
292                                         target = app;\r
293                                 } else {\r
294                                         target = app.Modules [moduleName];\r
295                                         if (target == null)\r
296                                                 continue;\r
297                                 }\r
298 \r
299                                 string eventName = key.Substring (pos + 1);\r
300                                 EventInfo evt = target.GetType ().GetEvent (eventName);\r
301                                 if (evt == null)\r
302                                         continue;\r
303                         \r
304                                 string usualName = moduleName + "_" + eventName;\r
305                                 object methodData = possibleEvents [usualName];\r
306                                 if (methodData == null)\r
307                                         continue;\r
308 \r
309                                 if (methodData is MethodInfo) {\r
310                                         AddHandler (evt, target, app, (MethodInfo) methodData);\r
311                                         continue;\r
312                                 }\r
313 \r
314                                 ArrayList list = (ArrayList) methodData;\r
315                                 foreach (MethodInfo method in list)\r
316                                         AddHandler (evt, target, app, method);\r
317                         }\r
318                 }\r
319 \r
320                 static void AddHandler (EventInfo evt, object target, HttpApplication app, MethodInfo method)\r
321                 {\r
322                         int length = method.GetParameters ().Length;\r
323 \r
324                         if (length == 0) {\r
325                                 NoParamsInvoker npi = new NoParamsInvoker (app, method.Name);\r
326                                 evt.AddEventHandler (target, npi.FakeDelegate);\r
327                         } else {\r
328                                 evt.AddEventHandler (target, Delegate.CreateDelegate (\r
329                                                         typeof (EventHandler), app, method.Name));\r
330                         }\r
331                 }\r
332 \r
333                 private IHttpHandler GetPublicInstance(HttpContext context) {\r
334                         HttpApplication app = null;\r
335 \r
336                         lock (_appFreePublicList) {\r
337                                 if (_appFreePublicInstances > 0) {\r
338                                         app = (HttpApplication) _appFreePublicList.Pop();\r
339                                         _appFreePublicInstances--;\r
340                                 }\r
341                         }\r
342 \r
343                         if (app == null) {\r
344                                 // Create non-public object\r
345                                 app = (HttpApplication) HttpRuntime.CreateInternalObject(_appType);\r
346 \r
347                                 app.Startup(context, HttpApplicationFactory.ApplicationState);\r
348                         }\r
349 \r
350                         return (IHttpHandler) app;\r
351                 }\r
352 \r
353                 internal void RecyclePublicInstance(HttpApplication app) {\r
354                         lock (_appFreePublicList) {\r
355                                 if (_appFreePublicInstances < _appMaxFreePublicInstances) {\r
356                                         _appFreePublicList.Push(app);\r
357                                         _appFreePublicInstances++;\r
358 \r
359                                         app = null;\r
360                                 }\r
361                         }\r
362                         \r
363                         if  (app != null) {\r
364                                 app.Cleanup();\r
365                         }\r
366                 }\r
367 \r
368                 static HttpStaticObjectsCollection MakeStaticCollection (ArrayList list)\r
369                 {\r
370                         if (list == null || list.Count == 0)\r
371                                 return null;\r
372 \r
373                         HttpStaticObjectsCollection coll = new HttpStaticObjectsCollection ();\r
374                         foreach (ObjectTagBuilder tag in list) {\r
375                                 coll.Add (tag);\r
376                         }\r
377 \r
378                         return coll;\r
379                 }\r
380                 \r
381                 static internal HttpApplicationState ApplicationState {\r
382                         get {\r
383                                 if (null == s_Factory._state) {\r
384                                         HttpStaticObjectsCollection app = MakeStaticCollection (GlobalAsaxCompiler.ApplicationObjects);\r
385                                         HttpStaticObjectsCollection ses = MakeStaticCollection (GlobalAsaxCompiler.SessionObjects);\r
386                                         s_Factory._state = new HttpApplicationState (app, ses);\r
387                                 }\r
388 \r
389                                 return s_Factory._state;\r
390                         }\r
391                 }\r
392 \r
393                 internal static void EndApplication() {\r
394                         s_Factory.Dispose();\r
395                 }\r
396 \r
397                 public static void SetCustomApplication (IHttpHandler customApplication)\r
398                 {\r
399                         custApplication = customApplication;\r
400                 }\r
401 \r
402                 internal Type AppType {\r
403                         get { return _appType; }\r
404                 }\r
405 \r
406                 internal static void SignalError(Exception exc) {\r
407                         // TODO: Raise an error (we probably don't have a HttpContext)\r
408                 }\r
409         }\r
410 }\r