1 // Copyright 2004-2008 Castle Project - http://www.castleproject.org/
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
7 // http://www.apache.org/licenses/LICENSE-2.0
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
18 using System
.Collections
;
19 using System
.Collections
.Generic
;
21 using System
.Reflection
;
22 using System
.Security
.Policy
;
24 using Castle
.Core
.Configuration
;
25 using Castle
.Core
.Logging
;
26 using Castle
.MicroKernel
;
27 using Castle
.MicroKernel
.Facilities
;
30 /// DynamicLoader facility.
32 public class DynamicLoaderFacility
: MarshalByRefObject
, IFacility
, IDisposable
34 private readonly DynamicLoaderRegistry registry
= new DynamicLoaderRegistry();
36 private ILogger log
= NullLogger
.Instance
;
40 private IKernel kernel
;
41 private IConfiguration facilityConfig
;
44 /// Initializes the facility.
46 public void Init(IKernel kernel
, IConfiguration facilityConfig
)
49 this.facilityConfig
= facilityConfig
;
55 /// Terminates the facility.
57 public void Terminate()
64 ~
DynamicLoaderFacility()
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
)
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
))
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
);
154 private RemoteLoader
CreateRemoteLoader(AppDomain appDomain
)
156 string remoteLoaderAsmName
= typeof(RemoteLoader
).Assembly
.FullName
;
157 string remoteLoaderClsName
= typeof(RemoteLoader
).FullName
;
161 appDomain
.DoCallBack(
164 Assembly
.Load("Castle.Core");
165 Assembly
.Load("Castle.MicroKernel");
166 Assembly
.Load("Castle.Facilities.DynamicLoader");
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
);
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)
193 foreach(IConfiguration comp
in batchRegistrationNode
.Children
)
198 InitializeBatchComponents(loader
, comp
);
201 throw new FacilityException("Unrecognized configuration node: " + comp
.Name
);
207 /// Register each batch component.
209 /// <param name="loader">The <see cref="RemoteLoader"/> instance in which to register</param>
210 /// <param name="componentsNode">The component configuration node</param>
213 /// An example of a valid configuration node:
215 /// <component id="componentid.*">
216 /// <providesService service="Company.Project.IService, Company.Project" />
217 /// </component>
221 private void InitializeBatchComponents(RemoteLoader loader
, IConfiguration componentsNode
)
223 if (componentsNode
== null)
226 string componentIdMask
= componentsNode
.Attributes
["id"];
227 List
<Type
> servicesProvided
= new List
<Type
>();
229 foreach(IConfiguration cond
in componentsNode
.Children
)
233 case "providesService":
234 servicesProvided
.Add(Type
.GetType(cond
.Attributes
["service"]));
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
)
250 IConfiguration node
= cfg
.Children
[nodeName
];
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
);
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;
278 #region Filesystem directory handling
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.
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
;
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.
299 protected virtual string GetCurrentAppRootDirectory()
302 return HttpContext
.Current
.Server
.MapPath("~");
304 return AppDomain
.CurrentDomain
.BaseDirectory
;
310 /// <see cref="IDisposable"/> implementation. Releases all <see cref="RemoteLoader"/>s
311 /// and <see cref="AppDomain"/>s.
313 public void Dispose()
317 GC
.SuppressFinalize(this);
320 protected virtual void Dispose(bool disposing
)