2012-07-20 01:04:35 +06:00
using System ;
2012-07-26 21:12:54 +06:00
using System.Configuration ;
2013-03-12 01:50:56 +04:00
using System.Threading ;
2016-05-18 23:34:56 +02:00
using Umbraco.Core.Cache ;
2012-08-01 22:06:15 +06:00
using Umbraco.Core.Configuration ;
using Umbraco.Core.Logging ;
2013-03-12 01:50:56 +04:00
using Umbraco.Core.ObjectResolution ;
2012-11-12 07:40:11 -01:00
using Umbraco.Core.Services ;
2015-07-06 13:58:10 +02:00
using Umbraco.Core.Sync ;
2012-07-20 01:04:35 +06:00
namespace Umbraco.Core
2012-07-28 23:08:02 +06:00
{
2012-07-26 21:12:54 +06:00
/// <summary>
2016-04-14 18:17:18 +02:00
/// Represents the Umbraco application context.
2012-07-20 01:04:35 +06:00
/// </summary>
2016-04-14 18:17:18 +02:00
/// <remarks>Only one singleton instance per running Umbraco application (AppDomain)</remarks>
2013-03-12 01:50:56 +04:00
public class ApplicationContext : IDisposable
2012-07-20 01:04:35 +06:00
{
2016-04-14 18:17:18 +02:00
private volatile bool _disposed ;
private readonly ReaderWriterLockSlim _disposalLocker = new ReaderWriterLockSlim ( ) ;
private bool _isReady ;
readonly ManualResetEventSlim _isReadyEvent = new ManualResetEventSlim ( false ) ;
private DatabaseContext _databaseContext ;
private ServiceContext _services ;
private Lazy < bool > _configured ;
// ReSharper disable once InconsistentNaming
internal string _umbracoApplicationUrl ;
2013-03-23 04:01:52 +06:00
/// <summary>
2016-04-14 18:17:18 +02:00
/// Initializes a new instance of the <see cref="ApplicationContext"/> class.
2013-03-23 04:01:52 +06:00
/// </summary>
2016-04-14 18:17:18 +02:00
/// <param name="dbContext">A database context.</param>
/// <param name="serviceContext">A service context.</param>
/// <param name="cache">A cache helper.</param>
/// <param name="logger">A logger.</param>
2015-01-16 12:29:27 +11:00
public ApplicationContext ( DatabaseContext dbContext , ServiceContext serviceContext , CacheHelper cache , ProfilingLogger logger )
2015-01-09 10:51:15 +11:00
{
2016-04-14 18:17:18 +02:00
if ( dbContext = = null ) throw new ArgumentNullException ( nameof ( dbContext ) ) ;
if ( serviceContext = = null ) throw new ArgumentNullException ( nameof ( serviceContext ) ) ;
if ( cache = = null ) throw new ArgumentNullException ( nameof ( cache ) ) ;
if ( logger = = null ) throw new ArgumentNullException ( nameof ( logger ) ) ;
2013-03-23 04:01:52 +06:00
_databaseContext = dbContext ;
2013-07-02 17:47:20 +10:00
_services = serviceContext ;
ApplicationCache = cache ;
2015-01-16 12:29:27 +11:00
ProfilingLogger = logger ;
2015-06-22 16:22:47 +02:00
2016-04-14 18:17:18 +02:00
Initialize ( ) ;
2015-01-09 10:51:15 +11:00
}
2016-04-14 18:17:18 +02:00
/// <summary>
/// Initializes a new instance of the <see cref="ApplicationContext"/> class.
/// </summary>
/// <param name="cache">A cache helper.</param>
/// <param name="logger">A logger.</param>
/// <remarks>For Unit Tests only.</remarks>
public ApplicationContext ( CacheHelper cache , ProfilingLogger logger )
2015-07-15 16:28:50 +02:00
{
2016-04-14 18:17:18 +02:00
if ( cache = = null ) throw new ArgumentNullException ( nameof ( cache ) ) ;
if ( logger = = null ) throw new ArgumentNullException ( nameof ( logger ) ) ;
2015-07-15 16:28:50 +02:00
ApplicationCache = cache ;
ProfilingLogger = logger ;
2016-04-14 18:17:18 +02:00
Initialize ( ) ;
2013-08-09 11:42:20 +10:00
}
2013-10-09 13:17:19 +11:00
/// <summary>
2016-04-14 18:17:18 +02:00
/// Sets and/or ensures that a global application context exists.
2013-10-09 13:17:19 +11:00
/// </summary>
2016-04-14 18:17:18 +02:00
/// <param name="appContext">The application context instance.</param>
/// <param name="replaceContext">A value indicating whether to replace the existing context, if any.</param>
/// <returns>The current global application context.</returns>
/// <remarks>This is NOT thread safe. For Unit Tests only.</remarks>
2013-10-09 13:17:19 +11:00
public static ApplicationContext EnsureContext ( ApplicationContext appContext , bool replaceContext )
{
2016-04-14 18:17:18 +02:00
if ( Current ! = null & & replaceContext = = false )
return Current ;
return Current = appContext ;
2013-10-09 13:17:19 +11:00
}
2016-04-14 18:17:18 +02:00
/// <summary>
/// Sets and/or ensures that a global application context exists.
/// </summary>
/// <param name="cache">A cache helper.</param>
/// <param name="logger">A logger.</param>
/// <param name="dbContext">A database context.</param>
/// <param name="serviceContext">A service context.</param>
/// <param name="replaceContext">A value indicating whether to replace the existing context, if any.</param>
/// <returns>The current global application context.</returns>
/// <remarks>This is NOT thread safe. For Unit Tests only.</remarks>
2015-01-16 12:29:27 +11:00
public static ApplicationContext EnsureContext ( DatabaseContext dbContext , ServiceContext serviceContext , CacheHelper cache , ProfilingLogger logger , bool replaceContext )
2015-01-09 10:51:15 +11:00
{
2016-04-14 18:17:18 +02:00
if ( Current ! = null & & replaceContext = = false )
2015-07-13 22:52:41 +02:00
return Current ;
2016-04-14 18:17:18 +02:00
return Current = new ApplicationContext ( dbContext , serviceContext , cache , logger ) ;
2015-01-09 10:51:15 +11:00
}
2013-10-09 13:17:19 +11:00
/// <summary>
2016-04-14 18:17:18 +02:00
/// Gets the current global application context.
2012-07-20 22:02:28 +06:00
/// </summary>
2012-07-20 23:42:55 +06:00
public static ApplicationContext Current { get ; internal set ; }
2012-07-20 01:04:35 +06:00
2012-10-31 11:36:22 +06:00
/// <summary>
/// Returns the application wide cache accessor
/// </summary>
/// <remarks>
/// Any caching that is done in the application (app wide) should be done through this property
/// </remarks>
2012-11-19 15:31:58 -01:00
public CacheHelper ApplicationCache { get ; private set ; }
2012-10-31 11:36:22 +06:00
2015-01-09 10:51:15 +11:00
/// <summary>
2016-04-14 18:17:18 +02:00
/// Gets the profiling logger.
/// </summary>
public ProfilingLogger ProfilingLogger { get ; }
/// <summary>
/// Gets the MainDom.
2015-01-09 10:51:15 +11:00
/// </summary>
2016-04-14 18:17:18 +02:00
internal MainDom MainDom { get ; private set ; }
2015-01-09 10:51:15 +11:00
2016-04-14 18:17:18 +02:00
/// <summary>
/// Gets a value indicating whether the application is configured.
/// </summary>
/// <remarks>Meaning: installed and no need to upgrade anything.</remarks>
public virtual bool IsConfigured = > _configured . Value ;
2012-12-14 08:06:32 +05:00
2016-04-14 18:17:18 +02:00
/// <summary>
/// Gets a value indicating whether the application is ready.
/// </summary>
/// <remarks><para>Meaning: ready to run, boot has completed, though maybe the application is not configured.</para>
/// <para>IsReady is set to true by the boot manager once it has successfully booted. The original
/// Umbraco module checked on content.Instance, now the boot task that sets the content
/// store ensures that it is ready.</para>
/// </remarks>
public bool IsReady
2012-07-20 01:04:35 +06:00
{
get
{
return _isReady ;
}
internal set
{
2016-04-14 18:17:18 +02:00
if ( IsReady )
throw new Exception ( "ApplicationContext has already been initialized." ) ;
if ( value = = false )
throw new Exception ( "Value must be true." ) ;
_isReady = true ;
2012-09-28 11:11:47 -02:00
_isReadyEvent . Set ( ) ;
2012-07-20 01:04:35 +06:00
}
}
2016-04-14 18:17:18 +02:00
/// <summary>
/// Blocks until the application is ready.
/// </summary>
/// <param name="timeout">The time to wait, or -1 to wait indefinitely.</param>
/// <returns>A value indicating whether the application is ready.</returns>
2012-09-28 11:11:47 -02:00
public bool WaitForReady ( int timeout )
{
return _isReadyEvent . WaitHandle . WaitOne ( timeout ) ;
}
2016-04-14 18:17:18 +02:00
/// <summary>
/// Gets a value indicating whether the application is upgrading.
2015-04-01 16:12:32 +11:00
/// </summary>
2016-04-14 18:17:18 +02:00
/// <remarks>Meaning: the database is configured and the database context has access to an existing Umbraco schema,
/// however we are not 'configured' because we still need to upgrade.</remarks>
2015-04-01 16:12:32 +11:00
public bool IsUpgrading
{
2015-06-09 12:17:45 +02:00
get
{
2016-04-14 18:17:18 +02:00
if ( IsConfigured // configured already
| | DatabaseContext = = null // no database context
| | DatabaseContext . IsDatabaseConfigured = = false // database is not configured
| | DatabaseContext . CanConnect = = false ) // database cannot connect
return false ;
2015-06-09 12:17:45 +02:00
2016-04-14 18:17:18 +02:00
// upgrading if we have some valid tables (else, no schema, need to install)
var schemaresult = DatabaseContext . ValidateDatabaseSchema ( ) ;
return schemaresult . ValidTables . Count > 0 ;
2015-06-09 12:17:45 +02:00
}
2015-04-01 16:12:32 +11:00
}
2013-09-13 14:06:36 +10:00
/// <summary>
2016-04-14 18:17:18 +02:00
/// Gets the application Url.
2013-04-05 23:28:43 +06:00
/// </summary>
/// <remarks>
2015-07-06 13:58:10 +02:00
/// The application url is the url that should be used by services to talk to the application,
/// eg keep alive or scheduled publishing services. It must exist on a global context because
/// some of these services may not run within a web context.
/// The format of the application url is:
/// - has a scheme (http or https)
/// - has the SystemDirectories.Umbraco path
/// - does not end with a slash
/// It is initialized on the first request made to the server, by UmbracoModule.EnsureApplicationUrl:
2015-07-14 19:23:38 +02:00
/// - if umbracoSettings:settings/web.routing/@umbracoApplicationUrl is set, use the value (new setting)
2015-07-06 13:58:10 +02:00
/// - if umbracoSettings:settings/scheduledTasks/@baseUrl is set, use the value (backward compatibility)
/// - otherwise, use the url of the (first) request.
/// Not locking, does not matter if several threads write to this.
/// See also issues:
/// - http://issues.umbraco.org/issue/U4-2059
/// - http://issues.umbraco.org/issue/U4-6788
/// - http://issues.umbraco.org/issue/U4-5728
/// - http://issues.umbraco.org/issue/U4-5391
2013-04-05 23:28:43 +06:00
/// </remarks>
2015-07-08 17:32:36 +02:00
internal string UmbracoApplicationUrl
{
get
{
2015-08-25 15:48:12 +02:00
ApplicationUrlHelper . EnsureApplicationUrl ( this ) ;
2015-07-06 13:58:10 +02:00
return _umbracoApplicationUrl ;
}
}
2013-04-05 23:28:43 +06:00
2016-04-14 18:17:18 +02:00
private void Initialize ( )
2012-07-26 21:12:54 +06:00
{
2015-07-13 22:52:41 +02:00
MainDom = new MainDom ( ProfilingLogger . Logger ) ;
2015-07-03 15:32:37 +02:00
MainDom . Acquire ( ) ;
2016-04-14 18:17:18 +02:00
ResetConfigured ( ) ;
}
/// <summary>
/// Resets the IsConfigured value, which will then be discovered again.
/// </summary>
/// <remarks>For Unit Tests usage, though it is harmless.</remarks>
public void ResetConfigured ( )
{
// create the lazy value to resolve whether or not the application is 'configured'
// meaning: installed and no need to upgrade anything
2015-06-22 16:22:47 +02:00
_configured = new Lazy < bool > ( ( ) = >
{
try
{
2016-04-14 18:17:18 +02:00
var configStatus = ConfigurationStatus ; // the value in the web.config
var currentVersion = UmbracoVersion . GetSemanticVersion ( ) ; // the hard-coded current version of the binaries that are executing
2012-07-26 21:12:54 +06:00
2016-04-14 18:17:18 +02:00
// if we have no value in web.config or value differs, we are not configured yet
if ( string . IsNullOrWhiteSpace ( configStatus ) | | configStatus ! = currentVersion )
2015-06-22 16:22:47 +02:00
{
2016-04-14 18:17:18 +02:00
ProfilingLogger . Logger . Debug < ApplicationContext > ( $"CurrentVersion different from configStatus: '{currentVersion.ToSemanticString()}','{configStatus}'." ) ;
return false ;
2015-06-22 16:22:47 +02:00
}
2016-04-14 18:17:18 +02:00
// versions match, now look for database state and migrations
// which requires that we do have a database that we can connect to
if ( DatabaseContext . IsDatabaseConfigured = = false | | DatabaseContext . CanConnect = = false )
2015-06-22 16:22:47 +02:00
{
2016-04-14 18:17:18 +02:00
ProfilingLogger . Logger . Debug < ApplicationContext > ( "Database is not configured, or could not connect to the database." ) ;
return false ;
2015-06-22 16:22:47 +02:00
}
2012-07-26 21:12:54 +06:00
2016-04-14 18:17:18 +02:00
// look for a migration entry for the current version
var entry = Services . MigrationEntryService . FindEntry ( GlobalSettings . UmbracoMigrationName , UmbracoVersion . GetSemanticVersion ( ) ) ;
if ( entry ! = null )
return true ; // all clear!
// even though the versions match, the db has not been properly upgraded
ProfilingLogger . Logger . Debug < ApplicationContext > ( $"The migration for version: '{currentVersion.ToSemanticString()} has not been executed, there is no record in the database." ) ;
return false ;
2015-06-22 16:22:47 +02:00
}
2015-11-26 13:07:22 +01:00
catch ( Exception ex )
2015-06-22 16:22:47 +02:00
{
2016-04-14 18:17:18 +02:00
LogHelper . Error < ApplicationContext > ( "Error determining if application is configured, returning false." , ex ) ;
2015-06-22 16:22:47 +02:00
return false ;
}
2016-04-14 18:17:18 +02:00
} ) ;
}
2015-06-22 16:22:47 +02:00
2016-04-14 18:17:18 +02:00
// gets the configuration status, ie the version that's in web.config
private string ConfigurationStatus
2012-07-26 21:12:54 +06:00
{
get
{
try
{
return ConfigurationManager . AppSettings [ "umbracoConfigurationStatus" ] ;
}
catch
{
2016-04-14 18:17:18 +02:00
return string . Empty ;
2012-07-26 21:12:54 +06:00
}
2016-04-14 18:17:18 +02:00
}
2012-07-26 21:12:54 +06:00
}
2012-12-14 08:06:32 +05:00
/// <summary>
2016-04-14 18:17:18 +02:00
/// Gets the current database context.
2012-12-14 08:06:32 +05:00
/// </summary>
public DatabaseContext DatabaseContext
{
get
{
if ( _databaseContext = = null )
2016-04-14 18:17:18 +02:00
throw new InvalidOperationException ( "The DatabaseContext has not been set on the ApplicationContext." ) ;
2012-12-14 08:06:32 +05:00
return _databaseContext ;
}
2016-04-14 18:17:18 +02:00
// INTERNAL FOR UNIT TESTS
2012-12-14 08:06:32 +05:00
internal set { _databaseContext = value ; }
}
2016-04-14 18:17:18 +02:00
2012-12-14 08:06:32 +05:00
/// <summary>
2016-04-14 18:17:18 +02:00
/// Gets the current service context.
2012-12-14 08:06:32 +05:00
/// </summary>
public ServiceContext Services
{
get
{
if ( _services = = null )
2016-04-14 18:17:18 +02:00
throw new InvalidOperationException ( "The ServiceContext has not been set on the ApplicationContext." ) ;
2012-12-14 08:06:32 +05:00
return _services ;
}
2016-04-14 18:17:18 +02:00
// INTERNAL FOR UNIT TESTS
internal set { _services = value ; }
2012-12-14 08:06:32 +05:00
}
2013-03-12 01:50:56 +04:00
2016-04-14 18:17:18 +02:00
/// <summary>
/// Gets the server role.
/// </summary>
/// <returns></returns>
2015-08-25 15:48:12 +02:00
internal ServerRole GetCurrentServerRole ( )
{
var registrar = ServerRegistrarResolver . Current . Registrar as IServerRegistrar2 ;
2016-04-14 18:17:18 +02:00
return registrar ? . GetCurrentServerRole ( ) ? ? ServerRole . Unknown ;
2015-08-25 15:48:12 +02:00
}
2013-03-12 01:50:56 +04:00
/// <summary>
2016-04-14 18:17:18 +02:00
/// Disposes the application context.
2013-03-12 01:50:56 +04:00
/// </summary>
2016-04-14 18:17:18 +02:00
/// <remarks>Do not this object if you require the Umbraco application to run. Disposing this object
2013-03-12 01:50:56 +04:00
/// is generally used for unit testing and when your application is shutting down after you have booted Umbraco.
/// </remarks>
void IDisposable . Dispose ( )
{
if ( _disposed ) return ;
using ( new WriteLock ( _disposalLocker ) )
{
2016-04-14 18:17:18 +02:00
// double check... bah...
2013-03-12 01:50:56 +04:00
if ( _disposed ) return ;
2016-04-14 18:17:18 +02:00
// clear the cache
2013-03-14 01:07:54 +04:00
if ( ApplicationCache ! = null )
{
2016-01-06 18:08:14 +01:00
ApplicationCache . RuntimeCache . ClearAllCache ( ) ;
ApplicationCache . IsolatedRuntimeCache . ClearAllCaches ( ) ;
2013-03-14 01:07:54 +04:00
}
2016-04-14 18:17:18 +02:00
// reset all resolvers
2013-03-12 01:50:56 +04:00
ResolverCollection . ResetAll ( ) ;
2016-04-14 18:17:18 +02:00
// reset resolution itself (though this should be taken care of by resetting any of the resolvers above)
2013-03-12 01:50:56 +04:00
Resolution . Reset ( ) ;
2016-04-14 18:17:18 +02:00
// reset the instance objects
ApplicationCache = null ;
2013-03-14 01:07:54 +04:00
if ( _databaseContext ! = null ) //need to check the internal field here
2013-03-13 18:31:07 +04:00
{
2016-04-14 18:17:18 +02:00
if ( DatabaseContext . IsDatabaseConfigured )
DatabaseContext . Database ? . Dispose ( ) ;
2013-03-13 18:31:07 +04:00
}
2016-04-14 18:17:18 +02:00
DatabaseContext = null ;
Services = null ;
_isReady = false ; //set the internal field
2013-03-12 01:50:56 +04:00
_disposed = true ;
}
}
2012-07-20 01:04:35 +06:00
}
}