// Copyright 2004-2008 Castle Project - http://www.castleproject.org/ // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. namespace Castle.MonoRail.Framework { using System; using System.Collections.Generic; using System.Threading; using System.Web; using Adapters; using Castle.Core; using Castle.MonoRail.Framework.Container; using Castle.MonoRail.Framework.Configuration; using Castle.MonoRail.Framework.Descriptors; using Providers; using Routing; using Services; /// /// Coordinates the creation of new /// and uses the configuration to obtain the correct factories /// instances. /// public class MonoRailHttpHandlerFactory : IHttpHandlerFactory { private static readonly string CurrentEngineContextKey = "currentmrengineinstance"; private static readonly string CurrentControllerKey = "currentmrcontroller"; private static readonly string CurrentControllerContextKey = "currentmrcontrollercontext"; private static readonly ReaderWriterLock locker = new ReaderWriterLock(); private static IMonoRailConfiguration configuration; private static IMonoRailContainer mrContainer; private static IUrlTokenizer urlTokenizer; private static IEngineContextFactory engineContextFactory; private static IServiceProviderLocator serviceProviderLocator; private static IControllerFactory controllerFactory; private static IControllerContextFactory controllerContextFactory; private static IStaticResourceRegistry staticResourceRegistry; /// /// Initializes a new instance of the class. /// public MonoRailHttpHandlerFactory() : this(ServiceProviderLocator.Instance) { } /// /// Initializes a new instance of the class. /// /// The service locator. public MonoRailHttpHandlerFactory(IServiceProviderLocator serviceLocator) { serviceProviderLocator = serviceLocator; } /// /// Returns an instance of a class that implements /// the interface. /// /// An instance of the class that provides references to intrinsic server objects (for example, Request, Response, Session, and Server) used to service HTTP requests. /// The HTTP data transfer method (GET or POST) that the client uses. /// The of the requested resource. /// The to the requested resource. /// /// A new object that processes the request. /// public virtual IHttpHandler GetHandler(HttpContext context, String requestType, String url, String pathTranslated) { PerformOneTimeInitializationIfNecessary(context); EnsureServices(); HttpRequest req = context.Request; RouteMatch routeMatch = (RouteMatch)context.Items[RouteMatch.RouteMatchKey] ?? new RouteMatch(); UrlInfo urlInfo = urlTokenizer.TokenizeUrl(req.FilePath, req.PathInfo, req.Url, req.IsLocal, req.ApplicationPath); if (IsResourceFileUrl(urlInfo)) { return new ResourceFileHandler(urlInfo, staticResourceRegistry); } // TODO: Identify requests for files (js files) and serve them directly bypassing the flow IEngineContext engineContext = engineContextFactory.Create(mrContainer, urlInfo, context, routeMatch); engineContext.AddService(typeof(IEngineContext), engineContext); context.Items[CurrentEngineContextKey] = engineContext; IController controller; try { controller = controllerFactory.CreateController(urlInfo.Area, urlInfo.Controller); } catch(ControllerNotFoundException) { return new NotFoundHandler(urlInfo.Area, urlInfo.Controller, engineContext); } catch(Exception ex) { HttpResponse response = context.Response; if (response.StatusCode == 200) { response.StatusCode = 500; } engineContext.LastException = ex; engineContext.Services.ExtensionManager.RaiseUnhandledError(engineContext); throw new MonoRailException("Error creating controller " + urlInfo.Controller, ex); } ControllerMetaDescriptor controllerDesc = mrContainer.ControllerDescriptorProvider.BuildDescriptor(controller); IControllerContext controllerContext = controllerContextFactory.Create(urlInfo.Area, urlInfo.Controller, urlInfo.Action, controllerDesc, routeMatch); engineContext.CurrentController = controller; engineContext.CurrentControllerContext = controllerContext; context.Items[CurrentControllerKey] = controller; context.Items[CurrentControllerContextKey] = controllerContext; if (IsAsyncAction(controllerContext) == false) { return CreateHandler(controllerDesc, engineContext, controller, controllerContext); } else { return CreateAsyncHandler(controllerDesc, engineContext, (IAsyncController)controller, controllerContext); } } /// /// Creates the handler. /// /// The controller descriptor. /// The engine context. /// The controller. /// The controller context. /// /// A new object that processes the request. /// protected virtual IHttpHandler CreateHandler(ControllerMetaDescriptor controllerDesc, IEngineContext engineContext, IController controller, IControllerContext controllerContext) { if (IgnoresSession(controllerDesc.ControllerDescriptor)) { return new SessionlessMonoRailHttpHandler(engineContext, controller, controllerContext); } return new MonoRailHttpHandler(engineContext, controller, controllerContext); } /// /// Creates the handler. /// /// The controller descriptor. /// The engine context. /// The controller. /// The controller context. /// /// A new object that processes the request. /// protected virtual IHttpAsyncHandler CreateAsyncHandler(ControllerMetaDescriptor controllerDesc, IEngineContext engineContext, IAsyncController controller, IControllerContext controllerContext) { if (IgnoresSession(controllerDesc.ControllerDescriptor)) { return new AsyncSessionlessMonoRailHttpHandler(engineContext, controller, controllerContext); } return new AsyncMonoRailHttpHandler(engineContext, controller, controllerContext); } /// /// Enables a factory to reuse an existing handler instance. /// /// The object to reuse. public virtual void ReleaseHandler(IHttpHandler handler) { } /// /// Resets the state (only used from test cases) /// public void ResetState() { configuration = null; mrContainer = null; urlTokenizer = null; engineContextFactory = null; serviceProviderLocator = null; controllerFactory = null; controllerContextFactory = null; staticResourceRegistry = null; } /// /// Gets or sets the configuration. /// /// The configuration. public IMonoRailConfiguration Configuration { get { return configuration; } set { configuration = value; } } /// /// Gets or sets the container. /// /// The container. public IMonoRailContainer Container { get { return mrContainer; } set { mrContainer = value; } } /// /// Gets or sets the service provider locator. /// /// The service provider locator. public IServiceProviderLocator ProviderLocator { get { return serviceProviderLocator; } set { serviceProviderLocator = value; } } /// /// Gets or sets the URL tokenizer. /// /// The URL tokenizer. public IUrlTokenizer UrlTokenizer { get { return urlTokenizer; } set { urlTokenizer = value; } } /// /// Gets or sets the engine context factory. /// /// The engine context factory. public IEngineContextFactory EngineContextFactory { get { return engineContextFactory; } set { engineContextFactory = value; } } /// /// Gets or sets the controller factory. /// /// The controller factory. public IControllerFactory ControllerFactory { get { return controllerFactory; } set { controllerFactory = value; } } /// /// Gets or sets the controller context factory. /// /// The controller context factory. public IControllerContextFactory ControllerContextFactory { get { return controllerContextFactory; } set { controllerContextFactory = value; } } /// /// Checks whether we should ignore session for the specified controller. /// /// The controller desc. /// protected virtual bool IgnoresSession(ControllerDescriptor controllerDesc) { return controllerDesc.Sessionless; } /// /// Checks whether the target action is an async method. /// /// The controller context. /// protected virtual bool IsAsyncAction(IControllerContext controllerContext) { if (controllerContext.ControllerDescriptor == null || controllerContext.Action == null) { return false; } return controllerContext.ControllerDescriptor.Actions[controllerContext.Action] is AsyncActionPair; } /// /// Checks whether the target url is a resource file url. /// /// The url info. /// protected virtual bool IsResourceFileUrl(UrlInfo urlInfo) { return urlInfo.Area == "MonoRail" && urlInfo.Controller == "Files"; } /// /// Creates the default service container. /// /// The user service provider. /// The app instance. /// protected virtual IMonoRailContainer CreateDefaultMonoRailContainer(IServiceProviderEx userServiceProvider, HttpApplication appInstance) { DefaultMonoRailContainer container = new DefaultMonoRailContainer(userServiceProvider); container.UseServicesFromParent(); container.InstallPrimordialServices(); container.Configure(Configuration); FireContainerCreated(appInstance, container); // Too dependent on Http and MR surroundings services to be moved to Container class if (!container.HasService()) { container.AddService(new ServerUtilityAdapter(appInstance.Context.Server)); } if (!container.HasService()) { container.AddService(RoutingModuleEx.Engine); } container.InstallMissingServices(); container.StartExtensionManager(); FireContainerInitialized(appInstance, container); return container; } #region Static accessors /// /// Gets the current engine context. /// /// The current engine context. public static IEngineContext CurrentEngineContext { get { if (HttpContext.Current == null)//for tests return null; return HttpContext.Current.Items[CurrentEngineContextKey] as IEngineContext; } } /// /// Gets the current controller. /// /// The current controller. public static IController CurrentController { get { return HttpContext.Current.Items[CurrentControllerKey] as IController; } } /// /// Gets the current controller context. /// /// The current controller context. public static IControllerContext CurrentControllerContext { get { return HttpContext.Current.Items[CurrentControllerContextKey] as IControllerContext; } } #endregion private void PerformOneTimeInitializationIfNecessary(HttpContext context) { locker.AcquireReaderLock(Timeout.Infinite); if (mrContainer != null) { locker.ReleaseReaderLock(); return; } locker.UpgradeToWriterLock(Timeout.Infinite); if (mrContainer != null) // remember remember the race condition { locker.ReleaseWriterLock(); return; } try { if (configuration == null) { configuration = ObtainConfiguration(context.ApplicationInstance); } IServiceProviderEx userServiceProvider = serviceProviderLocator.LocateProvider(); mrContainer = CreateDefaultMonoRailContainer(userServiceProvider, context.ApplicationInstance); } finally { locker.ReleaseWriterLock(); } } private void FireContainerCreated(HttpApplication instance, DefaultMonoRailContainer container) { IMonoRailContainerEvents events = instance as IMonoRailContainerEvents; if (events != null) { events.Created(container); } } private void FireContainerInitialized(HttpApplication instance, DefaultMonoRailContainer container) { IMonoRailContainerEvents events = instance as IMonoRailContainerEvents; if (events != null) { events.Initialized(container); } } private IMonoRailConfiguration ObtainConfiguration(HttpApplication appInstance) { IMonoRailConfiguration config = MonoRailConfiguration.GetConfig(); IMonoRailConfigurationEvents events = appInstance as IMonoRailConfigurationEvents; if (events != null) { config = config ?? new MonoRailConfiguration(); events.Configure(config); } if (config == null) { throw new ApplicationException("You have to provide a small configuration to use " + "MonoRail. This can be done using the web.config or " + "your global asax (your class that extends HttpApplication) " + "through the method MonoRail_Configure(IMonoRailConfiguration config). " + "Check the samples or the documentation."); } return config; } private void EnsureServices() { if (urlTokenizer == null) { urlTokenizer = mrContainer.UrlTokenizer; } if (engineContextFactory == null) { engineContextFactory = mrContainer.EngineContextFactory; } if (controllerFactory == null) { controllerFactory = mrContainer.ControllerFactory; } if (controllerContextFactory == null) { controllerContextFactory = mrContainer.ControllerContextFactory; } if (staticResourceRegistry == null) { staticResourceRegistry = mrContainer.StaticResourceRegistry; } } /// /// Handles the controller not found situation /// public class NotFoundHandler : IHttpHandler { private readonly string area; private readonly string controller; private readonly IEngineContext engineContext; /// /// Initializes a new instance of the class. /// /// The area. /// The controller. /// The engine context. public NotFoundHandler(string area, string controller, IEngineContext engineContext) { this.area = area; this.controller = controller; this.engineContext = engineContext; } /// /// Enables processing of HTTP Web requests by a custom HttpHandler that implements the interface. /// /// An object that provides references to the intrinsic server objects (for example, Request, Response, Session, and Server) used to service HTTP requests. public void ProcessRequest(HttpContext context) { engineContext.Response.StatusCode = 404; engineContext.Response.StatusDescription = "Not found"; if (engineContext.Services.ViewEngineManager.HasTemplate("rescues/404")) { Dictionary parameters = new Dictionary(); engineContext.Services.ViewEngineManager.Process("rescues/404", null, engineContext.Response.Output, parameters); return; // gracefully handled } throw new ControllerNotFoundException(area, controller); } /// /// Gets a value indicating whether another request can use the instance. /// /// /// true if the instance is reusable; otherwise, false. public bool IsReusable { get { return false; } } } } }