More working tests.
[castle.git] / MonoRail / Castle.MonoRail.Framework / MonoRailHttpHandlerFactory.cs
blob6eb60218fb22bafe74083907324623129727af4a
1 // Copyright 2004-2008 Castle Project - http://www.castleproject.org/
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
15 namespace Castle.MonoRail.Framework
17 using System;
18 using System.Collections.Generic;
19 using System.Reflection;
20 using System.Threading;
21 using System.Web;
22 using Adapters;
23 using Castle.Core;
24 using Castle.MonoRail.Framework.Container;
25 using Castle.MonoRail.Framework.Configuration;
26 using Castle.MonoRail.Framework.Descriptors;
27 using Routing;
28 using Services;
30 /// <summary>
31 /// Coordinates the creation of new <see cref="MonoRailHttpHandler"/>
32 /// and uses the configuration to obtain the correct factories
33 /// instances.
34 /// </summary>
35 public class MonoRailHttpHandlerFactory : IHttpHandlerFactory
37 private readonly static string CurrentEngineContextKey = "currentmrengineinstance";
38 private readonly static string CurrentControllerKey = "currentmrcontroller";
39 private readonly static string CurrentControllerContextKey = "currentmrcontrollercontext";
40 private readonly ReaderWriterLock locker = new ReaderWriterLock();
42 private static IMonoRailConfiguration configuration;
43 private static IMonoRailContainer mrContainer;
44 private static IUrlTokenizer urlTokenizer;
45 private static IEngineContextFactory engineContextFactory;
46 private static IServiceProviderLocator serviceProviderLocator;
47 private static IControllerFactory controllerFactory;
48 private static IControllerContextFactory controllerContextFactory;
49 private static IStaticResourceRegistry staticResourceRegistry;
51 /// <summary>
52 /// Initializes a new instance of the <see cref="MonoRailHttpHandlerFactory"/> class.
53 /// </summary>
54 public MonoRailHttpHandlerFactory() : this(ServiceProviderLocator.Instance)
58 /// <summary>
59 /// Initializes a new instance of the <see cref="MonoRailHttpHandlerFactory"/> class.
60 /// </summary>
61 /// <param name="serviceLocator">The service locator.</param>
62 public MonoRailHttpHandlerFactory(IServiceProviderLocator serviceLocator)
64 serviceProviderLocator = serviceLocator;
67 /// <summary>
68 /// Returns an instance of a class that implements
69 /// the <see cref="T:System.Web.IHttpHandler"></see> interface.
70 /// </summary>
71 /// <param name="context">An instance of the <see cref="T:System.Web.HttpContext"></see> class that provides references to intrinsic server objects (for example, Request, Response, Session, and Server) used to service HTTP requests.</param>
72 /// <param name="requestType">The HTTP data transfer method (GET or POST) that the client uses.</param>
73 /// <param name="url">The <see cref="P:System.Web.HttpRequest.RawUrl"></see> of the requested resource.</param>
74 /// <param name="pathTranslated">The <see cref="P:System.Web.HttpRequest.PhysicalApplicationPath"></see> to the requested resource.</param>
75 /// <returns>
76 /// A new <see cref="T:System.Web.IHttpHandler"></see> object that processes the request.
77 /// </returns>
78 public virtual IHttpHandler GetHandler(HttpContext context,
79 String requestType,
80 String url, String pathTranslated)
82 PerformOneTimeInitializationIfNecessary(context);
84 EnsureServices();
86 HttpRequest req = context.Request;
88 RouteMatch routeMatch = (RouteMatch) context.Items[RouteMatch.RouteMatchKey] ?? new RouteMatch();
90 UrlInfo urlInfo = urlTokenizer.TokenizeUrl(req.FilePath, req.PathInfo, req.Url, req.IsLocal, req.ApplicationPath);
92 if (urlInfo.Area == "MonoRail" && urlInfo.Controller == "Files")
94 return new ResourceFileHandler(urlInfo, new DefaultStaticResourceRegistry());
97 // TODO: Identify requests for files (js files) and serve them directly bypassing the flow
99 IEngineContext engineContext = engineContextFactory.Create(mrContainer, urlInfo, context, routeMatch);
100 engineContext.AddService(typeof(IEngineContext), engineContext);
102 IController controller;
106 controller = controllerFactory.CreateController(urlInfo.Area, urlInfo.Controller);
108 catch(ControllerNotFoundException)
110 return new NotFoundHandler(urlInfo.Area, urlInfo.Controller, engineContext);
113 ControllerMetaDescriptor controllerDesc =
114 mrContainer.ControllerDescriptorProvider.BuildDescriptor(controller);
116 IControllerContext controllerContext =
117 controllerContextFactory.Create(urlInfo.Area, urlInfo.Controller, urlInfo.Action, controllerDesc, routeMatch);
119 engineContext.CurrentController = controller;
120 engineContext.CurrentControllerContext = controllerContext;
122 context.Items[CurrentEngineContextKey] = engineContext;
123 context.Items[CurrentControllerKey] = controller;
124 context.Items[CurrentControllerContextKey] = controllerContext;
126 if (IgnoresSession(controllerDesc.ControllerDescriptor))
128 return new SessionlessMonoRailHttpHandler(engineContext, controller, controllerContext);
130 else
132 return new MonoRailHttpHandler(engineContext, controller, controllerContext);
136 /// <summary>
137 /// Enables a factory to reuse an existing handler instance.
138 /// </summary>
139 /// <param name="handler">The <see cref="T:System.Web.IHttpHandler"></see> object to reuse.</param>
140 public virtual void ReleaseHandler(IHttpHandler handler)
144 /// <summary>
145 /// Resets the state (only used from test cases)
146 /// </summary>
147 public void ResetState()
149 configuration = null;
150 mrContainer = null;
151 urlTokenizer = null;
152 engineContextFactory = null;
153 serviceProviderLocator = null;
154 controllerFactory = null;
155 controllerContextFactory = null;
156 staticResourceRegistry = null;
159 /// <summary>
160 /// Gets or sets the configuration.
161 /// </summary>
162 /// <value>The configuration.</value>
163 public IMonoRailConfiguration Configuration
165 get { return configuration; }
166 set { configuration = value; }
169 /// <summary>
170 /// Gets or sets the container.
171 /// </summary>
172 /// <value>The container.</value>
173 public IMonoRailContainer Container
175 get { return mrContainer; }
176 set { mrContainer = value; }
179 /// <summary>
180 /// Gets or sets the service provider locator.
181 /// </summary>
182 /// <value>The service provider locator.</value>
183 public IServiceProviderLocator ProviderLocator
185 get { return serviceProviderLocator; }
186 set { serviceProviderLocator = value; }
189 /// <summary>
190 /// Gets or sets the URL tokenizer.
191 /// </summary>
192 /// <value>The URL tokenizer.</value>
193 public IUrlTokenizer UrlTokenizer
195 get { return urlTokenizer; }
196 set { urlTokenizer = value; }
199 /// <summary>
200 /// Gets or sets the engine context factory.
201 /// </summary>
202 /// <value>The engine context factory.</value>
203 public IEngineContextFactory EngineContextFactory
205 get { return engineContextFactory; }
206 set { engineContextFactory = value; }
209 /// <summary>
210 /// Gets or sets the controller factory.
211 /// </summary>
212 /// <value>The controller factory.</value>
213 public IControllerFactory ControllerFactory
215 get { return controllerFactory; }
216 set { controllerFactory = value; }
219 /// <summary>
220 /// Gets or sets the controller context factory.
221 /// </summary>
222 /// <value>The controller context factory.</value>
223 public IControllerContextFactory ControllerContextFactory
225 get { return controllerContextFactory; }
226 set { controllerContextFactory = value; }
229 /// <summary>
230 /// Checks whether we should ignore session for the specified controller.
231 /// </summary>
232 /// <param name="controllerDesc">The controller desc.</param>
233 /// <returns></returns>
234 protected virtual bool IgnoresSession(ControllerDescriptor controllerDesc)
236 return controllerDesc.Sessionless;
239 /// <summary>
240 /// Creates the default service container.
241 /// </summary>
242 /// <param name="userServiceProvider">The user service provider.</param>
243 /// <param name="appInstance">The app instance.</param>
244 /// <returns></returns>
245 protected virtual IMonoRailContainer CreateDefaultMonoRailContainer(IServiceProviderEx userServiceProvider, HttpApplication appInstance)
247 DefaultMonoRailContainer container = new DefaultMonoRailContainer(userServiceProvider);
249 container.UseServicesFromParent();
250 container.InstallPrimordialServices();
251 container.Configure(Configuration);
253 FireContainerCreated(appInstance, container);
255 // Too dependent on Http and MR surroundings services to be moved to Container class
256 if (!container.HasService<IServerUtility>())
258 container.AddService<IServerUtility>(new ServerUtilityAdapter(appInstance.Context.Server));
260 if (!container.HasService<IRoutingEngine>())
262 container.AddService<IRoutingEngine>(RoutingModuleEx.Engine);
265 container.InstallMissingServices();
266 container.StartExtensionManager();
268 FireContainerInitialized(appInstance, container);
270 return container;
273 #region Static accessors
275 /// <summary>
276 /// Gets the current engine context.
277 /// </summary>
278 /// <value>The current engine context.</value>
279 public static IEngineContext CurrentEngineContext
281 get { return HttpContext.Current.Items[CurrentEngineContextKey] as IEngineContext; }
284 /// <summary>
285 /// Gets the current controller.
286 /// </summary>
287 /// <value>The current controller.</value>
288 public static IController CurrentController
290 get { return HttpContext.Current.Items[CurrentControllerKey] as IController; }
293 /// <summary>
294 /// Gets the current controller context.
295 /// </summary>
296 /// <value>The current controller context.</value>
297 public static IControllerContext CurrentControllerContext
299 get { return HttpContext.Current.Items[CurrentControllerContextKey] as IControllerContext; }
302 #endregion
304 private void PerformOneTimeInitializationIfNecessary(HttpContext context)
306 locker.AcquireReaderLock(Timeout.Infinite);
308 if (mrContainer != null)
310 locker.ReleaseReaderLock();
311 return;
314 locker.UpgradeToWriterLock(Timeout.Infinite);
316 if (mrContainer != null) // remember remember the race condition
318 locker.ReleaseWriterLock();
319 return;
324 if (configuration == null)
326 configuration = ObtainConfiguration(context.ApplicationInstance);
329 IServiceProviderEx userServiceProvider = serviceProviderLocator.LocateProvider();
331 mrContainer = CreateDefaultMonoRailContainer(userServiceProvider, context.ApplicationInstance);
333 finally
335 locker.ReleaseWriterLock();
339 private void FireContainerCreated(HttpApplication instance, DefaultMonoRailContainer container)
341 MethodInfo eventMethod = instance.GetType().GetMethod("MonoRail_ContainerCreated");
343 ExecuteContainerEvent(eventMethod, instance, container);
346 private void FireContainerInitialized(HttpApplication instance, DefaultMonoRailContainer container)
348 MethodInfo eventMethod = instance.GetType().GetMethod("MonoRail_ContainerInitialized");
350 ExecuteContainerEvent(eventMethod, instance, container);
353 private static void ExecuteContainerEvent(MethodInfo eventMethod, HttpApplication instance, DefaultMonoRailContainer container)
355 if (eventMethod == null)
357 return;
360 if (eventMethod.IsStatic)
362 eventMethod.Invoke(null, new object[] { container });
364 else
366 eventMethod.Invoke(instance, new object[] { container });
370 private IMonoRailConfiguration ObtainConfiguration(HttpApplication appInstance)
372 IMonoRailConfiguration config = MonoRailConfiguration.GetConfig();
374 MethodInfo mrConfigurer = appInstance.GetType().GetMethod("MonoRail_Configure");
376 if (mrConfigurer != null)
378 config = config ?? new MonoRailConfiguration();
380 if (mrConfigurer.IsStatic)
382 mrConfigurer.Invoke(null, new object[] { config });
384 else
386 mrConfigurer.Invoke(appInstance, new object[] { config });
390 if (config == null)
392 throw new ApplicationException("You have to provide a small configuration to use " +
393 "MonoRail. This can be done using the web.config or " +
394 "your global asax (your class that extends HttpApplication) " +
395 "through the method MonoRail_Configure(IMonoRailConfiguration config). " +
396 "Check the samples or the documentation.");
399 return config;
402 private void EnsureServices()
404 if (urlTokenizer == null)
406 urlTokenizer = mrContainer.UrlTokenizer;
408 if (engineContextFactory == null)
410 engineContextFactory = mrContainer.EngineContextFactory;
412 if (controllerFactory == null)
414 controllerFactory = mrContainer.ControllerFactory;
416 if (controllerContextFactory == null)
418 controllerContextFactory = mrContainer.ControllerContextFactory;
420 if (staticResourceRegistry == null)
422 staticResourceRegistry = mrContainer.StaticResourceRegistry;
426 /// <summary>
427 /// Handles the controller not found situation
428 /// </summary>
429 public class NotFoundHandler : IHttpHandler
431 private readonly string area;
432 private readonly string controller;
433 private readonly IEngineContext engineContext;
435 /// <summary>
436 /// Initializes a new instance of the <see cref="NotFoundHandler"/> class.
437 /// </summary>
438 /// <param name="area">The area.</param>
439 /// <param name="controller">The controller.</param>
440 /// <param name="engineContext">The engine context.</param>
441 public NotFoundHandler(string area, string controller, IEngineContext engineContext)
443 this.area = area;
444 this.controller = controller;
445 this.engineContext = engineContext;
448 /// <summary>
449 /// Enables processing of HTTP Web requests by a custom HttpHandler that implements the <see cref="T:System.Web.IHttpHandler"/> interface.
450 /// </summary>
451 /// <param name="context">An <see cref="T:System.Web.HttpContext"/> object that provides references to the intrinsic server objects (for example, Request, Response, Session, and Server) used to service HTTP requests.</param>
452 public void ProcessRequest(HttpContext context)
454 engineContext.Response.StatusCode = 404;
455 engineContext.Response.StatusDescription = "Not found";
457 if (engineContext.Services.ViewEngineManager.HasTemplate("rescues/404"))
459 Dictionary<string, object> parameters = new Dictionary<string, object>();
461 engineContext.Services.ViewEngineManager.Process("rescues/404", null, engineContext.Response.Output, parameters);
463 return; // gracefully handled
466 throw new ControllerNotFoundException(area, controller);
469 /// <summary>
470 /// Gets a value indicating whether another request can use the <see cref="T:System.Web.IHttpHandler"/> instance.
471 /// </summary>
472 /// <value></value>
473 /// <returns>true if the <see cref="T:System.Web.IHttpHandler"/> instance is reusable; otherwise, false.</returns>
474 public bool IsReusable
476 get { return false; }