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