1 // 2 // Copyright (c) ZeroC, Inc. All rights reserved. 3 // 4 5 namespace Ice 6 { 7 using System; 8 using System.Collections; 9 using System.Collections.Generic; 10 using System.Diagnostics; 11 12 /// <summary> 13 /// Applications implement this interface to provide a plug-in factory 14 /// to the Ice run time. 15 /// </summary> 16 public interface PluginFactory 17 { 18 /// <summary> 19 /// Called by the Ice run time to create a new plug-in. 20 /// </summary> 21 /// 22 /// <param name="communicator">The communicator that is in the process of being initialized.</param> 23 /// <param name="name">The name of the plug-in.</param> 24 /// <param name="args">The arguments that are specified in the plug-ins configuration.</param> 25 /// <returns>The plug-in that was created by this method.</returns> create(Communicator communicator, string name, string[] args)26 Plugin create(Communicator communicator, string name, string[] args); 27 } 28 29 public sealed class PluginManagerI : PluginManager 30 { 31 private static string _kindOfObject = "plugin"; 32 registerPluginFactory(string name, PluginFactory factory, bool loadOnInit)33 internal static void registerPluginFactory(string name, PluginFactory factory, bool loadOnInit) 34 { 35 if(!_factories.ContainsKey(name)) 36 { 37 _factories[name] = factory; 38 if(loadOnInit) 39 { 40 _loadOnInitialization.Add(name); 41 } 42 } 43 } 44 initializePlugins()45 public void initializePlugins() 46 { 47 if(_initialized) 48 { 49 InitializationException ex = new InitializationException(); 50 ex.reason = "plug-ins already initialized"; 51 throw ex; 52 } 53 54 // 55 // Invoke initialize() on the plug-ins, in the order they were loaded. 56 // 57 ArrayList initializedPlugins = new ArrayList(); 58 try 59 { 60 foreach(PluginInfo p in _plugins) 61 { 62 try 63 { 64 p.plugin.initialize(); 65 } 66 catch(PluginInitializationException) 67 { 68 throw; 69 } 70 catch(System.Exception ex) 71 { 72 throw new PluginInitializationException(String.Format("plugin `{0}' initialization failed", p.name), ex); 73 } 74 initializedPlugins.Add(p.plugin); 75 } 76 } 77 catch(System.Exception) 78 { 79 // 80 // Destroy the plug-ins that have been successfully initialized, in the 81 // reverse order. 82 // 83 initializedPlugins.Reverse(); 84 foreach(Plugin p in initializedPlugins) 85 { 86 try 87 { 88 p.destroy(); 89 } 90 catch(System.Exception) 91 { 92 // Ignore. 93 } 94 } 95 throw; 96 } 97 98 _initialized = true; 99 } 100 getPlugins()101 public string[] getPlugins() 102 { 103 lock(this) 104 { 105 ArrayList names = new ArrayList(); 106 foreach(PluginInfo p in _plugins) 107 { 108 names.Add(p.name); 109 } 110 return (string[])names.ToArray(typeof(string)); 111 } 112 } 113 getPlugin(string name)114 public Plugin getPlugin(string name) 115 { 116 lock(this) 117 { 118 if(_communicator == null) 119 { 120 throw new CommunicatorDestroyedException(); 121 } 122 123 Plugin p = findPlugin(name); 124 if(p != null) 125 { 126 return p; 127 } 128 129 NotRegisteredException ex = new NotRegisteredException(); 130 ex.id = name; 131 ex.kindOfObject = _kindOfObject; 132 throw ex; 133 } 134 } 135 addPlugin(string name, Plugin plugin)136 public void addPlugin(string name, Plugin plugin) 137 { 138 lock(this) 139 { 140 if(_communicator == null) 141 { 142 throw new CommunicatorDestroyedException(); 143 } 144 145 if(findPlugin(name) != null) 146 { 147 AlreadyRegisteredException ex = new AlreadyRegisteredException(); 148 ex.id = name; 149 ex.kindOfObject = _kindOfObject; 150 throw ex; 151 } 152 153 PluginInfo info = new PluginInfo(); 154 info.name = name; 155 info.plugin = plugin; 156 _plugins.Add(info); 157 } 158 } 159 destroy()160 public void destroy() 161 { 162 lock(this) 163 { 164 if(_communicator != null) 165 { 166 if(_initialized) 167 { 168 ArrayList plugins = (ArrayList)_plugins.Clone(); 169 plugins.Reverse(); 170 foreach(PluginInfo p in plugins) 171 { 172 try 173 { 174 p.plugin.destroy(); 175 } 176 catch(System.Exception ex) 177 { 178 Util.getProcessLogger().warning("unexpected exception raised by plug-in `" + 179 p.name + "' destruction:\n" + ex.ToString()); 180 } 181 } 182 } 183 184 _communicator = null; 185 } 186 } 187 } 188 PluginManagerI(Communicator communicator)189 public PluginManagerI(Communicator communicator) 190 { 191 _communicator = communicator; 192 _plugins = new ArrayList(); 193 _initialized = false; 194 } 195 loadPlugins(ref string[] cmdArgs)196 public void loadPlugins(ref string[] cmdArgs) 197 { 198 Debug.Assert(_communicator != null); 199 string prefix = "Ice.Plugin."; 200 Properties properties = _communicator.getProperties(); 201 Dictionary<string, string> plugins = properties.getPropertiesForPrefix(prefix); 202 203 // 204 // First, load static plugin factories which were setup to load on 205 // communicator initialization. If a matching plugin property is 206 // set, we load the plugin with the plugin specification. The 207 // entryPoint will be ignored but the rest of the plugin 208 // specification might be used. 209 // 210 foreach(var name in _loadOnInitialization) 211 { 212 string key = "Ice.Plugin." + name + ".clr"; 213 string r = null; 214 plugins.TryGetValue(key, out r); 215 if(r != null) 216 { 217 plugins.Remove("Ice.Plugin." + name); 218 } 219 else 220 { 221 key = "Ice.Plugin." + name; 222 plugins.TryGetValue(key, out r); 223 } 224 225 if(r != null) 226 { 227 loadPlugin(name, r, ref cmdArgs); 228 plugins.Remove(key); 229 } 230 else 231 { 232 loadPlugin(name, "", ref cmdArgs); 233 } 234 } 235 236 // 237 // Load and initialize the plug-ins defined in the property set 238 // with the prefix "Ice.Plugin.". These properties should 239 // have the following format: 240 // 241 // Ice.Plugin.name[.<language>]=entry_point [args] 242 // 243 // The code below is different from the Java/C++ algorithm 244 // because C# must support full assembly names such as: 245 // 246 // Ice.Plugin.Logger=logger, Version=0.0.0.0, Culture=neutral:LoginPluginFactory 247 // 248 // If the Ice.PluginLoadOrder property is defined, load the 249 // specified plug-ins in the specified order, then load any 250 // remaining plug-ins. 251 // 252 253 string[] loadOrder = properties.getPropertyAsList("Ice.PluginLoadOrder"); 254 for(int i = 0; i < loadOrder.Length; ++i) 255 { 256 if(loadOrder[i].Length == 0) 257 { 258 continue; 259 } 260 261 if(findPlugin(loadOrder[i]) != null) 262 { 263 PluginInitializationException e = new PluginInitializationException(); 264 e.reason = "plug-in `" + loadOrder[i] + "' already loaded"; 265 throw e; 266 } 267 268 string key = "Ice.Plugin." + loadOrder[i] + ".clr"; 269 string value = null; 270 plugins.TryGetValue(key, out value); 271 if(value != null) 272 { 273 plugins.Remove("Ice.Plugin." + loadOrder[i]); 274 } 275 else 276 { 277 key = "Ice.Plugin." + loadOrder[i]; 278 plugins.TryGetValue(key, out value); 279 } 280 281 if(value != null) 282 { 283 loadPlugin(loadOrder[i], value, ref cmdArgs); 284 plugins.Remove(key); 285 } 286 else 287 { 288 PluginInitializationException e = new PluginInitializationException(); 289 e.reason = "plug-in `" + loadOrder[i] + "' not defined"; 290 throw e; 291 } 292 } 293 294 // 295 // Load any remaining plug-ins that weren't specified in PluginLoadOrder. 296 // 297 while(plugins.Count > 0) 298 { 299 IEnumerator<KeyValuePair<string, string>> p = plugins.GetEnumerator(); 300 p.MoveNext(); 301 string key = p.Current.Key; 302 string val = p.Current.Value; 303 string name = key.Substring(prefix.Length); 304 305 int dotPos = name.LastIndexOf('.'); 306 if(dotPos != -1) 307 { 308 string suffix = name.Substring(dotPos + 1); 309 if(suffix.Equals("cpp") || suffix.Equals("java")) 310 { 311 // 312 // Ignored 313 // 314 plugins.Remove(key); 315 } 316 else if(suffix.Equals("clr")) 317 { 318 name = name.Substring(0, dotPos); 319 loadPlugin(name, val, ref cmdArgs); 320 plugins.Remove(key); 321 plugins.Remove("Ice.Plugin." + name); 322 323 } 324 else 325 { 326 // 327 // Name is just a regular name that happens to contain a dot 328 // 329 dotPos = -1; 330 } 331 } 332 333 if(dotPos == -1) 334 { 335 plugins.Remove(key); 336 337 // 338 // Is there a .clr entry? 339 // 340 string clrKey = "Ice.Plugin." + name + ".clr"; 341 if(plugins.ContainsKey(clrKey)) 342 { 343 val = plugins[clrKey]; 344 plugins.Remove(clrKey); 345 } 346 loadPlugin(name, val, ref cmdArgs); 347 } 348 } 349 } 350 loadPlugin(string name, string pluginSpec, ref string[] cmdArgs)351 private void loadPlugin(string name, string pluginSpec, ref string[] cmdArgs) 352 { 353 Debug.Assert(_communicator != null); 354 355 string[] args = null; 356 string entryPoint = null; 357 if(pluginSpec.Length > 0) 358 { 359 // 360 // Split the entire property value into arguments. An entry point containing spaces 361 // must be enclosed in quotes. 362 // 363 try 364 { 365 args = IceUtilInternal.Options.split(pluginSpec); 366 } 367 catch(IceUtilInternal.Options.BadQuote ex) 368 { 369 PluginInitializationException e = new PluginInitializationException(); 370 e.reason = "invalid arguments for plug-in `" + name + "':\n" + ex.Message; 371 throw e; 372 } 373 374 Debug.Assert(args.Length > 0); 375 376 entryPoint = args[0]; 377 378 // 379 // Shift the arguments. 380 // 381 string[] tmp = new string[args.Length - 1]; 382 Array.Copy(args, 1, tmp, 0, args.Length - 1); 383 args = tmp; 384 385 // 386 // Convert command-line options into properties. First 387 // we convert the options from the plug-in 388 // configuration, then we convert the options from the 389 // application command-line. 390 // 391 Properties properties = _communicator.getProperties(); 392 args = properties.parseCommandLineOptions(name, args); 393 cmdArgs = properties.parseCommandLineOptions(name, cmdArgs); 394 } 395 396 string err = "unable to load plug-in `" + entryPoint + "': "; 397 // 398 // Always check the static plugin factory table first, it takes 399 // precedence over the the entryPoint specified in the plugin 400 // property value. 401 // 402 PluginFactory pluginFactory = null; 403 if(!_factories.TryGetValue(name, out pluginFactory)) 404 { 405 // 406 // Extract the assembly name and the class name. 407 // 408 int sepPos = entryPoint.IndexOf(':'); 409 if(sepPos != -1) 410 { 411 const string driveLetters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; 412 if(entryPoint.Length > 3 && 413 sepPos == 1 && 414 driveLetters.IndexOf(entryPoint[0]) != -1 && 415 (entryPoint[2] == '\\' || entryPoint[2] == '/')) 416 { 417 sepPos = entryPoint.IndexOf(':', 3); 418 } 419 } 420 if(sepPos == -1) 421 { 422 PluginInitializationException e = new PluginInitializationException(); 423 e.reason = err + "invalid entry point format"; 424 throw e; 425 } 426 427 System.Reflection.Assembly pluginAssembly = null; 428 string assemblyName = entryPoint.Substring(0, sepPos); 429 string className = entryPoint.Substring(sepPos + 1); 430 431 try 432 { 433 // 434 // First try to load the assembly using Assembly.Load, which will succeed 435 // if a fully-qualified name is provided or if a partial name has been qualified 436 // in configuration. If that fails, try Assembly.LoadFrom(), which will succeed 437 // if a file name is configured or a partial name is configured and DEVPATH is used. 438 // 439 // We catch System.Exception as this can fail with System.ArgumentNullException 440 // or System.IO.IOException depending of the .NET framework and platform. 441 // 442 try 443 { 444 pluginAssembly = System.Reflection.Assembly.Load(assemblyName); 445 } 446 catch(System.Exception ex) 447 { 448 try 449 { 450 pluginAssembly = System.Reflection.Assembly.LoadFrom(assemblyName); 451 } 452 catch(System.IO.IOException) 453 { 454 throw ex; 455 } 456 } 457 } 458 catch(System.Exception ex) 459 { 460 PluginInitializationException e = new PluginInitializationException(); 461 e.reason = err + "unable to load assembly: `" + assemblyName + "': " + ex.ToString(); 462 throw e; 463 } 464 465 // 466 // Instantiate the class. 467 // 468 System.Type c = null; 469 try 470 { 471 c = pluginAssembly.GetType(className, true); 472 } 473 catch(System.Exception ex) 474 { 475 PluginInitializationException e = new PluginInitializationException(ex); 476 e.reason = err + "GetType failed for `" + className + "'"; 477 throw e; 478 } 479 480 try 481 { 482 pluginFactory = (PluginFactory)IceInternal.AssemblyUtil.createInstance(c); 483 if(pluginFactory == null) 484 { 485 PluginInitializationException e = new PluginInitializationException(); 486 e.reason = err + "can't find constructor for `" + className + "'"; 487 throw e; 488 } 489 } 490 catch(InvalidCastException ex) 491 { 492 PluginInitializationException e = new PluginInitializationException(ex); 493 e.reason = err + "InvalidCastException to Ice.PluginFactory"; 494 throw e; 495 } 496 catch(UnauthorizedAccessException ex) 497 { 498 PluginInitializationException e = new PluginInitializationException(ex); 499 e.reason = err + "UnauthorizedAccessException: " + ex.ToString(); 500 throw e; 501 } 502 catch(System.Exception ex) 503 { 504 PluginInitializationException e = new PluginInitializationException(ex); 505 e.reason = err + "System.Exception: " + ex.ToString(); 506 throw e; 507 } 508 } 509 510 Plugin plugin = null; 511 try 512 { 513 plugin = pluginFactory.create(_communicator, name, args); 514 } 515 catch(PluginInitializationException ex) 516 { 517 ex.reason = err + ex.reason; 518 throw; 519 } 520 catch(System.Exception ex) 521 { 522 PluginInitializationException e = new PluginInitializationException(ex); 523 e.reason = err + "System.Exception in factory.create: " + ex.ToString(); 524 throw e; 525 } 526 527 if(plugin == null) 528 { 529 PluginInitializationException ex = new PluginInitializationException(); 530 ex.reason = err + "factory.create returned null plug-in"; 531 throw ex; 532 } 533 534 PluginInfo info = new PluginInfo(); 535 info.name = name; 536 info.plugin = plugin; 537 _plugins.Add(info); 538 } 539 findPlugin(string name)540 private Plugin findPlugin(string name) 541 { 542 foreach(PluginInfo p in _plugins) 543 { 544 if(name.Equals(p.name)) 545 { 546 return p.plugin; 547 } 548 } 549 return null; 550 } 551 552 internal class PluginInfo 553 { 554 internal string name; 555 internal Plugin plugin; 556 } 557 558 private Communicator _communicator; 559 private ArrayList _plugins; 560 private bool _initialized; 561 562 private static Dictionary<string, PluginFactory> _factories = new Dictionary<string, PluginFactory>(); 563 private static List<string> _loadOnInitialization = new List<string>(); 564 } 565 } 566