// Copyright 2004-2009 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; }
}
}
}
}