Refactored the Kernel registration fluent interface to be more readable, better suppo...
[castle.git] / Facilities / DynamicLoader / Castle.Facilities.DynamicLoader / DynamicLoaderFacility.cs
blob9d233ca27154cc133efef116fc60cfddaf9f7ffc
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.Facilities.DynamicLoader
17 using System;
18 using System.Collections;
19 using System.Collections.Generic;
20 using System.IO;
21 using System.Reflection;
22 using System.Security.Policy;
23 using System.Web;
24 using Castle.Core.Configuration;
25 using Castle.Core.Logging;
26 using Castle.MicroKernel;
27 using Castle.MicroKernel.Facilities;
29 /// <summary>
30 /// DynamicLoader facility.
31 /// </summary>
32 public class DynamicLoaderFacility : MarshalByRefObject, IFacility, IDisposable
34 private readonly DynamicLoaderRegistry registry = new DynamicLoaderRegistry();
36 private ILogger log = NullLogger.Instance;
38 private bool isWeb;
40 private IKernel kernel;
41 private IConfiguration facilityConfig;
43 /// <summary>
44 /// Initializes the facility.
45 /// </summary>
46 public void Init(IKernel kernel, IConfiguration facilityConfig)
48 this.kernel = kernel;
49 this.facilityConfig = facilityConfig;
51 this.Init();
54 /// <summary>
55 /// Terminates the facility.
56 /// </summary>
57 public void Terminate()
59 Dispose(true);
61 kernel = null;
64 ~DynamicLoaderFacility()
66 Dispose(false);
69 protected void Init()
71 if (kernel.HasComponent(typeof(ILoggerFactory)))
72 log = ((ILoggerFactory) kernel.Resolve(typeof(ILoggerFactory), new Hashtable())).Create(GetType());
74 log.Info("DynamicLoader is being initialized");
76 kernel.ComponentModelBuilder.AddContributor(new DynamicLoaderInspector(registry));
78 isWeb = IsTrue(facilityConfig.Attributes["isWeb"]);
80 foreach(IConfiguration cfg in facilityConfig.Children)
82 switch(cfg.Name)
84 case "domain":
85 CreateAppDomain(cfg);
86 break;
87 default:
88 throw new FacilityException("Unrecognized configuration node: " + cfg.Name);
93 private AppDomain CreateAppDomain(IConfiguration domainNode)
95 IConfiguration cfg = domainNode.Children["config"];
97 string domainId = domainNode.Attributes["id"];
99 AppDomainSetup currentSetup = AppDomain.CurrentDomain.SetupInformation;
100 AppDomainSetup setup = new AppDomainSetup();
102 setup.ApplicationName = domainNode.Attributes["applicationName"];
103 setup.ApplicationBase = NormalizeDirectoryPath(GetChildNodeValue(cfg, "applicationBase"));
104 setup.ConfigurationFile = GetChildNodeValue(cfg, "configurationFile");
106 setup.PrivateBinPath = GetChildNodeValue(cfg, "privateBinPath");
107 if (!IsEmpty(setup.PrivateBinPath))
109 setup.PrivateBinPathProbe = Boolean.TrueString;
112 setup.ShadowCopyFiles = domainNode.Attributes["shadowCopyFiles"];
114 if (IsTrue(setup.ShadowCopyFiles))
116 setup.ShadowCopyDirectories = null;
118 setup.CachePath = cfg.Attributes["cachePath"];
119 if (IsEmpty(setup.CachePath))
121 // fix for ASP.NET:
122 // http://weblogs.asp.net/hernandl/archive/2004/10/28/appdomainshcopy.aspx
123 setup.CachePath = currentSetup.CachePath;
127 log.Info("Creating AppDomain '{0}'", setup.ApplicationName);
129 Evidence evidence = AppDomain.CurrentDomain.Evidence;
131 AppDomain appDomain = AppDomain.CreateDomain(setup.ApplicationName, evidence, setup);
133 log.Debug(" BaseDir: " + appDomain.BaseDirectory);
134 log.Debug(" RelativeSearchPath: " + appDomain.RelativeSearchPath);
135 log.Debug(" ShadowCopyDirectories: " + appDomain.SetupInformation.ShadowCopyDirectories);
136 log.Debug(" CachePath: " + appDomain.SetupInformation.CachePath);
137 log.Debug(" PrivateBinPath: " + appDomain.SetupInformation.PrivateBinPath);
139 RemoteLoader l = this.CreateRemoteLoader(appDomain);
141 // register the loader
142 registry.RegisterLoader(domainId, l);
144 this.InitializeBatchRegistration(l, domainNode.Children["batchRegistration"]);
146 log.Debug("Adding as child kernel");
147 kernel.AddChildKernel(l.Kernel);
149 log.Info("Domain '{0}' created successfully.", appDomain.FriendlyName);
151 return appDomain;
154 private RemoteLoader CreateRemoteLoader(AppDomain appDomain)
156 string remoteLoaderAsmName = typeof(RemoteLoader).Assembly.FullName;
157 string remoteLoaderClsName = typeof(RemoteLoader).FullName;
161 appDomain.DoCallBack(
162 delegate
164 Assembly.Load("Castle.Core");
165 Assembly.Load("Castle.MicroKernel");
166 Assembly.Load("Castle.Facilities.DynamicLoader");
169 catch(Exception ex)
171 log.Fatal("Error while loading required assemblies", ex);
172 throw new FacilityException("Failed to load RemoteLoader required assemblies", ex);
177 // creates the RemoteLoader
178 log.Debug("Creating the RemoteLoader");
179 return (RemoteLoader) appDomain.CreateInstanceAndUnwrap(remoteLoaderAsmName, remoteLoaderClsName);
181 catch(Exception ex)
183 log.Fatal("Error while creating the RemoteLoader", ex);
184 throw new FacilityException("Failed to create the RemoteLoader", ex);
188 protected virtual void InitializeBatchRegistration(RemoteLoader loader, IConfiguration batchRegistrationNode)
190 if (batchRegistrationNode == null)
191 return;
193 foreach(IConfiguration comp in batchRegistrationNode.Children)
195 switch(comp.Name)
197 case "components":
198 InitializeBatchComponents(loader, comp);
199 break;
200 default:
201 throw new FacilityException("Unrecognized configuration node: " + comp.Name);
206 /// <summary>
207 /// Register each batch component.
208 /// </summary>
209 /// <param name="loader">The <see cref="RemoteLoader"/> instance in which to register</param>
210 /// <param name="componentsNode">The component configuration node</param>
211 /// <remarks>
212 /// <example>
213 /// An example of a valid configuration node:
214 /// <code>
215 /// &lt;component id="componentid.*"&gt;
216 /// &lt;providesService service="Company.Project.IService, Company.Project" /&gt;
217 /// &lt;/component&gt;
218 /// </code>
219 /// </example>
220 /// </remarks>
221 private void InitializeBatchComponents(RemoteLoader loader, IConfiguration componentsNode)
223 if (componentsNode == null)
224 return;
226 string componentIdMask = componentsNode.Attributes["id"];
227 List<Type> servicesProvided = new List<Type>();
229 foreach(IConfiguration cond in componentsNode.Children)
231 switch(cond.Name)
233 case "providesService":
234 servicesProvided.Add(Type.GetType(cond.Attributes["service"]));
235 break;
236 default:
237 throw new FacilityException("Unrecognized configuration node: " + cond.Name);
241 loader.RegisterByServiceProvided(componentIdMask, servicesProvided.ToArray());
244 #region Configuration utility methods
246 private string GetChildNodeValue(IConfiguration cfg, string nodeName)
248 if (cfg == null)
249 return null;
250 IConfiguration node = cfg.Children[nodeName];
251 if (node == null)
252 return null;
253 return node.Value;
256 protected string GetConfigAttribute(IConfiguration cfg, string attribute, string defaultValue,
257 params object[] defaultValueArguments)
259 string value = cfg.Attributes[attribute];
260 if (value == null || value.Length == 0)
261 value = String.Format(defaultValue, defaultValueArguments);
263 return value;
266 private bool IsEmpty(string value)
268 return value == null || value.Length == 0;
271 private bool IsTrue(string value)
273 return String.Compare(value, "true", true) == 0;
276 #endregion
278 #region Filesystem directory handling
280 /// <summary>
281 /// Normalizes a directory path. It includes resolving parent (<c>..</c>) paths
282 /// and the <c>~</c> prefix, which maps to the root of the current application.
283 /// </summary>
284 /// <param name="path">The directory path</param>
285 /// <returns>The normalized directory path</returns>
286 /// <seealso cref="GetCurrentAppRootDirectory"/>
287 protected virtual string NormalizeDirectoryPath(string path)
289 if (path.StartsWith("~"))
290 path = GetCurrentAppRootDirectory() + path.Substring(1);
291 return new DirectoryInfo(path).FullName;
294 /// <summary>
295 /// Gets the root directory of the current application.
296 /// For web applications, it is obtained from <see cref="HttpServerUtility.MapPath"/>.
297 /// For other applications, <see cref="AppDomain.BaseDirectory"/> is used.
298 /// </summary>
299 protected virtual string GetCurrentAppRootDirectory()
301 if (isWeb)
302 return HttpContext.Current.Server.MapPath("~");
304 return AppDomain.CurrentDomain.BaseDirectory;
307 #endregion
309 /// <summary>
310 /// <see cref="IDisposable"/> implementation. Releases all <see cref="RemoteLoader"/>s
311 /// and <see cref="AppDomain"/>s.
312 /// </summary>
313 public void Dispose()
315 Dispose(true);
317 GC.SuppressFinalize(this);
320 protected virtual void Dispose(bool disposing)
322 if (disposing)
323 registry.Dispose();