< Summary

Information
Class: Ice.PluginManagerI.PluginInfo
Assembly: Ice
File(s): /_/csharp/src/Ice/PluginManagerI.cs
Tag: 91_21789722663
Line coverage
100%
Covered lines: 1
Uncovered lines: 0
Coverable lines: 1
Total lines: 422
Line coverage: 100%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage
100%
Covered methods: 5
Total methods: 5
Method coverage: 100%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
get_name()100%11100%
set_name(...)100%210%
get_plugin()100%11100%
set_plugin(...)100%210%

File(s)

/_/csharp/src/Ice/PluginManagerI.cs

#LineLine coverage
 1// Copyright (c) ZeroC, Inc.
 2
 3#nullable enable
 4
 5using System.Collections;
 6using System.Diagnostics;
 7
 8namespace Ice;
 9
 10/// <summary>
 11/// Applications implement this interface to provide a plug-in factory
 12/// to the Ice run time.
 13/// </summary>
 14public interface PluginFactory
 15{
 16    /// <summary>
 17    /// Gets the default and preferred name for plug-ins created by this factory.
 18    /// </summary>
 19    string pluginName { get; }
 20
 21    /// <summary>
 22    /// Called by the Ice run time to create a new plug-in.
 23    /// </summary>
 24    ///
 25    /// <param name="communicator">The communicator that is in the process of being initialized.</param>
 26    /// <param name="name">The name of the plug-in.</param>
 27    /// <param name="args">The arguments that are specified in the plug-ins configuration.</param>
 28    /// <returns>The plug-in that was created by this method.</returns>
 29    Plugin create(Communicator communicator, string name, string[] args);
 30}
 31
 32internal sealed class PluginManagerI : PluginManager
 33{
 34    private const string _kindOfObject = "plugin";
 35
 36    public void initializePlugins()
 37    {
 38        if (_initialized)
 39        {
 40            throw new InitializationException("Plug-ins already initialized.");
 41        }
 42
 43        //
 44        // Invoke initialize() on the plug-ins, in the order they were loaded.
 45        //
 46        var initializedPlugins = new ArrayList();
 47        try
 48        {
 49            foreach (PluginInfo p in _plugins)
 50            {
 51                try
 52                {
 53                    p.plugin.initialize();
 54                }
 55                catch (PluginInitializationException)
 56                {
 57                    throw;
 58                }
 59                catch (System.Exception ex)
 60                {
 61                    throw new PluginInitializationException($"Plugin '{p.name}' initialization failed.", ex);
 62                }
 63                initializedPlugins.Add(p.plugin);
 64            }
 65        }
 66        catch (System.Exception)
 67        {
 68            //
 69            // Destroy the plug-ins that have been successfully initialized, in the
 70            // reverse order.
 71            //
 72            initializedPlugins.Reverse();
 73            foreach (Plugin p in initializedPlugins)
 74            {
 75                try
 76                {
 77                    p.destroy();
 78                }
 79                catch (System.Exception)
 80                {
 81                    // Ignore.
 82                }
 83            }
 84            throw;
 85        }
 86
 87        _initialized = true;
 88    }
 89
 90    public string[] getPlugins()
 91    {
 92        lock (_mutex)
 93        {
 94            var names = new ArrayList();
 95            foreach (PluginInfo p in _plugins)
 96            {
 97                names.Add(p.name);
 98            }
 99            return (string[])names.ToArray(typeof(string));
 100        }
 101    }
 102
 103    public Plugin getPlugin(string name)
 104    {
 105        lock (_mutex)
 106        {
 107            if (_communicator is null)
 108            {
 109                throw new CommunicatorDestroyedException();
 110            }
 111
 112            Plugin? p = findPlugin(name);
 113            if (p is not null)
 114            {
 115                return p;
 116            }
 117
 118            throw new NotRegisteredException(_kindOfObject, name);
 119        }
 120    }
 121
 122    public void addPlugin(string name, Plugin plugin)
 123    {
 124        lock (_mutex)
 125        {
 126            if (_communicator is null)
 127            {
 128                throw new CommunicatorDestroyedException();
 129            }
 130
 131            if (findPlugin(name) is not null)
 132            {
 133                throw new AlreadyRegisteredException(_kindOfObject, name);
 134            }
 135
 136            _plugins.Add(new PluginInfo(name, plugin));
 137        }
 138    }
 139
 140    public void destroy()
 141    {
 142        lock (_mutex)
 143        {
 144            if (_communicator is not null)
 145            {
 146                if (_initialized)
 147                {
 148                    var plugins = (ArrayList)_plugins.Clone();
 149                    plugins.Reverse();
 150                    foreach (PluginInfo p in plugins)
 151                    {
 152                        try
 153                        {
 154                            p.plugin.destroy();
 155                        }
 156                        catch (System.Exception ex)
 157                        {
 158                            Util.getProcessLogger().warning("unexpected exception raised by plug-in `" +
 159                                                            p.name + "' destruction:\n" + ex.ToString());
 160                        }
 161                    }
 162                }
 163
 164                _communicator = null;
 165            }
 166        }
 167    }
 168
 169    internal PluginManagerI(Communicator communicator)
 170    {
 171        _communicator = communicator;
 172        _plugins = new ArrayList();
 173        _initialized = false;
 174    }
 175
 176    internal void loadPlugins()
 177    {
 178        Debug.Assert(_communicator is not null);
 179        string prefix = "Ice.Plugin.";
 180        Properties properties = _communicator.getProperties();
 181        Dictionary<string, string> plugins = properties.getPropertiesForPrefix(prefix);
 182
 183        // First, create plug-ins using the plug-in factories from initData, in order.
 184        foreach (PluginFactory pluginFactory in _communicator.instance.initializationData().pluginFactories)
 185        {
 186            string name = pluginFactory.pluginName;
 187            string key = $"Ice.Plugin.{name}";
 188            if (plugins.TryGetValue(key, out string? pluginSpec))
 189            {
 190                loadPlugin(pluginFactory, name, pluginSpec);
 191                plugins.Remove(key);
 192            }
 193            else
 194            {
 195                loadPlugin(pluginFactory, name, "");
 196            }
 197        }
 198
 199        //
 200        // Load and initialize the plug-ins defined in the property set
 201        // with the prefix "Ice.Plugin.". These properties should
 202        // have the following format:
 203        //
 204        // Ice.Plugin.<name>=entry_point [args]
 205        //
 206        // The code below is different from the Java/C++ algorithm
 207        // because C# must support full assembly names such as:
 208        //
 209        // Ice.Plugin.Logger=logger, Version=0.0.0.0, Culture=neutral:LoginPluginFactory
 210        //
 211        // If the Ice.PluginLoadOrder property is defined, load the
 212        // specified plug-ins in the specified order, then load any
 213        // remaining plug-ins.
 214        //
 215
 216        string[] loadOrder = properties.getIcePropertyAsList("Ice.PluginLoadOrder");
 217        for (int i = 0; i < loadOrder.Length; ++i)
 218        {
 219            if (loadOrder[i].Length == 0)
 220            {
 221                continue;
 222            }
 223
 224            if (findPlugin(loadOrder[i]) is not null)
 225            {
 226                throw new PluginInitializationException($"Plug-in '{loadOrder[i]}' already loaded.");
 227            }
 228
 229            string key = $"Ice.Plugin.{loadOrder[i]}";
 230            plugins.TryGetValue(key, out string? value);
 231            if (value is not null)
 232            {
 233                loadPlugin(null, loadOrder[i], value);
 234                plugins.Remove(key);
 235            }
 236            else
 237            {
 238                throw new PluginInitializationException($"Plug-in '{loadOrder[i]}' not defined.");
 239            }
 240        }
 241
 242        //
 243        // Load any remaining plug-ins that weren't specified in PluginLoadOrder.
 244        //
 245        foreach (KeyValuePair<string, string> entry in plugins)
 246        {
 247            loadPlugin(null, entry.Key[prefix.Length..], entry.Value);
 248        }
 249    }
 250
 251    private void loadPlugin(PluginFactory? pluginFactory, string name, string pluginSpec)
 252    {
 253        Debug.Assert(_communicator is not null);
 254
 255        if (findPlugin(name) is not null)
 256        {
 257            throw new AlreadyRegisteredException(_kindOfObject, name);
 258        }
 259
 260        string[] args = [];
 261        string entryPoint = "";
 262        if (pluginSpec.Length > 0)
 263        {
 264            //
 265            // Split the entire property value into arguments. An entry point containing spaces
 266            // must be enclosed in quotes.
 267            //
 268            try
 269            {
 270                args = Ice.UtilInternal.Options.split(pluginSpec);
 271            }
 272            catch (ParseException ex)
 273            {
 274                throw new PluginInitializationException($"Invalid arguments for plug-in '{name}'.", ex);
 275            }
 276
 277            Debug.Assert(args.Length > 0);
 278
 279            entryPoint = args[0];
 280
 281            //
 282            // Shift the arguments.
 283            //
 284            string[] tmp = new string[args.Length - 1];
 285            Array.Copy(args, 1, tmp, 0, args.Length - 1);
 286            args = tmp;
 287
 288            // Convert command-line options into properties.
 289            Properties properties = _communicator.getProperties();
 290            args = properties.parseCommandLineOptions(name, args);
 291        }
 292
 293        string err = "unable to load plug-in `" + entryPoint + "': ";
 294
 295        if (pluginFactory is null)
 296        {
 297            //
 298            // Extract the assembly name and the class name.
 299            //
 300            int sepPos = entryPoint.IndexOf(':', StringComparison.Ordinal);
 301            if (sepPos != -1)
 302            {
 303                const string driveLetters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
 304                if (entryPoint.Length > 3 &&
 305                   sepPos == 1 &&
 306                   driveLetters.Contains(entryPoint[0], StringComparison.Ordinal) &&
 307                   (entryPoint[2] == '\\' || entryPoint[2] == '/'))
 308                {
 309                    sepPos = entryPoint.IndexOf(':', 3);
 310                }
 311            }
 312            if (sepPos == -1)
 313            {
 314                throw new PluginInitializationException($"{err}invalid entry point format");
 315            }
 316
 317            string assemblyName = entryPoint[..sepPos];
 318            string className = entryPoint[(sepPos + 1)..];
 319
 320            System.Reflection.Assembly? pluginAssembly;
 321            try
 322            {
 323                //
 324                // First try to load the assembly using Assembly.Load, which will succeed
 325                // if a fully-qualified name is provided or if a partial name has been qualified
 326                // in configuration. If that fails, try Assembly.LoadFrom(), which will succeed
 327                // if a file name is configured or a partial name is configured and DEVPATH is used.
 328                //
 329                // We catch System.Exception as this can fail with System.ArgumentNullException
 330                // or System.IO.IOException depending of the .NET framework and platform.
 331                //
 332                try
 333                {
 334                    pluginAssembly = System.Reflection.Assembly.Load(assemblyName);
 335                }
 336                catch (System.Exception ex)
 337                {
 338                    try
 339                    {
 340                        pluginAssembly = System.Reflection.Assembly.LoadFrom(assemblyName);
 341                    }
 342                    catch (System.IO.IOException)
 343                    {
 344#pragma warning disable CA2200 // Rethrow to preserve stack details
 345                        throw ex;
 346#pragma warning restore CA2200 // Rethrow to preserve stack details
 347                    }
 348                }
 349            }
 350            catch (System.Exception ex)
 351            {
 352                throw new PluginInitializationException($"{err}unable to load assembly '{assemblyName}'.", ex);
 353            }
 354
 355            //
 356            // Instantiate the class.
 357            //
 358            Type? c;
 359            try
 360            {
 361                c = pluginAssembly.GetType(className, true);
 362            }
 363            catch (System.Exception ex)
 364            {
 365                throw new PluginInitializationException($"{err}GetType failed for '{className}'.", ex);
 366            }
 367
 368            try
 369            {
 370                pluginFactory = (PluginFactory)Ice.Internal.AssemblyUtil.createInstance(c);
 371                if (pluginFactory is null)
 372                {
 373                    throw new PluginInitializationException($"{err}can't find constructor for '{className}'.");
 374                }
 375            }
 376            catch (System.Exception ex)
 377            {
 378                throw new PluginInitializationException($"{err}SystemException", ex);
 379            }
 380        }
 381
 382        Plugin? plugin;
 383        try
 384        {
 385            plugin = pluginFactory.create(_communicator, name, args);
 386        }
 387        catch (PluginInitializationException)
 388        {
 389            throw;
 390        }
 391        catch (System.Exception ex)
 392        {
 393            throw new PluginInitializationException($"{err}System.Exception in factory.create", ex);
 394        }
 395
 396        if (plugin is null)
 397        {
 398            throw new PluginInitializationException($"{err}factory.create returned null plug-in");
 399        }
 400
 401        _plugins.Add(new PluginInfo(name, plugin));
 402    }
 403
 404    private Plugin? findPlugin(string name)
 405    {
 406        foreach (PluginInfo p in _plugins)
 407        {
 408            if (name.Equals(p.name, StringComparison.Ordinal))
 409            {
 410                return p.plugin;
 411            }
 412        }
 413        return null;
 414    }
 415
 1416    internal record class PluginInfo(string name, Plugin plugin);
 417
 418    private Communicator? _communicator;
 419    private readonly ArrayList _plugins;
 420    private bool _initialized;
 421    private readonly object _mutex = new();
 422}